diff options
514 files changed, 42152 insertions, 6947 deletions
diff --git a/bootstrap/bin/start.boot b/bootstrap/bin/start.boot Binary files differindex 940541dbf8..71d72c4b7d 100644 --- a/bootstrap/bin/start.boot +++ b/bootstrap/bin/start.boot diff --git a/bootstrap/bin/start.script b/bootstrap/bin/start.script index 08ed9939f7..1b7e8ae4f7 100644 --- a/bootstrap/bin/start.script +++ b/bootstrap/bin/start.script @@ -1,4 +1,4 @@ -%% script generated at {2010,5,11} {9,2,55} +%% script generated at {2010,6,2} {16,55,39} {script, {"OTP APN 181 01","R14A"}, [{preLoaded, @@ -24,7 +24,7 @@ standard_error,user,user_drv,user_sup,wrap_log_reader]}, {path,["$ROOT/lib/stdlib/ebin"]}, {primLoad, - [array,base64,beam_lib,c,calendar,dets,dets_server,dets_sup, + [array,base64,beam_lib,binary,c,calendar,dets,dets_server,dets_sup, dets_utils,dets_v8,dets_v9,dict,digraph,digraph_utils,edlin, edlin_expand,epp,erl_bits,erl_compile,erl_eval,erl_expand_records, erl_internal,erl_lint,erl_parse,erl_posix_msg,erl_pp,erl_scan, @@ -83,19 +83,19 @@ {vsn,"1.17"}, {id,[]}, {modules, - [array,base64,beam_lib,c,calendar,dets,dets_server, - dets_sup,dets_utils,dets_v8,dets_v9,dict,digraph, - digraph_utils,edlin,edlin_expand,epp,eval_bits, - erl_bits,erl_compile,erl_eval,erl_expand_records, - erl_internal,erl_lint,erl_parse,erl_posix_msg,erl_pp, - erl_scan,erl_tar,error_logger_file_h, - error_logger_tty_h,escript,ets,file_sorter,filelib, - filename,gb_trees,gb_sets,gen,gen_event,gen_fsm, - gen_server,io,io_lib,io_lib_format,io_lib_fread, - io_lib_pretty,lib,lists,log_mf_h,math,ms_transform, - orddict,ordsets,otp_internal,pg,pool,proc_lib, - proplists,qlc,qlc_pt,queue,random,re,regexp,sets, - shell,shell_default,slave,sofs,string,supervisor, + [array,base64,beam_lib,binary,c,calendar,dets, + dets_server,dets_sup,dets_utils,dets_v8,dets_v9,dict, + digraph,digraph_utils,edlin,edlin_expand,epp, + eval_bits,erl_bits,erl_compile,erl_eval, + erl_expand_records,erl_internal,erl_lint,erl_parse, + erl_posix_msg,erl_pp,erl_scan,erl_tar, + error_logger_file_h,error_logger_tty_h,escript,ets, + file_sorter,filelib,filename,gb_trees,gb_sets,gen, + gen_event,gen_fsm,gen_server,io,io_lib,io_lib_format, + io_lib_fread,io_lib_pretty,lib,lists,log_mf_h,math, + ms_transform,orddict,ordsets,otp_internal,pg,pool, + proc_lib,proplists,qlc,qlc_pt,queue,random,re,regexp, + sets,shell,shell_default,slave,sofs,string,supervisor, supervisor_bridge,sys,timer,unicode,win32reg,zip]}, {registered, [timer_server,rsh_starter,take_over_monitor, diff --git a/bootstrap/bin/start_clean.boot b/bootstrap/bin/start_clean.boot Binary files differindex 940541dbf8..71d72c4b7d 100644 --- a/bootstrap/bin/start_clean.boot +++ b/bootstrap/bin/start_clean.boot diff --git a/bootstrap/bin/start_clean.script b/bootstrap/bin/start_clean.script index 08ed9939f7..1b7e8ae4f7 100644 --- a/bootstrap/bin/start_clean.script +++ b/bootstrap/bin/start_clean.script @@ -1,4 +1,4 @@ -%% script generated at {2010,5,11} {9,2,55} +%% script generated at {2010,6,2} {16,55,39} {script, {"OTP APN 181 01","R14A"}, [{preLoaded, @@ -24,7 +24,7 @@ standard_error,user,user_drv,user_sup,wrap_log_reader]}, {path,["$ROOT/lib/stdlib/ebin"]}, {primLoad, - [array,base64,beam_lib,c,calendar,dets,dets_server,dets_sup, + [array,base64,beam_lib,binary,c,calendar,dets,dets_server,dets_sup, dets_utils,dets_v8,dets_v9,dict,digraph,digraph_utils,edlin, edlin_expand,epp,erl_bits,erl_compile,erl_eval,erl_expand_records, erl_internal,erl_lint,erl_parse,erl_posix_msg,erl_pp,erl_scan, @@ -83,19 +83,19 @@ {vsn,"1.17"}, {id,[]}, {modules, - [array,base64,beam_lib,c,calendar,dets,dets_server, - dets_sup,dets_utils,dets_v8,dets_v9,dict,digraph, - digraph_utils,edlin,edlin_expand,epp,eval_bits, - erl_bits,erl_compile,erl_eval,erl_expand_records, - erl_internal,erl_lint,erl_parse,erl_posix_msg,erl_pp, - erl_scan,erl_tar,error_logger_file_h, - error_logger_tty_h,escript,ets,file_sorter,filelib, - filename,gb_trees,gb_sets,gen,gen_event,gen_fsm, - gen_server,io,io_lib,io_lib_format,io_lib_fread, - io_lib_pretty,lib,lists,log_mf_h,math,ms_transform, - orddict,ordsets,otp_internal,pg,pool,proc_lib, - proplists,qlc,qlc_pt,queue,random,re,regexp,sets, - shell,shell_default,slave,sofs,string,supervisor, + [array,base64,beam_lib,binary,c,calendar,dets, + dets_server,dets_sup,dets_utils,dets_v8,dets_v9,dict, + digraph,digraph_utils,edlin,edlin_expand,epp, + eval_bits,erl_bits,erl_compile,erl_eval, + erl_expand_records,erl_internal,erl_lint,erl_parse, + erl_posix_msg,erl_pp,erl_scan,erl_tar, + error_logger_file_h,error_logger_tty_h,escript,ets, + file_sorter,filelib,filename,gb_trees,gb_sets,gen, + gen_event,gen_fsm,gen_server,io,io_lib,io_lib_format, + io_lib_fread,io_lib_pretty,lib,lists,log_mf_h,math, + ms_transform,orddict,ordsets,otp_internal,pg,pool, + proc_lib,proplists,qlc,qlc_pt,queue,random,re,regexp, + sets,shell,shell_default,slave,sofs,string,supervisor, supervisor_bridge,sys,timer,unicode,win32reg,zip]}, {registered, [timer_server,rsh_starter,take_over_monitor, diff --git a/bootstrap/lib/compiler/ebin/beam_asm.beam b/bootstrap/lib/compiler/ebin/beam_asm.beam Binary files differindex 2db98a2013..23046bac9f 100644 --- a/bootstrap/lib/compiler/ebin/beam_asm.beam +++ b/bootstrap/lib/compiler/ebin/beam_asm.beam diff --git a/bootstrap/lib/compiler/ebin/beam_block.beam b/bootstrap/lib/compiler/ebin/beam_block.beam Binary files differindex e494c90788..4d71b65e23 100644 --- a/bootstrap/lib/compiler/ebin/beam_block.beam +++ b/bootstrap/lib/compiler/ebin/beam_block.beam diff --git a/bootstrap/lib/compiler/ebin/beam_bool.beam b/bootstrap/lib/compiler/ebin/beam_bool.beam Binary files differindex a1ff7e705d..ef6e7823cc 100644 --- a/bootstrap/lib/compiler/ebin/beam_bool.beam +++ b/bootstrap/lib/compiler/ebin/beam_bool.beam diff --git a/bootstrap/lib/compiler/ebin/beam_bsm.beam b/bootstrap/lib/compiler/ebin/beam_bsm.beam Binary files differindex 87bdc81d1e..fb331ee690 100644 --- a/bootstrap/lib/compiler/ebin/beam_bsm.beam +++ b/bootstrap/lib/compiler/ebin/beam_bsm.beam diff --git a/bootstrap/lib/compiler/ebin/beam_clean.beam b/bootstrap/lib/compiler/ebin/beam_clean.beam Binary files differindex 9127a20bda..0b28815f2a 100644 --- a/bootstrap/lib/compiler/ebin/beam_clean.beam +++ b/bootstrap/lib/compiler/ebin/beam_clean.beam diff --git a/bootstrap/lib/compiler/ebin/beam_dead.beam b/bootstrap/lib/compiler/ebin/beam_dead.beam Binary files differindex bdba7d1bfd..56288dc79e 100644 --- a/bootstrap/lib/compiler/ebin/beam_dead.beam +++ b/bootstrap/lib/compiler/ebin/beam_dead.beam diff --git a/bootstrap/lib/compiler/ebin/beam_disasm.beam b/bootstrap/lib/compiler/ebin/beam_disasm.beam Binary files differindex b58b44c68c..c8e2b27623 100644 --- a/bootstrap/lib/compiler/ebin/beam_disasm.beam +++ b/bootstrap/lib/compiler/ebin/beam_disasm.beam diff --git a/bootstrap/lib/compiler/ebin/beam_flatten.beam b/bootstrap/lib/compiler/ebin/beam_flatten.beam Binary files differindex 8546e1233a..402f2a14ae 100644 --- a/bootstrap/lib/compiler/ebin/beam_flatten.beam +++ b/bootstrap/lib/compiler/ebin/beam_flatten.beam diff --git a/bootstrap/lib/compiler/ebin/beam_jump.beam b/bootstrap/lib/compiler/ebin/beam_jump.beam Binary files differindex 2643874e07..1ee7b725fb 100644 --- a/bootstrap/lib/compiler/ebin/beam_jump.beam +++ b/bootstrap/lib/compiler/ebin/beam_jump.beam diff --git a/bootstrap/lib/compiler/ebin/beam_opcodes.beam b/bootstrap/lib/compiler/ebin/beam_opcodes.beam Binary files differindex 8c5378e05e..6ded472cb0 100644 --- a/bootstrap/lib/compiler/ebin/beam_opcodes.beam +++ b/bootstrap/lib/compiler/ebin/beam_opcodes.beam diff --git a/bootstrap/lib/compiler/ebin/beam_peep.beam b/bootstrap/lib/compiler/ebin/beam_peep.beam Binary files differindex 9fcb2536c8..279dd272b5 100644 --- a/bootstrap/lib/compiler/ebin/beam_peep.beam +++ b/bootstrap/lib/compiler/ebin/beam_peep.beam diff --git a/bootstrap/lib/compiler/ebin/beam_receive.beam b/bootstrap/lib/compiler/ebin/beam_receive.beam Binary files differindex ef7afd8113..4d88e80acd 100644 --- a/bootstrap/lib/compiler/ebin/beam_receive.beam +++ b/bootstrap/lib/compiler/ebin/beam_receive.beam diff --git a/bootstrap/lib/compiler/ebin/beam_trim.beam b/bootstrap/lib/compiler/ebin/beam_trim.beam Binary files differindex 74b0cec2f6..5c0d405843 100644 --- a/bootstrap/lib/compiler/ebin/beam_trim.beam +++ b/bootstrap/lib/compiler/ebin/beam_trim.beam diff --git a/bootstrap/lib/compiler/ebin/beam_type.beam b/bootstrap/lib/compiler/ebin/beam_type.beam Binary files differindex 2f2b518994..cccf58b7a4 100644 --- a/bootstrap/lib/compiler/ebin/beam_type.beam +++ b/bootstrap/lib/compiler/ebin/beam_type.beam diff --git a/bootstrap/lib/compiler/ebin/beam_utils.beam b/bootstrap/lib/compiler/ebin/beam_utils.beam Binary files differindex 51975f77d4..7b9c08439e 100644 --- a/bootstrap/lib/compiler/ebin/beam_utils.beam +++ b/bootstrap/lib/compiler/ebin/beam_utils.beam diff --git a/bootstrap/lib/compiler/ebin/beam_validator.beam b/bootstrap/lib/compiler/ebin/beam_validator.beam Binary files differindex e04c9efa69..6af598519f 100644 --- a/bootstrap/lib/compiler/ebin/beam_validator.beam +++ b/bootstrap/lib/compiler/ebin/beam_validator.beam diff --git a/bootstrap/lib/compiler/ebin/cerl.beam b/bootstrap/lib/compiler/ebin/cerl.beam Binary files differindex b0fef4fd1a..9f45f9f441 100644 --- a/bootstrap/lib/compiler/ebin/cerl.beam +++ b/bootstrap/lib/compiler/ebin/cerl.beam diff --git a/bootstrap/lib/compiler/ebin/cerl_inline.beam b/bootstrap/lib/compiler/ebin/cerl_inline.beam Binary files differindex 430f5e72b5..e0cfce01ae 100644 --- a/bootstrap/lib/compiler/ebin/cerl_inline.beam +++ b/bootstrap/lib/compiler/ebin/cerl_inline.beam diff --git a/bootstrap/lib/compiler/ebin/cerl_trees.beam b/bootstrap/lib/compiler/ebin/cerl_trees.beam Binary files differindex 49fb42c1c6..d46cfe54b7 100644 --- a/bootstrap/lib/compiler/ebin/cerl_trees.beam +++ b/bootstrap/lib/compiler/ebin/cerl_trees.beam diff --git a/bootstrap/lib/compiler/ebin/compile.beam b/bootstrap/lib/compiler/ebin/compile.beam Binary files differindex 3d44822af6..3db3a08335 100644 --- a/bootstrap/lib/compiler/ebin/compile.beam +++ b/bootstrap/lib/compiler/ebin/compile.beam diff --git a/bootstrap/lib/compiler/ebin/core_lib.beam b/bootstrap/lib/compiler/ebin/core_lib.beam Binary files differindex 43c29138b8..8796f7e13e 100644 --- a/bootstrap/lib/compiler/ebin/core_lib.beam +++ b/bootstrap/lib/compiler/ebin/core_lib.beam diff --git a/bootstrap/lib/compiler/ebin/core_lint.beam b/bootstrap/lib/compiler/ebin/core_lint.beam Binary files differindex 75e8b9b162..e7db1d3f72 100644 --- a/bootstrap/lib/compiler/ebin/core_lint.beam +++ b/bootstrap/lib/compiler/ebin/core_lint.beam diff --git a/bootstrap/lib/compiler/ebin/core_parse.beam b/bootstrap/lib/compiler/ebin/core_parse.beam Binary files differindex 19202ce827..631c5d6aba 100644 --- a/bootstrap/lib/compiler/ebin/core_parse.beam +++ b/bootstrap/lib/compiler/ebin/core_parse.beam diff --git a/bootstrap/lib/compiler/ebin/core_pp.beam b/bootstrap/lib/compiler/ebin/core_pp.beam Binary files differindex 94baecf93c..fb9001c52f 100644 --- a/bootstrap/lib/compiler/ebin/core_pp.beam +++ b/bootstrap/lib/compiler/ebin/core_pp.beam diff --git a/bootstrap/lib/compiler/ebin/core_scan.beam b/bootstrap/lib/compiler/ebin/core_scan.beam Binary files differindex d9a088f4c6..cd2146d722 100644 --- a/bootstrap/lib/compiler/ebin/core_scan.beam +++ b/bootstrap/lib/compiler/ebin/core_scan.beam diff --git a/bootstrap/lib/compiler/ebin/erl_bifs.beam b/bootstrap/lib/compiler/ebin/erl_bifs.beam Binary files differindex 8132234cb1..128f8a88d2 100644 --- a/bootstrap/lib/compiler/ebin/erl_bifs.beam +++ b/bootstrap/lib/compiler/ebin/erl_bifs.beam diff --git a/bootstrap/lib/compiler/ebin/rec_env.beam b/bootstrap/lib/compiler/ebin/rec_env.beam Binary files differindex 9e3d93482e..8a1de81396 100644 --- a/bootstrap/lib/compiler/ebin/rec_env.beam +++ b/bootstrap/lib/compiler/ebin/rec_env.beam diff --git a/bootstrap/lib/compiler/ebin/sys_core_fold.beam b/bootstrap/lib/compiler/ebin/sys_core_fold.beam Binary files differindex 6d96618ce7..f780a2fae6 100644 --- a/bootstrap/lib/compiler/ebin/sys_core_fold.beam +++ b/bootstrap/lib/compiler/ebin/sys_core_fold.beam diff --git a/bootstrap/lib/compiler/ebin/sys_expand_pmod.beam b/bootstrap/lib/compiler/ebin/sys_expand_pmod.beam Binary files differindex c0b1ebd802..3323279d7d 100644 --- a/bootstrap/lib/compiler/ebin/sys_expand_pmod.beam +++ b/bootstrap/lib/compiler/ebin/sys_expand_pmod.beam diff --git a/bootstrap/lib/compiler/ebin/sys_pre_expand.beam b/bootstrap/lib/compiler/ebin/sys_pre_expand.beam Binary files differindex 2333028391..7b4d278e30 100644 --- a/bootstrap/lib/compiler/ebin/sys_pre_expand.beam +++ b/bootstrap/lib/compiler/ebin/sys_pre_expand.beam diff --git a/bootstrap/lib/compiler/ebin/v3_codegen.beam b/bootstrap/lib/compiler/ebin/v3_codegen.beam Binary files differindex d55373f6ac..5555d01b2a 100644 --- a/bootstrap/lib/compiler/ebin/v3_codegen.beam +++ b/bootstrap/lib/compiler/ebin/v3_codegen.beam diff --git a/bootstrap/lib/compiler/ebin/v3_core.beam b/bootstrap/lib/compiler/ebin/v3_core.beam Binary files differindex df65af76c7..5d889ea4f3 100644 --- a/bootstrap/lib/compiler/ebin/v3_core.beam +++ b/bootstrap/lib/compiler/ebin/v3_core.beam diff --git a/bootstrap/lib/compiler/ebin/v3_kernel.beam b/bootstrap/lib/compiler/ebin/v3_kernel.beam Binary files differindex 2f3efc7dfb..f87360f259 100644 --- a/bootstrap/lib/compiler/ebin/v3_kernel.beam +++ b/bootstrap/lib/compiler/ebin/v3_kernel.beam diff --git a/bootstrap/lib/compiler/ebin/v3_life.beam b/bootstrap/lib/compiler/ebin/v3_life.beam Binary files differindex 3b39cffbf9..3f0a409447 100644 --- a/bootstrap/lib/compiler/ebin/v3_life.beam +++ b/bootstrap/lib/compiler/ebin/v3_life.beam diff --git a/bootstrap/lib/compiler/egen/beam_opcodes.erl b/bootstrap/lib/compiler/egen/beam_opcodes.erl index 05587800b4..fda227f90f 100644 --- a/bootstrap/lib/compiler/egen/beam_opcodes.erl +++ b/bootstrap/lib/compiler/egen/beam_opcodes.erl @@ -8,7 +8,7 @@ -spec format_number() -> 0. format_number() -> 0. --spec opcode(atom(), 0..8) -> 1..151. +-spec opcode(atom(), 0..8) -> 1..152. opcode(label, 1) -> 1; opcode(func_info, 3) -> 2; opcode(int_code_end, 0) -> 3; @@ -118,7 +118,7 @@ opcode(try_case, 1) -> 106; opcode(try_case_end, 1) -> 107; opcode(raise, 2) -> 108; opcode(bs_init2, 6) -> 109; -opcode(bs_bits_to_bytes, 3) -> 110; +%%opcode(bs_bits_to_bytes, 3) -> 110; opcode(bs_add, 5) -> 111; opcode(apply, 1) -> 112; opcode(apply_last, 2) -> 113; @@ -160,9 +160,10 @@ opcode(bs_put_utf32, 3) -> 148; opcode(on_load, 0) -> 149; opcode(recv_mark, 1) -> 150; opcode(recv_set, 1) -> 151; +opcode(gc_bif3, 7) -> 152; opcode(Name, Arity) -> erlang:error(badarg, [Name,Arity]). --spec opname(1..151) -> {atom(),0..8}. +-spec opname(1..152) -> {atom(),0..8}. opname(1) -> {label,1}; opname(2) -> {func_info,3}; opname(3) -> {int_code_end,0}; @@ -314,4 +315,5 @@ opname(148) -> {bs_put_utf32,3}; opname(149) -> {on_load,0}; opname(150) -> {recv_mark,1}; opname(151) -> {recv_set,1}; +opname(152) -> {gc_bif3,7}; opname(Number) -> erlang:error(badarg, [Number]). diff --git a/bootstrap/lib/compiler/egen/core_parse.erl b/bootstrap/lib/compiler/egen/core_parse.erl index 093ab4d1a3..9740cb91bb 100644 --- a/bootstrap/lib/compiler/egen/core_parse.erl +++ b/bootstrap/lib/compiler/egen/core_parse.erl @@ -13,7 +13,7 @@ tok_val(T) -> element(3, T). tok_line(T) -> element(2, T). --file("/usr/local/lib/erlang/lib/parsetools-2.0.2/include/yeccpre.hrl", 0). +-file("/usr/local/otp/releases/otp_beam_linux_sles10_x64_r13b02_patched/lib/parsetools-2.0/include/yeccpre.hrl", 0). %% %% %CopyrightBegin% %% @@ -36,32 +36,33 @@ tok_line(T) -> element(2, T). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % The parser generator will insert appropriate declarations before this line.% --type yecc_ret() :: {'error', _} | {'ok', _}. +-type(yecc_ret() :: {'error', _} | {'ok', _}). -spec parse(Tokens :: list()) -> yecc_ret(). parse(Tokens) -> yeccpars0(Tokens, {no_func, no_line}, 0, [], []). --spec parse_and_scan({function() | {atom(), atom()}, [_]} | {atom(), atom(), [_]}) -> - yecc_ret(). +-spec(parse_and_scan/1 :: + ({function() | {atom(), atom()}, [_]} | {atom(), atom(), [_]}) -> + yecc_ret()). parse_and_scan({F, A}) -> % Fun or {M, F} yeccpars0([], {{F, A}, no_line}, 0, [], []); parse_and_scan({M, F, A}) -> yeccpars0([], {{{M, F}, A}, no_line}, 0, [], []). --spec format_error(any()) -> [char() | list()]. +-spec(format_error/1 :: (any()) -> [char() | list()]). format_error(Message) -> case io_lib:deep_char_list(Message) of - true -> - Message; - _ -> - io_lib:write(Message) + true -> + Message; + _ -> + io_lib:write(Message) end. -%% To be used in grammar files to throw an error message to the parser -%% toplevel. Doesn't have to be exported! --compile({nowarn_unused_function,{return_error,2}}). --spec return_error(integer(), any()) -> no_return(). +% To be used in grammar files to throw an error message to the parser +% toplevel. Doesn't have to be exported! +-compile({nowarn_unused_function, return_error/2}). +-spec(return_error/2 :: (integer(), any()) -> no_return()). return_error(Line, Message) -> throw({error, {Line, ?MODULE, Message}}). @@ -100,7 +101,7 @@ yeccpars1([Token | Tokens], Tzr, State, States, Vstack) -> yeccpars1([], {{F, A},_Line}, State, States, Vstack) -> case apply(F, A) of {ok, Tokens, Endline} -> - yeccpars1(Tokens, {{F, A}, Endline}, State, States, Vstack); + yeccpars1(Tokens, {{F, A}, Endline}, State, States, Vstack); {eof, Endline} -> yeccpars1([], {no_func, Endline}, State, States, Vstack); {error, Descriptor, _Endline} -> @@ -133,7 +134,7 @@ yeccpars1(State1, State, States, Vstack, Token0, [], {no_func, Line}) -> yeccpars2(State, '$end', [State1 | States], [Token0 | Vstack], yecc_end(Line), [], {no_func, Line}). -%% For internal use only. +% For internal use only. yecc_end({Line,_Column}) -> {'$end', Line}; yecc_end(Line) -> @@ -194,7 +195,7 @@ yecctoken2string(Other) -> --file("/Users/bjorng/Downloads/otp/bootstrap/lib/compiler/egen/core_parse.erl", 197). +-file("/ldisk/pan/git/otp/bootstrap/lib/compiler/egen/core_parse.erl", 198). yeccpars2(0=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_0(S, Cat, Ss, Stack, T, Ts, Tzr); diff --git a/bootstrap/lib/kernel/ebin/application_controller.beam b/bootstrap/lib/kernel/ebin/application_controller.beam Binary files differindex 093c2adbdf..113faeda01 100644 --- a/bootstrap/lib/kernel/ebin/application_controller.beam +++ b/bootstrap/lib/kernel/ebin/application_controller.beam diff --git a/bootstrap/lib/kernel/ebin/application_master.beam b/bootstrap/lib/kernel/ebin/application_master.beam Binary files differindex 1d82fcd682..602dc36104 100644 --- a/bootstrap/lib/kernel/ebin/application_master.beam +++ b/bootstrap/lib/kernel/ebin/application_master.beam diff --git a/bootstrap/lib/kernel/ebin/application_starter.beam b/bootstrap/lib/kernel/ebin/application_starter.beam Binary files differindex e33c9a5028..405d2f388a 100644 --- a/bootstrap/lib/kernel/ebin/application_starter.beam +++ b/bootstrap/lib/kernel/ebin/application_starter.beam diff --git a/bootstrap/lib/kernel/ebin/auth.beam b/bootstrap/lib/kernel/ebin/auth.beam Binary files differindex 9fc133efe7..3c038f87aa 100644 --- a/bootstrap/lib/kernel/ebin/auth.beam +++ b/bootstrap/lib/kernel/ebin/auth.beam diff --git a/bootstrap/lib/kernel/ebin/code.beam b/bootstrap/lib/kernel/ebin/code.beam Binary files differindex e82b91cf8e..bca88dfe7c 100644 --- a/bootstrap/lib/kernel/ebin/code.beam +++ b/bootstrap/lib/kernel/ebin/code.beam diff --git a/bootstrap/lib/kernel/ebin/code_server.beam b/bootstrap/lib/kernel/ebin/code_server.beam Binary files differindex 5bf9b13367..8f95f91348 100644 --- a/bootstrap/lib/kernel/ebin/code_server.beam +++ b/bootstrap/lib/kernel/ebin/code_server.beam diff --git a/bootstrap/lib/kernel/ebin/disk_log.beam b/bootstrap/lib/kernel/ebin/disk_log.beam Binary files differindex 86927f1125..b432811d6e 100644 --- a/bootstrap/lib/kernel/ebin/disk_log.beam +++ b/bootstrap/lib/kernel/ebin/disk_log.beam diff --git a/bootstrap/lib/kernel/ebin/disk_log_1.beam b/bootstrap/lib/kernel/ebin/disk_log_1.beam Binary files differindex 942e1188ac..96f4e1953f 100644 --- a/bootstrap/lib/kernel/ebin/disk_log_1.beam +++ b/bootstrap/lib/kernel/ebin/disk_log_1.beam diff --git a/bootstrap/lib/kernel/ebin/disk_log_server.beam b/bootstrap/lib/kernel/ebin/disk_log_server.beam Binary files differindex 46d7a9f546..089b5d578b 100644 --- a/bootstrap/lib/kernel/ebin/disk_log_server.beam +++ b/bootstrap/lib/kernel/ebin/disk_log_server.beam diff --git a/bootstrap/lib/kernel/ebin/dist_ac.beam b/bootstrap/lib/kernel/ebin/dist_ac.beam Binary files differindex 3060733116..59c75602ec 100644 --- a/bootstrap/lib/kernel/ebin/dist_ac.beam +++ b/bootstrap/lib/kernel/ebin/dist_ac.beam diff --git a/bootstrap/lib/kernel/ebin/dist_util.beam b/bootstrap/lib/kernel/ebin/dist_util.beam Binary files differindex d823f8d5c6..167a70e99d 100644 --- a/bootstrap/lib/kernel/ebin/dist_util.beam +++ b/bootstrap/lib/kernel/ebin/dist_util.beam diff --git a/bootstrap/lib/kernel/ebin/erl_boot_server.beam b/bootstrap/lib/kernel/ebin/erl_boot_server.beam Binary files differindex 52d638fb32..d88224b5d0 100644 --- a/bootstrap/lib/kernel/ebin/erl_boot_server.beam +++ b/bootstrap/lib/kernel/ebin/erl_boot_server.beam diff --git a/bootstrap/lib/kernel/ebin/erl_ddll.beam b/bootstrap/lib/kernel/ebin/erl_ddll.beam Binary files differindex 81daa5376b..c83b5e393c 100644 --- a/bootstrap/lib/kernel/ebin/erl_ddll.beam +++ b/bootstrap/lib/kernel/ebin/erl_ddll.beam diff --git a/bootstrap/lib/kernel/ebin/erl_epmd.beam b/bootstrap/lib/kernel/ebin/erl_epmd.beam Binary files differindex 9ba74e3303..09746d0403 100644 --- a/bootstrap/lib/kernel/ebin/erl_epmd.beam +++ b/bootstrap/lib/kernel/ebin/erl_epmd.beam diff --git a/bootstrap/lib/kernel/ebin/erts_debug.beam b/bootstrap/lib/kernel/ebin/erts_debug.beam Binary files differindex 26288e0633..9108d7e6d5 100644 --- a/bootstrap/lib/kernel/ebin/erts_debug.beam +++ b/bootstrap/lib/kernel/ebin/erts_debug.beam diff --git a/bootstrap/lib/kernel/ebin/file.beam b/bootstrap/lib/kernel/ebin/file.beam Binary files differindex 0f11da59e1..2504c16024 100644 --- a/bootstrap/lib/kernel/ebin/file.beam +++ b/bootstrap/lib/kernel/ebin/file.beam diff --git a/bootstrap/lib/kernel/ebin/file_io_server.beam b/bootstrap/lib/kernel/ebin/file_io_server.beam Binary files differindex 9b02183180..ce14722f4c 100644 --- a/bootstrap/lib/kernel/ebin/file_io_server.beam +++ b/bootstrap/lib/kernel/ebin/file_io_server.beam diff --git a/bootstrap/lib/kernel/ebin/file_server.beam b/bootstrap/lib/kernel/ebin/file_server.beam Binary files differindex 37e598a10f..70bdb58805 100644 --- a/bootstrap/lib/kernel/ebin/file_server.beam +++ b/bootstrap/lib/kernel/ebin/file_server.beam diff --git a/bootstrap/lib/kernel/ebin/gen_sctp.beam b/bootstrap/lib/kernel/ebin/gen_sctp.beam Binary files differindex f66b4635a2..a55e9090a5 100644 --- a/bootstrap/lib/kernel/ebin/gen_sctp.beam +++ b/bootstrap/lib/kernel/ebin/gen_sctp.beam diff --git a/bootstrap/lib/kernel/ebin/global.beam b/bootstrap/lib/kernel/ebin/global.beam Binary files differindex 4dc43450f0..20053eb877 100644 --- a/bootstrap/lib/kernel/ebin/global.beam +++ b/bootstrap/lib/kernel/ebin/global.beam diff --git a/bootstrap/lib/kernel/ebin/global_group.beam b/bootstrap/lib/kernel/ebin/global_group.beam Binary files differindex 6e9d6e8dfd..00d03a24f5 100644 --- a/bootstrap/lib/kernel/ebin/global_group.beam +++ b/bootstrap/lib/kernel/ebin/global_group.beam diff --git a/bootstrap/lib/kernel/ebin/global_search.beam b/bootstrap/lib/kernel/ebin/global_search.beam Binary files differindex 8270772543..fe18f8f49a 100644 --- a/bootstrap/lib/kernel/ebin/global_search.beam +++ b/bootstrap/lib/kernel/ebin/global_search.beam diff --git a/bootstrap/lib/kernel/ebin/group.beam b/bootstrap/lib/kernel/ebin/group.beam Binary files differindex 8e75aaef55..b72a13dbc7 100644 --- a/bootstrap/lib/kernel/ebin/group.beam +++ b/bootstrap/lib/kernel/ebin/group.beam diff --git a/bootstrap/lib/kernel/ebin/heart.beam b/bootstrap/lib/kernel/ebin/heart.beam Binary files differindex e2aa55f9b0..9d381acaff 100644 --- a/bootstrap/lib/kernel/ebin/heart.beam +++ b/bootstrap/lib/kernel/ebin/heart.beam diff --git a/bootstrap/lib/kernel/ebin/inet.beam b/bootstrap/lib/kernel/ebin/inet.beam Binary files differindex a8983e79c1..c712be3bdc 100644 --- a/bootstrap/lib/kernel/ebin/inet.beam +++ b/bootstrap/lib/kernel/ebin/inet.beam diff --git a/bootstrap/lib/kernel/ebin/inet6_tcp_dist.beam b/bootstrap/lib/kernel/ebin/inet6_tcp_dist.beam Binary files differindex 1f9ace4ad5..c2dac7dee1 100644 --- a/bootstrap/lib/kernel/ebin/inet6_tcp_dist.beam +++ b/bootstrap/lib/kernel/ebin/inet6_tcp_dist.beam diff --git a/bootstrap/lib/kernel/ebin/inet_config.beam b/bootstrap/lib/kernel/ebin/inet_config.beam Binary files differindex ad3f46010e..edc1e2f6fb 100644 --- a/bootstrap/lib/kernel/ebin/inet_config.beam +++ b/bootstrap/lib/kernel/ebin/inet_config.beam diff --git a/bootstrap/lib/kernel/ebin/inet_db.beam b/bootstrap/lib/kernel/ebin/inet_db.beam Binary files differindex 7009d9b9c1..2bba1e88c9 100644 --- a/bootstrap/lib/kernel/ebin/inet_db.beam +++ b/bootstrap/lib/kernel/ebin/inet_db.beam diff --git a/bootstrap/lib/kernel/ebin/inet_dns.beam b/bootstrap/lib/kernel/ebin/inet_dns.beam Binary files differindex 59e8936722..cfd46c29da 100644 --- a/bootstrap/lib/kernel/ebin/inet_dns.beam +++ b/bootstrap/lib/kernel/ebin/inet_dns.beam diff --git a/bootstrap/lib/kernel/ebin/inet_gethost_native.beam b/bootstrap/lib/kernel/ebin/inet_gethost_native.beam Binary files differindex e712d25405..0b901a8f27 100644 --- a/bootstrap/lib/kernel/ebin/inet_gethost_native.beam +++ b/bootstrap/lib/kernel/ebin/inet_gethost_native.beam diff --git a/bootstrap/lib/kernel/ebin/inet_parse.beam b/bootstrap/lib/kernel/ebin/inet_parse.beam Binary files differindex 926f21e99c..914397158a 100644 --- a/bootstrap/lib/kernel/ebin/inet_parse.beam +++ b/bootstrap/lib/kernel/ebin/inet_parse.beam diff --git a/bootstrap/lib/kernel/ebin/inet_res.beam b/bootstrap/lib/kernel/ebin/inet_res.beam Binary files differindex 3b6f72aaa0..0167a10b36 100644 --- a/bootstrap/lib/kernel/ebin/inet_res.beam +++ b/bootstrap/lib/kernel/ebin/inet_res.beam diff --git a/bootstrap/lib/kernel/ebin/inet_tcp_dist.beam b/bootstrap/lib/kernel/ebin/inet_tcp_dist.beam Binary files differindex 07f6ad2521..6c98977be5 100644 --- a/bootstrap/lib/kernel/ebin/inet_tcp_dist.beam +++ b/bootstrap/lib/kernel/ebin/inet_tcp_dist.beam diff --git a/bootstrap/lib/kernel/ebin/inet_udp.beam b/bootstrap/lib/kernel/ebin/inet_udp.beam Binary files differindex 2ac9537087..7bd4848f07 100644 --- a/bootstrap/lib/kernel/ebin/inet_udp.beam +++ b/bootstrap/lib/kernel/ebin/inet_udp.beam diff --git a/bootstrap/lib/kernel/ebin/kernel.beam b/bootstrap/lib/kernel/ebin/kernel.beam Binary files differindex 3eea9dda68..471ab154e8 100644 --- a/bootstrap/lib/kernel/ebin/kernel.beam +++ b/bootstrap/lib/kernel/ebin/kernel.beam diff --git a/bootstrap/lib/kernel/ebin/kernel_config.beam b/bootstrap/lib/kernel/ebin/kernel_config.beam Binary files differindex 73431675bf..da5fa04de5 100644 --- a/bootstrap/lib/kernel/ebin/kernel_config.beam +++ b/bootstrap/lib/kernel/ebin/kernel_config.beam diff --git a/bootstrap/lib/kernel/ebin/net_adm.beam b/bootstrap/lib/kernel/ebin/net_adm.beam Binary files differindex 02b6c8b486..21d54cac85 100644 --- a/bootstrap/lib/kernel/ebin/net_adm.beam +++ b/bootstrap/lib/kernel/ebin/net_adm.beam diff --git a/bootstrap/lib/kernel/ebin/net_kernel.beam b/bootstrap/lib/kernel/ebin/net_kernel.beam Binary files differindex 50b4c7ac4c..562ee24678 100644 --- a/bootstrap/lib/kernel/ebin/net_kernel.beam +++ b/bootstrap/lib/kernel/ebin/net_kernel.beam diff --git a/bootstrap/lib/kernel/ebin/os.beam b/bootstrap/lib/kernel/ebin/os.beam Binary files differindex 4a4051d0c1..4b3bcf80dd 100644 --- a/bootstrap/lib/kernel/ebin/os.beam +++ b/bootstrap/lib/kernel/ebin/os.beam diff --git a/bootstrap/lib/kernel/ebin/packages.beam b/bootstrap/lib/kernel/ebin/packages.beam Binary files differindex 710fc55908..cbe6bc6590 100644 --- a/bootstrap/lib/kernel/ebin/packages.beam +++ b/bootstrap/lib/kernel/ebin/packages.beam diff --git a/bootstrap/lib/kernel/ebin/pg2.beam b/bootstrap/lib/kernel/ebin/pg2.beam Binary files differindex 51912f2263..b14425b01c 100644 --- a/bootstrap/lib/kernel/ebin/pg2.beam +++ b/bootstrap/lib/kernel/ebin/pg2.beam diff --git a/bootstrap/lib/kernel/ebin/ram_file.beam b/bootstrap/lib/kernel/ebin/ram_file.beam Binary files differindex f9488df846..402c7ea2d3 100644 --- a/bootstrap/lib/kernel/ebin/ram_file.beam +++ b/bootstrap/lib/kernel/ebin/ram_file.beam diff --git a/bootstrap/lib/kernel/ebin/rpc.beam b/bootstrap/lib/kernel/ebin/rpc.beam Binary files differindex 1763808337..5fe7a7fffe 100644 --- a/bootstrap/lib/kernel/ebin/rpc.beam +++ b/bootstrap/lib/kernel/ebin/rpc.beam diff --git a/bootstrap/lib/kernel/ebin/standard_error.beam b/bootstrap/lib/kernel/ebin/standard_error.beam Binary files differindex 95edb1860a..62ab951bff 100644 --- a/bootstrap/lib/kernel/ebin/standard_error.beam +++ b/bootstrap/lib/kernel/ebin/standard_error.beam diff --git a/bootstrap/lib/kernel/ebin/user.beam b/bootstrap/lib/kernel/ebin/user.beam Binary files differindex 0f2fe211ab..76e9973a93 100644 --- a/bootstrap/lib/kernel/ebin/user.beam +++ b/bootstrap/lib/kernel/ebin/user.beam diff --git a/bootstrap/lib/kernel/ebin/user_drv.beam b/bootstrap/lib/kernel/ebin/user_drv.beam Binary files differindex 3feffb9332..ddb0442f28 100644 --- a/bootstrap/lib/kernel/ebin/user_drv.beam +++ b/bootstrap/lib/kernel/ebin/user_drv.beam diff --git a/bootstrap/lib/kernel/ebin/wrap_log_reader.beam b/bootstrap/lib/kernel/ebin/wrap_log_reader.beam Binary files differindex cdb87c3fd5..9f648c9a42 100644 --- a/bootstrap/lib/kernel/ebin/wrap_log_reader.beam +++ b/bootstrap/lib/kernel/ebin/wrap_log_reader.beam diff --git a/bootstrap/lib/stdlib/ebin/array.beam b/bootstrap/lib/stdlib/ebin/array.beam Binary files differindex a3049ae953..1c26f19403 100644 --- a/bootstrap/lib/stdlib/ebin/array.beam +++ b/bootstrap/lib/stdlib/ebin/array.beam diff --git a/bootstrap/lib/stdlib/ebin/base64.beam b/bootstrap/lib/stdlib/ebin/base64.beam Binary files differindex 101ffc3904..d48d8d6f58 100644 --- a/bootstrap/lib/stdlib/ebin/base64.beam +++ b/bootstrap/lib/stdlib/ebin/base64.beam diff --git a/bootstrap/lib/stdlib/ebin/beam_lib.beam b/bootstrap/lib/stdlib/ebin/beam_lib.beam Binary files differindex 58123d66be..e7ba44d542 100644 --- a/bootstrap/lib/stdlib/ebin/beam_lib.beam +++ b/bootstrap/lib/stdlib/ebin/beam_lib.beam diff --git a/bootstrap/lib/stdlib/ebin/binary.beam b/bootstrap/lib/stdlib/ebin/binary.beam Binary files differnew file mode 100644 index 0000000000..5180c122d2 --- /dev/null +++ b/bootstrap/lib/stdlib/ebin/binary.beam diff --git a/bootstrap/lib/stdlib/ebin/c.beam b/bootstrap/lib/stdlib/ebin/c.beam Binary files differindex ea36930179..099bb5ad90 100644 --- a/bootstrap/lib/stdlib/ebin/c.beam +++ b/bootstrap/lib/stdlib/ebin/c.beam diff --git a/bootstrap/lib/stdlib/ebin/dets.beam b/bootstrap/lib/stdlib/ebin/dets.beam Binary files differindex 8b637ab064..ccd277b7ad 100644 --- a/bootstrap/lib/stdlib/ebin/dets.beam +++ b/bootstrap/lib/stdlib/ebin/dets.beam diff --git a/bootstrap/lib/stdlib/ebin/dets_utils.beam b/bootstrap/lib/stdlib/ebin/dets_utils.beam Binary files differindex 36d38c4e9a..b0c3e351d2 100644 --- a/bootstrap/lib/stdlib/ebin/dets_utils.beam +++ b/bootstrap/lib/stdlib/ebin/dets_utils.beam diff --git a/bootstrap/lib/stdlib/ebin/dets_v8.beam b/bootstrap/lib/stdlib/ebin/dets_v8.beam Binary files differindex 6537f0e44d..f48768ca53 100644 --- a/bootstrap/lib/stdlib/ebin/dets_v8.beam +++ b/bootstrap/lib/stdlib/ebin/dets_v8.beam diff --git a/bootstrap/lib/stdlib/ebin/dets_v9.beam b/bootstrap/lib/stdlib/ebin/dets_v9.beam Binary files differindex 2a247effc1..0694437929 100644 --- a/bootstrap/lib/stdlib/ebin/dets_v9.beam +++ b/bootstrap/lib/stdlib/ebin/dets_v9.beam diff --git a/bootstrap/lib/stdlib/ebin/dict.beam b/bootstrap/lib/stdlib/ebin/dict.beam Binary files differindex 3d0b4194b6..ebf01cb9b5 100644 --- a/bootstrap/lib/stdlib/ebin/dict.beam +++ b/bootstrap/lib/stdlib/ebin/dict.beam diff --git a/bootstrap/lib/stdlib/ebin/digraph.beam b/bootstrap/lib/stdlib/ebin/digraph.beam Binary files differindex 41043bf729..38195e8957 100644 --- a/bootstrap/lib/stdlib/ebin/digraph.beam +++ b/bootstrap/lib/stdlib/ebin/digraph.beam diff --git a/bootstrap/lib/stdlib/ebin/digraph_utils.beam b/bootstrap/lib/stdlib/ebin/digraph_utils.beam Binary files differindex e0c15a218a..ce329ab2ab 100644 --- a/bootstrap/lib/stdlib/ebin/digraph_utils.beam +++ b/bootstrap/lib/stdlib/ebin/digraph_utils.beam diff --git a/bootstrap/lib/stdlib/ebin/edlin.beam b/bootstrap/lib/stdlib/ebin/edlin.beam Binary files differindex fe51f7f417..9fc93d042c 100644 --- a/bootstrap/lib/stdlib/ebin/edlin.beam +++ b/bootstrap/lib/stdlib/ebin/edlin.beam diff --git a/bootstrap/lib/stdlib/ebin/epp.beam b/bootstrap/lib/stdlib/ebin/epp.beam Binary files differindex 71e0d21db1..8059231d6e 100644 --- a/bootstrap/lib/stdlib/ebin/epp.beam +++ b/bootstrap/lib/stdlib/ebin/epp.beam diff --git a/bootstrap/lib/stdlib/ebin/erl_bits.beam b/bootstrap/lib/stdlib/ebin/erl_bits.beam Binary files differindex 7b1f8bb764..d338fe3778 100644 --- a/bootstrap/lib/stdlib/ebin/erl_bits.beam +++ b/bootstrap/lib/stdlib/ebin/erl_bits.beam diff --git a/bootstrap/lib/stdlib/ebin/erl_compile.beam b/bootstrap/lib/stdlib/ebin/erl_compile.beam Binary files differindex cc4ef260d0..3f0cb585e8 100644 --- a/bootstrap/lib/stdlib/ebin/erl_compile.beam +++ b/bootstrap/lib/stdlib/ebin/erl_compile.beam diff --git a/bootstrap/lib/stdlib/ebin/erl_eval.beam b/bootstrap/lib/stdlib/ebin/erl_eval.beam Binary files differindex dfd10fb4bf..cac3a46319 100644 --- a/bootstrap/lib/stdlib/ebin/erl_eval.beam +++ b/bootstrap/lib/stdlib/ebin/erl_eval.beam diff --git a/bootstrap/lib/stdlib/ebin/erl_expand_records.beam b/bootstrap/lib/stdlib/ebin/erl_expand_records.beam Binary files differindex 2a2ec9b4e1..983fe23879 100644 --- a/bootstrap/lib/stdlib/ebin/erl_expand_records.beam +++ b/bootstrap/lib/stdlib/ebin/erl_expand_records.beam diff --git a/bootstrap/lib/stdlib/ebin/erl_internal.beam b/bootstrap/lib/stdlib/ebin/erl_internal.beam Binary files differindex 1609ae4997..588f761199 100644 --- a/bootstrap/lib/stdlib/ebin/erl_internal.beam +++ b/bootstrap/lib/stdlib/ebin/erl_internal.beam diff --git a/bootstrap/lib/stdlib/ebin/erl_lint.beam b/bootstrap/lib/stdlib/ebin/erl_lint.beam Binary files differindex 21ec2e638f..1f1c43242d 100644 --- a/bootstrap/lib/stdlib/ebin/erl_lint.beam +++ b/bootstrap/lib/stdlib/ebin/erl_lint.beam diff --git a/bootstrap/lib/stdlib/ebin/erl_parse.beam b/bootstrap/lib/stdlib/ebin/erl_parse.beam Binary files differindex 1a61eb31ac..426bd23e36 100644 --- a/bootstrap/lib/stdlib/ebin/erl_parse.beam +++ b/bootstrap/lib/stdlib/ebin/erl_parse.beam diff --git a/bootstrap/lib/stdlib/ebin/erl_pp.beam b/bootstrap/lib/stdlib/ebin/erl_pp.beam Binary files differindex 161db6cef0..2b6f0a5bd6 100644 --- a/bootstrap/lib/stdlib/ebin/erl_pp.beam +++ b/bootstrap/lib/stdlib/ebin/erl_pp.beam diff --git a/bootstrap/lib/stdlib/ebin/erl_scan.beam b/bootstrap/lib/stdlib/ebin/erl_scan.beam Binary files differindex 0b04a411b5..cffdda03ae 100644 --- a/bootstrap/lib/stdlib/ebin/erl_scan.beam +++ b/bootstrap/lib/stdlib/ebin/erl_scan.beam diff --git a/bootstrap/lib/stdlib/ebin/erl_tar.beam b/bootstrap/lib/stdlib/ebin/erl_tar.beam Binary files differindex 40d41d0202..369e5a8c6d 100644 --- a/bootstrap/lib/stdlib/ebin/erl_tar.beam +++ b/bootstrap/lib/stdlib/ebin/erl_tar.beam diff --git a/bootstrap/lib/stdlib/ebin/error_logger_file_h.beam b/bootstrap/lib/stdlib/ebin/error_logger_file_h.beam Binary files differindex ae5a7020ea..13a91def18 100644 --- a/bootstrap/lib/stdlib/ebin/error_logger_file_h.beam +++ b/bootstrap/lib/stdlib/ebin/error_logger_file_h.beam diff --git a/bootstrap/lib/stdlib/ebin/error_logger_tty_h.beam b/bootstrap/lib/stdlib/ebin/error_logger_tty_h.beam Binary files differindex 6065cfa8f1..50dd448b1b 100644 --- a/bootstrap/lib/stdlib/ebin/error_logger_tty_h.beam +++ b/bootstrap/lib/stdlib/ebin/error_logger_tty_h.beam diff --git a/bootstrap/lib/stdlib/ebin/escript.beam b/bootstrap/lib/stdlib/ebin/escript.beam Binary files differindex 3c4b3f4eb6..15f1cb8354 100644 --- a/bootstrap/lib/stdlib/ebin/escript.beam +++ b/bootstrap/lib/stdlib/ebin/escript.beam diff --git a/bootstrap/lib/stdlib/ebin/ets.beam b/bootstrap/lib/stdlib/ebin/ets.beam Binary files differindex 70916dd1ae..bcb1b9684c 100644 --- a/bootstrap/lib/stdlib/ebin/ets.beam +++ b/bootstrap/lib/stdlib/ebin/ets.beam diff --git a/bootstrap/lib/stdlib/ebin/eval_bits.beam b/bootstrap/lib/stdlib/ebin/eval_bits.beam Binary files differindex de2d528f2e..1db24ccfc9 100644 --- a/bootstrap/lib/stdlib/ebin/eval_bits.beam +++ b/bootstrap/lib/stdlib/ebin/eval_bits.beam diff --git a/bootstrap/lib/stdlib/ebin/file_sorter.beam b/bootstrap/lib/stdlib/ebin/file_sorter.beam Binary files differindex f03cd21072..c27d4e141f 100644 --- a/bootstrap/lib/stdlib/ebin/file_sorter.beam +++ b/bootstrap/lib/stdlib/ebin/file_sorter.beam diff --git a/bootstrap/lib/stdlib/ebin/filelib.beam b/bootstrap/lib/stdlib/ebin/filelib.beam Binary files differindex e03b753c09..8a59a62379 100644 --- a/bootstrap/lib/stdlib/ebin/filelib.beam +++ b/bootstrap/lib/stdlib/ebin/filelib.beam diff --git a/bootstrap/lib/stdlib/ebin/filename.beam b/bootstrap/lib/stdlib/ebin/filename.beam Binary files differindex f419480f6e..4cdb6064d2 100644 --- a/bootstrap/lib/stdlib/ebin/filename.beam +++ b/bootstrap/lib/stdlib/ebin/filename.beam diff --git a/bootstrap/lib/stdlib/ebin/gb_sets.beam b/bootstrap/lib/stdlib/ebin/gb_sets.beam Binary files differindex 1ce3e0bd83..1928430e56 100644 --- a/bootstrap/lib/stdlib/ebin/gb_sets.beam +++ b/bootstrap/lib/stdlib/ebin/gb_sets.beam diff --git a/bootstrap/lib/stdlib/ebin/gb_trees.beam b/bootstrap/lib/stdlib/ebin/gb_trees.beam Binary files differindex c462bee0c1..7bebf1a54e 100644 --- a/bootstrap/lib/stdlib/ebin/gb_trees.beam +++ b/bootstrap/lib/stdlib/ebin/gb_trees.beam diff --git a/bootstrap/lib/stdlib/ebin/gen.beam b/bootstrap/lib/stdlib/ebin/gen.beam Binary files differindex 4b047eb838..3ad4d282f0 100644 --- a/bootstrap/lib/stdlib/ebin/gen.beam +++ b/bootstrap/lib/stdlib/ebin/gen.beam diff --git a/bootstrap/lib/stdlib/ebin/gen_event.beam b/bootstrap/lib/stdlib/ebin/gen_event.beam Binary files differindex 69a18e0c6e..264071fe5f 100644 --- a/bootstrap/lib/stdlib/ebin/gen_event.beam +++ b/bootstrap/lib/stdlib/ebin/gen_event.beam diff --git a/bootstrap/lib/stdlib/ebin/gen_fsm.beam b/bootstrap/lib/stdlib/ebin/gen_fsm.beam Binary files differindex 4d7528be15..73df87a7ef 100644 --- a/bootstrap/lib/stdlib/ebin/gen_fsm.beam +++ b/bootstrap/lib/stdlib/ebin/gen_fsm.beam diff --git a/bootstrap/lib/stdlib/ebin/gen_server.beam b/bootstrap/lib/stdlib/ebin/gen_server.beam Binary files differindex 8d76d138f1..3b066ada0b 100644 --- a/bootstrap/lib/stdlib/ebin/gen_server.beam +++ b/bootstrap/lib/stdlib/ebin/gen_server.beam diff --git a/bootstrap/lib/stdlib/ebin/io.beam b/bootstrap/lib/stdlib/ebin/io.beam Binary files differindex a9f24817b3..36c276166c 100644 --- a/bootstrap/lib/stdlib/ebin/io.beam +++ b/bootstrap/lib/stdlib/ebin/io.beam diff --git a/bootstrap/lib/stdlib/ebin/io_lib.beam b/bootstrap/lib/stdlib/ebin/io_lib.beam Binary files differindex af10b8d0e9..c0af612d77 100644 --- a/bootstrap/lib/stdlib/ebin/io_lib.beam +++ b/bootstrap/lib/stdlib/ebin/io_lib.beam diff --git a/bootstrap/lib/stdlib/ebin/io_lib_format.beam b/bootstrap/lib/stdlib/ebin/io_lib_format.beam Binary files differindex 16907bb3aa..25f8f7b37a 100644 --- a/bootstrap/lib/stdlib/ebin/io_lib_format.beam +++ b/bootstrap/lib/stdlib/ebin/io_lib_format.beam diff --git a/bootstrap/lib/stdlib/ebin/io_lib_pretty.beam b/bootstrap/lib/stdlib/ebin/io_lib_pretty.beam Binary files differindex 6b8b7a3267..9a8b68c6e0 100644 --- a/bootstrap/lib/stdlib/ebin/io_lib_pretty.beam +++ b/bootstrap/lib/stdlib/ebin/io_lib_pretty.beam diff --git a/bootstrap/lib/stdlib/ebin/lib.beam b/bootstrap/lib/stdlib/ebin/lib.beam Binary files differindex d7e591ed35..fb08310f36 100644 --- a/bootstrap/lib/stdlib/ebin/lib.beam +++ b/bootstrap/lib/stdlib/ebin/lib.beam diff --git a/bootstrap/lib/stdlib/ebin/lists.beam b/bootstrap/lib/stdlib/ebin/lists.beam Binary files differindex c4ad32a7ec..153d6fb2ee 100644 --- a/bootstrap/lib/stdlib/ebin/lists.beam +++ b/bootstrap/lib/stdlib/ebin/lists.beam diff --git a/bootstrap/lib/stdlib/ebin/ms_transform.beam b/bootstrap/lib/stdlib/ebin/ms_transform.beam Binary files differindex 0dc991553b..918c2537ea 100644 --- a/bootstrap/lib/stdlib/ebin/ms_transform.beam +++ b/bootstrap/lib/stdlib/ebin/ms_transform.beam diff --git a/bootstrap/lib/stdlib/ebin/otp_internal.beam b/bootstrap/lib/stdlib/ebin/otp_internal.beam Binary files differindex e2550578e7..cac4a97ae0 100644 --- a/bootstrap/lib/stdlib/ebin/otp_internal.beam +++ b/bootstrap/lib/stdlib/ebin/otp_internal.beam diff --git a/bootstrap/lib/stdlib/ebin/pg.beam b/bootstrap/lib/stdlib/ebin/pg.beam Binary files differindex aadf03ca28..026d102a75 100644 --- a/bootstrap/lib/stdlib/ebin/pg.beam +++ b/bootstrap/lib/stdlib/ebin/pg.beam diff --git a/bootstrap/lib/stdlib/ebin/pool.beam b/bootstrap/lib/stdlib/ebin/pool.beam Binary files differindex 35c2da9051..74377d404b 100644 --- a/bootstrap/lib/stdlib/ebin/pool.beam +++ b/bootstrap/lib/stdlib/ebin/pool.beam diff --git a/bootstrap/lib/stdlib/ebin/proc_lib.beam b/bootstrap/lib/stdlib/ebin/proc_lib.beam Binary files differindex c366aafadf..f6c7a4f748 100644 --- a/bootstrap/lib/stdlib/ebin/proc_lib.beam +++ b/bootstrap/lib/stdlib/ebin/proc_lib.beam diff --git a/bootstrap/lib/stdlib/ebin/proplists.beam b/bootstrap/lib/stdlib/ebin/proplists.beam Binary files differindex 378392c791..bed96c6b1a 100644 --- a/bootstrap/lib/stdlib/ebin/proplists.beam +++ b/bootstrap/lib/stdlib/ebin/proplists.beam diff --git a/bootstrap/lib/stdlib/ebin/qlc.beam b/bootstrap/lib/stdlib/ebin/qlc.beam Binary files differindex 27dd5d5be0..2e05a4c546 100644 --- a/bootstrap/lib/stdlib/ebin/qlc.beam +++ b/bootstrap/lib/stdlib/ebin/qlc.beam diff --git a/bootstrap/lib/stdlib/ebin/qlc_pt.beam b/bootstrap/lib/stdlib/ebin/qlc_pt.beam Binary files differindex 10349ebd1a..92b1bc8bee 100644 --- a/bootstrap/lib/stdlib/ebin/qlc_pt.beam +++ b/bootstrap/lib/stdlib/ebin/qlc_pt.beam diff --git a/bootstrap/lib/stdlib/ebin/re.beam b/bootstrap/lib/stdlib/ebin/re.beam Binary files differindex 06c02fc10c..fb8888ad58 100644 --- a/bootstrap/lib/stdlib/ebin/re.beam +++ b/bootstrap/lib/stdlib/ebin/re.beam diff --git a/bootstrap/lib/stdlib/ebin/regexp.beam b/bootstrap/lib/stdlib/ebin/regexp.beam Binary files differindex f45899702c..d021c5779b 100644 --- a/bootstrap/lib/stdlib/ebin/regexp.beam +++ b/bootstrap/lib/stdlib/ebin/regexp.beam diff --git a/bootstrap/lib/stdlib/ebin/sets.beam b/bootstrap/lib/stdlib/ebin/sets.beam Binary files differindex b8ce3ebe89..9be4e8ab5b 100644 --- a/bootstrap/lib/stdlib/ebin/sets.beam +++ b/bootstrap/lib/stdlib/ebin/sets.beam diff --git a/bootstrap/lib/stdlib/ebin/shell.beam b/bootstrap/lib/stdlib/ebin/shell.beam Binary files differindex a537d0936e..737223aaf8 100644 --- a/bootstrap/lib/stdlib/ebin/shell.beam +++ b/bootstrap/lib/stdlib/ebin/shell.beam diff --git a/bootstrap/lib/stdlib/ebin/sofs.beam b/bootstrap/lib/stdlib/ebin/sofs.beam Binary files differindex d8e984f217..51af30d7e8 100644 --- a/bootstrap/lib/stdlib/ebin/sofs.beam +++ b/bootstrap/lib/stdlib/ebin/sofs.beam diff --git a/bootstrap/lib/stdlib/ebin/stdlib.app b/bootstrap/lib/stdlib/ebin/stdlib.app index b7cb765910..880e85627e 100644 --- a/bootstrap/lib/stdlib/ebin/stdlib.app +++ b/bootstrap/lib/stdlib/ebin/stdlib.app @@ -23,6 +23,7 @@ {modules, [array, base64, beam_lib, + binary, c, calendar, dets, diff --git a/bootstrap/lib/stdlib/ebin/supervisor.beam b/bootstrap/lib/stdlib/ebin/supervisor.beam Binary files differindex 0526384a20..9cbf8912fe 100644 --- a/bootstrap/lib/stdlib/ebin/supervisor.beam +++ b/bootstrap/lib/stdlib/ebin/supervisor.beam diff --git a/bootstrap/lib/stdlib/ebin/sys.beam b/bootstrap/lib/stdlib/ebin/sys.beam Binary files differindex 5a75152833..e08ec9e70d 100644 --- a/bootstrap/lib/stdlib/ebin/sys.beam +++ b/bootstrap/lib/stdlib/ebin/sys.beam diff --git a/bootstrap/lib/stdlib/ebin/timer.beam b/bootstrap/lib/stdlib/ebin/timer.beam Binary files differindex 9d9d42f96d..a20cc97935 100644 --- a/bootstrap/lib/stdlib/ebin/timer.beam +++ b/bootstrap/lib/stdlib/ebin/timer.beam diff --git a/bootstrap/lib/stdlib/ebin/unicode.beam b/bootstrap/lib/stdlib/ebin/unicode.beam Binary files differindex 66d6991283..4ca769b9a2 100644 --- a/bootstrap/lib/stdlib/ebin/unicode.beam +++ b/bootstrap/lib/stdlib/ebin/unicode.beam diff --git a/bootstrap/lib/stdlib/ebin/zip.beam b/bootstrap/lib/stdlib/ebin/zip.beam Binary files differindex f0df604d79..6c5b31cd1f 100644 --- a/bootstrap/lib/stdlib/ebin/zip.beam +++ b/bootstrap/lib/stdlib/ebin/zip.beam diff --git a/bootstrap/lib/stdlib/egen/erl_parse.erl b/bootstrap/lib/stdlib/egen/erl_parse.erl index 2f985589c9..46456b51c5 100644 --- a/bootstrap/lib/stdlib/egen/erl_parse.erl +++ b/bootstrap/lib/stdlib/egen/erl_parse.erl @@ -1,6 +1,6 @@ -module(erl_parse). -export([parse/1, parse_and_scan/1, format_error/1]). --file("erl_parse.yrl", 491). +-file("erl_parse.yrl", 502). -export([parse_form/1,parse_exprs/1,parse_term/1]). -export([normalise/1,abstract/1,tokens/1,tokens/2]). @@ -109,11 +109,20 @@ find_arity_from_specs([Spec|_]) -> {type, _, 'fun', [{type, _, product, Args},_]} = Fun, length(Args). +build_def(LHS, Types) -> + IsSubType = {atom, ?line(LHS), is_subtype}, + {type, ?line(LHS), constraint, [IsSubType, [LHS, Types]]}. + lift_unions(T1, {type, _La, union, List}) -> {type, ?line(T1), union, [T1|List]}; lift_unions(T1, T2) -> {type, ?line(T1), union, [T1, T2]}. +skip_paren({paren_type,_L,[Type]}) -> + skip_paren(Type); +skip_paren(Type) -> + Type. + build_gen_type({atom, La, tuple}) -> {type, La, tuple, any}; build_gen_type({atom, La, Name}) -> @@ -122,7 +131,7 @@ build_gen_type({atom, La, Name}) -> build_bin_type([{var, _, '_'}|Left], Int) -> build_bin_type(Left, Int); build_bin_type([], Int) -> - Int; + skip_paren(Int); build_bin_type([{var, La, _}|_], _) -> ret_err(La, "Bad binary type"). @@ -547,7 +556,7 @@ get_attribute(L, Name) -> get_attributes(L) -> erl_scan:attributes_info(L). --file("/usr/local/lib/erlang/lib/parsetools-2.0.2/include/yeccpre.hrl", 0). +-file("/usr/local/otp/releases/otp_beam_linux_sles10_x64_r13b02_patched/lib/parsetools-2.0/include/yeccpre.hrl", 0). %% %% %CopyrightBegin% %% @@ -570,32 +579,33 @@ get_attributes(L) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % The parser generator will insert appropriate declarations before this line.% --type yecc_ret() :: {'error', _} | {'ok', _}. +-type(yecc_ret() :: {'error', _} | {'ok', _}). -spec parse(Tokens :: list()) -> yecc_ret(). parse(Tokens) -> yeccpars0(Tokens, {no_func, no_line}, 0, [], []). --spec parse_and_scan({function() | {atom(), atom()}, [_]} | {atom(), atom(), [_]}) -> - yecc_ret(). +-spec(parse_and_scan/1 :: + ({function() | {atom(), atom()}, [_]} | {atom(), atom(), [_]}) -> + yecc_ret()). parse_and_scan({F, A}) -> % Fun or {M, F} yeccpars0([], {{F, A}, no_line}, 0, [], []); parse_and_scan({M, F, A}) -> yeccpars0([], {{{M, F}, A}, no_line}, 0, [], []). --spec format_error(any()) -> [char() | list()]. +-spec(format_error/1 :: (any()) -> [char() | list()]). format_error(Message) -> case io_lib:deep_char_list(Message) of - true -> - Message; - _ -> - io_lib:write(Message) + true -> + Message; + _ -> + io_lib:write(Message) end. -%% To be used in grammar files to throw an error message to the parser -%% toplevel. Doesn't have to be exported! --compile({nowarn_unused_function,{return_error,2}}). --spec return_error(integer(), any()) -> no_return(). +% To be used in grammar files to throw an error message to the parser +% toplevel. Doesn't have to be exported! +-compile({nowarn_unused_function, return_error/2}). +-spec(return_error/2 :: (integer(), any()) -> no_return()). return_error(Line, Message) -> throw({error, {Line, ?MODULE, Message}}). @@ -634,7 +644,7 @@ yeccpars1([Token | Tokens], Tzr, State, States, Vstack) -> yeccpars1([], {{F, A},_Line}, State, States, Vstack) -> case apply(F, A) of {ok, Tokens, Endline} -> - yeccpars1(Tokens, {{F, A}, Endline}, State, States, Vstack); + yeccpars1(Tokens, {{F, A}, Endline}, State, States, Vstack); {eof, Endline} -> yeccpars1([], {no_func, Endline}, State, States, Vstack); {error, Descriptor, _Endline} -> @@ -667,7 +677,7 @@ yeccpars1(State1, State, States, Vstack, Token0, [], {no_func, Line}) -> yeccpars2(State, '$end', [State1 | States], [Token0 | Vstack], yecc_end(Line), [], {no_func, Line}). -%% For internal use only. +% For internal use only. yecc_end({Line,_Column}) -> {'$end', Line}; yecc_end(Line) -> @@ -728,7 +738,7 @@ yecctoken2string(Other) -> --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 731). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 741). yeccpars2(0=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_0(S, Cat, Ss, Stack, T, Ts, Tzr); @@ -1364,14 +1374,14 @@ yeccpars2(310=S, Cat, Ss, Stack, T, Ts, Tzr) -> %% yeccpars2_315(S, Cat, Ss, Stack, T, Ts, Tzr); %% yeccpars2(316=S, Cat, Ss, Stack, T, Ts, Tzr) -> %% yeccpars2_316(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(317=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_317(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(318=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_318(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(319=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_319(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(320=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_320(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(317=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_317(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(318=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_318(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(319=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_319(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(320=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_320(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(321=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_321(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(322=S, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -1386,30 +1396,30 @@ yeccpars2(326=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_326(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(327=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_327(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(328=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_328(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(328=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_328(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(329=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_329(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(330=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_330(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(331=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_331(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(332=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_332(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(331=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_331(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(332=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_332(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(333=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_333(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(334=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_334(S, Cat, Ss, Stack, T, Ts, Tzr); %% yeccpars2(335=S, Cat, Ss, Stack, T, Ts, Tzr) -> %% yeccpars2_335(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(336=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_336(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(336=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_336(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(337=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_337(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(338=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_338(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(339=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_339(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(338=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_338(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(339=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_339(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(340=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_340(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(341=S, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -1417,11 +1427,11 @@ yeccpars2(341=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2(342=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_342(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(343=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_318(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(344=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_344(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(345=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_345(S, Cat, Ss, Stack, T, Ts, Tzr); + yeccpars2_343(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(344=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_322(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(345=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_345(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(346=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_346(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(347=S, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -1430,22 +1440,22 @@ yeccpars2(348=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_348(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(349=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_349(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(350=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_350(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(351=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_351(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(350=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_350(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(351=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_351(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(352=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_352(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(353=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_353(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(354=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_354(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(353=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_353(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(354=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_354(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(355=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_355(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(356=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_356(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(357=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_357(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(356=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_356(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(357=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_357(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(358=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_358(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(359=S, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -1456,32 +1466,32 @@ yeccpars2(361=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_361(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(362=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_362(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(363=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_363(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(363=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_363(S, Cat, Ss, Stack, T, Ts, Tzr); %% yeccpars2(364=S, Cat, Ss, Stack, T, Ts, Tzr) -> %% yeccpars2_364(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(365=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_365(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(365=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_365(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(366=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_366(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(367=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_367(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(368=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_368(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(368=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_368(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(369=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_369(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(370=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_370(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(371=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_371(S, Cat, Ss, Stack, T, Ts, Tzr); + yeccpars2_319(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(371=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_371(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(372=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_372(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(373=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_373(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(374=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_374(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(375=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_375(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(374=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_374(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(375=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_375(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(376=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_376(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(377=S, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -1491,151 +1501,151 @@ yeccpars2(378=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2(379=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_379(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(380=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_380(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(381=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_318(S, Cat, Ss, Stack, T, Ts, Tzr); + yeccpars2_322(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(381=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_381(S, Cat, Ss, Stack, T, Ts, Tzr); %% yeccpars2(382=S, Cat, Ss, Stack, T, Ts, Tzr) -> %% yeccpars2_382(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(383=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_383(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(383=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_383(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(384=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_384(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(385=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_385(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(386=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_386(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(386=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_386(S, Cat, Ss, Stack, T, Ts, Tzr); %% yeccpars2(387=S, Cat, Ss, Stack, T, Ts, Tzr) -> %% yeccpars2_387(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(388=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_388(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(388=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_388(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(389=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_389(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(390=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_390(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(391=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_318(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(392=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_392(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(393=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_393(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(394=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_394(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(395=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_395(S, Cat, Ss, Stack, T, Ts, Tzr); + yeccpars2_322(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(391=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_391(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(392=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_392(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(393=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_393(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(394=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_394(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(395=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_395(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(396=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_396(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(397=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_397(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(398=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_398(S, Cat, Ss, Stack, T, Ts, Tzr); + yeccpars2_322(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(397=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_397(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(398=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_398(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(399=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_318(S, Cat, Ss, Stack, T, Ts, Tzr); + yeccpars2_322(S, Cat, Ss, Stack, T, Ts, Tzr); %% yeccpars2(400=S, Cat, Ss, Stack, T, Ts, Tzr) -> %% yeccpars2_400(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(401=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_401(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(402=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_318(S, Cat, Ss, Stack, T, Ts, Tzr); + yeccpars2_334(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(402=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_402(S, Cat, Ss, Stack, T, Ts, Tzr); %% yeccpars2(403=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_403(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2_334(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(404=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_331(S, Cat, Ss, Stack, T, Ts, Tzr); + yeccpars2_334(S, Cat, Ss, Stack, T, Ts, Tzr); %% yeccpars2(405=S, Cat, Ss, Stack, T, Ts, Tzr) -> %% yeccpars2_405(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(406=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_406(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(406=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_406(S, Cat, Ss, Stack, T, Ts, Tzr); %% yeccpars2(407=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_407(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2_334(S, Cat, Ss, Stack, T, Ts, Tzr); %% yeccpars2(408=S, Cat, Ss, Stack, T, Ts, Tzr) -> %% yeccpars2_408(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(409=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_409(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(410=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_318(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(410=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_410(S, Cat, Ss, Stack, T, Ts, Tzr); %% yeccpars2(411=S, Cat, Ss, Stack, T, Ts, Tzr) -> %% yeccpars2_411(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(412=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_412(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(413=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_406(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(414=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_414(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(415=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_295(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(416=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_416(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(417=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_417(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(418=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_418(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(419=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_419(S, Cat, Ss, Stack, T, Ts, Tzr); + yeccpars2_413(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(414=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_322(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(415=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_415(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(416=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_322(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(417=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_417(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(418=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_418(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(419=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_409(S, Cat, Ss, Stack, T, Ts, Tzr); %% yeccpars2(420=S, Cat, Ss, Stack, T, Ts, Tzr) -> %% yeccpars2_420(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(421=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_421(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(422=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_45(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(423=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_423(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(421=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_295(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(422=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_422(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(423=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_423(S, Cat, Ss, Stack, T, Ts, Tzr); %% yeccpars2(424=S, Cat, Ss, Stack, T, Ts, Tzr) -> %% yeccpars2_424(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(425=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_425(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(426=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_318(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(425=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_425(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(426=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_426(S, Cat, Ss, Stack, T, Ts, Tzr); %% yeccpars2(427=S, Cat, Ss, Stack, T, Ts, Tzr) -> %% yeccpars2_427(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(428=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_428(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(428=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_45(S, Cat, Ss, Stack, T, Ts, Tzr); %% yeccpars2(429=S, Cat, Ss, Stack, T, Ts, Tzr) -> %% yeccpars2_429(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(430=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_68(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(431=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_431(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(432=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_432(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(430=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_430(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(431=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_431(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(432=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_322(S, Cat, Ss, Stack, T, Ts, Tzr); %% yeccpars2(433=S, Cat, Ss, Stack, T, Ts, Tzr) -> %% yeccpars2_433(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(434=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_45(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(435=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_318(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(436=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_436(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(434=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_434(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(435=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_435(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(436=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_68(S, Cat, Ss, Stack, T, Ts, Tzr); %% yeccpars2(437=S, Cat, Ss, Stack, T, Ts, Tzr) -> %% yeccpars2_437(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(438=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_45(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(438=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_438(S, Cat, Ss, Stack, T, Ts, Tzr); %% yeccpars2(439=S, Cat, Ss, Stack, T, Ts, Tzr) -> %% yeccpars2_439(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(440=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_440(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(440=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_45(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(441=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_441(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(442=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_442(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(443=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_443(S, Cat, Ss, Stack, T, Ts, Tzr); + yeccpars2_322(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(442=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_442(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(443=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_443(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(444=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_425(S, Cat, Ss, Stack, T, Ts, Tzr); + yeccpars2_45(S, Cat, Ss, Stack, T, Ts, Tzr); %% yeccpars2(445=S, Cat, Ss, Stack, T, Ts, Tzr) -> %% yeccpars2_445(S, Cat, Ss, Stack, T, Ts, Tzr); -yeccpars2(446=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_446(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(446=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_446(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(447=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_447(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(448=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_448(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(449=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_449(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(449=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_449(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(450=S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_10(S, Cat, Ss, Stack, T, Ts, Tzr); + yeccpars2_431(S, Cat, Ss, Stack, T, Ts, Tzr); %% yeccpars2(451=S, Cat, Ss, Stack, T, Ts, Tzr) -> %% yeccpars2_451(S, Cat, Ss, Stack, T, Ts, Tzr); -%% yeccpars2(452=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_83(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(452=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_452(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(453=S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_453(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(454=S, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -1647,7 +1657,19 @@ yeccpars2(456=S, Cat, Ss, Stack, T, Ts, Tzr) -> %% yeccpars2(457=S, Cat, Ss, Stack, T, Ts, Tzr) -> %% yeccpars2_457(S, Cat, Ss, Stack, T, Ts, Tzr); %% yeccpars2(458=S, Cat, Ss, Stack, T, Ts, Tzr) -> -%% yeccpars2_458(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2_83(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(459=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_459(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(460=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_460(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(461=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_461(S, Cat, Ss, Stack, T, Ts, Tzr); +yeccpars2(462=S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_10(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(463=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_463(S, Cat, Ss, Stack, T, Ts, Tzr); +%% yeccpars2(464=S, Cat, Ss, Stack, T, Ts, Tzr) -> +%% yeccpars2_464(S, Cat, Ss, Stack, T, Ts, Tzr); yeccpars2(Other, _, _, _, _, _, _) -> erlang:error({yecc_bug,"1.3",{missing_state_in_action_table, Other}}). @@ -1661,32 +1683,32 @@ yeccpars2_1(_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccgoto_rule(hd(Ss), Cat, Ss, NewStack, T, Ts, Tzr). yeccpars2_2(S, ';', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 454, Ss, Stack, T, Ts, Tzr); + yeccpars1(S, 460, Ss, Stack, T, Ts, Tzr); yeccpars2_2(_S, Cat, Ss, Stack, T, Ts, Tzr) -> NewStack = yeccpars2_2_(Stack), yeccgoto_rule_clauses(hd(Ss), Cat, Ss, NewStack, T, Ts, Tzr). yeccpars2_3(S, dot, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 453, Ss, Stack, T, Ts, Tzr). + yeccpars1(S, 459, Ss, Stack, T, Ts, Tzr). yeccpars2_4(_S, Cat, Ss, Stack, T, Ts, Tzr) -> NewStack = yeccpars2_4_(Stack), yeccgoto_function(hd(Ss), Cat, Ss, NewStack, T, Ts, Tzr). yeccpars2_5(S, ';', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 448, Ss, Stack, T, Ts, Tzr); + yeccpars1(S, 454, Ss, Stack, T, Ts, Tzr); yeccpars2_5(_S, Cat, Ss, Stack, T, Ts, Tzr) -> NewStack = yeccpars2_5_(Stack), yeccgoto_function_clauses(hd(Ss), Cat, Ss, NewStack, T, Ts, Tzr). yeccpars2_6(S, dot, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 447, Ss, Stack, T, Ts, Tzr). + yeccpars1(S, 453, Ss, Stack, T, Ts, Tzr). yeccpars2_7(_S, '$end', _Ss, Stack, _T, _Ts, _Tzr) -> {ok, hd(Stack)}. yeccpars2_8(S, dot, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 446, Ss, Stack, T, Ts, Tzr). + yeccpars1(S, 452, Ss, Stack, T, Ts, Tzr). yeccpars2_9(S, atom, Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 292, Ss, Stack, T, Ts, Tzr); @@ -3075,7 +3097,7 @@ yeccpars2_291(_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_292(S, '#', Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 44, Ss, Stack, T, Ts, Tzr); yeccpars2_292(S, '(', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 422, Ss, Stack, T, Ts, Tzr); + yeccpars1(S, 428, Ss, Stack, T, Ts, Tzr); yeccpars2_292(S, '+', Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 47, Ss, Stack, T, Ts, Tzr); yeccpars2_292(S, '-', Ss, Stack, T, Ts, Tzr) -> @@ -3155,354 +3177,419 @@ yeccpars2_305(_S, Cat, Ss, Stack, T, Ts, Tzr) -> %% yeccpars2_306: see yeccpars2_295 yeccpars2_307(S, ')', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 417, Ss, Stack, T, Ts, Tzr). + yeccpars1(S, 423, Ss, Stack, T, Ts, Tzr). yeccpars2_308(S, ';', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 415, Ss, Stack, T, Ts, Tzr); + yeccpars1(S, 421, Ss, Stack, T, Ts, Tzr); yeccpars2_308(_S, Cat, Ss, Stack, T, Ts, Tzr) -> NewStack = yeccpars2_308_(Stack), yeccgoto_type_sigs(hd(Ss), Cat, Ss, NewStack, T, Ts, Tzr). yeccpars2_309(S, 'when', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 406, Ss, Stack, T, Ts, Tzr); + yeccpars1(S, 409, Ss, Stack, T, Ts, Tzr); yeccpars2_309(_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccgoto_type_sig(hd(Ss), Cat, Ss, Stack, T, Ts, Tzr). yeccpars2_310(S, ')', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 319, Ss, Stack, T, Ts, Tzr); + yeccpars1(S, 323, Ss, Stack, T, Ts, Tzr); +yeccpars2_310(S, '+', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 47, Ss, Stack, T, Ts, Tzr); yeccpars2_310(S, '-', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 320, Ss, Stack, T, Ts, Tzr); -yeccpars2_310(S, integer, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 325, Ss, Stack, T, Ts, Tzr); + yeccpars1(S, 48, Ss, Stack, T, Ts, Tzr); +yeccpars2_310(S, 'bnot', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 54, Ss, Stack, T, Ts, Tzr); +yeccpars2_310(S, 'not', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 62, Ss, Stack, T, Ts, Tzr); yeccpars2_310(S, var, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 326, Ss, Stack, T, Ts, Tzr); + yeccpars1(S, 329, Ss, Stack, T, Ts, Tzr); yeccpars2_310(S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_cont_310(S, Cat, Ss, Stack, T, Ts, Tzr). yeccpars2_cont_310(S, '#', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 317, Ss, Stack, T, Ts, Tzr); + yeccpars1(S, 321, Ss, Stack, T, Ts, Tzr); yeccpars2_cont_310(S, '(', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 318, Ss, Stack, T, Ts, Tzr); + yeccpars1(S, 322, Ss, Stack, T, Ts, Tzr); yeccpars2_cont_310(S, '<<', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 321, Ss, Stack, T, Ts, Tzr); + yeccpars1(S, 324, Ss, Stack, T, Ts, Tzr); yeccpars2_cont_310(S, '[', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 322, Ss, Stack, T, Ts, Tzr); + yeccpars1(S, 325, Ss, Stack, T, Ts, Tzr); yeccpars2_cont_310(S, atom, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 323, Ss, Stack, T, Ts, Tzr); + yeccpars1(S, 326, Ss, Stack, T, Ts, Tzr); yeccpars2_cont_310(S, 'fun', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 324, Ss, Stack, T, Ts, Tzr); + yeccpars1(S, 327, Ss, Stack, T, Ts, Tzr); +yeccpars2_cont_310(S, integer, Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 328, Ss, Stack, T, Ts, Tzr); yeccpars2_cont_310(S, '{', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 327, Ss, Stack, T, Ts, Tzr). + yeccpars1(S, 330, Ss, Stack, T, Ts, Tzr). -yeccpars2_311(S, '|', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 404, Ss, Stack, T, Ts, Tzr); yeccpars2_311(_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccgoto_top_type_100(hd(Ss), Cat, Ss, Stack, T, Ts, Tzr). + yeccgoto_type_400(hd(Ss), Cat, Ss, Stack, T, Ts, Tzr). -yeccpars2_312(S, ')', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 401, Ss, Stack, T, Ts, Tzr). +yeccpars2_312(S, '*', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 262, Ss, Stack, T, Ts, Tzr); +yeccpars2_312(S, '/', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 263, Ss, Stack, T, Ts, Tzr); +yeccpars2_312(S, 'and', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 264, Ss, Stack, T, Ts, Tzr); +yeccpars2_312(S, 'band', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 265, Ss, Stack, T, Ts, Tzr); +yeccpars2_312(S, 'div', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 266, Ss, Stack, T, Ts, Tzr); +yeccpars2_312(S, 'rem', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 267, Ss, Stack, T, Ts, Tzr); +yeccpars2_312(_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccgoto_type_300(hd(Ss), Cat, Ss, Stack, T, Ts, Tzr). +yeccpars2_313(S, '+', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 250, Ss, Stack, T, Ts, Tzr); +yeccpars2_313(S, '-', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 252, Ss, Stack, T, Ts, Tzr); +yeccpars2_313(S, '..', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 404, Ss, Stack, T, Ts, Tzr); +yeccpars2_313(S, 'bor', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 254, Ss, Stack, T, Ts, Tzr); +yeccpars2_313(S, 'bsl', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 255, Ss, Stack, T, Ts, Tzr); +yeccpars2_313(S, 'bsr', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 256, Ss, Stack, T, Ts, Tzr); +yeccpars2_313(S, 'bxor', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 257, Ss, Stack, T, Ts, Tzr); +yeccpars2_313(S, 'or', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 258, Ss, Stack, T, Ts, Tzr); +yeccpars2_313(S, 'xor', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 259, Ss, Stack, T, Ts, Tzr); yeccpars2_313(_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccgoto_top_type(hd(Ss), Cat, Ss, Stack, T, Ts, Tzr). + yeccgoto_type_200(hd(Ss), Cat, Ss, Stack, T, Ts, Tzr). -yeccpars2_314(S, ',', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 399, Ss, Stack, T, Ts, Tzr); +yeccpars2_314(S, '|', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 401, Ss, Stack, T, Ts, Tzr); yeccpars2_314(_S, Cat, Ss, Stack, T, Ts, Tzr) -> - NewStack = yeccpars2_314_(Stack), - yeccgoto_top_types(hd(Ss), Cat, Ss, NewStack, T, Ts, Tzr). + yeccgoto_top_type_100(hd(Ss), Cat, Ss, Stack, T, Ts, Tzr). -yeccpars2_315(S, '.', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 396, Ss, Stack, T, Ts, Tzr); yeccpars2_315(_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccgoto_type(hd(Ss), Cat, Ss, Stack, T, Ts, Tzr). + yeccgoto_type_500(hd(Ss), Cat, Ss, Stack, T, Ts, Tzr). -yeccpars2_316(_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccgoto_type(hd(Ss), Cat, Ss, Stack, T, Ts, Tzr). +yeccpars2_316(S, ')', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 398, Ss, Stack, T, Ts, Tzr). -yeccpars2_317(S, atom, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 385, Ss, Stack, T, Ts, Tzr). +yeccpars2_317(_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccgoto_top_type(hd(Ss), Cat, Ss, Stack, T, Ts, Tzr). -yeccpars2_318(S, '-', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 320, Ss, Stack, T, Ts, Tzr); -yeccpars2_318(S, integer, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 325, Ss, Stack, T, Ts, Tzr); -yeccpars2_318(S, var, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 326, Ss, Stack, T, Ts, Tzr); -yeccpars2_318(S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_cont_310(S, Cat, Ss, Stack, T, Ts, Tzr). +yeccpars2_318(S, ',', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 396, Ss, Stack, T, Ts, Tzr); +yeccpars2_318(_S, Cat, Ss, Stack, T, Ts, Tzr) -> + NewStack = yeccpars2_318_(Stack), + yeccgoto_top_types(hd(Ss), Cat, Ss, NewStack, T, Ts, Tzr). -yeccpars2_319(S, '->', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 381, Ss, Stack, T, Ts, Tzr). +yeccpars2_319(S, var, Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 336, Ss, Stack, T, Ts, Tzr); +yeccpars2_319(S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_cont_310(S, Cat, Ss, Stack, T, Ts, Tzr). -yeccpars2_320(S, integer, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 380, Ss, Stack, T, Ts, Tzr). +yeccpars2_320(_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccgoto_type(hd(Ss), Cat, Ss, Stack, T, Ts, Tzr). -yeccpars2_321(S, '>>', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 366, Ss, Stack, T, Ts, Tzr); -yeccpars2_321(S, var, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 367, Ss, Stack, T, Ts, Tzr). +yeccpars2_321(S, atom, Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 384, Ss, Stack, T, Ts, Tzr). +yeccpars2_322(S, '+', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 47, Ss, Stack, T, Ts, Tzr); yeccpars2_322(S, '-', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 320, Ss, Stack, T, Ts, Tzr); -yeccpars2_322(S, ']', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 357, Ss, Stack, T, Ts, Tzr); -yeccpars2_322(S, integer, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 325, Ss, Stack, T, Ts, Tzr); + yeccpars1(S, 48, Ss, Stack, T, Ts, Tzr); +yeccpars2_322(S, 'bnot', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 54, Ss, Stack, T, Ts, Tzr); +yeccpars2_322(S, 'not', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 62, Ss, Stack, T, Ts, Tzr); yeccpars2_322(S, var, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 326, Ss, Stack, T, Ts, Tzr); + yeccpars1(S, 329, Ss, Stack, T, Ts, Tzr); yeccpars2_322(S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_cont_310(S, Cat, Ss, Stack, T, Ts, Tzr). -yeccpars2_323(S, '(', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 346, Ss, Stack, T, Ts, Tzr); -yeccpars2_323(S, ':', Ss, Stack, T, Ts, Tzr) -> +yeccpars2_323(S, '->', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 380, Ss, Stack, T, Ts, Tzr). + +yeccpars2_324(S, '>>', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 365, Ss, Stack, T, Ts, Tzr); +yeccpars2_324(S, var, Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 366, Ss, Stack, T, Ts, Tzr). + +yeccpars2_325(S, '+', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 47, Ss, Stack, T, Ts, Tzr); +yeccpars2_325(S, '-', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 48, Ss, Stack, T, Ts, Tzr); +yeccpars2_325(S, ']', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 358, Ss, Stack, T, Ts, Tzr); +yeccpars2_325(S, 'bnot', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 54, Ss, Stack, T, Ts, Tzr); +yeccpars2_325(S, 'not', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 62, Ss, Stack, T, Ts, Tzr); +yeccpars2_325(S, var, Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 329, Ss, Stack, T, Ts, Tzr); +yeccpars2_325(S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_cont_310(S, Cat, Ss, Stack, T, Ts, Tzr). + +yeccpars2_326(S, '(', Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 347, Ss, Stack, T, Ts, Tzr); -yeccpars2_323(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_326(S, ':', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 348, Ss, Stack, T, Ts, Tzr); +yeccpars2_326(_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccgoto_type(hd(Ss), Cat, Ss, Stack, T, Ts, Tzr). -yeccpars2_324(S, '(', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 334, Ss, Stack, T, Ts, Tzr). +yeccpars2_327(S, '(', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 337, Ss, Stack, T, Ts, Tzr). -yeccpars2_325(_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccgoto_int_type(hd(Ss), Cat, Ss, Stack, T, Ts, Tzr). +yeccpars2_328(_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccgoto_type(hd(Ss), Cat, Ss, Stack, T, Ts, Tzr). -yeccpars2_326(S, '::', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 331, Ss, Stack, T, Ts, Tzr); -yeccpars2_326(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_329(S, '::', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 334, Ss, Stack, T, Ts, Tzr); +yeccpars2_329(_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccgoto_type(hd(Ss), Cat, Ss, Stack, T, Ts, Tzr). -yeccpars2_327(S, '-', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 320, Ss, Stack, T, Ts, Tzr); -yeccpars2_327(S, integer, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 325, Ss, Stack, T, Ts, Tzr); -yeccpars2_327(S, var, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 326, Ss, Stack, T, Ts, Tzr); -yeccpars2_327(S, '}', Ss, Stack, T, Ts, Tzr) -> +yeccpars2_330(S, '+', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 47, Ss, Stack, T, Ts, Tzr); +yeccpars2_330(S, '-', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 48, Ss, Stack, T, Ts, Tzr); +yeccpars2_330(S, 'bnot', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 54, Ss, Stack, T, Ts, Tzr); +yeccpars2_330(S, 'not', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 62, Ss, Stack, T, Ts, Tzr); +yeccpars2_330(S, var, Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 329, Ss, Stack, T, Ts, Tzr); -yeccpars2_327(S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_330(S, '}', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 332, Ss, Stack, T, Ts, Tzr); +yeccpars2_330(S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_cont_310(S, Cat, Ss, Stack, T, Ts, Tzr). -yeccpars2_328(S, '}', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 330, Ss, Stack, T, Ts, Tzr). +yeccpars2_331(S, '}', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 333, Ss, Stack, T, Ts, Tzr). -yeccpars2_329(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_332(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_|Nss] = Ss, - NewStack = yeccpars2_329_(Stack), + NewStack = yeccpars2_332_(Stack), yeccgoto_type(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_330(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_333(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_330_(Stack), + NewStack = yeccpars2_333_(Stack), yeccgoto_type(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_331(S, '-', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 320, Ss, Stack, T, Ts, Tzr); -yeccpars2_331(S, integer, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 325, Ss, Stack, T, Ts, Tzr); -yeccpars2_331(S, var, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 333, Ss, Stack, T, Ts, Tzr); -yeccpars2_331(S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_334(S, '+', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 47, Ss, Stack, T, Ts, Tzr); +yeccpars2_334(S, '-', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 48, Ss, Stack, T, Ts, Tzr); +yeccpars2_334(S, 'bnot', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 54, Ss, Stack, T, Ts, Tzr); +yeccpars2_334(S, 'not', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 62, Ss, Stack, T, Ts, Tzr); +yeccpars2_334(S, var, Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 336, Ss, Stack, T, Ts, Tzr); +yeccpars2_334(S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_cont_310(S, Cat, Ss, Stack, T, Ts, Tzr). -yeccpars2_332(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_335(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_332_(Stack), + NewStack = yeccpars2_335_(Stack), yeccgoto_top_type(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_333(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_336(_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccgoto_type(hd(Ss), Cat, Ss, Stack, T, Ts, Tzr). -yeccpars2_334(S, '(', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 337, Ss, Stack, T, Ts, Tzr); -yeccpars2_334(S, ')', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 338, Ss, Stack, T, Ts, Tzr). +yeccpars2_337(S, '(', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 340, Ss, Stack, T, Ts, Tzr); +yeccpars2_337(S, ')', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 341, Ss, Stack, T, Ts, Tzr). -yeccpars2_335(S, ')', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 345, Ss, Stack, T, Ts, Tzr). +yeccpars2_338(S, ')', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 346, Ss, Stack, T, Ts, Tzr). -yeccpars2_336(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_339(_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccgoto_fun_type_100(hd(Ss), Cat, Ss, Stack, T, Ts, Tzr). -yeccpars2_337(S, ')', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 319, Ss, Stack, T, Ts, Tzr); -yeccpars2_337(S, '-', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 320, Ss, Stack, T, Ts, Tzr); -yeccpars2_337(S, '.', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 339, Ss, Stack, T, Ts, Tzr); -yeccpars2_337(S, integer, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 325, Ss, Stack, T, Ts, Tzr); -yeccpars2_337(S, var, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 326, Ss, Stack, T, Ts, Tzr); -yeccpars2_337(S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_340(S, ')', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 323, Ss, Stack, T, Ts, Tzr); +yeccpars2_340(S, '+', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 47, Ss, Stack, T, Ts, Tzr); +yeccpars2_340(S, '-', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 48, Ss, Stack, T, Ts, Tzr); +yeccpars2_340(S, '...', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 342, Ss, Stack, T, Ts, Tzr); +yeccpars2_340(S, 'bnot', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 54, Ss, Stack, T, Ts, Tzr); +yeccpars2_340(S, 'not', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 62, Ss, Stack, T, Ts, Tzr); +yeccpars2_340(S, var, Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 329, Ss, Stack, T, Ts, Tzr); +yeccpars2_340(S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_cont_310(S, Cat, Ss, Stack, T, Ts, Tzr). -yeccpars2_338(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_341(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_338_(Stack), + NewStack = yeccpars2_341_(Stack), yeccgoto_type(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_339(S, '.', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 340, Ss, Stack, T, Ts, Tzr). - -yeccpars2_340(S, '.', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 341, Ss, Stack, T, Ts, Tzr). - -yeccpars2_341(S, ')', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 342, Ss, Stack, T, Ts, Tzr). - -yeccpars2_342(S, '->', Ss, Stack, T, Ts, Tzr) -> +yeccpars2_342(S, ')', Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 343, Ss, Stack, T, Ts, Tzr). -%% yeccpars2_343: see yeccpars2_318 +yeccpars2_343(S, '->', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 344, Ss, Stack, T, Ts, Tzr). -yeccpars2_344(_S, Cat, Ss, Stack, T, Ts, Tzr) -> - [_,_,_,_,_,_|Nss] = Ss, - NewStack = yeccpars2_344_(Stack), - yeccgoto_fun_type_100(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). +%% yeccpars2_344: see yeccpars2_322 yeccpars2_345(_S, Cat, Ss, Stack, T, Ts, Tzr) -> - [_,_,_|Nss] = Ss, + [_,_,_,_|Nss] = Ss, NewStack = yeccpars2_345_(Stack), + yeccgoto_fun_type_100(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). + +yeccpars2_346(_S, Cat, Ss, Stack, T, Ts, Tzr) -> + [_,_,_|Nss] = Ss, + NewStack = yeccpars2_346_(Stack), yeccgoto_type(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_346(S, ')', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 354, Ss, Stack, T, Ts, Tzr); -yeccpars2_346(S, '-', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 320, Ss, Stack, T, Ts, Tzr); -yeccpars2_346(S, integer, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 325, Ss, Stack, T, Ts, Tzr); -yeccpars2_346(S, var, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 326, Ss, Stack, T, Ts, Tzr); -yeccpars2_346(S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_347(S, ')', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 355, Ss, Stack, T, Ts, Tzr); +yeccpars2_347(S, '+', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 47, Ss, Stack, T, Ts, Tzr); +yeccpars2_347(S, '-', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 48, Ss, Stack, T, Ts, Tzr); +yeccpars2_347(S, 'bnot', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 54, Ss, Stack, T, Ts, Tzr); +yeccpars2_347(S, 'not', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 62, Ss, Stack, T, Ts, Tzr); +yeccpars2_347(S, var, Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 329, Ss, Stack, T, Ts, Tzr); +yeccpars2_347(S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_cont_310(S, Cat, Ss, Stack, T, Ts, Tzr). -yeccpars2_347(S, atom, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 348, Ss, Stack, T, Ts, Tzr). - -yeccpars2_348(S, '(', Ss, Stack, T, Ts, Tzr) -> +yeccpars2_348(S, atom, Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 349, Ss, Stack, T, Ts, Tzr). -yeccpars2_349(S, ')', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 351, Ss, Stack, T, Ts, Tzr); -yeccpars2_349(S, '-', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 320, Ss, Stack, T, Ts, Tzr); -yeccpars2_349(S, integer, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 325, Ss, Stack, T, Ts, Tzr); -yeccpars2_349(S, var, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 326, Ss, Stack, T, Ts, Tzr); -yeccpars2_349(S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_cont_310(S, Cat, Ss, Stack, T, Ts, Tzr). +yeccpars2_349(S, '(', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 350, Ss, Stack, T, Ts, Tzr). yeccpars2_350(S, ')', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 352, Ss, Stack, T, Ts, Tzr). + yeccpars1(S, 352, Ss, Stack, T, Ts, Tzr); +yeccpars2_350(S, '+', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 47, Ss, Stack, T, Ts, Tzr); +yeccpars2_350(S, '-', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 48, Ss, Stack, T, Ts, Tzr); +yeccpars2_350(S, 'bnot', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 54, Ss, Stack, T, Ts, Tzr); +yeccpars2_350(S, 'not', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 62, Ss, Stack, T, Ts, Tzr); +yeccpars2_350(S, var, Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 329, Ss, Stack, T, Ts, Tzr); +yeccpars2_350(S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_cont_310(S, Cat, Ss, Stack, T, Ts, Tzr). -yeccpars2_351(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_351(S, ')', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 353, Ss, Stack, T, Ts, Tzr). + +yeccpars2_352(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_,_,_|Nss] = Ss, - NewStack = yeccpars2_351_(Stack), + NewStack = yeccpars2_352_(Stack), yeccgoto_type(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_352(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_353(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_,_,_,_|Nss] = Ss, - NewStack = yeccpars2_352_(Stack), + NewStack = yeccpars2_353_(Stack), yeccgoto_type(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_353(S, ')', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 355, Ss, Stack, T, Ts, Tzr). +yeccpars2_354(S, ')', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 356, Ss, Stack, T, Ts, Tzr). -yeccpars2_354(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_355(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_354_(Stack), + NewStack = yeccpars2_355_(Stack), yeccgoto_type(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_355(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_356(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_,_|Nss] = Ss, - NewStack = yeccpars2_355_(Stack), + NewStack = yeccpars2_356_(Stack), yeccgoto_type(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_356(S, ',', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 358, Ss, Stack, T, Ts, Tzr); -yeccpars2_356(S, ']', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 359, Ss, Stack, T, Ts, Tzr). +yeccpars2_357(S, ',', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 359, Ss, Stack, T, Ts, Tzr); +yeccpars2_357(S, ']', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 360, Ss, Stack, T, Ts, Tzr). -yeccpars2_357(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_358(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_|Nss] = Ss, - NewStack = yeccpars2_357_(Stack), + NewStack = yeccpars2_358_(Stack), yeccgoto_type(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_358(S, '.', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 360, Ss, Stack, T, Ts, Tzr). +yeccpars2_359(S, '...', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 361, Ss, Stack, T, Ts, Tzr). -yeccpars2_359(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_360(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_359_(Stack), + NewStack = yeccpars2_360_(Stack), yeccgoto_type(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_360(S, '.', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 361, Ss, Stack, T, Ts, Tzr). - -yeccpars2_361(S, '.', Ss, Stack, T, Ts, Tzr) -> +yeccpars2_361(S, ']', Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 362, Ss, Stack, T, Ts, Tzr). -yeccpars2_362(S, ']', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 363, Ss, Stack, T, Ts, Tzr). - -yeccpars2_363(_S, Cat, Ss, Stack, T, Ts, Tzr) -> - [_,_,_,_,_,_|Nss] = Ss, - NewStack = yeccpars2_363_(Stack), +yeccpars2_362(_S, Cat, Ss, Stack, T, Ts, Tzr) -> + [_,_,_,_|Nss] = Ss, + NewStack = yeccpars2_362_(Stack), yeccgoto_type(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_364(S, '>>', Ss, Stack, T, Ts, Tzr) -> +yeccpars2_363(S, '>>', Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 379, Ss, Stack, T, Ts, Tzr). -yeccpars2_365(S, ',', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 373, Ss, Stack, T, Ts, Tzr); -yeccpars2_365(S, '>>', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 374, Ss, Stack, T, Ts, Tzr). +yeccpars2_364(S, ',', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 372, Ss, Stack, T, Ts, Tzr); +yeccpars2_364(S, '>>', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 373, Ss, Stack, T, Ts, Tzr). -yeccpars2_366(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_365(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_|Nss] = Ss, - NewStack = yeccpars2_366_(Stack), + NewStack = yeccpars2_365_(Stack), yeccgoto_binary_type(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_367(S, ':', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 368, Ss, Stack, T, Ts, Tzr). +yeccpars2_366(S, ':', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 367, Ss, Stack, T, Ts, Tzr). -yeccpars2_368(S, integer, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_367(S, var, Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 369, Ss, Stack, T, Ts, Tzr); -yeccpars2_368(S, var, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 370, Ss, Stack, T, Ts, Tzr). +yeccpars2_367(S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_cont_310(S, Cat, Ss, Stack, T, Ts, Tzr). -yeccpars2_369(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_368(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_369_(Stack), + NewStack = yeccpars2_368_(Stack), yeccgoto_bin_base_type(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_370(S, '*', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 371, Ss, Stack, T, Ts, Tzr). +yeccpars2_369(S, '*', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 370, Ss, Stack, T, Ts, Tzr); +yeccpars2_369(_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccgoto_type(hd(Ss), Cat, Ss, Stack, T, Ts, Tzr). -yeccpars2_371(S, integer, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 372, Ss, Stack, T, Ts, Tzr). +%% yeccpars2_370: see yeccpars2_319 -yeccpars2_372(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_371(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_,_,_|Nss] = Ss, - NewStack = yeccpars2_372_(Stack), + NewStack = yeccpars2_371_(Stack), yeccgoto_bin_unit_type(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_373(S, var, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 376, Ss, Stack, T, Ts, Tzr). +yeccpars2_372(S, var, Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 375, Ss, Stack, T, Ts, Tzr). -yeccpars2_374(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_373(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_374_(Stack), + NewStack = yeccpars2_373_(Stack), yeccgoto_binary_type(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_375(S, '>>', Ss, Stack, T, Ts, Tzr) -> +yeccpars2_374(S, '>>', Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 378, Ss, Stack, T, Ts, Tzr). -yeccpars2_376(S, ':', Ss, Stack, T, Ts, Tzr) -> +yeccpars2_375(S, ':', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 376, Ss, Stack, T, Ts, Tzr). + +yeccpars2_376(S, var, Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 377, Ss, Stack, T, Ts, Tzr). -yeccpars2_377(S, var, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_377(S, '*', Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 370, Ss, Stack, T, Ts, Tzr). yeccpars2_378(_S, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -3515,350 +3602,402 @@ yeccpars2_379(_S, Cat, Ss, Stack, T, Ts, Tzr) -> NewStack = yeccpars2_379_(Stack), yeccgoto_binary_type(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_380(_S, Cat, Ss, Stack, T, Ts, Tzr) -> - [_|Nss] = Ss, - NewStack = yeccpars2_380_(Stack), - yeccgoto_int_type(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). - -%% yeccpars2_381: see yeccpars2_318 +%% yeccpars2_380: see yeccpars2_322 -yeccpars2_382(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_381(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_,_|Nss] = Ss, - NewStack = yeccpars2_382_(Stack), + NewStack = yeccpars2_381_(Stack), yeccgoto_fun_type(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_383(S, ')', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 384, Ss, Stack, T, Ts, Tzr). +yeccpars2_382(S, ')', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 383, Ss, Stack, T, Ts, Tzr). -yeccpars2_384(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_383(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_384_(Stack), + NewStack = yeccpars2_383_(Stack), yeccgoto_type(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_385(S, '{', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 386, Ss, Stack, T, Ts, Tzr). +yeccpars2_384(S, '{', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 385, Ss, Stack, T, Ts, Tzr). -yeccpars2_386(S, atom, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 389, Ss, Stack, T, Ts, Tzr); -yeccpars2_386(S, '}', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 390, Ss, Stack, T, Ts, Tzr). +yeccpars2_385(S, atom, Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 388, Ss, Stack, T, Ts, Tzr); +yeccpars2_385(S, '}', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 389, Ss, Stack, T, Ts, Tzr). -yeccpars2_387(S, '}', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 395, Ss, Stack, T, Ts, Tzr). +yeccpars2_386(S, '}', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 394, Ss, Stack, T, Ts, Tzr). -yeccpars2_388(S, ',', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 393, Ss, Stack, T, Ts, Tzr); -yeccpars2_388(_S, Cat, Ss, Stack, T, Ts, Tzr) -> - NewStack = yeccpars2_388_(Stack), +yeccpars2_387(S, ',', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 392, Ss, Stack, T, Ts, Tzr); +yeccpars2_387(_S, Cat, Ss, Stack, T, Ts, Tzr) -> + NewStack = yeccpars2_387_(Stack), yeccgoto_field_types(hd(Ss), Cat, Ss, NewStack, T, Ts, Tzr). -yeccpars2_389(S, '::', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 391, Ss, Stack, T, Ts, Tzr). +yeccpars2_388(S, '::', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 390, Ss, Stack, T, Ts, Tzr). -yeccpars2_390(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_389(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_,_|Nss] = Ss, - NewStack = yeccpars2_390_(Stack), + NewStack = yeccpars2_389_(Stack), yeccgoto_type(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -%% yeccpars2_391: see yeccpars2_318 +%% yeccpars2_390: see yeccpars2_322 -yeccpars2_392(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_391(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_392_(Stack), + NewStack = yeccpars2_391_(Stack), yeccgoto_field_type(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_393(S, atom, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 389, Ss, Stack, T, Ts, Tzr). +yeccpars2_392(S, atom, Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 388, Ss, Stack, T, Ts, Tzr). -yeccpars2_394(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_393(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_394_(Stack), + NewStack = yeccpars2_393_(Stack), yeccgoto_field_types(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_395(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_394(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_,_,_|Nss] = Ss, - NewStack = yeccpars2_395_(Stack), + NewStack = yeccpars2_394_(Stack), yeccgoto_type(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_396(S, '.', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 397, Ss, Stack, T, Ts, Tzr). - -yeccpars2_397(S, '-', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 320, Ss, Stack, T, Ts, Tzr); -yeccpars2_397(S, integer, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 325, Ss, Stack, T, Ts, Tzr). - -yeccpars2_398(_S, Cat, Ss, Stack, T, Ts, Tzr) -> - [_,_,_|Nss] = Ss, - NewStack = yeccpars2_398_(Stack), - yeccgoto_type(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). +yeccpars2_395(_S, Cat, Ss, Stack, T, Ts, Tzr) -> + [_|Nss] = Ss, + NewStack = yeccpars2_395_(Stack), + yeccgoto_type_500(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -%% yeccpars2_399: see yeccpars2_318 +%% yeccpars2_396: see yeccpars2_322 -yeccpars2_400(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_397(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_400_(Stack), + NewStack = yeccpars2_397_(Stack), yeccgoto_top_types(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_401(S, '->', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 402, Ss, Stack, T, Ts, Tzr). +yeccpars2_398(S, '->', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 399, Ss, Stack, T, Ts, Tzr). -%% yeccpars2_402: see yeccpars2_318 +%% yeccpars2_399: see yeccpars2_322 -yeccpars2_403(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_400(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_,_,_|Nss] = Ss, - NewStack = yeccpars2_403_(Stack), + NewStack = yeccpars2_400_(Stack), yeccgoto_fun_type(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -%% yeccpars2_404: see yeccpars2_331 +%% yeccpars2_401: see yeccpars2_334 -yeccpars2_405(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_402(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_405_(Stack), + NewStack = yeccpars2_402_(Stack), yeccgoto_top_type_100(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_406(S, atom, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 409, Ss, Stack, T, Ts, Tzr). +%% yeccpars2_403: see yeccpars2_334 + +%% yeccpars2_404: see yeccpars2_334 + +yeccpars2_405(S, '+', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 250, Ss, Stack, T, Ts, Tzr); +yeccpars2_405(S, '-', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 252, Ss, Stack, T, Ts, Tzr); +yeccpars2_405(S, 'bor', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 254, Ss, Stack, T, Ts, Tzr); +yeccpars2_405(S, 'bsl', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 255, Ss, Stack, T, Ts, Tzr); +yeccpars2_405(S, 'bsr', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 256, Ss, Stack, T, Ts, Tzr); +yeccpars2_405(S, 'bxor', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 257, Ss, Stack, T, Ts, Tzr); +yeccpars2_405(S, 'or', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 258, Ss, Stack, T, Ts, Tzr); +yeccpars2_405(S, 'xor', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 259, Ss, Stack, T, Ts, Tzr); +yeccpars2_405(_S, Cat, Ss, Stack, T, Ts, Tzr) -> + [_,_|Nss] = Ss, + NewStack = yeccpars2_405_(Stack), + yeccgoto_type_200(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_407(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_406(S, '*', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 262, Ss, Stack, T, Ts, Tzr); +yeccpars2_406(S, '/', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 263, Ss, Stack, T, Ts, Tzr); +yeccpars2_406(S, 'and', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 264, Ss, Stack, T, Ts, Tzr); +yeccpars2_406(S, 'band', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 265, Ss, Stack, T, Ts, Tzr); +yeccpars2_406(S, 'div', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 266, Ss, Stack, T, Ts, Tzr); +yeccpars2_406(S, 'rem', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 267, Ss, Stack, T, Ts, Tzr); +yeccpars2_406(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_407_(Stack), - yeccgoto_type_sig(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). + NewStack = yeccpars2_406_(Stack), + yeccgoto_type_300(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). + +%% yeccpars2_407: see yeccpars2_334 -yeccpars2_408(S, ',', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 413, Ss, Stack, T, Ts, Tzr); yeccpars2_408(_S, Cat, Ss, Stack, T, Ts, Tzr) -> + [_,_|Nss] = Ss, NewStack = yeccpars2_408_(Stack), + yeccgoto_type_400(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). + +yeccpars2_409(S, atom, Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 412, Ss, Stack, T, Ts, Tzr); +yeccpars2_409(S, var, Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 413, Ss, Stack, T, Ts, Tzr). + +yeccpars2_410(_S, Cat, Ss, Stack, T, Ts, Tzr) -> + [_,_|Nss] = Ss, + NewStack = yeccpars2_410_(Stack), + yeccgoto_type_sig(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). + +yeccpars2_411(S, ',', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 419, Ss, Stack, T, Ts, Tzr); +yeccpars2_411(_S, Cat, Ss, Stack, T, Ts, Tzr) -> + NewStack = yeccpars2_411_(Stack), yeccgoto_type_guards(hd(Ss), Cat, Ss, NewStack, T, Ts, Tzr). -yeccpars2_409(S, '(', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 410, Ss, Stack, T, Ts, Tzr). +yeccpars2_412(S, '(', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 416, Ss, Stack, T, Ts, Tzr). -%% yeccpars2_410: see yeccpars2_318 +yeccpars2_413(S, '::', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 414, Ss, Stack, T, Ts, Tzr). -yeccpars2_411(S, ')', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 412, Ss, Stack, T, Ts, Tzr). +%% yeccpars2_414: see yeccpars2_322 -yeccpars2_412(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_415(_S, Cat, Ss, Stack, T, Ts, Tzr) -> + [_,_|Nss] = Ss, + NewStack = yeccpars2_415_(Stack), + yeccgoto_type_guard(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). + +%% yeccpars2_416: see yeccpars2_322 + +yeccpars2_417(S, ')', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 418, Ss, Stack, T, Ts, Tzr). + +yeccpars2_418(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_,_|Nss] = Ss, - NewStack = yeccpars2_412_(Stack), + NewStack = yeccpars2_418_(Stack), yeccgoto_type_guard(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -%% yeccpars2_413: see yeccpars2_406 +%% yeccpars2_419: see yeccpars2_409 -yeccpars2_414(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_420(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_414_(Stack), + NewStack = yeccpars2_420_(Stack), yeccgoto_type_guards(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -%% yeccpars2_415: see yeccpars2_295 +%% yeccpars2_421: see yeccpars2_295 -yeccpars2_416(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_422(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_416_(Stack), + NewStack = yeccpars2_422_(Stack), yeccgoto_type_sigs(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_417(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_423(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_,_|Nss] = Ss, - NewStack = yeccpars2_417_(Stack), + NewStack = yeccpars2_423_(Stack), yeccgoto_type_spec(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_418(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_424(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_|Nss] = Ss, - NewStack = yeccpars2_418_(Stack), + NewStack = yeccpars2_424_(Stack), yeccgoto_type_spec(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_419(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_425(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_419_(Stack), + NewStack = yeccpars2_425_(Stack), yeccgoto_attribute(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_420(S, ',', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 444, Ss, Stack, T, Ts, Tzr); -yeccpars2_420(S, '::', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 426, Ss, Stack, T, Ts, Tzr); -yeccpars2_420(_S, Cat, Ss, Stack, T, Ts, Tzr) -> - NewStack = yeccpars2_420_(Stack), +yeccpars2_426(S, ',', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 450, Ss, Stack, T, Ts, Tzr); +yeccpars2_426(S, '::', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 432, Ss, Stack, T, Ts, Tzr); +yeccpars2_426(_S, Cat, Ss, Stack, T, Ts, Tzr) -> + NewStack = yeccpars2_426_(Stack), yeccgoto_attr_val(hd(Ss), Cat, Ss, NewStack, T, Ts, Tzr). -yeccpars2_421(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_427(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_421_(Stack), + NewStack = yeccpars2_427_(Stack), yeccgoto_attribute(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -%% yeccpars2_422: see yeccpars2_45 +%% yeccpars2_428: see yeccpars2_45 -yeccpars2_423(S, ')', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 443, Ss, Stack, T, Ts, Tzr). +yeccpars2_429(S, ')', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 449, Ss, Stack, T, Ts, Tzr). -yeccpars2_424(S, ')', Ss, Stack, T, Ts, Tzr) -> +yeccpars2_430(S, ')', Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 210, Ss, Stack, T, Ts, Tzr); -yeccpars2_424(S, ',', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 425, Ss, Stack, T, Ts, Tzr); -yeccpars2_424(S, '::', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 426, Ss, Stack, T, Ts, Tzr). +yeccpars2_430(S, ',', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 431, Ss, Stack, T, Ts, Tzr); +yeccpars2_430(S, '::', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 432, Ss, Stack, T, Ts, Tzr). -yeccpars2_425(S, '#', Ss, Stack, T, Ts, Tzr) -> +yeccpars2_431(S, '#', Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 44, Ss, Stack, T, Ts, Tzr); -yeccpars2_425(S, '(', Ss, Stack, T, Ts, Tzr) -> +yeccpars2_431(S, '(', Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 45, Ss, Stack, T, Ts, Tzr); -yeccpars2_425(S, '+', Ss, Stack, T, Ts, Tzr) -> +yeccpars2_431(S, '+', Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 47, Ss, Stack, T, Ts, Tzr); -yeccpars2_425(S, '-', Ss, Stack, T, Ts, Tzr) -> +yeccpars2_431(S, '-', Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 48, Ss, Stack, T, Ts, Tzr); -yeccpars2_425(S, '.', Ss, Stack, T, Ts, Tzr) -> +yeccpars2_431(S, '.', Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 49, Ss, Stack, T, Ts, Tzr); -yeccpars2_425(S, atom, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_431(S, atom, Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 52, Ss, Stack, T, Ts, Tzr); -yeccpars2_425(S, 'bnot', Ss, Stack, T, Ts, Tzr) -> +yeccpars2_431(S, 'bnot', Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 54, Ss, Stack, T, Ts, Tzr); -yeccpars2_425(S, 'catch', Ss, Stack, T, Ts, Tzr) -> +yeccpars2_431(S, 'catch', Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 56, Ss, Stack, T, Ts, Tzr); -yeccpars2_425(S, 'not', Ss, Stack, T, Ts, Tzr) -> +yeccpars2_431(S, 'not', Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 62, Ss, Stack, T, Ts, Tzr); -yeccpars2_425(S, var, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_431(S, var, Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 67, Ss, Stack, T, Ts, Tzr); -yeccpars2_425(S, '{', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 430, Ss, Stack, T, Ts, Tzr); -yeccpars2_425(S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_431(S, '{', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 436, Ss, Stack, T, Ts, Tzr); +yeccpars2_431(S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_cont_13(S, Cat, Ss, Stack, T, Ts, Tzr). -%% yeccpars2_426: see yeccpars2_318 +%% yeccpars2_432: see yeccpars2_322 -yeccpars2_427(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_433(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_427_(Stack), + NewStack = yeccpars2_433_(Stack), yeccgoto_typed_attr_val(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_428(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_434(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_428_(Stack), + NewStack = yeccpars2_434_(Stack), yeccgoto_typed_attr_val(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_429(S, ')', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 442, Ss, Stack, T, Ts, Tzr). +yeccpars2_435(S, ')', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 448, Ss, Stack, T, Ts, Tzr). -%% yeccpars2_430: see yeccpars2_68 +%% yeccpars2_436: see yeccpars2_68 -yeccpars2_431(S, '}', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 441, Ss, Stack, T, Ts, Tzr). +yeccpars2_437(S, '}', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 447, Ss, Stack, T, Ts, Tzr). -yeccpars2_432(S, ',', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 438, Ss, Stack, T, Ts, Tzr); -yeccpars2_432(_S, Cat, Ss, Stack, T, Ts, Tzr) -> - NewStack = yeccpars2_432_(Stack), +yeccpars2_438(S, ',', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 444, Ss, Stack, T, Ts, Tzr); +yeccpars2_438(_S, Cat, Ss, Stack, T, Ts, Tzr) -> + NewStack = yeccpars2_438_(Stack), yeccgoto_typed_exprs(hd(Ss), Cat, Ss, NewStack, T, Ts, Tzr). -yeccpars2_433(S, ',', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 434, Ss, Stack, T, Ts, Tzr); -yeccpars2_433(S, '::', Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 435, Ss, Stack, T, Ts, Tzr); -yeccpars2_433(_S, Cat, Ss, Stack, T, Ts, Tzr) -> - NewStack = yeccpars2_433_(Stack), +yeccpars2_439(S, ',', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 440, Ss, Stack, T, Ts, Tzr); +yeccpars2_439(S, '::', Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 441, Ss, Stack, T, Ts, Tzr); +yeccpars2_439(_S, Cat, Ss, Stack, T, Ts, Tzr) -> + NewStack = yeccpars2_439_(Stack), yeccgoto_exprs(hd(Ss), Cat, Ss, NewStack, T, Ts, Tzr). -%% yeccpars2_434: see yeccpars2_45 +%% yeccpars2_440: see yeccpars2_45 -%% yeccpars2_435: see yeccpars2_318 +%% yeccpars2_441: see yeccpars2_322 -yeccpars2_436(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_442(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_436_(Stack), + NewStack = yeccpars2_442_(Stack), yeccgoto_typed_expr(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_437(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_443(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_437_(Stack), + NewStack = yeccpars2_443_(Stack), yeccgoto_typed_exprs(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -%% yeccpars2_438: see yeccpars2_45 +%% yeccpars2_444: see yeccpars2_45 -yeccpars2_439(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_445(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_439_(Stack), + NewStack = yeccpars2_445_(Stack), yeccgoto_typed_exprs(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_440(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_446(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_440_(Stack), + NewStack = yeccpars2_446_(Stack), yeccgoto_typed_exprs(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_441(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_447(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_441_(Stack), + NewStack = yeccpars2_447_(Stack), yeccgoto_typed_record_fields(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_442(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_448(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_,_,_|Nss] = Ss, - NewStack = yeccpars2_442_(Stack), + NewStack = yeccpars2_448_(Stack), yeccgoto_attr_val(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_443(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_449(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_,_,_|Nss] = Ss, - NewStack = yeccpars2_443_(Stack), + NewStack = yeccpars2_449_(Stack), yeccgoto_attribute(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -%% yeccpars2_444: see yeccpars2_425 +%% yeccpars2_450: see yeccpars2_431 -yeccpars2_445(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_451(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_445_(Stack), + NewStack = yeccpars2_451_(Stack), yeccgoto_attr_val(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_446(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_452(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_|Nss] = Ss, - NewStack = yeccpars2_446_(Stack), + NewStack = yeccpars2_452_(Stack), yeccgoto_form(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_447(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_453(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_|Nss] = Ss, - NewStack = yeccpars2_447_(Stack), + NewStack = yeccpars2_453_(Stack), yeccgoto_form(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_448(S, atom, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 450, Ss, Stack, T, Ts, Tzr). +yeccpars2_454(S, atom, Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 456, Ss, Stack, T, Ts, Tzr). -yeccpars2_449(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_455(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_449_(Stack), + NewStack = yeccpars2_455_(Stack), yeccgoto_function_clauses(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -%% yeccpars2_450: see yeccpars2_10 +%% yeccpars2_456: see yeccpars2_10 -yeccpars2_451(S, 'when', Ss, Stack, T, Ts, Tzr) -> +yeccpars2_457(S, 'when', Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 84, Ss, Stack, T, Ts, Tzr); -yeccpars2_451(_S, Cat, Ss, Stack, T, Ts, Tzr) -> - NewStack = yeccpars2_451_(Stack), - yeccpars2_83(452, Cat, [451 | Ss], NewStack, T, Ts, Tzr). +yeccpars2_457(_S, Cat, Ss, Stack, T, Ts, Tzr) -> + NewStack = yeccpars2_457_(Stack), + yeccpars2_83(458, Cat, [457 | Ss], NewStack, T, Ts, Tzr). -%% yeccpars2_452: see yeccpars2_83 +%% yeccpars2_458: see yeccpars2_83 -yeccpars2_453(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_459(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_|Nss] = Ss, - NewStack = yeccpars2_453_(Stack), + NewStack = yeccpars2_459_(Stack), yeccgoto_form(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -yeccpars2_454(S, atom, Ss, Stack, T, Ts, Tzr) -> - yeccpars1(S, 456, Ss, Stack, T, Ts, Tzr). +yeccpars2_460(S, atom, Ss, Stack, T, Ts, Tzr) -> + yeccpars1(S, 462, Ss, Stack, T, Ts, Tzr). -yeccpars2_455(_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccpars2_461(_S, Cat, Ss, Stack, T, Ts, Tzr) -> [_,_|Nss] = Ss, - NewStack = yeccpars2_455_(Stack), + NewStack = yeccpars2_461_(Stack), yeccgoto_rule_clauses(hd(Nss), Cat, Nss, NewStack, T, Ts, Tzr). -%% yeccpars2_456: see yeccpars2_10 +%% yeccpars2_462: see yeccpars2_10 -yeccpars2_457(S, 'when', Ss, Stack, T, Ts, Tzr) -> +yeccpars2_463(S, 'when', Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 84, Ss, Stack, T, Ts, Tzr); -yeccpars2_457(_S, Cat, Ss, Stack, T, Ts, Tzr) -> - NewStack = yeccpars2_457_(Stack), - yeccpars2_458(458, Cat, [457 | Ss], NewStack, T, Ts, Tzr). +yeccpars2_463(_S, Cat, Ss, Stack, T, Ts, Tzr) -> + NewStack = yeccpars2_463_(Stack), + yeccpars2_464(464, Cat, [463 | Ss], NewStack, T, Ts, Tzr). -yeccpars2_458(S, ':-', Ss, Stack, T, Ts, Tzr) -> +yeccpars2_464(S, ':-', Ss, Stack, T, Ts, Tzr) -> yeccpars1(S, 290, Ss, Stack, T, Ts, Tzr). yeccgoto_add_op(33, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_230(249, Cat, Ss, Stack, T, Ts, Tzr). + yeccpars2_230(249, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_add_op(313, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_334(403, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_add_op(405, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_334(403, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_argument_list(10=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_12(_S, Cat, Ss, Stack, T, Ts, Tzr); @@ -3868,9 +4007,9 @@ yeccgoto_argument_list(59, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_151(151, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_argument_list(161, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_151(151, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_argument_list(450=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_12(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_argument_list(456=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_12(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_argument_list(462=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_12(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_atomic(13=_S, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -3977,27 +4116,27 @@ yeccgoto_atomic(290=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_43(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_atomic(292=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_43(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_atomic(422=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_43(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_atomic(425=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_atomic(428=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_43(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_atomic(430=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_atomic(431=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_43(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_atomic(434=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_atomic(436=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_43(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_atomic(438=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_atomic(440=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_43(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_atomic(444=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_43(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_atomic(450=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_43(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_attr_val(292=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_421(_S, Cat, Ss, Stack, T, Ts, Tzr). + yeccpars2_427(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_attribute(0, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_8(8, Cat, Ss, Stack, T, Ts, Tzr). -yeccgoto_bin_base_type(321, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_365(365, Cat, Ss, Stack, T, Ts, Tzr). +yeccgoto_bin_base_type(324, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_364(364, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_bin_element(50, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_186(186, Cat, Ss, Stack, T, Ts, Tzr); @@ -4009,10 +4148,10 @@ yeccgoto_bin_elements(50, Cat, Ss, Stack, T, Ts, Tzr) -> yeccgoto_bin_elements(188=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_189(_S, Cat, Ss, Stack, T, Ts, Tzr). -yeccgoto_bin_unit_type(321, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_364(364, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_bin_unit_type(373, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_375(375, Cat, Ss, Stack, T, Ts, Tzr). +yeccgoto_bin_unit_type(324, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_363(363, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_bin_unit_type(372, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_374(374, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_binary(13=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_42(_S, Cat, Ss, Stack, T, Ts, Tzr); @@ -4118,17 +4257,17 @@ yeccgoto_binary(290, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_133(133, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_binary(292=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_42(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_binary(422=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_binary(428=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_42(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_binary(425=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_binary(431=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_42(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_binary(430=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_binary(436=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_42(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_binary(434=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_42(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_binary(438=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_binary(440=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_42(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_binary(444=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_42(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_binary(450=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_42(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_binary_comprehension(13=_S, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -4235,53 +4374,67 @@ yeccgoto_binary_comprehension(290=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_41(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_binary_comprehension(292=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_41(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_binary_comprehension(422=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_binary_comprehension(428=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_41(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_binary_comprehension(425=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_binary_comprehension(431=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_41(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_binary_comprehension(430=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_binary_comprehension(436=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_41(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_binary_comprehension(434=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_41(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_binary_comprehension(438=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_binary_comprehension(440=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_41(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_binary_comprehension(444=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_41(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_binary_comprehension(450=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_41(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_binary_type(310=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_316(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_binary_type(318=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_316(_S, Cat, Ss, Stack, T, Ts, Tzr); + yeccpars2_320(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_binary_type(319=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_320(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_binary_type(322=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_316(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_binary_type(327=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_316(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_binary_type(331=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_316(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_binary_type(337=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_316(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_binary_type(343=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_316(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_binary_type(346=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_316(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_binary_type(349=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_316(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_binary_type(381=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_316(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_binary_type(391=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_316(_S, Cat, Ss, Stack, T, Ts, Tzr); + yeccpars2_320(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_binary_type(325=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_320(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_binary_type(330=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_320(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_binary_type(334=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_320(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_binary_type(340=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_320(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_binary_type(344=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_320(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_binary_type(347=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_320(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_binary_type(350=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_320(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_binary_type(367=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_320(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_binary_type(370=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_320(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_binary_type(380=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_320(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_binary_type(390=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_320(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_binary_type(396=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_320(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_binary_type(399=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_316(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_binary_type(402=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_316(_S, Cat, Ss, Stack, T, Ts, Tzr); + yeccpars2_320(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_binary_type(401=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_320(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_binary_type(403=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_320(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_binary_type(404=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_316(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_binary_type(410=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_316(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_binary_type(426=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_316(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_binary_type(435=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_316(_S, Cat, Ss, Stack, T, Ts, Tzr). + yeccpars2_320(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_binary_type(407=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_320(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_binary_type(414=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_320(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_binary_type(416=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_320(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_binary_type(432=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_320(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_binary_type(441=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_320(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_bit_expr(50, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_183(183, Cat, Ss, Stack, T, Ts, Tzr); @@ -4405,25 +4558,25 @@ yeccgoto_case_expr(290=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_40(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_case_expr(292=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_40(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_case_expr(422=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_40(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_case_expr(425=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_case_expr(428=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_40(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_case_expr(430=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_case_expr(431=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_40(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_case_expr(434=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_case_expr(436=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_40(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_case_expr(438=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_case_expr(440=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_40(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_case_expr(444=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_40(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_case_expr(450=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_40(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_clause_args(10, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_11(11, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_clause_args(450, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_451(451, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_clause_args(456, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_457(457, Cat, Ss, Stack, T, Ts, Tzr). + yeccpars2_457(457, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_clause_args(462, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_463(463, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_clause_body(83=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_89(_S, Cat, Ss, Stack, T, Ts, Tzr); @@ -4443,7 +4596,7 @@ yeccgoto_clause_body(159=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_160(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_clause_body(287=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_289(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_clause_body(452=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_clause_body(458=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_289(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_clause_guard(11, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -4458,10 +4611,10 @@ yeccgoto_clause_guard(102, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_83(103, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_clause_guard(151, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_83(159, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_clause_guard(451, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_83(452, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_clause_guard(457, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_458(458, Cat, Ss, Stack, T, Ts, Tzr). + yeccpars2_83(458, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_clause_guard(463, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_464(464, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_comp_op(34, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_230(238, Cat, Ss, Stack, T, Ts, Tzr). @@ -4559,18 +4712,18 @@ yeccgoto_expr(228, Cat, Ss, Stack, T, Ts, Tzr) -> yeccgoto_expr(290, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_132(132, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_expr(292, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_420(420, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr(422, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_424(424, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr(425, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_426(426, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_expr(428, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_430(430, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_expr(431, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_39(39, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr(430, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_433(433, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr(434, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_433(433, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr(438, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_433(433, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_expr(436, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_439(439, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_expr(440, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_439(439, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_expr(444, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_439(439, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_expr(450, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_39(39, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_expr_100(13=_S, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -4653,17 +4806,17 @@ yeccgoto_expr_100(290=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_38(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_expr_100(292=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_38(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_100(422=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_38(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_100(425=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_100(428=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_38(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_100(430=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_100(431=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_38(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_100(434=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_100(436=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_38(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_100(438=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_100(440=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_38(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_expr_100(444=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_38(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_expr_100(450=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_38(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_expr_150(13, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -4748,17 +4901,17 @@ yeccgoto_expr_150(290, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_37(37, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_expr_150(292, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_37(37, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_150(422, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_150(428, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_37(37, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_150(425, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_150(431, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_37(37, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_150(430, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_150(436, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_37(37, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_150(434, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_37(37, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_150(438, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_150(440, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_37(37, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_expr_150(444, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_37(37, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_expr_150(450, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_37(37, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_expr_160(13, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -4845,17 +4998,17 @@ yeccgoto_expr_160(290, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_36(36, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_expr_160(292, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_36(36, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_160(422, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_36(36, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_160(425, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_160(428, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_36(36, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_160(430, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_160(431, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_36(36, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_160(434, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_160(436, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_36(36, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_160(438, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_160(440, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_36(36, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_expr_160(444, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_36(36, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_expr_160(450, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_36(36, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_expr_200(13, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -4942,17 +5095,17 @@ yeccgoto_expr_200(290, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_35(35, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_expr_200(292, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_35(35, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_200(422, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_35(35, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_200(425, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_200(428, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_35(35, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_200(430, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_200(431, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_35(35, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_200(434, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_200(436, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_35(35, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_200(438, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_200(440, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_35(35, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_expr_200(444, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_35(35, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_expr_200(450, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_35(35, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_expr_300(13, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -5043,17 +5196,17 @@ yeccgoto_expr_300(290, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_34(34, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_expr_300(292, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_34(34, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_300(422, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_34(34, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_300(425, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_300(428, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_34(34, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_300(430, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_300(431, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_34(34, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_300(434, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_300(436, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_34(34, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_300(438, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_300(440, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_34(34, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_expr_300(444, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_34(34, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_expr_300(450, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_34(34, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_expr_400(13, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -5144,17 +5297,17 @@ yeccgoto_expr_400(290, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_33(33, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_expr_400(292, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_33(33, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_400(422, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_400(428, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_33(33, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_400(425, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_400(431, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_33(33, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_400(430, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_400(436, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_33(33, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_400(434, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_33(33, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_400(438, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_400(440, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_33(33, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_expr_400(444, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_33(33, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_expr_400(450, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_33(33, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_expr_500(13, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -5247,17 +5400,17 @@ yeccgoto_expr_500(290, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_32(32, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_expr_500(292, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_32(32, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_500(422, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_32(32, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_500(425, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_500(428, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_32(32, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_500(430, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_500(431, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_32(32, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_500(434, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_500(436, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_32(32, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_500(438, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_500(440, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_32(32, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_expr_500(444, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_32(32, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_expr_500(450, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_32(32, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_expr_600(13=_S, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -5352,17 +5505,17 @@ yeccgoto_expr_600(290=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_31(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_expr_600(292=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_31(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_600(422=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_31(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_600(425=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_600(428=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_31(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_600(430=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_600(431=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_31(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_600(434=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_600(436=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_31(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_600(438=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_600(440=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_31(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_expr_600(444=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_31(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_expr_600(450=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_31(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_expr_700(13=_S, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -5459,17 +5612,17 @@ yeccgoto_expr_700(290=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_30(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_expr_700(292=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_30(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_700(422=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_700(428=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_30(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_700(425=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_700(431=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_30(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_700(430=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_700(436=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_30(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_700(434=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_30(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_700(438=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_700(440=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_30(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_expr_700(444=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_30(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_expr_700(450=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_30(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_expr_800(13, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -5566,17 +5719,17 @@ yeccgoto_expr_800(290, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_29(29, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_expr_800(292, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_29(29, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_800(422, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_800(428, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_29(29, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_800(425, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_800(431, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_29(29, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_800(430, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_800(436, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_29(29, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_800(434, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_29(29, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_800(438, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_800(440, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_29(29, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_expr_800(444, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_29(29, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_expr_800(450, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_29(29, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_expr_900(13, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -5673,17 +5826,17 @@ yeccgoto_expr_900(290, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_28(28, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_expr_900(292, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_28(28, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_900(422, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_28(28, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_900(425, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_900(428, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_28(28, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_900(430, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_900(431, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_28(28, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_900(434, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_900(436, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_28(28, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_900(438, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_900(440, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_28(28, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_expr_900(444, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_28(28, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_expr_900(450, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_28(28, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_expr_max(13, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -5790,17 +5943,17 @@ yeccgoto_expr_max(290, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_27(27, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_expr_max(292, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_27(27, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_max(422, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_27(27, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_max(425, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_max(428, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_27(27, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_max(430, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_max(431, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_27(27, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_max(434, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_max(436, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_27(27, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_expr_max(438, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_expr_max(440, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_27(27, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_expr_max(444, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_27(27, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_expr_max(450, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_27(27, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_exprs(13, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -5827,26 +5980,26 @@ yeccgoto_exprs(146, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_86(86, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_exprs(228=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_229(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_exprs(425, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_429(429, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_exprs(430, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_exprs(431, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_435(435, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_exprs(436, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_69(69, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_exprs(434=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_exprs(440=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_229(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_exprs(438=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_440(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_exprs(444=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_445(_S, Cat, Ss, Stack, T, Ts, Tzr). - -yeccgoto_field_type(386, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_388(388, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_field_type(393, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_388(388, Cat, Ss, Stack, T, Ts, Tzr). + yeccpars2_446(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_exprs(450=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_451(_S, Cat, Ss, Stack, T, Ts, Tzr). -yeccgoto_field_types(386, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_field_type(385, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_387(387, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_field_types(393=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_394(_S, Cat, Ss, Stack, T, Ts, Tzr). +yeccgoto_field_type(392, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_387(387, Cat, Ss, Stack, T, Ts, Tzr). + +yeccgoto_field_types(385, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_386(386, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_field_types(392=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_393(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_form(0, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_7(7, Cat, Ss, Stack, T, Ts, Tzr). @@ -5965,30 +6118,30 @@ yeccgoto_fun_expr(290=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_25(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_fun_expr(292=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_25(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_fun_expr(422=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_fun_expr(428=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_25(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_fun_expr(425=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_fun_expr(431=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_25(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_fun_expr(430=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_fun_expr(436=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_25(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_fun_expr(434=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_25(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_fun_expr(438=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_fun_expr(440=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_25(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_fun_expr(444=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_25(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_fun_expr(450=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_25(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_fun_type(295, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_309(309, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_fun_type(306, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_309(309, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_fun_type(334=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_336(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_fun_type(415, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_fun_type(337=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_339(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_fun_type(421, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_309(309, Cat, Ss, Stack, T, Ts, Tzr). -yeccgoto_fun_type_100(334, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_335(335, Cat, Ss, Stack, T, Ts, Tzr). +yeccgoto_fun_type_100(337, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_338(338, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_function(0, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_6(6, Cat, Ss, Stack, T, Ts, Tzr). @@ -6087,28 +6240,28 @@ yeccgoto_function_call(290=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_24(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_function_call(292=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_24(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_function_call(422=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_function_call(428=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_24(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_function_call(425=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_function_call(431=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_24(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_function_call(430=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_function_call(436=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_24(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_function_call(434=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_24(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_function_call(438=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_function_call(440=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_24(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_function_call(444=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_24(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_function_call(450=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_24(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_function_clause(0, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_5(5, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_function_clause(448, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_function_clause(454, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_5(5, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_function_clauses(0=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_4(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_function_clauses(448=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_449(_S, Cat, Ss, Stack, T, Ts, Tzr). +yeccgoto_function_clauses(454=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_455(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_guard(60, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_83(144, Cat, Ss, Stack, T, Ts, Tzr); @@ -6233,56 +6386,19 @@ yeccgoto_if_expr(290=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_23(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_if_expr(292=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_23(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_if_expr(422=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_if_expr(428=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_23(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_if_expr(425=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_if_expr(431=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_23(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_if_expr(430=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_if_expr(436=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_23(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_if_expr(434=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_23(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_if_expr(438=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_if_expr(440=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_23(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_if_expr(444=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_23(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_if_expr(450=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_23(_S, Cat, Ss, Stack, T, Ts, Tzr). -yeccgoto_int_type(310, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_315(315, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_int_type(318, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_315(315, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_int_type(322, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_315(315, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_int_type(327, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_315(315, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_int_type(331, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_315(315, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_int_type(337, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_315(315, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_int_type(343, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_315(315, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_int_type(346, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_315(315, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_int_type(349, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_315(315, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_int_type(381, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_315(315, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_int_type(391, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_315(315, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_int_type(397=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_398(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_int_type(399, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_315(315, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_int_type(402, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_315(315, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_int_type(404, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_315(315, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_int_type(410, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_315(315, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_int_type(426, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_315(315, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_int_type(435, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_315(315, Cat, Ss, Stack, T, Ts, Tzr). - yeccgoto_lc_expr(129, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_131(131, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_lc_expr(138, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -6405,17 +6521,17 @@ yeccgoto_list(290=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_22(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_list(292=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_22(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_list(422=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_22(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_list(425=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_list(428=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_22(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_list(430=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_list(431=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_22(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_list(434=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_list(436=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_22(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_list(438=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_list(440=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_22(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_list(444=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_22(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_list(450=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_22(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_list_comprehension(13=_S, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -6524,17 +6640,17 @@ yeccgoto_list_comprehension(290=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_21(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_list_comprehension(292=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_21(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_list_comprehension(422=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_list_comprehension(428=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_21(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_list_comprehension(425=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_list_comprehension(431=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_21(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_list_comprehension(430=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_list_comprehension(436=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_21(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_list_comprehension(434=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_21(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_list_comprehension(438=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_list_comprehension(440=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_21(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_list_comprehension(444=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_21(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_list_comprehension(450=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_21(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_list_op(33, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -6543,7 +6659,11 @@ yeccgoto_list_op(33, Cat, Ss, Stack, T, Ts, Tzr) -> yeccgoto_mult_op(32, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_230(261, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_mult_op(260, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_230(261, Cat, Ss, Stack, T, Ts, Tzr). + yeccpars2_230(261, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_mult_op(312, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_334(407, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_mult_op(406, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_334(407, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_opt_bit_size_expr(183, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_194(194, Cat, Ss, Stack, T, Ts, Tzr). @@ -6647,17 +6767,59 @@ yeccgoto_prefix_op(290, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_20(20, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_prefix_op(292, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_20(20, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_prefix_op(422, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_20(20, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_prefix_op(425, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_prefix_op(310, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_319(319, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_prefix_op(322, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_319(319, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_prefix_op(325, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_319(319, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_prefix_op(330, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_319(319, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_prefix_op(334, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_319(319, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_prefix_op(340, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_319(319, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_prefix_op(344, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_319(319, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_prefix_op(347, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_319(319, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_prefix_op(350, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_319(319, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_prefix_op(380, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_319(319, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_prefix_op(390, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_319(319, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_prefix_op(396, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_319(319, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_prefix_op(399, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_319(319, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_prefix_op(401, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_319(319, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_prefix_op(403, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_319(319, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_prefix_op(404, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_319(319, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_prefix_op(407, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_319(319, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_prefix_op(414, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_319(319, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_prefix_op(416, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_319(319, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_prefix_op(428, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_20(20, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_prefix_op(430, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_prefix_op(431, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_20(20, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_prefix_op(434, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_prefix_op(432, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_319(319, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_prefix_op(436, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_20(20, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_prefix_op(438, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_prefix_op(440, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_20(20, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_prefix_op(441, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_319(319, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_prefix_op(444, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_20(20, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_prefix_op(450, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_20(20, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_query_expr(13=_S, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -6764,17 +6926,17 @@ yeccgoto_query_expr(290=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_19(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_query_expr(292=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_19(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_query_expr(422=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_query_expr(428=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_19(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_query_expr(425=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_query_expr(431=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_19(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_query_expr(430=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_query_expr(436=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_19(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_query_expr(434=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_19(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_query_expr(438=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_query_expr(440=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_19(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_query_expr(444=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_19(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_query_expr(450=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_19(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_receive_expr(13=_S, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -6881,17 +7043,17 @@ yeccgoto_receive_expr(290=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_18(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_receive_expr(292=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_18(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_receive_expr(422=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_18(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_receive_expr(425=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_receive_expr(428=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_18(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_receive_expr(430=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_receive_expr(431=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_18(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_receive_expr(434=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_receive_expr(436=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_18(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_receive_expr(438=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_receive_expr(440=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_18(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_receive_expr(444=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_18(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_receive_expr(450=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_18(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_record_expr(13, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -6988,17 +7150,17 @@ yeccgoto_record_expr(290, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_17(17, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_record_expr(292, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_17(17, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_record_expr(422, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_17(17, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_record_expr(425, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_record_expr(428, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_17(17, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_record_expr(430, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_record_expr(431, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_17(17, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_record_expr(434, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_record_expr(436, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_17(17, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_record_expr(438, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_record_expr(440, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_17(17, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_record_expr(444, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_17(17, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_record_expr(450, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_17(17, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_record_field(214, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -7023,18 +7185,18 @@ yeccgoto_rule(0, Cat, Ss, Stack, T, Ts, Tzr) -> yeccgoto_rule_body(287=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_288(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_rule_body(458=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_rule_body(464=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_288(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_rule_clause(0, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_2(2, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_rule_clause(454, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_rule_clause(460, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_2(2, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_rule_clauses(0=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_1(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_rule_clauses(454=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_455(_S, Cat, Ss, Stack, T, Ts, Tzr). +yeccgoto_rule_clauses(460=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_461(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_spec_fun(293, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_295(295, Cat, Ss, Stack, T, Ts, Tzr); @@ -7147,17 +7309,17 @@ yeccgoto_strings(290=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_16(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_strings(292=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_16(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_strings(422=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_16(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_strings(425=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_strings(428=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_16(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_strings(430=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_strings(431=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_16(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_strings(434=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_strings(436=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_16(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_strings(438=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_strings(440=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_16(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_strings(444=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_16(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_strings(450=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_16(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_tail(171=_S, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -7166,85 +7328,89 @@ yeccgoto_tail(179=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_180(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_top_type(310, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_314(314, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type(318, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_383(383, Cat, Ss, Stack, T, Ts, Tzr); + yeccpars2_318(318, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_top_type(322, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_356(356, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type(327, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_314(314, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type(337, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_314(314, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type(343=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_344(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type(346, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_314(314, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type(349, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_314(314, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type(381=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_382(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type(391=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_392(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type(399, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_314(314, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type(402=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_403(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type(410, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_314(314, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type(426=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_427(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type(435=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_436(_S, Cat, Ss, Stack, T, Ts, Tzr). + yeccpars2_382(382, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type(325, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_357(357, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type(330, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_318(318, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type(340, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_318(318, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type(344=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_345(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type(347, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_318(318, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type(350, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_318(318, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type(380=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_381(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type(390=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_391(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type(396, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_318(318, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type(399=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_400(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type(414=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_415(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type(416, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_318(318, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type(432=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_433(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type(441=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_442(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_top_type_100(310=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_313(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type_100(318=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_313(_S, Cat, Ss, Stack, T, Ts, Tzr); + yeccpars2_317(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_top_type_100(322=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_313(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type_100(327=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_313(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type_100(331=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_332(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type_100(337=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_313(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type_100(343=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_313(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type_100(346=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_313(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type_100(349=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_313(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type_100(381=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_313(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type_100(391=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_313(_S, Cat, Ss, Stack, T, Ts, Tzr); + yeccpars2_317(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type_100(325=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_317(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type_100(330=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_317(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type_100(334=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_335(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type_100(340=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_317(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type_100(344=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_317(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type_100(347=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_317(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type_100(350=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_317(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type_100(380=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_317(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type_100(390=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_317(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type_100(396=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_317(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_top_type_100(399=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_313(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type_100(402=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_313(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type_100(404=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_405(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type_100(410=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_313(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type_100(426=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_313(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_type_100(435=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_313(_S, Cat, Ss, Stack, T, Ts, Tzr). + yeccpars2_317(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type_100(401=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_402(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type_100(414=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_317(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type_100(416=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_317(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type_100(432=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_317(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_type_100(441=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_317(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_top_types(310, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_312(312, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_types(327, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_328(328, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_types(337, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_312(312, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_types(346, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_353(353, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_types(349, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_350(350, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_types(399=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_400(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_top_types(410, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_411(411, Cat, Ss, Stack, T, Ts, Tzr). + yeccpars2_316(316, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_types(330, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_331(331, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_types(340, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_316(316, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_types(347, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_354(354, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_types(350, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_351(351, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_types(396=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_397(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_top_types(416, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_417(417, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_try_catch(72=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_73(_S, Cat, Ss, Stack, T, Ts, Tzr); @@ -7365,17 +7531,17 @@ yeccgoto_try_expr(290=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_15(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_try_expr(292=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_15(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_try_expr(422=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_try_expr(428=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_15(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_try_expr(425=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_try_expr(431=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_15(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_try_expr(430=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_try_expr(436=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_15(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_try_expr(434=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_15(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_try_expr(438=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_try_expr(440=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_15(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_try_expr(444=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_15(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_try_expr(450=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_15(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_tuple(13=_S, Cat, Ss, Stack, T, Ts, Tzr) -> @@ -7482,107 +7648,281 @@ yeccgoto_tuple(290=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_14(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_tuple(292=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_14(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_tuple(422=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_14(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_tuple(425=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_tuple(428=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_14(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_tuple(430=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_tuple(431=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_14(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_tuple(434=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_tuple(436=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_14(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_tuple(438=_S, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_tuple(440=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_14(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_tuple(444=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_14(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_tuple(450=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_14(_S, Cat, Ss, Stack, T, Ts, Tzr). -yeccgoto_type(310, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_311(311, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_type(318, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_311(311, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_type(322, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_311(311, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_type(327, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_311(311, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_type(331, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_311(311, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_type(337, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_311(311, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_type(343, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_311(311, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_type(346, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_311(311, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_type(349, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_311(311, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_type(381, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_311(311, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_type(391, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_311(311, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_type(399, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_311(311, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_type(402, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_311(311, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_type(404, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_311(311, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_type(410, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_311(311, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_type(426, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_311(311, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_type(435, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_311(311, Cat, Ss, Stack, T, Ts, Tzr). - -yeccgoto_type_guard(406, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_408(408, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_type_guard(413, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_408(408, Cat, Ss, Stack, T, Ts, Tzr). - -yeccgoto_type_guards(406=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_407(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_type_guards(413=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_414(_S, Cat, Ss, Stack, T, Ts, Tzr). +yeccgoto_type(310=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_315(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type(319=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_395(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type(322=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_315(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type(325=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_315(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type(330=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_315(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type(334=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_315(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type(340=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_315(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type(344=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_315(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type(347=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_315(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type(350=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_315(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type(367=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_368(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type(370=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_371(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type(380=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_315(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type(390=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_315(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type(396=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_315(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type(399=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_315(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type(401=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_315(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type(403=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_315(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type(404=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_315(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type(407=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_315(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type(414=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_315(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type(416=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_315(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type(432=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_315(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type(441=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_315(_S, Cat, Ss, Stack, T, Ts, Tzr). + +yeccgoto_type_200(310, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_314(314, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_200(322, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_314(314, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_200(325, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_314(314, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_200(330, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_314(314, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_200(334, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_314(314, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_200(340, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_314(314, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_200(344, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_314(314, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_200(347, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_314(314, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_200(350, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_314(314, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_200(380, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_314(314, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_200(390, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_314(314, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_200(396, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_314(314, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_200(399, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_314(314, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_200(401, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_314(314, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_200(414, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_314(314, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_200(416, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_314(314, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_200(432, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_314(314, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_200(441, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_314(314, Cat, Ss, Stack, T, Ts, Tzr). + +yeccgoto_type_300(310, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_313(313, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_300(322, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_313(313, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_300(325, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_313(313, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_300(330, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_313(313, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_300(334, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_313(313, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_300(340, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_313(313, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_300(344, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_313(313, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_300(347, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_313(313, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_300(350, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_313(313, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_300(380, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_313(313, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_300(390, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_313(313, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_300(396, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_313(313, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_300(399, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_313(313, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_300(401, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_313(313, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_300(404, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_405(405, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_300(414, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_313(313, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_300(416, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_313(313, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_300(432, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_313(313, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_300(441, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_313(313, Cat, Ss, Stack, T, Ts, Tzr). + +yeccgoto_type_400(310, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_312(312, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_400(322, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_312(312, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_400(325, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_312(312, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_400(330, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_312(312, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_400(334, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_312(312, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_400(340, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_312(312, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_400(344, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_312(312, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_400(347, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_312(312, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_400(350, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_312(312, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_400(380, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_312(312, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_400(390, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_312(312, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_400(396, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_312(312, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_400(399, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_312(312, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_400(401, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_312(312, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_400(403, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_406(406, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_400(404, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_312(312, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_400(414, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_312(312, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_400(416, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_312(312, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_400(432, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_312(312, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_400(441, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_312(312, Cat, Ss, Stack, T, Ts, Tzr). + +yeccgoto_type_500(310=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_311(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_500(322=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_311(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_500(325=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_311(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_500(330=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_311(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_500(334=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_311(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_500(340=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_311(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_500(344=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_311(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_500(347=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_311(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_500(350=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_311(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_500(380=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_311(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_500(390=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_311(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_500(396=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_311(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_500(399=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_311(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_500(401=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_311(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_500(403=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_311(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_500(404=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_311(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_500(407=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_408(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_500(414=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_311(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_500(416=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_311(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_500(432=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_311(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_500(441=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_311(_S, Cat, Ss, Stack, T, Ts, Tzr). + +yeccgoto_type_guard(409, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_411(411, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_guard(419, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_411(411, Cat, Ss, Stack, T, Ts, Tzr). + +yeccgoto_type_guards(409=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_410(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_type_guards(419=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_420(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_type_sig(295, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_308(308, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_type_sig(306, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_308(308, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_type_sig(415, Cat, Ss, Stack, T, Ts, Tzr) -> +yeccgoto_type_sig(421, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_308(308, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_type_sigs(295=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_418(_S, Cat, Ss, Stack, T, Ts, Tzr); + yeccpars2_424(_S, Cat, Ss, Stack, T, Ts, Tzr); yeccgoto_type_sigs(306, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_307(307, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_type_sigs(415=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_416(_S, Cat, Ss, Stack, T, Ts, Tzr). +yeccgoto_type_sigs(421=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_422(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_type_spec(293=_S, Cat, Ss, Stack, T, Ts, Tzr) -> yeccpars2_294(_S, Cat, Ss, Stack, T, Ts, Tzr). yeccgoto_typed_attr_val(292=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_419(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_typed_attr_val(422, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_423(423, Cat, Ss, Stack, T, Ts, Tzr). - -yeccgoto_typed_expr(430, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_432(432, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_typed_expr(434, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_432(432, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_typed_expr(438, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_432(432, Cat, Ss, Stack, T, Ts, Tzr). - -yeccgoto_typed_exprs(430, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_431(431, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_typed_exprs(434=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_437(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_typed_exprs(438=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_439(_S, Cat, Ss, Stack, T, Ts, Tzr). - -yeccgoto_typed_record_fields(425=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_428(_S, Cat, Ss, Stack, T, Ts, Tzr); -yeccgoto_typed_record_fields(444=_S, Cat, Ss, Stack, T, Ts, Tzr) -> - yeccpars2_428(_S, Cat, Ss, Stack, T, Ts, Tzr). + yeccpars2_425(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_typed_attr_val(428, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_429(429, Cat, Ss, Stack, T, Ts, Tzr). + +yeccgoto_typed_expr(436, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_438(438, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_typed_expr(440, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_438(438, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_typed_expr(444, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_438(438, Cat, Ss, Stack, T, Ts, Tzr). + +yeccgoto_typed_exprs(436, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_437(437, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_typed_exprs(440=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_443(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_typed_exprs(444=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_445(_S, Cat, Ss, Stack, T, Ts, Tzr). + +yeccgoto_typed_record_fields(431=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_434(_S, Cat, Ss, Stack, T, Ts, Tzr); +yeccgoto_typed_record_fields(450=_S, Cat, Ss, Stack, T, Ts, Tzr) -> + yeccpars2_434(_S, Cat, Ss, Stack, T, Ts, Tzr). -compile({inline,yeccpars2_1_/1}). --file("erl_parse.yrl", 476). +-file("erl_parse.yrl", 487). yeccpars2_1_(__Stack0) -> [__1 | __Stack] = __Stack0, [begin @@ -7590,7 +7930,7 @@ yeccpars2_1_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_2_/1}). --file("erl_parse.yrl", 478). +-file("erl_parse.yrl", 489). yeccpars2_2_(__Stack0) -> [__1 | __Stack] = __Stack0, [begin @@ -7598,7 +7938,7 @@ yeccpars2_2_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_4_/1}). --file("erl_parse.yrl", 186). +-file("erl_parse.yrl", 197). yeccpars2_4_(__Stack0) -> [__1 | __Stack] = __Stack0, [begin @@ -7606,7 +7946,7 @@ yeccpars2_4_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_5_/1}). --file("erl_parse.yrl", 188). +-file("erl_parse.yrl", 199). yeccpars2_5_(__Stack0) -> [__1 | __Stack] = __Stack0, [begin @@ -7614,14 +7954,14 @@ yeccpars2_5_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_11_/1}). --file("erl_parse.yrl", 198). +-file("erl_parse.yrl", 209). yeccpars2_11_(__Stack0) -> [begin [ ] end | __Stack0]. -compile({inline,yeccpars2_12_/1}). --file("erl_parse.yrl", 195). +-file("erl_parse.yrl", 206). yeccpars2_12_(__Stack0) -> [__1 | __Stack] = __Stack0, [begin @@ -7629,43 +7969,43 @@ yeccpars2_12_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_39_/1}). --file("erl_parse.yrl", 427). +-file("erl_parse.yrl", 438). yeccpars2_39_(__Stack0) -> [__1 | __Stack] = __Stack0, [begin [ __1 ] end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 7639). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 7979). -compile({inline,yeccpars2_46_/1}). --file("erl_parse.yrl", 423). +-file("erl_parse.yrl", 434). yeccpars2_46_(__Stack0) -> [__2,__1 | __Stack] = __Stack0, [begin { [ ] , ? line ( __1 ) } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 7648). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 7988). -compile({inline,yeccpars2_70_/1}). --file("erl_parse.yrl", 314). +-file("erl_parse.yrl", 325). yeccpars2_70_(__Stack0) -> [__2,__1 | __Stack] = __Stack0, [begin { tuple , ? line ( __1 ) , [ ] } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 7657). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 7997). -compile({inline,yeccpars2_71_/1}). --file("erl_parse.yrl", 315). +-file("erl_parse.yrl", 326). yeccpars2_71_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin { tuple , ? line ( __1 ) , __2 } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 7666). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8006). -compile({inline,yeccpars2_73_/1}). --file("erl_parse.yrl", 397). +-file("erl_parse.yrl", 408). yeccpars2_73_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin @@ -7673,14 +8013,14 @@ yeccpars2_73_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_77_/1}). --file("erl_parse.yrl", 198). +-file("erl_parse.yrl", 209). yeccpars2_77_(__Stack0) -> [begin [ ] end | __Stack0]. -compile({inline,yeccpars2_79_/1}). --file("erl_parse.yrl", 366). +-file("erl_parse.yrl", 377). yeccpars2_79_(__Stack0) -> [__1 | __Stack] = __Stack0, [begin @@ -7688,16 +8028,16 @@ yeccpars2_79_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_81_/1}). --file("erl_parse.yrl", 367). +-file("erl_parse.yrl", 378). yeccpars2_81_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin [ __1 | __3 ] end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 7698). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8038). -compile({inline,yeccpars2_82_/1}). --file("erl_parse.yrl", 395). +-file("erl_parse.yrl", 406). yeccpars2_82_(__Stack0) -> [__5,__4,__3,__2,__1 | __Stack] = __Stack0, [begin @@ -7705,7 +8045,7 @@ yeccpars2_82_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_85_/1}). --file("erl_parse.yrl", 197). +-file("erl_parse.yrl", 208). yeccpars2_85_(__Stack0) -> [__2,__1 | __Stack] = __Stack0, [begin @@ -7713,7 +8053,7 @@ yeccpars2_85_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_86_/1}). --file("erl_parse.yrl", 430). +-file("erl_parse.yrl", 441). yeccpars2_86_(__Stack0) -> [__1 | __Stack] = __Stack0, [begin @@ -7721,16 +8061,16 @@ yeccpars2_86_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_88_/1}). --file("erl_parse.yrl", 431). +-file("erl_parse.yrl", 442). yeccpars2_88_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin [ __1 | __3 ] end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 7731). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8071). -compile({inline,yeccpars2_89_/1}). --file("erl_parse.yrl", 370). +-file("erl_parse.yrl", 381). yeccpars2_89_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin @@ -7738,7 +8078,7 @@ yeccpars2_89_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_91_/1}). --file("erl_parse.yrl", 200). +-file("erl_parse.yrl", 211). yeccpars2_91_(__Stack0) -> [__2,__1 | __Stack] = __Stack0, [begin @@ -7746,7 +8086,7 @@ yeccpars2_91_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_93_/1}). --file("erl_parse.yrl", 406). +-file("erl_parse.yrl", 417). yeccpars2_93_(__Stack0) -> [__1 | __Stack] = __Stack0, [begin @@ -7754,22 +8094,22 @@ yeccpars2_93_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_94_/1}). --file("erl_parse.yrl", 198). +-file("erl_parse.yrl", 209). yeccpars2_94_(__Stack0) -> [begin [ ] end | __Stack0]. -compile({inline,yeccpars2_98_/1}). --file("erl_parse.yrl", 198). +-file("erl_parse.yrl", 209). yeccpars2_98_(__Stack0) -> [begin [ ] end | __Stack0]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 7770). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8110). -compile({inline,yeccpars2_100_/1}). --file("erl_parse.yrl", 416). +-file("erl_parse.yrl", 427). yeccpars2_100_(__Stack0) -> [__5,__4,__3,__2,__1 | __Stack] = __Stack0, [begin @@ -7778,15 +8118,15 @@ yeccpars2_100_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_102_/1}). --file("erl_parse.yrl", 198). +-file("erl_parse.yrl", 209). yeccpars2_102_(__Stack0) -> [begin [ ] end | __Stack0]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 7787). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8127). -compile({inline,yeccpars2_104_/1}). --file("erl_parse.yrl", 413). +-file("erl_parse.yrl", 424). yeccpars2_104_(__Stack0) -> [__5,__4,__3,__2,__1 | __Stack] = __Stack0, [begin @@ -7794,9 +8134,9 @@ yeccpars2_104_(__Stack0) -> { clause , L , [ { tuple , L , [ __1 , __3 , { var , L , '_' } ] } ] , __4 , __5 } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 7797). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8137). -compile({inline,yeccpars2_106_/1}). --file("erl_parse.yrl", 410). +-file("erl_parse.yrl", 421). yeccpars2_106_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin @@ -7805,7 +8145,7 @@ yeccpars2_106_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_108_/1}). --file("erl_parse.yrl", 407). +-file("erl_parse.yrl", 418). yeccpars2_108_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin @@ -7813,7 +8153,7 @@ yeccpars2_108_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_110_/1}). --file("erl_parse.yrl", 400). +-file("erl_parse.yrl", 411). yeccpars2_110_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin @@ -7821,7 +8161,7 @@ yeccpars2_110_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_112_/1}). --file("erl_parse.yrl", 402). +-file("erl_parse.yrl", 413). yeccpars2_112_(__Stack0) -> [__5,__4,__3,__2,__1 | __Stack] = __Stack0, [begin @@ -7829,43 +8169,43 @@ yeccpars2_112_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_114_/1}). --file("erl_parse.yrl", 404). +-file("erl_parse.yrl", 415). yeccpars2_114_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin { [ ] , __2 } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 7839). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8179). -compile({inline,yeccpars2_115_/1}). --file("erl_parse.yrl", 441). +-file("erl_parse.yrl", 452). yeccpars2_115_(__Stack0) -> [__2,__1 | __Stack] = __Stack0, [begin { string , ? line ( __1 ) , element ( 3 , __1 ) ++ element ( 3 , __2 ) } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 7848). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8188). -compile({inline,yeccpars2_120_/1}). --file("erl_parse.yrl", 375). +-file("erl_parse.yrl", 386). yeccpars2_120_(__Stack0) -> [__5,__4,__3,__2,__1 | __Stack] = __Stack0, [begin { 'receive' , ? line ( __1 ) , [ ] , __3 , __4 } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 7857). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8197). -compile({inline,yeccpars2_122_/1}). --file("erl_parse.yrl", 373). +-file("erl_parse.yrl", 384). yeccpars2_122_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin { 'receive' , ? line ( __1 ) , __2 } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 7866). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8206). -compile({inline,yeccpars2_125_/1}). --file("erl_parse.yrl", 377). +-file("erl_parse.yrl", 388). yeccpars2_125_(__Stack0) -> [__6,__5,__4,__3,__2,__1 | __Stack] = __Stack0, [begin @@ -7873,25 +8213,25 @@ yeccpars2_125_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_131_/1}). --file("erl_parse.yrl", 307). +-file("erl_parse.yrl", 318). yeccpars2_131_(__Stack0) -> [__1 | __Stack] = __Stack0, [begin [ __1 ] end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 7883). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8223). -compile({inline,yeccpars2_135_/1}). --file("erl_parse.yrl", 312). +-file("erl_parse.yrl", 323). yeccpars2_135_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin { b_generate , ? line ( __2 ) , __1 , __3 } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 7892). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8232). -compile({inline,yeccpars2_137_/1}). --file("erl_parse.yrl", 311). +-file("erl_parse.yrl", 322). yeccpars2_137_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin @@ -7899,25 +8239,25 @@ yeccpars2_137_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_139_/1}). --file("erl_parse.yrl", 308). +-file("erl_parse.yrl", 319). yeccpars2_139_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin [ __1 | __3 ] end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 7909). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8249). -compile({inline,yeccpars2_140_/1}). --file("erl_parse.yrl", 304). +-file("erl_parse.yrl", 315). yeccpars2_140_(__Stack0) -> [__5,__4,__3,__2,__1 | __Stack] = __Stack0, [begin { lc , ? line ( __1 ) , __2 , __4 } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 7918). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8258). -compile({inline,yeccpars2_141_/1}). --file("erl_parse.yrl", 420). +-file("erl_parse.yrl", 431). yeccpars2_141_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin @@ -7925,16 +8265,16 @@ yeccpars2_141_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_143_/1}). --file("erl_parse.yrl", 356). +-file("erl_parse.yrl", 367). yeccpars2_143_(__Stack0) -> [__1 | __Stack] = __Stack0, [begin [ __1 ] end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 7935). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8275). -compile({inline,yeccpars2_145_/1}). --file("erl_parse.yrl", 360). +-file("erl_parse.yrl", 371). yeccpars2_145_(__Stack0) -> [__2,__1 | __Stack] = __Stack0, [begin @@ -7942,16 +8282,16 @@ yeccpars2_145_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_147_/1}). --file("erl_parse.yrl", 357). +-file("erl_parse.yrl", 368). yeccpars2_147_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin [ __1 | __3 ] end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 7952). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8292). -compile({inline,yeccpars2_148_/1}). --file("erl_parse.yrl", 354). +-file("erl_parse.yrl", 365). yeccpars2_148_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin @@ -7959,7 +8299,7 @@ yeccpars2_148_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_150_/1}). --file("erl_parse.yrl", 387). +-file("erl_parse.yrl", 398). yeccpars2_150_(__Stack0) -> [__1 | __Stack] = __Stack0, [begin @@ -7967,24 +8307,24 @@ yeccpars2_150_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_151_/1}). --file("erl_parse.yrl", 198). +-file("erl_parse.yrl", 209). yeccpars2_151_(__Stack0) -> [begin [ ] end | __Stack0]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 7976). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8316). -compile({inline,yeccpars2_157_/1}). --file("erl_parse.yrl", 383). +-file("erl_parse.yrl", 394). yeccpars2_157_(__Stack0) -> [__6,__5,__4,__3,__2,__1 | __Stack] = __Stack0, [begin { 'fun' , ? line ( __1 ) , { function , element ( 3 , __2 ) , element ( 3 , __4 ) , element ( 3 , __6 ) } } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 7985). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8325). -compile({inline,yeccpars2_158_/1}). --file("erl_parse.yrl", 381). +-file("erl_parse.yrl", 392). yeccpars2_158_(__Stack0) -> [__4,__3,__2,__1 | __Stack] = __Stack0, [begin @@ -7992,7 +8332,7 @@ yeccpars2_158_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_160_/1}). --file("erl_parse.yrl", 391). +-file("erl_parse.yrl", 402). yeccpars2_160_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin @@ -8001,70 +8341,70 @@ yeccpars2_160_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_162_/1}). --file("erl_parse.yrl", 388). +-file("erl_parse.yrl", 399). yeccpars2_162_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin [ __1 | __3 ] end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8011). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8351). -compile({inline,yeccpars2_163_/1}). --file("erl_parse.yrl", 385). +-file("erl_parse.yrl", 396). yeccpars2_163_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin build_fun ( ? line ( __1 ) , __2 ) end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8020). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8360). -compile({inline,yeccpars2_164_/1}). --file("erl_parse.yrl", 203). +-file("erl_parse.yrl", 214). yeccpars2_164_(__Stack0) -> [__2,__1 | __Stack] = __Stack0, [begin { 'catch' , ? line ( __1 ) , __2 } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8029). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8369). -compile({inline,yeccpars2_168_/1}). --file("erl_parse.yrl", 364). +-file("erl_parse.yrl", 375). yeccpars2_168_(__Stack0) -> [__5,__4,__3,__2,__1 | __Stack] = __Stack0, [begin { 'case' , ? line ( __1 ) , __2 , __4 } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8038). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8378). -compile({inline,yeccpars2_170_/1}). --file("erl_parse.yrl", 259). +-file("erl_parse.yrl", 270). yeccpars2_170_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin { block , ? line ( __1 ) , __2 } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8047). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8387). -compile({inline,yeccpars2_172_/1}). --file("erl_parse.yrl", 268). +-file("erl_parse.yrl", 279). yeccpars2_172_(__Stack0) -> [__2,__1 | __Stack] = __Stack0, [begin { nil , ? line ( __1 ) } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8056). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8396). -compile({inline,yeccpars2_173_/1}). --file("erl_parse.yrl", 269). +-file("erl_parse.yrl", 280). yeccpars2_173_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin { cons , ? line ( __1 ) , __2 , __3 } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8065). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8405). -compile({inline,yeccpars2_175_/1}). --file("erl_parse.yrl", 271). +-file("erl_parse.yrl", 282). yeccpars2_175_(__Stack0) -> [__1 | __Stack] = __Stack0, [begin @@ -8072,16 +8412,16 @@ yeccpars2_175_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_178_/1}). --file("erl_parse.yrl", 272). +-file("erl_parse.yrl", 283). yeccpars2_178_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin __2 end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8082). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8422). -compile({inline,yeccpars2_180_/1}). --file("erl_parse.yrl", 273). +-file("erl_parse.yrl", 284). yeccpars2_180_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin @@ -8089,23 +8429,23 @@ yeccpars2_180_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_183_/1}). --file("erl_parse.yrl", 289). +-file("erl_parse.yrl", 300). yeccpars2_183_(__Stack0) -> [begin default end | __Stack0]. -compile({inline,yeccpars2_186_/1}). --file("erl_parse.yrl", 279). +-file("erl_parse.yrl", 290). yeccpars2_186_(__Stack0) -> [__1 | __Stack] = __Stack0, [begin [ __1 ] end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8106). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8446). -compile({inline,yeccpars2_187_/1}). --file("erl_parse.yrl", 276). +-file("erl_parse.yrl", 287). yeccpars2_187_(__Stack0) -> [__2,__1 | __Stack] = __Stack0, [begin @@ -8113,25 +8453,25 @@ yeccpars2_187_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_189_/1}). --file("erl_parse.yrl", 280). +-file("erl_parse.yrl", 291). yeccpars2_189_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin [ __1 | __3 ] end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8123). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8463). -compile({inline,yeccpars2_190_/1}). --file("erl_parse.yrl", 277). +-file("erl_parse.yrl", 288). yeccpars2_190_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin { bin , ? line ( __1 ) , __2 } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8132). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8472). -compile({inline,yeccpars2_193_/1}). --file("erl_parse.yrl", 306). +-file("erl_parse.yrl", 317). yeccpars2_193_(__Stack0) -> [__5,__4,__3,__2,__1 | __Stack] = __Stack0, [begin @@ -8139,23 +8479,23 @@ yeccpars2_193_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_194_/1}). --file("erl_parse.yrl", 292). +-file("erl_parse.yrl", 303). yeccpars2_194_(__Stack0) -> [begin default end | __Stack0]. -compile({inline,yeccpars2_197_/1}). --file("erl_parse.yrl", 288). +-file("erl_parse.yrl", 299). yeccpars2_197_(__Stack0) -> [__2,__1 | __Stack] = __Stack0, [begin __2 end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8156). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8496). -compile({inline,yeccpars2_198_/1}). --file("erl_parse.yrl", 283). +-file("erl_parse.yrl", 294). yeccpars2_198_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin @@ -8163,7 +8503,7 @@ yeccpars2_198_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_200_/1}). --file("erl_parse.yrl", 291). +-file("erl_parse.yrl", 302). yeccpars2_200_(__Stack0) -> [__2,__1 | __Stack] = __Stack0, [begin @@ -8171,7 +8511,7 @@ yeccpars2_200_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_201_/1}). --file("erl_parse.yrl", 295). +-file("erl_parse.yrl", 306). yeccpars2_201_(__Stack0) -> [__1 | __Stack] = __Stack0, [begin @@ -8179,7 +8519,7 @@ yeccpars2_201_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_202_/1}). --file("erl_parse.yrl", 297). +-file("erl_parse.yrl", 308). yeccpars2_202_(__Stack0) -> [__1 | __Stack] = __Stack0, [begin @@ -8187,7 +8527,7 @@ yeccpars2_202_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_204_/1}). --file("erl_parse.yrl", 298). +-file("erl_parse.yrl", 309). yeccpars2_204_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin @@ -8195,25 +8535,25 @@ yeccpars2_204_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_206_/1}). --file("erl_parse.yrl", 294). +-file("erl_parse.yrl", 305). yeccpars2_206_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin [ __1 | __3 ] end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8205). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8545). -compile({inline,yeccpars2_207_/1}). --file("erl_parse.yrl", 285). +-file("erl_parse.yrl", 296). yeccpars2_207_(__Stack0) -> [__2,__1 | __Stack] = __Stack0, [begin ? mkop1 ( __1 , __2 ) end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8214). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8554). -compile({inline,yeccpars2_208_/1}). --file("erl_parse.yrl", 245). +-file("erl_parse.yrl", 256). yeccpars2_208_(__Stack0) -> [__2,__1 | __Stack] = __Stack0, [begin @@ -8221,16 +8561,16 @@ yeccpars2_208_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_210_/1}). --file("erl_parse.yrl", 258). +-file("erl_parse.yrl", 269). yeccpars2_210_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin __2 end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8231). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8571). -compile({inline,yeccpars2_212_/1}). --file("erl_parse.yrl", 329). +-file("erl_parse.yrl", 340). yeccpars2_212_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin @@ -8238,7 +8578,7 @@ yeccpars2_212_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_216_/1}). --file("erl_parse.yrl", 342). +-file("erl_parse.yrl", 353). yeccpars2_216_(__Stack0) -> [__1 | __Stack] = __Stack0, [begin @@ -8246,25 +8586,25 @@ yeccpars2_216_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_219_/1}). --file("erl_parse.yrl", 339). +-file("erl_parse.yrl", 350). yeccpars2_219_(__Stack0) -> [__2,__1 | __Stack] = __Stack0, [begin [ ] end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8256). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8596). -compile({inline,yeccpars2_221_/1}). --file("erl_parse.yrl", 345). +-file("erl_parse.yrl", 356). yeccpars2_221_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin { record_field , ? line ( __1 ) , __1 , __3 } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8265). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8605). -compile({inline,yeccpars2_223_/1}). --file("erl_parse.yrl", 346). +-file("erl_parse.yrl", 357). yeccpars2_223_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin @@ -8272,7 +8612,7 @@ yeccpars2_223_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_225_/1}). --file("erl_parse.yrl", 343). +-file("erl_parse.yrl", 354). yeccpars2_225_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin @@ -8280,16 +8620,16 @@ yeccpars2_225_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_226_/1}). --file("erl_parse.yrl", 340). +-file("erl_parse.yrl", 351). yeccpars2_226_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin __2 end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8290). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8630). -compile({inline,yeccpars2_227_/1}). --file("erl_parse.yrl", 327). +-file("erl_parse.yrl", 338). yeccpars2_227_(__Stack0) -> [__4,__3,__2,__1 | __Stack] = __Stack0, [begin @@ -8297,178 +8637,178 @@ yeccpars2_227_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_229_/1}). --file("erl_parse.yrl", 428). +-file("erl_parse.yrl", 439). yeccpars2_229_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin [ __1 | __3 ] end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8307). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8647). -compile({inline,yeccpars2_232_/1}). --file("erl_parse.yrl", 206). +-file("erl_parse.yrl", 217). yeccpars2_232_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin { match , ? line ( __2 ) , __1 , __3 } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8316). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8656). -compile({inline,yeccpars2_233_/1}). --file("erl_parse.yrl", 207). +-file("erl_parse.yrl", 218). yeccpars2_233_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin ? mkop2 ( __1 , __2 , __3 ) end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8325). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8665). -compile({inline,yeccpars2_235_/1}). --file("erl_parse.yrl", 210). +-file("erl_parse.yrl", 221). yeccpars2_235_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin ? mkop2 ( __1 , __2 , __3 ) end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8334). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8674). -compile({inline,yeccpars2_237_/1}). --file("erl_parse.yrl", 213). +-file("erl_parse.yrl", 224). yeccpars2_237_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin ? mkop2 ( __1 , __2 , __3 ) end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8343). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8683). -compile({inline,yeccpars2_247_/1}). --file("erl_parse.yrl", 217). +-file("erl_parse.yrl", 228). yeccpars2_247_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin ? mkop2 ( __1 , __2 , __3 ) end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8352). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8692). -compile({inline,yeccpars2_260_/1}). --file("erl_parse.yrl", 225). +-file("erl_parse.yrl", 236). yeccpars2_260_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin ? mkop2 ( __1 , __2 , __3 ) end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8361). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8701). -compile({inline,yeccpars2_268_/1}). --file("erl_parse.yrl", 229). +-file("erl_parse.yrl", 240). yeccpars2_268_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin ? mkop2 ( __1 , __2 , __3 ) end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8370). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8710). -compile({inline,yeccpars2_269_/1}). --file("erl_parse.yrl", 221). +-file("erl_parse.yrl", 232). yeccpars2_269_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin ? mkop2 ( __1 , __2 , __3 ) end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8379). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8719). -compile({inline,yeccpars2_270_/1}). --file("erl_parse.yrl", 351). +-file("erl_parse.yrl", 362). yeccpars2_270_(__Stack0) -> [__2,__1 | __Stack] = __Stack0, [begin { call , ? line ( __1 ) , __1 , element ( 1 , __2 ) } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8388). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8728). -compile({inline,yeccpars2_273_/1}). --file("erl_parse.yrl", 241). +-file("erl_parse.yrl", 252). yeccpars2_273_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin { remote , ? line ( __2 ) , __1 , __3 } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8397). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8737). -compile({inline,yeccpars2_274_/1}). --file("erl_parse.yrl", 247). +-file("erl_parse.yrl", 258). yeccpars2_274_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin { record_field , ? line ( __2 ) , __1 , __3 } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8406). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8746). -compile({inline,yeccpars2_277_/1}). --file("erl_parse.yrl", 333). +-file("erl_parse.yrl", 344). yeccpars2_277_(__Stack0) -> [__4,__3,__2,__1 | __Stack] = __Stack0, [begin { record , ? line ( __2 ) , __1 , element ( 3 , __3 ) , __4 } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8415). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8755). -compile({inline,yeccpars2_279_/1}). --file("erl_parse.yrl", 331). +-file("erl_parse.yrl", 342). yeccpars2_279_(__Stack0) -> [__5,__4,__3,__2,__1 | __Stack] = __Stack0, [begin { record_field , ? line ( __2 ) , __1 , element ( 3 , __3 ) , __5 } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8424). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8764). -compile({inline,yeccpars2_280_/1}). --file("erl_parse.yrl", 424). +-file("erl_parse.yrl", 435). yeccpars2_280_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin { __2 , ? line ( __1 ) } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8433). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8773). -compile({inline,yeccpars2_281_/1}). --file("erl_parse.yrl", 233). +-file("erl_parse.yrl", 244). yeccpars2_281_(__Stack0) -> [__2,__1 | __Stack] = __Stack0, [begin ? mkop1 ( __1 , __2 ) end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8442). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8782). -compile({inline,yeccpars2_284_/1}). --file("erl_parse.yrl", 337). +-file("erl_parse.yrl", 348). yeccpars2_284_(__Stack0) -> [__4,__3,__2,__1 | __Stack] = __Stack0, [begin { record , ? line ( __2 ) , __1 , element ( 3 , __3 ) , __4 } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8451). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8791). -compile({inline,yeccpars2_286_/1}). --file("erl_parse.yrl", 335). +-file("erl_parse.yrl", 346). yeccpars2_286_(__Stack0) -> [__5,__4,__3,__2,__1 | __Stack] = __Stack0, [begin { record_field , ? line ( __2 ) , __1 , element ( 3 , __3 ) , __5 } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8460). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8800). -compile({inline,yeccpars2_288_/1}). --file("erl_parse.yrl", 482). +-file("erl_parse.yrl", 493). yeccpars2_288_(__Stack0) -> [__4,__3,__2,__1 | __Stack] = __Stack0, [begin { clause , ? line ( __1 ) , element ( 3 , __1 ) , __2 , __3 , __4 } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8469). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8809). -compile({inline,yeccpars2_289_/1}). --file("erl_parse.yrl", 192). +-file("erl_parse.yrl", 203). yeccpars2_289_(__Stack0) -> [__4,__3,__2,__1 | __Stack] = __Stack0, [begin @@ -8476,7 +8816,7 @@ yeccpars2_289_(__Stack0) -> end | __Stack]. -compile({inline,yeccpars2_291_/1}). --file("erl_parse.yrl", 484). +-file("erl_parse.yrl", 495). yeccpars2_291_(__Stack0) -> [__2,__1 | __Stack] = __Stack0, [begin @@ -8523,138 +8863,138 @@ yeccpars2_308_(__Stack0) -> [ __1 ] end | __Stack]. --compile({inline,yeccpars2_314_/1}). --file("erl_parse.yrl", 112). -yeccpars2_314_(__Stack0) -> +-compile({inline,yeccpars2_318_/1}). +-file("erl_parse.yrl", 113). +yeccpars2_318_(__Stack0) -> [__1 | __Stack] = __Stack0, [begin [ __1 ] end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8534). --compile({inline,yeccpars2_329_/1}). --file("erl_parse.yrl", 135). -yeccpars2_329_(__Stack0) -> +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8874). +-compile({inline,yeccpars2_332_/1}). +-file("erl_parse.yrl", 152). +yeccpars2_332_(__Stack0) -> [__2,__1 | __Stack] = __Stack0, [begin { type , ? line ( __1 ) , tuple , [ ] } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8543). --compile({inline,yeccpars2_330_/1}). --file("erl_parse.yrl", 136). -yeccpars2_330_(__Stack0) -> +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8883). +-compile({inline,yeccpars2_333_/1}). +-file("erl_parse.yrl", 153). +yeccpars2_333_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin { type , ? line ( __1 ) , tuple , __2 } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8552). --compile({inline,yeccpars2_332_/1}). --file("erl_parse.yrl", 115). -yeccpars2_332_(__Stack0) -> +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8892). +-compile({inline,yeccpars2_335_/1}). +-file("erl_parse.yrl", 116). +yeccpars2_335_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin { ann_type , ? line ( __1 ) , [ __1 , __3 ] } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8561). --compile({inline,yeccpars2_338_/1}). --file("erl_parse.yrl", 144). -yeccpars2_338_(__Stack0) -> +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8901). +-compile({inline,yeccpars2_341_/1}). +-file("erl_parse.yrl", 159). +yeccpars2_341_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin { type , ? line ( __1 ) , 'fun' , [ ] } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8570). --compile({inline,yeccpars2_344_/1}). --file("erl_parse.yrl", 152). -yeccpars2_344_(__Stack0) -> - [__7,__6,__5,__4,__3,__2,__1 | __Stack] = __Stack0, +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8910). +-compile({inline,yeccpars2_345_/1}). +-file("erl_parse.yrl", 163). +yeccpars2_345_(__Stack0) -> + [__5,__4,__3,__2,__1 | __Stack] = __Stack0, [begin { type , ? line ( __1 ) , 'fun' , - [ { type , ? line ( __1 ) , any } , __7 ] } + [ { type , ? line ( __1 ) , any } , __5 ] } end | __Stack]. --compile({inline,yeccpars2_345_/1}). --file("erl_parse.yrl", 145). -yeccpars2_345_(__Stack0) -> +-compile({inline,yeccpars2_346_/1}). +-file("erl_parse.yrl", 160). +yeccpars2_346_(__Stack0) -> [__4,__3,__2,__1 | __Stack] = __Stack0, [begin __3 end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8588). --compile({inline,yeccpars2_351_/1}). --file("erl_parse.yrl", 127). -yeccpars2_351_(__Stack0) -> +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8928). +-compile({inline,yeccpars2_352_/1}). +-file("erl_parse.yrl", 144). +yeccpars2_352_(__Stack0) -> [__5,__4,__3,__2,__1 | __Stack] = __Stack0, [begin { remote_type , ? line ( __1 ) , [ __1 , __3 , [ ] ] } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8598). --compile({inline,yeccpars2_352_/1}). --file("erl_parse.yrl", 129). -yeccpars2_352_(__Stack0) -> +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8938). +-compile({inline,yeccpars2_353_/1}). +-file("erl_parse.yrl", 146). +yeccpars2_353_(__Stack0) -> [__6,__5,__4,__3,__2,__1 | __Stack] = __Stack0, [begin { remote_type , ? line ( __1 ) , [ __1 , __3 , __5 ] } end | __Stack]. --compile({inline,yeccpars2_354_/1}). --file("erl_parse.yrl", 124). -yeccpars2_354_(__Stack0) -> +-compile({inline,yeccpars2_355_/1}). +-file("erl_parse.yrl", 141). +yeccpars2_355_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin build_gen_type ( __1 ) end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8616). --compile({inline,yeccpars2_355_/1}). --file("erl_parse.yrl", 125). -yeccpars2_355_(__Stack0) -> +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8956). +-compile({inline,yeccpars2_356_/1}). +-file("erl_parse.yrl", 142). +yeccpars2_356_(__Stack0) -> [__4,__3,__2,__1 | __Stack] = __Stack0, [begin { type , ? line ( __1 ) , normalise ( __1 ) , __3 } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8626). --compile({inline,yeccpars2_357_/1}). --file("erl_parse.yrl", 131). -yeccpars2_357_(__Stack0) -> +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8966). +-compile({inline,yeccpars2_358_/1}). +-file("erl_parse.yrl", 148). +yeccpars2_358_(__Stack0) -> [__2,__1 | __Stack] = __Stack0, [begin { type , ? line ( __1 ) , nil , [ ] } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8635). --compile({inline,yeccpars2_359_/1}). --file("erl_parse.yrl", 132). -yeccpars2_359_(__Stack0) -> +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8975). +-compile({inline,yeccpars2_360_/1}). +-file("erl_parse.yrl", 149). +yeccpars2_360_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin { type , ? line ( __1 ) , list , [ __2 ] } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8644). --compile({inline,yeccpars2_363_/1}). --file("erl_parse.yrl", 133). -yeccpars2_363_(__Stack0) -> - [__7,__6,__5,__4,__3,__2,__1 | __Stack] = __Stack0, +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8984). +-compile({inline,yeccpars2_362_/1}). +-file("erl_parse.yrl", 150). +yeccpars2_362_(__Stack0) -> + [__5,__4,__3,__2,__1 | __Stack] = __Stack0, [begin { type , ? line ( __1 ) , nonempty_list , [ __2 ] } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8654). --compile({inline,yeccpars2_366_/1}). --file("erl_parse.yrl", 168). -yeccpars2_366_(__Stack0) -> +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8994). +-compile({inline,yeccpars2_365_/1}). +-file("erl_parse.yrl", 179). +yeccpars2_365_(__Stack0) -> [__2,__1 | __Stack] = __Stack0, [begin { type , ? line ( __1 ) , binary , @@ -8662,44 +9002,44 @@ yeccpars2_366_(__Stack0) -> abstract ( 0 , ? line ( __1 ) ) ] } end | __Stack]. --compile({inline,yeccpars2_369_/1}). --file("erl_parse.yrl", 178). -yeccpars2_369_(__Stack0) -> +-compile({inline,yeccpars2_368_/1}). +-file("erl_parse.yrl", 189). +yeccpars2_368_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin build_bin_type ( [ __1 ] , __3 ) end | __Stack]. --compile({inline,yeccpars2_372_/1}). --file("erl_parse.yrl", 180). -yeccpars2_372_(__Stack0) -> +-compile({inline,yeccpars2_371_/1}). +-file("erl_parse.yrl", 191). +yeccpars2_371_(__Stack0) -> [__5,__4,__3,__2,__1 | __Stack] = __Stack0, [begin build_bin_type ( [ __1 , __3 ] , __5 ) end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8681). --compile({inline,yeccpars2_374_/1}). --file("erl_parse.yrl", 171). -yeccpars2_374_(__Stack0) -> +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 9021). +-compile({inline,yeccpars2_373_/1}). +-file("erl_parse.yrl", 182). +yeccpars2_373_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin { type , ? line ( __1 ) , binary , [ __2 , abstract ( 0 , ? line ( __1 ) ) ] } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8691). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 9031). -compile({inline,yeccpars2_378_/1}). --file("erl_parse.yrl", 176). +-file("erl_parse.yrl", 187). yeccpars2_378_(__Stack0) -> [__5,__4,__3,__2,__1 | __Stack] = __Stack0, [begin { type , ? line ( __1 ) , binary , [ __2 , __4 ] } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8700). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 9040). -compile({inline,yeccpars2_379_/1}). --file("erl_parse.yrl", 173). +-file("erl_parse.yrl", 184). yeccpars2_379_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin @@ -8707,350 +9047,378 @@ yeccpars2_379_(__Stack0) -> [ abstract ( 0 , ? line ( __1 ) ) , __2 ] } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8710). --compile({inline,yeccpars2_380_/1}). --file("erl_parse.yrl", 148). -yeccpars2_380_(__Stack0) -> - [__2,__1 | __Stack] = __Stack0, - [begin - abstract ( - normalise ( __2 ) , - ? line ( __2 ) ) - end | __Stack]. - --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8720). --compile({inline,yeccpars2_382_/1}). --file("erl_parse.yrl", 156). -yeccpars2_382_(__Stack0) -> +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 9050). +-compile({inline,yeccpars2_381_/1}). +-file("erl_parse.yrl", 167). +yeccpars2_381_(__Stack0) -> [__4,__3,__2,__1 | __Stack] = __Stack0, [begin { type , ? line ( __1 ) , 'fun' , [ { type , ? line ( __1 ) , product , [ ] } , __4 ] } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8730). --compile({inline,yeccpars2_384_/1}). --file("erl_parse.yrl", 121). -yeccpars2_384_(__Stack0) -> +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 9060). +-compile({inline,yeccpars2_383_/1}). +-file("erl_parse.yrl", 138). +yeccpars2_383_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin { paren_type , ? line ( __2 ) , [ __2 ] } end | __Stack]. --compile({inline,yeccpars2_388_/1}). --file("erl_parse.yrl", 162). -yeccpars2_388_(__Stack0) -> +-compile({inline,yeccpars2_387_/1}). +-file("erl_parse.yrl", 173). +yeccpars2_387_(__Stack0) -> [__1 | __Stack] = __Stack0, [begin [ __1 ] end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8747). --compile({inline,yeccpars2_390_/1}). --file("erl_parse.yrl", 137). -yeccpars2_390_(__Stack0) -> +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 9077). +-compile({inline,yeccpars2_389_/1}). +-file("erl_parse.yrl", 154). +yeccpars2_389_(__Stack0) -> [__4,__3,__2,__1 | __Stack] = __Stack0, [begin { type , ? line ( __1 ) , record , [ __2 ] } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8756). --compile({inline,yeccpars2_392_/1}). --file("erl_parse.yrl", 165). -yeccpars2_392_(__Stack0) -> +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 9086). +-compile({inline,yeccpars2_391_/1}). +-file("erl_parse.yrl", 176). +yeccpars2_391_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin { type , ? line ( __1 ) , field_type , [ __1 , __3 ] } end | __Stack]. --compile({inline,yeccpars2_394_/1}). --file("erl_parse.yrl", 163). -yeccpars2_394_(__Stack0) -> +-compile({inline,yeccpars2_393_/1}). +-file("erl_parse.yrl", 174). +yeccpars2_393_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin [ __1 | __3 ] end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8774). --compile({inline,yeccpars2_395_/1}). --file("erl_parse.yrl", 138). -yeccpars2_395_(__Stack0) -> +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 9104). +-compile({inline,yeccpars2_394_/1}). +-file("erl_parse.yrl", 155). +yeccpars2_394_(__Stack0) -> [__5,__4,__3,__2,__1 | __Stack] = __Stack0, [begin { type , ? line ( __1 ) , record , [ __2 | __4 ] } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8784). --compile({inline,yeccpars2_398_/1}). --file("erl_parse.yrl", 142). -yeccpars2_398_(__Stack0) -> - [__4,__3,__2,__1 | __Stack] = __Stack0, +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 9114). +-compile({inline,yeccpars2_395_/1}). +-file("erl_parse.yrl", 135). +yeccpars2_395_(__Stack0) -> + [__2,__1 | __Stack] = __Stack0, [begin - { type , ? line ( __1 ) , range , - [ __1 , __4 ] } + ? mkop1 ( __1 , skip_paren ( __2 ) ) end | __Stack]. --compile({inline,yeccpars2_400_/1}). --file("erl_parse.yrl", 113). -yeccpars2_400_(__Stack0) -> +-compile({inline,yeccpars2_397_/1}). +-file("erl_parse.yrl", 114). +yeccpars2_397_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin [ __1 | __3 ] end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8802). --compile({inline,yeccpars2_403_/1}). --file("erl_parse.yrl", 159). -yeccpars2_403_(__Stack0) -> +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 9131). +-compile({inline,yeccpars2_400_/1}). +-file("erl_parse.yrl", 170). +yeccpars2_400_(__Stack0) -> [__5,__4,__3,__2,__1 | __Stack] = __Stack0, [begin { type , ? line ( __1 ) , 'fun' , [ { type , ? line ( __1 ) , product , __2 } , __5 ] } end | __Stack]. +-compile({inline,yeccpars2_402_/1}). +-file("erl_parse.yrl", 120). +yeccpars2_402_(__Stack0) -> + [__3,__2,__1 | __Stack] = __Stack0, + [begin + lift_unions ( __1 , __3 ) + end | __Stack]. + +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 9149). -compile({inline,yeccpars2_405_/1}). --file("erl_parse.yrl", 119). +-file("erl_parse.yrl", 122). yeccpars2_405_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin - lift_unions ( __1 , __3 ) + { type , ? line ( __1 ) , range , + [ skip_paren ( __1 ) , + skip_paren ( __3 ) ] } end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8820). --compile({inline,yeccpars2_407_/1}). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 9160). +-compile({inline,yeccpars2_406_/1}). +-file("erl_parse.yrl", 127). +yeccpars2_406_(__Stack0) -> + [__3,__2,__1 | __Stack] = __Stack0, + [begin + ? mkop2 ( skip_paren ( __1 ) , + __2 , skip_paren ( __3 ) ) + end | __Stack]. + +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 9170). +-compile({inline,yeccpars2_408_/1}). +-file("erl_parse.yrl", 131). +yeccpars2_408_(__Stack0) -> + [__3,__2,__1 | __Stack] = __Stack0, + [begin + ? mkop2 ( skip_paren ( __1 ) , + __2 , skip_paren ( __3 ) ) + end | __Stack]. + +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 9180). +-compile({inline,yeccpars2_410_/1}). -file("erl_parse.yrl", 103). -yeccpars2_407_(__Stack0) -> +yeccpars2_410_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin { type , ? line ( __1 ) , bounded_fun , [ __1 , __3 ] } end | __Stack]. --compile({inline,yeccpars2_408_/1}). +-compile({inline,yeccpars2_411_/1}). -file("erl_parse.yrl", 106). -yeccpars2_408_(__Stack0) -> +yeccpars2_411_(__Stack0) -> [__1 | __Stack] = __Stack0, [begin [ __1 ] end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8838). --compile({inline,yeccpars2_412_/1}). +-compile({inline,yeccpars2_415_/1}). +-file("erl_parse.yrl", 111). +yeccpars2_415_(__Stack0) -> + [__3,__2,__1 | __Stack] = __Stack0, + [begin + build_def ( __1 , __3 ) + end | __Stack]. + +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 9206). +-compile({inline,yeccpars2_418_/1}). -file("erl_parse.yrl", 109). -yeccpars2_412_(__Stack0) -> +yeccpars2_418_(__Stack0) -> [__4,__3,__2,__1 | __Stack] = __Stack0, [begin { type , ? line ( __1 ) , constraint , [ __1 , __3 ] } end | __Stack]. --compile({inline,yeccpars2_414_/1}). +-compile({inline,yeccpars2_420_/1}). -file("erl_parse.yrl", 107). -yeccpars2_414_(__Stack0) -> +yeccpars2_420_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin [ __1 | __3 ] end | __Stack]. --compile({inline,yeccpars2_416_/1}). +-compile({inline,yeccpars2_422_/1}). -file("erl_parse.yrl", 100). -yeccpars2_416_(__Stack0) -> +yeccpars2_422_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin [ __1 | __3 ] end | __Stack]. --compile({inline,yeccpars2_417_/1}). +-compile({inline,yeccpars2_423_/1}). -file("erl_parse.yrl", 78). -yeccpars2_417_(__Stack0) -> +yeccpars2_423_(__Stack0) -> [__4,__3,__2,__1 | __Stack] = __Stack0, [begin { __2 , __3 } end | __Stack]. --compile({inline,yeccpars2_418_/1}). +-compile({inline,yeccpars2_424_/1}). -file("erl_parse.yrl", 77). -yeccpars2_418_(__Stack0) -> +yeccpars2_424_(__Stack0) -> [__2,__1 | __Stack] = __Stack0, [begin { __1 , __2 } end | __Stack]. --compile({inline,yeccpars2_419_/1}). +-compile({inline,yeccpars2_425_/1}). -file("erl_parse.yrl", 73). -yeccpars2_419_(__Stack0) -> +yeccpars2_425_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin build_typed_attribute ( __2 , __3 ) end | __Stack]. --compile({inline,yeccpars2_420_/1}). --file("erl_parse.yrl", 182). -yeccpars2_420_(__Stack0) -> +-compile({inline,yeccpars2_426_/1}). +-file("erl_parse.yrl", 193). +yeccpars2_426_(__Stack0) -> [__1 | __Stack] = __Stack0, [begin [ __1 ] end | __Stack]. --compile({inline,yeccpars2_421_/1}). +-compile({inline,yeccpars2_427_/1}). -file("erl_parse.yrl", 72). -yeccpars2_421_(__Stack0) -> +yeccpars2_427_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin build_attribute ( __2 , __3 ) end | __Stack]. --compile({inline,yeccpars2_427_/1}). +-compile({inline,yeccpars2_433_/1}). -file("erl_parse.yrl", 88). -yeccpars2_427_(__Stack0) -> +yeccpars2_433_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin { type_def , __1 , __3 } end | __Stack]. --compile({inline,yeccpars2_428_/1}). +-compile({inline,yeccpars2_434_/1}). -file("erl_parse.yrl", 87). -yeccpars2_428_(__Stack0) -> +yeccpars2_434_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin { typed_record , __1 , __3 } end | __Stack]. --compile({inline,yeccpars2_432_/1}). +-compile({inline,yeccpars2_438_/1}). -file("erl_parse.yrl", 92). -yeccpars2_432_(__Stack0) -> +yeccpars2_438_(__Stack0) -> [__1 | __Stack] = __Stack0, [begin [ __1 ] end | __Stack]. --compile({inline,yeccpars2_433_/1}). --file("erl_parse.yrl", 427). -yeccpars2_433_(__Stack0) -> +-compile({inline,yeccpars2_439_/1}). +-file("erl_parse.yrl", 438). +yeccpars2_439_(__Stack0) -> [__1 | __Stack] = __Stack0, [begin [ __1 ] end | __Stack]. --compile({inline,yeccpars2_436_/1}). +-compile({inline,yeccpars2_442_/1}). -file("erl_parse.yrl", 97). -yeccpars2_436_(__Stack0) -> +yeccpars2_442_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin { typed , __1 , __3 } end | __Stack]. --compile({inline,yeccpars2_437_/1}). +-compile({inline,yeccpars2_443_/1}). -file("erl_parse.yrl", 94). -yeccpars2_437_(__Stack0) -> +yeccpars2_443_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin [ __1 | __3 ] end | __Stack]. --compile({inline,yeccpars2_439_/1}). +-compile({inline,yeccpars2_445_/1}). -file("erl_parse.yrl", 93). -yeccpars2_439_(__Stack0) -> +yeccpars2_445_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin [ __1 | __3 ] end | __Stack]. --compile({inline,yeccpars2_440_/1}). +-compile({inline,yeccpars2_446_/1}). -file("erl_parse.yrl", 95). -yeccpars2_440_(__Stack0) -> +yeccpars2_446_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin [ __1 | __3 ] end | __Stack]. --file("/Users/bjorng/Downloads/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 8968). --compile({inline,yeccpars2_441_/1}). +-file("/ldisk/pan/git/otp/bootstrap/lib/stdlib/egen/erl_parse.erl", 9336). +-compile({inline,yeccpars2_447_/1}). -file("erl_parse.yrl", 90). -yeccpars2_441_(__Stack0) -> +yeccpars2_447_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin { tuple , ? line ( __1 ) , __2 } end | __Stack]. --compile({inline,yeccpars2_442_/1}). --file("erl_parse.yrl", 184). -yeccpars2_442_(__Stack0) -> +-compile({inline,yeccpars2_448_/1}). +-file("erl_parse.yrl", 195). +yeccpars2_448_(__Stack0) -> [__5,__4,__3,__2,__1 | __Stack] = __Stack0, [begin [ __2 | __4 ] end | __Stack]. --compile({inline,yeccpars2_443_/1}). +-compile({inline,yeccpars2_449_/1}). -file("erl_parse.yrl", 74). -yeccpars2_443_(__Stack0) -> +yeccpars2_449_(__Stack0) -> [__5,__4,__3,__2,__1 | __Stack] = __Stack0, [begin build_typed_attribute ( __2 , __4 ) end | __Stack]. --compile({inline,yeccpars2_445_/1}). --file("erl_parse.yrl", 183). -yeccpars2_445_(__Stack0) -> +-compile({inline,yeccpars2_451_/1}). +-file("erl_parse.yrl", 194). +yeccpars2_451_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin [ __1 | __3 ] end | __Stack]. --compile({inline,yeccpars2_446_/1}). +-compile({inline,yeccpars2_452_/1}). -file("erl_parse.yrl", 68). -yeccpars2_446_(__Stack0) -> +yeccpars2_452_(__Stack0) -> [__2,__1 | __Stack] = __Stack0, [begin __1 end | __Stack]. --compile({inline,yeccpars2_447_/1}). +-compile({inline,yeccpars2_453_/1}). -file("erl_parse.yrl", 69). -yeccpars2_447_(__Stack0) -> +yeccpars2_453_(__Stack0) -> [__2,__1 | __Stack] = __Stack0, [begin __1 end | __Stack]. --compile({inline,yeccpars2_449_/1}). --file("erl_parse.yrl", 189). -yeccpars2_449_(__Stack0) -> +-compile({inline,yeccpars2_455_/1}). +-file("erl_parse.yrl", 200). +yeccpars2_455_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin [ __1 | __3 ] end | __Stack]. --compile({inline,yeccpars2_451_/1}). --file("erl_parse.yrl", 198). -yeccpars2_451_(__Stack0) -> +-compile({inline,yeccpars2_457_/1}). +-file("erl_parse.yrl", 209). +yeccpars2_457_(__Stack0) -> [begin [ ] end | __Stack0]. --compile({inline,yeccpars2_453_/1}). +-compile({inline,yeccpars2_459_/1}). -file("erl_parse.yrl", 70). -yeccpars2_453_(__Stack0) -> +yeccpars2_459_(__Stack0) -> [__2,__1 | __Stack] = __Stack0, [begin __1 end | __Stack]. --compile({inline,yeccpars2_455_/1}). --file("erl_parse.yrl", 479). -yeccpars2_455_(__Stack0) -> +-compile({inline,yeccpars2_461_/1}). +-file("erl_parse.yrl", 490). +yeccpars2_461_(__Stack0) -> [__3,__2,__1 | __Stack] = __Stack0, [begin [ __1 | __3 ] end | __Stack]. --compile({inline,yeccpars2_457_/1}). --file("erl_parse.yrl", 198). -yeccpars2_457_(__Stack0) -> +-compile({inline,yeccpars2_463_/1}). +-file("erl_parse.yrl", 209). +yeccpars2_463_(__Stack0) -> [begin [ ] end | __Stack0]. --file("erl_parse.yrl", 1037). +-file("erl_parse.yrl", 1057). diff --git a/erts/aclocal.m4 b/erts/aclocal.m4 index 3d935b7295..61244c7cd3 100644 --- a/erts/aclocal.m4 +++ b/erts/aclocal.m4 @@ -512,6 +512,8 @@ dnl AC_DEFUN(LM_CHECK_THR_LIB, [ +NEED_NPTL_PTHREAD_H=no + dnl win32? AC_MSG_CHECKING([for native win32 threads]) if test "X$host_os" = "Xwin32"; then @@ -585,7 +587,9 @@ dnl On ofs1 the '-pthread' switch should be used fi if test $nptl = yes; then need_nptl_incldir=no - AC_CHECK_HEADER(nptl/pthread.h, need_nptl_incldir=yes) + AC_CHECK_HEADER(nptl/pthread.h, + [need_nptl_incldir=yes + NEED_NPTL_PTHREAD_H=yes]) if test $need_nptl_incldir = yes; then # Ahh... nptl_path="$C_INCLUDE_PATH:$CPATH" @@ -799,6 +803,11 @@ case "$THR_LIB_NAME" in AC_DEFINE(ETHR_HAVE_MIT_PTHREAD_H, 1, \ [Define if the pthread.h header file is in pthread/mit directory.])) + if test $NEED_NPTL_PTHREAD_H = yes; then + AC_DEFINE(ETHR_NEED_NPTL_PTHREAD_H, 1, \ +[Define if you need the <nptl/pthread.h> header file.]) + fi + AC_CHECK_HEADER(sys/time.h, \ AC_DEFINE(ETHR_HAVE_SYS_TIME_H, 1, \ [Define if you have the <sys/time.h> header file.])) @@ -826,23 +835,70 @@ case "$THR_LIB_NAME" in AC_CHECK_FUNC(pthread_spin_lock, \ AC_DEFINE(ETHR_HAVE_PTHREAD_SPIN_LOCK, 1, \ [Define if you have the pthread_spin_lock function.])) - test "$force_linux_pthread_rwlocks" = "yes" || { - force_linux_pthread_rwlocks=no - } - case "$force_linux_pthread_rwlocks-$host_os" in - no-linux*) # Writers may get starved - # TODO: write a test that tests the implementation - ;; - *) - AC_CHECK_FUNC(pthread_rwlock_init, \ - AC_DEFINE(ETHR_HAVE_PTHREAD_RWLOCK_INIT, 1, \ -[Define if you have a pthread_rwlock implementation that can be used.])) - ;; - esac + + have_pthread_rwlock_init=no + AC_CHECK_FUNC(pthread_rwlock_init, [have_pthread_rwlock_init=yes]) + if test $have_pthread_rwlock_init = yes; then + + AC_DEFINE(ETHR_HAVE_PTHREAD_RWLOCK_INIT, 1, \ +[Define if you have a pthread_rwlock implementation that can be used.]) + + ethr_have_pthread_rwlockattr_setkind_np=no + AC_CHECK_FUNC(pthread_rwlockattr_setkind_np, + [ethr_have_pthread_rwlockattr_setkind_np=yes]) + + if test $ethr_have_pthread_rwlockattr_setkind_np = yes; then + AC_DEFINE(ETHR_HAVE_PTHREAD_RWLOCKATTR_SETKIND_NP, 1, \ +[Define if you have the pthread_rwlockattr_setkind_np() function.]) + + AC_MSG_CHECKING([for PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP]) + ethr_pthread_rwlock_writer_nonrecursive_initializer_np=no + AC_TRY_LINK([ + #if defined(ETHR_NEED_NPTL_PTHREAD_H) + #include <nptl/pthread.h> + #elif defined(ETHR_HAVE_MIT_PTHREAD_H) + #include <pthread/mit/pthread.h> + #elif defined(ETHR_HAVE_PTHREAD_H) + #include <pthread.h> + #endif + ], + [ + pthread_rwlockattr_t *attr; + return pthread_rwlockattr_setkind_np(attr, + PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP); + ], + [ethr_pthread_rwlock_writer_nonrecursive_initializer_np=yes]) + AC_MSG_RESULT([$ethr_pthread_rwlock_writer_nonrecursive_initializer_np]) + if test $ethr_pthread_rwlock_writer_nonrecursive_initializer_np = yes; then + AC_DEFINE(ETHR_HAVE_PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP, 1, \ +[Define if you have the PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP rwlock attribute.]) + fi + fi + fi + + + AC_CHECK_FUNC(pthread_attr_setguardsize, \ AC_DEFINE(ETHR_HAVE_PTHREAD_ATTR_SETGUARDSIZE, 1, \ [Define if you have the pthread_attr_setguardsize function.])) + AC_MSG_CHECKING([for GCC atomic operations]) + ethr_have_gcc_atomic_ops=no + AC_TRY_LINK([], + [ + long res; + volatile long val; + res = __sync_val_compare_and_swap(&val, (long) 1, (long) 0); + res = __sync_add_and_fetch(&val, (long) 1); + res = __sync_sub_and_fetch(&val, (long) 1); + res = __sync_fetch_and_and(&val, (long) 1); + res = __sync_fetch_and_or(&val, (long) 1); + ], + [ethr_have_native_atomics=yes + ethr_have_gcc_atomic_ops=yes]) + AC_MSG_RESULT([$ethr_have_gcc_atomic_ops]) + test $ethr_have_gcc_atomic_ops = yes && AC_DEFINE(ETHR_HAVE_GCC_ATOMIC_OPS, 1, [Define if you have gcc atomic operations]) + dnl Restore LIBS LIBS=$saved_libs dnl restore CPPFLAGS @@ -873,6 +929,17 @@ if test "X$disable_native_ethr_impls" = "Xyes"; then AC_DEFINE(ETHR_DISABLE_NATIVE_IMPLS, 1, [Define if you want to disable native ethread implementations]) fi +AC_ARG_ENABLE(prefer-gcc-native-ethr-impls, + AS_HELP_STRING([--enable-prefer-gcc-native-ethr-impls], + [enable prefer gcc native ethread implementations]), +[ case "$enableval" in + yes) enable_prefer_gcc_native_ethr_impls=yes ;; + *) enable_prefer_gcc_native_ethr_impls=no ;; + esac ], enable_prefer_gcc_native_ethr_impls=no) + +test $enable_prefer_gcc_native_ethr_impls = yes && + AC_DEFINE(ETHR_PREFER_GCC_NATIVE_IMPLS, 1, [Define if you prefer gcc native ethread implementations]) + AC_DEFINE(ETHR_HAVE_ETHREAD_DEFINES, 1, \ [Define if you have all ethread defines]) diff --git a/erts/doc/src/erl.xml b/erts/doc/src/erl.xml index bb741c7836..df80142ce1 100644 --- a/erts/doc/src/erl.xml +++ b/erts/doc/src/erl.xml @@ -41,6 +41,26 @@ to scroll back to text which has scrolled off the screen. The <c><![CDATA[erl]]></c> program must be used, however, in pipelines or if you want to redirect standard input or output.</p> + <note><p>As of ERTS version 5.8 (OTP-R14A) the runtime system will by + default bind schedulers to logical processors using the + <c>default_bind</c> bind type if the amount of schedulers are + at least equal to the amount of logical processors configured, + binding of schedulers is supported, and a CPU topology is + available at startup. + </p><p> + If the Erlang runtime system is the only operating system + process that binds threads to logical processors, this + improves the performance of the runtime system. However, + if other operating system processes (as for example + another Erlang runtime system) also bind threads to + logical processors, there might be a performance penalty + instead. If this is the case you, are are advised to + unbind the schedulers using the + <seealso marker="#+sbt">+sbtu</seealso> command line argument, + or by invoking + <seealso marker="erlang#system_flag_scheduler_bind_type">erlang:system_flag(scheduler_bind_type, + unbound)</seealso>.</p> + </note> </description> <funcs> <func> @@ -657,6 +677,22 @@ that the <c>+sct</c> flag may have to be passed before the <c>+sbt</c> flag on the command line (in case no CPU topology has been automatically detected).</p> + <p>The runtime system will by default bind schedulers to logical + processors using the <c>default_bind</c> bind type if the amount + of schedulers are at least equal to the amount of logical + processors configured, binding of schedulers is supported, + and a CPU topology is available at startup. + </p> + <p><em>NOTE:</em> If the Erlang runtime system is the only operating + system process that binds threads to logical processors, this + improves the performance of the runtime system. However, if other + operating system processes (as for example another Erlang runtime + system) also bind threads to logical processors, there might be a + performance penalty instead. If this is the case you, are advised + to unbind the schedulers using the <c>+sbtu</c> command line + argument, or by invoking + <seealso marker="erlang#system_flag_scheduler_bind_type">erlang:system_flag(scheduler_bind_type, + unbound)</seealso>.</p> <p>For more information, see <seealso marker="erlang#system_flag_scheduler_bind_type">erlang:system_flag(scheduler_bind_type, SchedulerBindType)</seealso>. </p> diff --git a/erts/doc/src/erl_nif.xml b/erts/doc/src/erl_nif.xml index 03bd42d3b1..f7b7b2f346 100644 --- a/erts/doc/src/erl_nif.xml +++ b/erts/doc/src/erl_nif.xml @@ -38,6 +38,18 @@ EXPERIMENTAL feature. The interfaces may be changed in any way in coming releases. The plan is however to lift the experimental label and maintain interface backward compatibility from R14B.</p> + <p>Incompatible changes in <em>R14A</em>:</p> + <list> + <item>Environment argument removed for <c>enif_alloc</c>, + <c>enif_realloc</c>, <c>enif_free</c>, <c>enif_alloc_binary</c>, + <c>enif_realloc_binary</c>, <c>enif_release_binary</c>, + <c>enif_alloc_resource</c>, <c>enif_release_resource</c>, + <c>enif_is_identical</c> and <c>enif_compare</c>.</item> + <item>Character encoding argument added to <c>enif_get_atom</c> + and <c>enif_make_existing_atom</c>.</item> + <item>Module argument added to <c>enif_open_resource_type</c> + while changing name spaces of resource types from global to module local.</item> + </list> <p>Incompatible changes in <em>R13B04</em>:</p> <list> <item>The function prototypes of the NIFs have changed to expect <c>argc</c> and <c>argv</c> @@ -109,9 +121,9 @@ ok the new directive <seealso marker="doc/reference_manual:code_loading#on_load">on_load</seealso> to automatically load the NIF library when the module is loaded.</p> - <note><p>A NIF must be exported or used locally by the module (or both). - An unused local stub function will be optimized away by the compiler - causing loading of the NIF library to fail.</p> + <note><p>A NIF does not have to be exported, it can be local to the module. + Note however that unused local stub functions will be optimized + away by the compiler causing loading of the NIF library to fail.</p> </note> <p>A loaded NIF library is tied to the Erlang module code version that loaded it. If the module is upgraded with a new version, the @@ -122,7 +134,7 @@ ok will be shared as well. To avoid unintentionally shared static data, each Erlang module code can keep its own private data. This private data can be set when the NIF library is loaded and - then retrieved by calling <seealso marker="#enif_priv_data">enif_priv_data()</seealso>.</p> + then retrieved by calling <seealso marker="#enif_priv_data">enif_priv_data</seealso>.</p> <p>There is no way to explicitly unload a NIF library. A library will be automatically unloaded when the module code that it belongs to is purged by the code server. A NIF library will also be unloaded if it is replaced @@ -137,14 +149,20 @@ ok <taglist> <tag>Read and write Erlang terms</tag> <item><p>Any Erlang terms can be passed to a NIF as function arguments and - be returned as function return values. The terms are of C-type <c>ERL_NIF_TERM</c> + be returned as function return values. The terms are of C-type + <seealso marker="#ERL_NIF_TERM">ERL_NIF_TERM</seealso> and can only be read or written using API functions. Most functions to read the content of a term are prefixed <c>enif_get_</c> and usually return true (or false) if the term was of the expected type (or not). The functions to write terms are all prefixed <c>enif_make_</c> and usually return the created <c>ERL_NIF_TERM</c>. There are also some functions to query terms, like <c>enif_is_atom</c>, <c>enif_is_identical</c> and - <c>enif_compare</c>.</p></item> + <c>enif_compare</c>.</p> + <p>All terms of type <c>ERL_NIF_TERM</c> belong to an environment of type + <seealso marker="#ErlNifEnv">ErlNifEnv</seealso>. The lifetime of a term is + controlled by the lifetime of its environment object. All API functions that read + or write terms has the environment, that the term belongs to, as the first + function argument.</p></item> <tag>Binaries</tag> <item><p>Terms of type binary are accessed with the help of the struct type <seealso marker="#ErlNifBinary">ErlNifBinary</seealso> @@ -172,28 +190,29 @@ ok <item><p>The use of resource objects is a way to return pointers to native data structures from a NIF in a safe way. A resource object is just a block of memory allocated with - <seealso marker="#enif_alloc_resource">enif_alloc_resource()</seealso>. + <seealso marker="#enif_alloc_resource">enif_alloc_resource</seealso>. A handle ("safe pointer") to this memory block can then be returned to Erlang by the use of - <seealso marker="#enif_make_resource">enif_make_resource()</seealso>. + <seealso marker="#enif_make_resource">enif_make_resource</seealso>. The term returned by <c>enif_make_resource</c> is totally opaque in nature. It can be stored and passed between processses on the same node, but the only real end usage is to pass it back as argument to a NIF. - The NIF can then do <seealso marker="#enif_get_resource">enif_get_resource()</seealso> + The NIF can then do <seealso marker="#enif_get_resource">enif_get_resource</seealso> and get back a pointer to the memory block that is guaranteed to still be valid. A resource object will not be deallocated until the last handle term has been garbage collected by the VM and the resource has been - released with <seealso marker="#enif_release_resource">enif_release_resource()</seealso> + released with <seealso marker="#enif_release_resource">enif_release_resource</seealso> (not necessarily in that order).</p> <p>All resource objects are created as instances of some <em>resource type</em>. This makes resources from different modules to be distinguishable. A resource type is created by calling - <seealso marker="#enif_open_resource_type">enif_open_resource_type()</seealso> + <seealso marker="#enif_open_resource_type">enif_open_resource_type</seealso> when a library is loaded. Objects of that resource type can then later be allocated and <c>enif_get_resource</c> verifies that the resource is of the expected type. A resource type can have a user supplied destructor function that is automatically called when resources of that type are released (by either the garbage collector or <c>enif_release_resource</c>). Resource types - are uniquely identified by a supplied name string.</p> + are uniquely identified by a supplied name string and the name of the + implementing module.</p> <p>Resource types support upgrade in runtime by allowing a loaded NIF library to takeover an already existing resource type and thereby "inherit" all existing objects of that type. The destructor of the new @@ -208,7 +227,7 @@ ok <p/> <code type="none"> ERL_NIF_TERM term; - MyStruct* ptr = enif_alloc_resource(env, my_resource_type, sizeof(MyStruct)); + MyStruct* ptr = enif_alloc_resource(my_resource_type, sizeof(MyStruct)); /* initialize struct ... */ @@ -218,21 +237,31 @@ ok /* store 'ptr' in static variable, private data or other resource object */ } else { - enif_release_resource(env, obj); + enif_release_resource(obj); /* resource now only owned by "Erlang" */ } return term; } </code> - + <p>Another usage of resource objects is to create binary terms with + user defined memory management. + <seealso marker="#enif_make_resource_binary">enif_make_resource_binary</seealso> + will create a binary term that is connected to a resource object. The + destructor of the resource will be called when the binary is garbage + collected, at which time the binary data can be released. An example of + this can be a binary term consisting of data from a <c>mmap</c>'ed file. + The destructor can then do <c>munmap</c> to release the memory + region.</p> </item> <tag>Threads and concurrency</tag> <item><p>A NIF is thread-safe without any explicit synchronization as long as it acts as a pure function and only reads the supplied arguments. As soon as you write towards a shared state either through static variables or <seealso marker="#enif_priv_data">enif_priv_data</seealso> - you need to supply your own explicit synchronization. Resource objects - will also require synchronization if you treat them as mutable.</p> + you need to supply your own explicit synchronization. This includes terms + in process independent environments that are shared between threads. + Resource objects will also require synchronization if you treat them as + mutable.</p> <p>The library initialization callbacks <c>load</c>, <c>reload</c> and <c>upgrade</c> are all thread-safe even for shared state data.</p> <p>Avoid doing lengthy work in NIF calls as that may degrade the @@ -265,7 +294,7 @@ ok and there is no previously loaded library for this module.</p> <p><c>*priv_data</c> can be set to point to some private data that the library needs in order to keep a state between NIF - calls. <c>enif_priv_data()</c> will return this pointer. + calls. <c>enif_priv_data</c> will return this pointer. <c>*priv_data</c> will be initialized to NULL when <c>load</c> is called.</p> <p><c>load_info</c> is the second argument to <seealso @@ -317,19 +346,37 @@ ok <item> <p>Variables of type <c>ERL_NIF_TERM</c> can refer to any Erlang term. This is an opaque type and values of it can only by used either as - arguments to API functions or as return values from NIFs. A variable of - type <c>ERL_NIF_TERM</c> is only valid until the NIF call, where it was - obtained, returns.</p> + arguments to API functions or as return values from NIFs. All + <c>ERL_NIF_TERM</c>'s belong to an environment + (<seealso marker="#ErlNifEnv">ErlNifEnv</seealso>). A term can not be + destructed individually, it is valid until its environment is destructed.</p> </item> <tag><marker id="ErlNifEnv"/>ErlNifEnv</tag> <item> - <p><c>ErlNifEnv</c> contains information about the context in - which a NIF call is made. This pointer should not be - dereferenced in any way, but only passed on to API - functions. An <c>ErlNifEnv</c> pointer is only valid until - the function, where it was supplied as argument, - returns. It is thus useless and dangerous to store <c>ErlNifEnv</c> - pointers in between NIF calls.</p> + <p><c>ErlNifEnv</c> represents an environment that can host Erlang terms. + All terms in an environment are valid as long as the environment is valid. + <c>ErlNifEnv</c> is an opaque type and pointers to it can only be passed + on to API functions. There are two types of environments; process + bound and process independent.</p> + <p>A <em>process bound environment</em> is passed as the first argument to all NIFs. + All function arguments passed to a NIF will belong to that environment. + The return value from a NIF must also be a term belonging to the same + environment. + In addition a process bound environment contains transient information + about the calling Erlang process. The environment is only valid in the + thread where it was supplied as argument until the NIF returns. It is + thus useless and dangerous to store pointers to process bound + environments between NIF calls. </p> + <p>A <em>process independent environment</em> is created by calling + <seealso marker="#enif_alloc_env">enif_alloc_env</seealso>. It can be + used to store terms beteen NIF calls and to send terms with + <seealso marker="#enif_send">enif_send</seealso>. A process + independent environment with all its terms is valid until you explicitly + invalidates it with <seealso marker="#enif_free_env">enif_free_env</seealso> + or <c>enif_send</c>.</p> + <p>All elements of a list/tuple must belong to the same environment as the + list/tuple itself. Terms can be copied between environments with + <seealso marker="#enif_make_copy">enif_make_copy</seealso>.</p> </item> <tag><marker id="ErlNifFunc"/>ErlNifFunc</tag> <item> @@ -363,7 +410,18 @@ typedef struct { <p><c>ErlNifBinary</c> contains transient information about an inspected binary term. <c>data</c> is a pointer to a buffer of <c>size</c> bytes with the raw content of the binary.</p> + <p>Note that <c>ErlNifBinary</c> is a semi-opaque type and you are + only allowed to read fields <c>size</c> and <c>data</c>.</p> </item> + <tag><marker id="ErlNifPid"/>ErlNifPid</tag> + <item> + <p><c>ErlNifPid</c> is a process identifier (pid). In contrast to + pid terms (instances of <c>ERL_NIF_TERM</c>), <c>ErlNifPid</c>'s are self + contained and not bound to any + <seealso marker="#ErlNifEnv">environment</seealso>. <c>ErlNifPid</c> + is an opaque type.</p> + </item> + <tag><marker id="ErlNifResourceType"/>ErlNifResourceType</tag> <item> <p>Each instance of <c>ErlNifResourceType</c> represent a class of @@ -388,9 +446,9 @@ typedef enum { ERL_NIF_LATIN1 }ErlNifCharEncoding; </code> - <p>The character encoding used in strings. The only supported - encoding is currently <c>ERL_NIF_LATIN1</c> for iso-latin-1 - (8-bit ascii).</p> + <p>The character encoding used in strings and atoms. The only + supported encoding is currently <c>ERL_NIF_LATIN1</c> for + iso-latin-1 (8-bit ascii).</p> </item> <tag><marker id="ErlNifSysInfo"/>ErlNifSysInfo</tag> <item> @@ -407,24 +465,40 @@ typedef enum { <fsummary>Allocate dynamic memory.</fsummary> <desc><p>Allocate memory of <c>size</c> bytes. Return NULL if allocation failed.</p></desc> </func> - <func><name><ret>int</ret><nametext>enif_alloc_binary(ErlNifEnv* env, unsigned size, ErlNifBinary* bin)</nametext></name> + <func><name><ret>int</ret><nametext>enif_alloc_binary(size_t size, ErlNifBinary* bin)</nametext></name> <fsummary>Create a new binary.</fsummary> <desc><p>Allocate a new binary of size <c>size</c> bytes. Initialize the structure pointed to by <c>bin</c> to refer to the allocated binary. The binary must either be released by - <seealso marker="#enif_release_binary">enif_release_binary()</seealso> + <seealso marker="#enif_release_binary">enif_release_binary</seealso> or ownership transferred to an Erlang term with - <seealso marker="#enif_make_binary">enif_make_binary()</seealso>. + <seealso marker="#enif_make_binary">enif_make_binary</seealso>. An allocated (and owned) <c>ErlNifBinary</c> can be kept between NIF calls.</p> - <p>Return false if allocation failed.</p> + <p>Return true on success or false if allocation failed.</p> </desc> </func> - <func><name><ret>void*</ret><nametext>enif_alloc_resource(ErlNifEnv* env, ErlNifResourceType* type, unsigned size)</nametext></name> + <func><name><ret>ErlNifEnv*</ret><nametext>enif_alloc_env()</nametext></name> + <fsummary>Create a new environment</fsummary> + <desc><p>Allocate a new process independent environment. The environment can + be used to hold terms that is not bound to any process. Such terms can + later be copied to a process environment with + <seealso marker="#enif_make_copy">enif_make_copy</seealso> + or be sent to a process as a message with <seealso marker="#enif_send">enif_send</seealso>.</p> + <p>Return pointer to the new environment.</p> + </desc> + </func> + <func><name><ret>void*</ret><nametext>enif_alloc_resource(ErlNifResourceType* type, unsigned size)</nametext></name> <fsummary>Allocate a memory managed resource object</fsummary> <desc><p>Allocate a memory managed resource object of type <c>type</c> and size <c>size</c> bytes.</p></desc> </func> - <func><name><ret>int</ret><nametext>enif_compare(ErlNifEnv* env, ERL_NIF_TERM lhs, ERL_NIF_TERM rhs)</nametext></name> + <func><name><ret>void</ret><nametext>enif_clear_env(ErlNifEnv* env)</nametext></name> + <fsummary>Clear an environment for reuse.</fsummary> + <desc><p>Free all terms in an environment and clear it for reuse. The environment must + have been allocated with <seealso marker="#enif_alloc_env">enif_alloc_env</seealso>. + </p></desc> + </func> + <func><name><ret>int</ret><nametext>enif_compare(ERL_NIF_TERM lhs, ERL_NIF_TERM rhs)</nametext></name> <fsummary>Compare two terms</fsummary> <desc><p>Return an integer less than, equal to, or greater than zero if <c>lhs</c> is found, respectively, to be less than, @@ -434,88 +508,98 @@ typedef enum { </func> <func><name><ret>void</ret><nametext>enif_cond_broadcast(ErlNifCond *cnd)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_cond_broadcast">erl_drv_cond_broadcast()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_cond_broadcast">erl_drv_cond_broadcast</seealso>. </p></desc> </func> <func><name><ret>ErlNifCond*</ret><nametext>enif_cond_create(char *name)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_cond_create">erl_drv_cond_create()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_cond_create">erl_drv_cond_create</seealso>. </p></desc> </func> <func><name><ret>void</ret><nametext>enif_cond_destroy(ErlNifCond *cnd)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_cond_destroy">erl_drv_cond_destroy()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_cond_destroy">erl_drv_cond_destroy</seealso>. </p></desc> </func> <func><name><ret>void</ret><nametext>enif_cond_signal(ErlNifCond *cnd)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_cond_signal">erl_drv_cond_signal()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_cond_signal">erl_drv_cond_signal</seealso>. </p></desc> </func> <func><name><ret>void</ret><nametext>enif_cond_wait(ErlNifCond *cnd, ErlNifMutex *mtx)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_cond_wait">erl_drv_cond_wait()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_cond_wait">erl_drv_cond_wait</seealso>. </p></desc> </func> <func><name><ret>int</ret><nametext>enif_equal_tids(ErlNifTid tid1, ErlNifTid tid2)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_equal_tids">erl_drv_equal_tids()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_equal_tids">erl_drv_equal_tids</seealso>. </p></desc> </func> <func><name><ret>void</ret><nametext>enif_free(ErlNifEnv* env, void* ptr)</nametext></name> <fsummary>Free dynamic memory</fsummary> <desc><p>Free memory allocated by <c>enif_alloc</c>.</p></desc> </func> - <func><name><ret>int</ret><nametext>enif_get_atom(ErlNifEnv* env, - ERL_NIF_TERM term, char* buf, unsigned size) - </nametext></name> + <func><name><ret>void</ret><nametext>enif_free_env(ErlNifEnv* env)</nametext></name> + <fsummary>Free an environment allocated with enif_alloc_env</fsummary> + <desc><p>Free an environment allocated with <seealso marker="#enif_alloc_env">enif_alloc_env</seealso>. + All terms created in the environment will be freed as well.</p></desc> + </func> + <func><name><ret>int</ret><nametext>enif_get_atom(ErlNifEnv* env, ERL_NIF_TERM term, char* buf, unsigned size, ErlNifCharEncoding encode)</nametext></name> <fsummary>Get the text representation of an atom term</fsummary> <desc><p>Write a null-terminated string, in the buffer pointed to by <c>buf</c> of size <c>size</c>, consisting of the string - representation of the atom <c>term</c>. Return the number of bytes - written (including terminating null character) or 0 if + representation of the atom <c>term</c> with encoding + <seealso marker="#ErlNifCharEncoding">encode</seealso>. Return + the number of bytes written (including terminating null character) or 0 if <c>term</c> is not an atom with maximum length of <c>size-1</c>.</p></desc> </func> - <func><name><ret>int</ret><nametext>enif_get_atom_length(ErlNifEnv* env, ERL_NIF_TERM term, unsigned* len)</nametext></name> + <func><name><ret>int</ret><nametext>enif_get_atom_length(ErlNifEnv* env, ERL_NIF_TERM term, unsigned* len, ErlNifCharEncoding encode)</nametext></name> <fsummary>Get the length of atom <c>term</c>.</fsummary> <desc><p>Set <c>*len</c> to the length (number of bytes excluding - terminating null character) of <c>term</c> or return false if - <c>term</c> is not an atom.</p></desc> + terminating null character) of the atom <c>term</c> with encoding + <c>encode</c>. Return true on success or false if <c>term</c> is not an + atom.</p></desc> </func> <func><name><ret>int</ret><nametext>enif_get_double(ErlNifEnv* env, ERL_NIF_TERM term, double* dp)</nametext></name> <fsummary>Read a floating-point number term.</fsummary> <desc><p>Set <c>*dp</c> to the floating point value of - <c>term</c> or return false if <c>term</c> is not a float.</p></desc> + <c>term</c>. Return true on success or false if <c>term</c> is not a float.</p></desc> </func> <func><name><ret>int</ret><nametext>enif_get_int(ErlNifEnv* env, ERL_NIF_TERM term, int* ip)</nametext></name> - <fsummary>Read an integer term.</fsummary> + <fsummary>Read an integer term</fsummary> <desc><p>Set <c>*ip</c> to the integer value of - <c>term</c> or return false if <c>term</c> is not an integer or is - outside the bounds of type <c>int</c></p></desc> + <c>term</c>. Return true on success or false if <c>term</c> is not an + integer or is outside the bounds of type <c>int</c></p></desc> + </func> + <func><name><ret>int</ret><nametext>enif_get_local_pid(ErlNifEnv* env, ERL_NIF_TERM term, ErlNifPid* pid)</nametext></name> + <fsummary>Read an local pid term</fsummary> + <desc><p>If <c>term</c> is the pid of a node local process, initialize the + pid variable <c>*pid</c> from it and return true. Otherwise return false. + No check if the process is alive is done.</p></desc> </func> <func><name><ret>int</ret><nametext>enif_get_list_cell(ErlNifEnv* env, ERL_NIF_TERM list, ERL_NIF_TERM* head, ERL_NIF_TERM* tail)</nametext></name> <fsummary>Get head and tail from a list</fsummary> <desc><p>Set <c>*head</c> and <c>*tail</c> from - <c>list</c> or return false if <c>list</c> is not a non-empty - list.</p></desc> + <c>list</c> and return true, or return false if <c>list</c> is not a + non-empty list.</p></desc> </func> <func><name><ret>int</ret><nametext>enif_get_list_length(ErlNifEnv* env, ERL_NIF_TERM term, unsigned* len)</nametext></name> <fsummary>Get the length of list <c>term</c>.</fsummary> - <desc><p>Set <c>*len</c> to the length of <c>term</c> or return - false if <c>term</c> is not a list.</p></desc> + <desc><p>Set <c>*len</c> to the length of list <c>term</c> and return true, + or return false if <c>term</c> is not a list.</p></desc> </func> <func><name><ret>int</ret><nametext>enif_get_long(ErlNifEnv* env, ERL_NIF_TERM term, long int* ip)</nametext></name> <fsummary>Read an long integer term.</fsummary> - <desc><p>Set <c>*ip</c> to the long integer value of - <c>term</c> or return false if <c>term</c> is not an integer or is + <desc><p>Set <c>*ip</c> to the long integer value of <c>term</c> and + return true, or return false if <c>term</c> is not an integer or is outside the bounds of type <c>long int</c>.</p></desc> </func> <func><name><ret>int</ret><nametext>enif_get_resource(ErlNifEnv* env, ERL_NIF_TERM term, ErlNifResourceType* type, void** objp)</nametext></name> <fsummary>Get the pointer to a resource object</fsummary> - <desc><p>Set <c>*objp</c> to point to the resource object referred to by <c>term</c>. - The pointer is valid until the calling NIF returns and should not be released.</p> - <p>Return false if <c>term</c> is not a handle to a resource object + <desc><p>Set <c>*objp</c> to point to the resource object referred to by <c>term</c>.</p> + <p>Return true on success or false if <c>term</c> is not a handle to a resource object of type <c>type</c>.</p></desc> </func> <func><name><ret>int</ret><nametext>enif_get_string(ErlNifEnv* env, @@ -540,27 +624,27 @@ typedef enum { <c>*arity</c> to the number of elements. Note that the array is read-only and <c>(*array)[N-1]</c> will be the Nth element of the tuple. <c>*array</c> is undefined if the arity of the tuple - is zero.</p><p>Return false if <c>term</c> is not a + is zero.</p><p>Return true on success or false if <c>term</c> is not a tuple.</p></desc> </func> <func><name><ret>int</ret><nametext>enif_get_uint(ErlNifEnv* env, ERL_NIF_TERM term, unsigned int* ip)</nametext></name> <fsummary>Read an unsigned integer term.</fsummary> - <desc><p>Set <c>*ip</c> to the unsigned integer value of - <c>term</c> or return false if <c>term</c> is not an unsigned integer or is - outside the bounds of type <c>unsigned int</c></p></desc> + <desc><p>Set <c>*ip</c> to the unsigned integer value of <c>term</c> and + return true, or return false if <c>term</c> is not an unsigned integer or + is outside the bounds of type <c>unsigned int</c>.</p></desc> </func> <func><name><ret>int</ret><nametext>enif_get_ulong(ErlNifEnv* env, ERL_NIF_TERM term, unsigned long* ip)</nametext></name> <fsummary>Read an unsigned integer term.</fsummary> - <desc><p>Set <c>*ip</c> to the unsigned long integer value of - <c>term</c> or return false if <c>term</c> is not an unsigned integer or is - outside the bounds of type <c>unsigned long</c></p></desc> + <desc><p>Set <c>*ip</c> to the unsigned long integer value of <c>term</c> + and return true, or return false if <c>term</c> is not an unsigned integer or is + outside the bounds of type <c>unsigned long</c>.</p></desc> </func> <func><name><ret>int</ret><nametext>enif_inspect_binary(ErlNifEnv* env, ERL_NIF_TERM bin_term, ErlNifBinary* bin)</nametext></name> <fsummary>Inspect the content of a binary</fsummary> <desc><p>Initialize the structure pointed to by <c>bin</c> with information about the binary term - <c>bin_term</c>. Return false if <c>bin_term</c> is not a binary.</p></desc> + <c>bin_term</c>. Return true on success or false if <c>bin_term</c> is not a binary.</p></desc> </func> <func><name><ret>int</ret><nametext>enif_inspect_iolist_as_binary(ErlNifEnv* env, ERL_NIF_TERM term, ErlNifBinary* bin) @@ -569,7 +653,7 @@ typedef enum { <desc><p>Initialize the structure pointed to by <c>bin</c> with one continuous buffer with the same byte content as <c>iolist</c>. As with inspect_binary, the data pointed to by <c>bin</c> is transient and does - not need to be released. Return false if <c>iolist</c> is not an + not need to be released. Return true on success or false if <c>iolist</c> is not an iolist.</p> </desc> </func> @@ -589,7 +673,7 @@ typedef enum { <fsummary>Determine if a term is a fun</fsummary> <desc><p>Return true if <c>term</c> is a fun.</p></desc> </func> - <func><name><ret>int</ret><nametext>enif_is_identical(ErlNifEnv* env, ERL_NIF_TERM lhs, ERL_NIF_TERM rhs)</nametext></name> + <func><name><ret>int</ret><nametext>enif_is_identical(ERL_NIF_TERM lhs, ERL_NIF_TERM rhs)</nametext></name> <fsummary>Erlang operator =:=</fsummary> <desc><p>Return true if the two terms are identical. Corresponds to the Erlang operators <c>=:=</c> and @@ -603,7 +687,6 @@ typedef enum { <fsummary>Determine if a term is a port</fsummary> <desc><p>Return true if <c>term</c> is a port.</p></desc> </func> - <func><name><ret>int</ret><nametext>enif_is_ref(ErlNifEnv* env, ERL_NIF_TERM term)</nametext></name> <fsummary>Determine if a term is a reference</fsummary> <desc><p>Return true if <c>term</c> is a reference.</p></desc> @@ -616,16 +699,23 @@ typedef enum { <fsummary>Determine if a term is a list</fsummary> <desc><p>Return true if <c>term</c> is a list.</p></desc> </func> + <func><name><ret>int</ret><nametext>enif_keep_resource(void* obj)</nametext></name> + <fsummary>Add a reference to a resource object</fsummary> + <desc><p>Add a reference to resource object <c>obj</c> obtained from + <seealso marker="#enif_alloc_resource">enif_alloc_resource</seealso>. + Each call to <c>enif_keep_resource</c> for an object must be balanced by + a call to <seealso marker="#enif_release_resource">enif_release_resource</seealso> + before the object will be destructed.</p></desc> + </func> <func><name><ret>ERL_NIF_TERM</ret><nametext>enif_make_atom(ErlNifEnv* env, const char* name)</nametext></name> <fsummary>Create an atom term</fsummary> - <desc><p>Create an atom term from the null-terminated C-string <c>name</c>. - Unlike other terms, atom terms may be saved and used between NIF calls.</p></desc> + <desc><p>Create an atom term from the null-terminated C-string <c>name</c> + with iso-latin-1 encoding.</p></desc> </func> <func><name><ret>ERL_NIF_TERM</ret><nametext>enif_make_atom_len(ErlNifEnv* env, const char* name, size_t len)</nametext></name> <fsummary>Create an atom term</fsummary> <desc><p>Create an atom term from the string <c>name</c> with length <c>len</c>. - Null-characters are treated as any other characters. - Unlike other terms, atom terms may be saved and used between NIF calls.</p></desc> + Null-characters are treated as any other characters.</p></desc> </func> <func><name><ret>ERL_NIF_TERM</ret><nametext>enif_make_badarg(ErlNifEnv* env)</nametext></name> <fsummary>Make a badarg exception.</fsummary> @@ -638,22 +728,30 @@ typedef enum { <c>bin</c> should be considered read-only for the rest of the NIF call and then as released.</p></desc> </func> + <func><name><ret>ERL_NIF_TERM</ret><nametext>enif_make_copy(ErlNifEnv* dst_env, ERL_NIF_TERM src_term)</nametext></name> + <fsummary>Make a copy of a term.</fsummary> + <desc><p>Make a copy of term <c>src_term</c>. The copy will be created in + environment <c>dst_env</c>. The source term may be located in any + environment.</p></desc> + </func> <func><name><ret>ERL_NIF_TERM</ret><nametext>enif_make_double(ErlNifEnv* env, double d)</nametext></name> <fsummary>Create a floating-point term</fsummary> <desc><p>Create a floating-point term from a <c>double</c>.</p></desc> </func> - <func><name><ret>int</ret><nametext>enif_make_existing_atom(ErlNifEnv* env, const char* name, ERL_NIF_TERM* atom)</nametext></name> + <func><name><ret>int</ret><nametext>enif_make_existing_atom(ErlNifEnv* env, const char* name, ERL_NIF_TERM* atom, ErlNifCharEncoding encode)</nametext></name> <fsummary>Create an existing atom term</fsummary> <desc><p>Try to create the term of an already existing atom from - the null-terminated C-string <c>name</c>. If the atom already exists store the - term in <c>*atom</c> and return true, otherwise return - false.</p></desc> + the null-terminated C-string <c>name</c> with encoding + <seealso marker="#ErlNifCharEncoding">encode</seealso>. If the atom + already exists store the term in <c>*atom</c> and return true, otherwise + return false.</p></desc> </func> - <func><name><ret>int</ret><nametext>enif_make_existing_atom_len(ErlNifEnv* env, const char* name, size_t len, ERL_NIF_TERM* atom)</nametext></name> + <func><name><ret>int</ret><nametext>enif_make_existing_atom_len(ErlNifEnv* env, const char* name, size_t len, ERL_NIF_TERM* atom, ErlNifCharEncoding encoding)</nametext></name> <fsummary>Create an existing atom term</fsummary> <desc><p>Try to create the term of an already existing atom from the - string <c>name</c> with length <c>len</c>. Null-characters are treated - as any other characters. If the atom already exists store the term + string <c>name</c> with length <c>len</c> and encoding + <seealso marker="#ErlNifCharEncoding">encode</seealso>. Null-characters + are treated as any other characters. If the atom already exists store the term in <c>*atom</c> and return true, otherwise return false.</p></desc> </func> <func><name><ret>ERL_NIF_TERM</ret><nametext>enif_make_int(ErlNifEnv* env, int i)</nametext></name> @@ -694,7 +792,7 @@ typedef enum { <fsummary>Create an integer term from a long int</fsummary> <desc><p>Create an integer term from a <c>long int</c>.</p></desc> </func> - <func><name><ret>unsigned char*</ret><nametext>enif_make_new_binary(ErlNifEnv* env, unsigned size, ERL_NIF_TERM* termp)</nametext></name> + <func><name><ret>unsigned char*</ret><nametext>enif_make_new_binary(ErlNifEnv* env, size_t size, ERL_NIF_TERM* termp)</nametext></name> <fsummary>Allocate and create a new binary term</fsummary> <desc><p>Allocate a binary of size <c>size</c> bytes and create an owning term. The binary data is mutable until the calling NIF returns. This is a @@ -704,6 +802,10 @@ typedef enum { reallocated.</p><p>Return a pointer to the raw binary data and set <c>*termp</c> to the binary term.</p></desc> </func> + <func><name><ret>ERL_NIF_TERM</ret><nametext>enif_make_pid(ErlNifEnv* env, const ErlNifPid* pid)</nametext></name> + <fsummary>Make a pid term</fsummary> + <desc><p>Make a pid term from <c>*pid</c>.</p></desc> + </func> <func><name><ret>ERL_NIF_TERM</ret><nametext>enif_make_ref(ErlNifEnv* env)</nametext></name> <fsummary>Create a reference.</fsummary> <desc><p>Create a reference like <seealso marker="erlang#make_ref-0">erlang:make_ref/0</seealso>.</p></desc> @@ -719,6 +821,29 @@ typedef enum { same node. Other operations such as matching or <c>term_to_binary</c> will have unpredictable (but harmless) results.</p></desc> </func> + <func><name><ret>ERL_NIF_TERM</ret><nametext>enif_make_resource_binary(ErlNifEnv* env, void* obj, const void* data, size_t size)</nametext></name> + <fsummary>Create a custom binary term</fsummary> + <desc><p>Create a binary term that is memory managed by a resource object + <c>obj</c> obtained by <seealso marker="#enif_alloc_resource">enif_alloc_resource</seealso>. + The returned binary term will consist of <c>size</c> bytes pointed to + by <c>data</c>. This raw binary data must be kept readable and unchanged + until the destructor of the resource is called. The binary data may be + stored external to the resource object in which case it is the responsibility + of the destructor to release the data.</p> + <p>Several binary terms may be managed by the same resource object. The + destructor will not be called until the last binary is garbage collected. + This can be useful as a way to return different parts of a larger binary + buffer.</p> + <p>As with <seealso marker="#enif_make_resource">enif_make_resource</seealso>, + no ownership transfer is done. The resource still needs to be released with + <seealso marker="#enif_release_resource">enif_release_resource</seealso>.</p> + </desc> + </func> + <func><name><ret>ErlNifPid*</ret><nametext>enif_self(ErlNifEnv* caller_env, ErlNifPid* pid)</nametext></name> + <fsummary>Get the pid of the calling process.</fsummary> + <desc><p>Initialize the pid variable <c>*pid</c> to represent the + calling process. Return <c>pid</c>.</p></desc> + </func> <func><name><ret>ERL_NIF_TERM</ret><nametext>enif_make_string(ErlNifEnv* env, const char* string, ErlNifCharEncoding encoding)</nametext></name> <fsummary>Create a string.</fsummary> <desc><p>Create a list containing the characters of the @@ -731,7 +856,7 @@ typedef enum { Null-characters are treated as any other characters.</p></desc> </func> <func><name><ret>ERL_NIF_TERM</ret><nametext>enif_make_sub_binary(ErlNifEnv* - env, ERL_NIF_TERM bin_term, unsigned pos, unsigned size)</nametext></name> + env, ERL_NIF_TERM bin_term, size_t pos, size_t size)</nametext></name> <fsummary>Make a subbinary term.</fsummary> <desc><p>Make a subbinary of binary <c>bin_term</c>, starting at zero-based position <c>pos</c> with a length of <c>size</c> bytes. @@ -775,30 +900,31 @@ typedef enum { </func> <func><name><ret>ErlNifMutex*</ret><nametext>enif_mutex_create(char *name)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_mutex_create">erl_drv_mutex_create()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_mutex_create">erl_drv_mutex_create</seealso>. </p></desc> </func> <func><name><ret>void</ret><nametext>enif_mutex_destroy(ErlNifMutex *mtx)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_mutex_destroy">erl_drv_mutex_destroy()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_mutex_destroy">erl_drv_mutex_destroy</seealso>. </p></desc> </func> <func><name><ret>void</ret><nametext>enif_mutex_lock(ErlNifMutex *mtx)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_mutex_lock">erl_drv_mutex_lock()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_mutex_lock">erl_drv_mutex_lock</seealso>. </p></desc> </func> <func><name><ret>int</ret><nametext>enif_mutex_trylock(ErlNifMutex *mtx)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_mutex_trylock">erl_drv_mutex_trylock()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_mutex_trylock">erl_drv_mutex_trylock</seealso>. </p></desc> </func> <func><name><ret>void</ret><nametext>enif_mutex_unlock(ErlNifMutex *mtx)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_mutex_unlock">erl_drv_mutex_unlock()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_mutex_unlock">erl_drv_mutex_unlock</seealso>. </p></desc> </func> - <func><name><ret>ErlNifResourceType*</ret><nametext>enif_open_resource_type(ErlNifEnv* env, const char* name, + <func><name><ret>ErlNifResourceType*</ret><nametext>enif_open_resource_type(ErlNifEnv* env, + const char* module_str, const char* name, ErlNifResourceDtor* dtor, ErlNifResourceFlags flags, ErlNifResourceFlags* tried)</nametext></name> <fsummary>Create or takeover a resource type</fsummary> <desc><p>Create or takeover a resource type identified by the string @@ -812,10 +938,10 @@ typedef enum { The supplied destructor <c>dtor</c> will be called both for existing instances as well as new instances not yet created by the calling NIF library.</item> </taglist> - <p>The two flag values can be combined with bitwise-or. To avoid unintentional - name clashes a good practice is to include the module name as part of the - type <c>name</c>. The <c>dtor</c> may be <c>NULL</c> in case no destructor - is needed.</p> + <p>The two flag values can be combined with bitwise-or. The name of the + resource type is local to the calling module. Argument <c>module_str</c> + is not (yet) used and must be NULL. The <c>dtor</c> may be <c>NULL</c> + in case no destructor is needed.</p> <p>On success, return a pointer to the resource type and <c>*tried</c> will be set to either <c>ERL_NIF_RT_CREATE</c> or <c>ERL_NIF_RT_TAKEOVER</c> to indicate what was actually done. @@ -832,123 +958,151 @@ typedef enum { <c>reload</c> or <c>upgrade</c>.</p> <p>Was previously named <c>enif_get_data</c>.</p></desc> </func> - <func><name><ret>void</ret><nametext>enif_realloc_binary(ErlNifEnv* env, ErlNifBinary* bin, unsigned size)</nametext></name> + <func><name><ret>void</ret><nametext>enif_realloc_binary(ErlNifBinary* bin, size_t size)</nametext></name> <fsummary>Change the size of a binary.</fsummary> <desc><p>Change the size of a binary <c>bin</c>. The source binary may be read-only, in which case it will be left untouched and a mutable copy is allocated and assigned to <c>*bin</c>.</p></desc> </func> - <func><name><ret>void</ret><nametext>enif_release_binary(ErlNifEnv* env, ErlNifBinary* bin)</nametext></name> + <func><name><ret>void</ret><nametext>enif_release_binary(ErlNifBinary* bin)</nametext></name> <fsummary>Release a binary.</fsummary> - <desc><p>Release a binary obtained - from <c>enif_alloc_binary</c>.</p></desc> + <desc><p>Release a binary obtained from <c>enif_alloc_binary</c>.</p></desc> </func> - <func><name><ret>void</ret><nametext>enif_release_resource(ErlNifEnv* env, void* obj)</nametext></name> + <func><name><ret>void</ret><nametext>enif_release_resource(void* obj)</nametext></name> <fsummary>Release a resource object.</fsummary> - <desc><p>Release a resource object obtained from <c>enif_alloc_resource</c>. - The object may still be alive if it is referred to by Erlang terms. Each call to - <c>enif_release_resource</c> must correspond to a previous call to <c>enif_alloc_resource</c>. - References made by <c>enif_make_resource</c> can only be released by the garbage collector.</p></desc> + <desc><p>Remove a reference to resource object <c>obj</c>obtained from + <seealso marker="#enif_alloc_resource">enif_alloc_resource</seealso>. + The resource object will be destructed when the last reference is removed. + Each call to <c>enif_release_resource</c> must correspond to a previous + call to <c>enif_alloc_resource</c> or + <seealso marker="#enif_keep_resource">enif_keep_resource</seealso>. + References made by <seealso marker="#enif_make_resource">enif_make_resource</seealso> + can only be removed by the garbage collector.</p></desc> </func> <func><name><ret>ErlNifRWLock*</ret><nametext>enif_rwlock_create(char *name)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_rwlock_create">erl_drv_rwlock_create()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_rwlock_create">erl_drv_rwlock_create</seealso>. </p></desc> </func> <func><name><ret>void</ret><nametext>enif_rwlock_destroy(ErlNifRWLock *rwlck)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_rwlock_destroy">erl_drv_rwlock_destroy()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_rwlock_destroy">erl_drv_rwlock_destroy</seealso>. </p></desc> </func> <func><name><ret>void</ret><nametext>enif_rwlock_rlock(ErlNifRWLock *rwlck)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_rwlock_rlock">erl_drv_rwlock_rlock()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_rwlock_rlock">erl_drv_rwlock_rlock</seealso>. </p></desc> </func> <func><name><ret>void</ret><nametext>enif_rwlock_runlock(ErlNifRWLock *rwlck)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_rwlock_runlock">erl_drv_rwlock_runlock()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_rwlock_runlock">erl_drv_rwlock_runlock</seealso>. </p></desc> </func> <func><name><ret>void</ret><nametext>enif_rwlock_rwlock(ErlNifRWLock *rwlck)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_rwlock_rwlock">erl_drv_rwlock_rwlock()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_rwlock_rwlock">erl_drv_rwlock_rwlock</seealso>. </p></desc> </func> <func><name><ret>void</ret><nametext>enif_rwlock_rwunlock(ErlNifRWLock *rwlck)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_rwlock_rwunlock">erl_drv_rwlock_rwunlock()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_rwlock_rwunlock">erl_drv_rwlock_rwunlock</seealso>. </p></desc> </func> <func><name><ret>int</ret><nametext>enif_rwlock_tryrlock(ErlNifRWLock *rwlck)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_rwlock_tryrlock">erl_drv_rwlock_tryrlock()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_rwlock_tryrlock">erl_drv_rwlock_tryrlock</seealso>. </p></desc> </func> <func><name><ret>int</ret><nametext>enif_rwlock_tryrwlock(ErlNifRWLock *rwlck)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_rwlock_tryrwlock">erl_drv_rwlock_tryrwlock()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_rwlock_tryrwlock">erl_drv_rwlock_tryrwlock</seealso>. </p></desc> </func> - <func><name><ret>unsigned</ret><nametext>enif_sizeof_resource(ErlNifEnv* env, void* obj)</nametext></name> + <func><name><ret>unsigned</ret><nametext>enif_send(ErlNifEnv* env, ErlNifPid* to_pid, ErlNifEnv* msg_env, ERL_NIF_TERM msg)</nametext></name> + <fsummary>Send a message to a process.</fsummary> + <desc><p>Send a message to a process.</p> + <taglist> + <tag><c>env</c></tag> + <item>The environment of the calling process. Must be NULL if and + only if calling from a created thread.</item> + <tag><c>*to_pid</c></tag> + <item>The pid of the receiving process. The pid should refer to a process on the local node.</item> + <tag><c>msg_env</c></tag> + <item>The environment of the message term. Must be a process + independent environment allocated with + <seealso marker="#enif_alloc_env">enif_alloc_env</seealso>.</item> + <tag><c>msg</c></tag> + <item>The message term to send.</item> + </taglist> + <p>Return true on success, or false if <c>*to_pid</c> does not refer to an alive local process.</p> + <p>The message environment <c>msg_env</c> with all its terms (including + <c>msg</c>) will be invalidated by a successful call to <c>enif_send</c>. The environment + should either be freed with <seealso marker="#enif_free_env">enif_free_env</seealso> + of cleared for reuse with <seealso marker="#enif_clear_env">enif_clear_env</seealso>.</p> + <p>This function is only thread-safe when the emulator with SMP support is used. + It can only be used in a non-SMP emulator from a NIF-calling thread.</p> + </desc> + </func> + <func><name><ret>unsigned</ret><nametext>enif_sizeof_resource(void* obj)</nametext></name> <fsummary>Get the byte size of a resource object</fsummary> <desc><p>Get the byte size of a resource object <c>obj</c> obtained by - <c>enif_alloc_resource</c>.</p></desc> + <seealso marker="#enif_alloc_resource">enif_alloc_resource</seealso>.</p></desc> </func> <func> <name><ret>void</ret><nametext>enif_system_info(ErlNifSysInfo *sys_info_ptr, size_t size)</nametext></name> <fsummary>Get information about the Erlang runtime system</fsummary> - <desc><p>Same as <seealso marker="erl_driver#driver_system_info">driver_system_info()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#driver_system_info">driver_system_info</seealso>. </p></desc> </func> <func><name><ret>int</ret><nametext>enif_thread_create(char *name,ErlNifTid *tid,void * (*func)(void *),void *args,ErlNifThreadOpts *opts)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_thread_create">erl_drv_thread_create()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_thread_create">erl_drv_thread_create</seealso>. </p></desc> </func> <func><name><ret>void</ret><nametext>enif_thread_exit(void *resp)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_thread_exit">erl_drv_thread_exit()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_thread_exit">erl_drv_thread_exit</seealso>. </p></desc> </func> <func><name><ret>int</ret><nametext>enif_thread_join(ErlNifTid, void **respp)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_thread_join">erl_drv_thread_join ()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_thread_join">erl_drv_thread_join </seealso>. </p></desc> </func> <func><name><ret>ErlNifThreadOpts*</ret><nametext>enif_thread_opts_create(char *name)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_thread_opts_create">erl_drv_thread_opts_create()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_thread_opts_create">erl_drv_thread_opts_create</seealso>. </p></desc> </func> <func><name><ret>void</ret><nametext>enif_thread_opts_destroy(ErlNifThreadOpts *opts)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_thread_opts_destroy">erl_drv_thread_opts_destroy()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_thread_opts_destroy">erl_drv_thread_opts_destroy</seealso>. </p></desc> </func> <func><name><ret>ErlNifTid</ret><nametext>enif_thread_self(void)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_thread_self">erl_drv_thread_self()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_thread_self">erl_drv_thread_self</seealso>. </p></desc> </func> <func><name><ret>int</ret><nametext>enif_tsd_key_create(char *name, ErlNifTSDKey *key)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_tsd_key_create">erl_drv_tsd_key_create()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_tsd_key_create">erl_drv_tsd_key_create</seealso>. </p></desc> </func> <func><name><ret>void</ret><nametext>enif_tsd_key_destroy(ErlNifTSDKey key)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_tsd_key_destroy">erl_drv_tsd_key_destroy()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_tsd_key_destroy">erl_drv_tsd_key_destroy</seealso>. </p></desc> </func> <func><name><ret>void*</ret><nametext>enif_tsd_get(ErlNifTSDKey key)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_tsd_get">erl_drv_tsd_get()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_tsd_get">erl_drv_tsd_get</seealso>. </p></desc> </func> <func><name><ret>void</ret><nametext>enif_tsd_set(ErlNifTSDKey key, void *data)</nametext></name> <fsummary></fsummary> - <desc><p>Same as <seealso marker="erl_driver#erl_drv_tsd_set">erl_drv_tsd_set()</seealso>. + <desc><p>Same as <seealso marker="erl_driver#erl_drv_tsd_set">erl_drv_tsd_set</seealso>. </p></desc> </func> </funcs> diff --git a/erts/doc/src/erlang.xml b/erts/doc/src/erlang.xml index 7ff52c5b50..3c64fe2bef 100644 --- a/erts/doc/src/erlang.xml +++ b/erts/doc/src/erlang.xml @@ -407,7 +407,7 @@ iolist() = [char() | binary() | iolist()] </desc> </func> <func> - <name>erlang:binary_to_term(Binary, Opts) -> term()</name> + <name>binary_to_term(Binary, Opts) -> term()</name> <fsummary>Decode an Erlang external term format binary</fsummary> <type> <v>Opts = [safe]</v> @@ -5187,10 +5187,21 @@ true</pre> For more information on how to define the CPU topology, see <seealso marker="#system_flag_cpu_topology">erlang:system_flag(cpu_topology, CpuTopology)</seealso>. </p> - <p><em>NOTE:</em> If other programs on the system have bound - to processors, e.g. another Erlang runtime system, you - may lose performance when binding schedulers. Therefore, - schedulers are by default not bound.</p> + <p>The runtime system will by default bind schedulers to logical + processors using the <c>default_bind</c> bind type if the amount + of schedulers are at least equal to the amount of logical + processors configured, binding of schedulers is supported, + and a CPU topology is available at startup. + </p> + <p><em>NOTE:</em> If the Erlang runtime system is the only + operating system process that binds threads to logical processors, + this improves the performance of the runtime system. However, + if other operating system processes (as for example another Erlang + runtime system) also bind threads to logical processors, there + might be a performance penalty instead. If this is the case you, + are are advised to unbind the schedulers using the + <seealso marker="erl#+sbt">+sbtu</seealso> command line argument, + or <c>erlang:system_flag(scheduler_bind_type, unbound)</c>.</p> <p>Schedulers can be bound in different ways. The <c>How</c> argument determines how schedulers are bound. <c>How</c> can currently be one of:</p> @@ -6743,6 +6754,17 @@ true</pre> See also <seealso marker="#trace_pattern/3">erlang:trace_pattern/3</seealso>.</p> </item> + <tag><c>call_time</c></tag> + <item> + <p>Return the call time values for this function or + <c>true</c> for the pseudo function <c>on_load</c> if call + time tracing is active. Returns <c>false</c> otherwise. + The call time values returned, <c>[{Pid, Count, S, Us}]</c>, + is a list of each process that has executed the function and its specific counters. + See also + <seealso marker="#trace_pattern/3">erlang:trace_pattern/3</seealso>.</p> + </item> + <tag><c>all</c></tag> <item> <p>Return a list containing the <c>{Item, Value}</c> tuples @@ -6845,13 +6867,13 @@ true</pre> </item> <tag><c>restart</c></tag> <item> - <p>For the <c>FlagList</c> option <c>call_count</c>: + <p>For the <c>FlagList</c> option <c>call_count</c> and <c>call_time</c>: restart the existing counters. The behaviour is undefined for other <c>FlagList</c> options.</p> </item> <tag><c>pause</c></tag> <item> - <p>For the <c>FlagList</c> option <c>call_count</c>: pause + <p>For the <c>FlagList</c> option <c>call_count</c> and <c>call_time</c>: pause the existing counters. The behaviour is undefined for other <c>FlagList</c> options.</p> </item> @@ -6906,6 +6928,23 @@ true</pre> <p>The counter value can be read with <seealso marker="#trace_info/2">erlang:trace_info/2</seealso>.</p> </item> + <tag><c>call_time</c></tag> + <item> + <p>Starts (<c>MatchSpec == true</c>) or stops + (<c>MatchSpec == false</c>) call time tracing for all + types of function calls. For every function a counter is + incremented when the function is called. Time spent in the function + is accumulated in two other counters, seconds and micro-seconds. + The counters are stored for each call traced process.</p> + <p>If call time tracing is started while already running, + the count and time is restarted from zero. Running counters can be + paused with <c>MatchSpec == pause</c>. Paused and running + counters can be restarted from zero with + <c>MatchSpec == restart</c>.</p> + <p>The counter value can be read with + <seealso marker="#trace_info/2">erlang:trace_info/2</seealso>.</p> + </item> + </taglist> <p>The <c>global</c> and <c>local</c> options are mutually exclusive and <c>global</c> is the default (if no options are @@ -6913,7 +6952,7 @@ true</pre> perform a kind of local tracing, and can also not be combined with <c>global</c>. A function can be either globally or locally traced. If global tracing is specified for a - specified set of functions; local, meta and call count + specified set of functions; local, meta, call time and call count tracing for the matching set of local functions will be disabled, and vice versa.</p> <p>When disabling trace, the option must match the type of trace diff --git a/erts/doc/src/match_spec.xml b/erts/doc/src/match_spec.xml index d6492198ca..f0390c9db8 100644 --- a/erts/doc/src/match_spec.xml +++ b/erts/doc/src/match_spec.xml @@ -60,7 +60,7 @@ <section> <title>Grammar</title> - <p>A match_spec can be described in this <em>informal</em> grammar:</p> + <p>A match_spec used in tracing can be described in this <em>informal</em> grammar:</p> <list type="bulleted"> <item>MatchExpression ::= [ MatchFunction, ... ] </item> @@ -117,6 +117,52 @@ <c><![CDATA[display]]></c> | <c><![CDATA[caller]]></c> | <c><![CDATA[set_tcw]]></c> | <c><![CDATA[silent]]></c></item> </list> + + <p>A match_spec used in ets can be described in this <em>informal</em> grammar:</p> + <list type="bulleted"> + <item>MatchExpression ::= [ MatchFunction, ... ] + </item> + <item>MatchFunction ::= { MatchHead, MatchConditions, MatchBody } + </item> + <item>MatchHead ::= MatchVariable | <c><![CDATA['_']]></c> | { MatchHeadPart, ... } + </item> + <item>MatchHeadPart ::= term() | MatchVariable | <c><![CDATA['_']]></c></item> + <item>MatchVariable ::= '$<number>' + </item> + <item>MatchConditions ::= [ MatchCondition, ...] | <c><![CDATA[[]]]></c></item> + <item>MatchCondition ::= { GuardFunction } | + { GuardFunction, ConditionExpression, ... } + </item> + <item>BoolFunction ::= <c><![CDATA[is_atom]]></c> | <c><![CDATA[is_constant]]></c> | + <c><![CDATA[is_float]]></c> | <c><![CDATA[is_integer]]></c> | <c><![CDATA[is_list]]></c> | + <c><![CDATA[is_number]]></c> | <c><![CDATA[is_pid]]></c> | <c><![CDATA[is_port]]></c> | + <c><![CDATA[is_reference]]></c> | <c><![CDATA[is_tuple]]></c> | <c><![CDATA[is_binary]]></c> | + <c><![CDATA[is_function]]></c> | <c><![CDATA[is_record]]></c> | <c><![CDATA[is_seq_trace]]></c> | + <c><![CDATA['and']]></c> | <c><![CDATA['or']]></c> | <c><![CDATA['not']]></c> | <c><![CDATA['xor']]></c> | + <c><![CDATA[andalso]]></c> | <c><![CDATA[orelse]]></c></item> + <item>ConditionExpression ::= ExprMatchVariable | { GuardFunction } | + { GuardFunction, ConditionExpression, ... } | TermConstruct + </item> + <item>ExprMatchVariable ::= MatchVariable (bound in the MatchHead) | + <c><![CDATA['$_']]></c> | <c><![CDATA['$$']]></c></item> + <item>TermConstruct = {{}} | {{ ConditionExpression, ... }} | + <c><![CDATA[[]]]></c> | [ConditionExpression, ...] | NonCompositeTerm | Constant + </item> + <item>NonCompositeTerm ::= term() (not list or tuple) + </item> + <item>Constant ::= {<c><![CDATA[const]]></c>, term()} + </item> + <item>GuardFunction ::= BoolFunction | <c><![CDATA[abs]]></c> | + <c><![CDATA[element]]></c> | <c><![CDATA[hd]]></c> | <c><![CDATA[length]]></c> | <c><![CDATA[node]]></c> | + <c><![CDATA[round]]></c> | <c><![CDATA[size]]></c> | <c><![CDATA[tl]]></c> | <c><![CDATA[trunc]]></c> | + <c><![CDATA['+']]></c> | <c><![CDATA['-']]></c> | <c><![CDATA['*']]></c> | <c><![CDATA['div']]></c> | + <c><![CDATA['rem']]></c> | <c><![CDATA['band']]></c> | <c><![CDATA['bor']]></c> | <c><![CDATA['bxor']]></c> | + <c><![CDATA['bnot']]></c> | <c><![CDATA['bsl']]></c> | <c><![CDATA['bsr']]></c> | <c><![CDATA['>']]></c> | + <c><![CDATA['>=']]></c> | <c><![CDATA['<']]></c> | <c><![CDATA['=<']]></c> | <c><![CDATA['=:=']]></c> | + <c><![CDATA['==']]></c> | <c><![CDATA['=/=']]></c> | <c><![CDATA['/=']]></c> | <c><![CDATA[self]]></c> | + <c><![CDATA[get_tcw]]></c></item> + <item>MatchBody ::= [ ConditionExpression, ... ]</item> + </list> </section> <section> @@ -453,8 +499,8 @@ <section> <title>Differences between match specifications in ETS and tracing</title> <p>ETS match specifications are there to produce a return - value. Usually the expression contains one single - <c><![CDATA[ActionTerm]]></c> which defines the return value without having + value. Usually the <c><![CDATA[MatchBody]]></c> contains one single + <c><![CDATA[ConditionExpression]]></c> which defines the return value without having any side effects. Calls with side effects are not allowed in the ETS context.</p> <p>When tracing there is no return value to produce, the diff --git a/erts/emulator/Makefile.in b/erts/emulator/Makefile.in index d767194d4d..eca6121a1e 100644 --- a/erts/emulator/Makefile.in +++ b/erts/emulator/Makefile.in @@ -341,7 +341,6 @@ ERLANG_OSTYPE = @ERLANG_OSTYPE@ ENABLE_ALLOC_TYPE_VARS += @ERLANG_OSTYPE@ -EMULATOR_EXECUTABLE_SAE = beam_evm$(TF_MARKER) EMULATOR_EXECUTABLE_ELIB = beam.elib$(TF_MARKER) ifeq ($(TARGET), win32) EMULATOR_EXECUTABLE = beam$(TF_MARKER).dll @@ -406,7 +405,7 @@ endif @set -e ; cd zlib && $(MAKE) clean @set -e ; cd pcre && $(MAKE) clean -.PHONY: all sae zlib pcre clean +.PHONY: all zlib pcre clean docs: @@ -640,10 +639,6 @@ $(BINDIR)/$(CS_EXECUTABLE): $(CS_SRC) $(OBJDIR)/%.elib.o: beam/%.c $(CC) $(ELIB_FLAGS) $(subst -O2, $(GEN_OPT_FLGS), $(CFLAGS)) $(INCLUDES) -c $< -o $@ -# Disable vfork() for sae (then we don't need the child_setup program) -$(OBJDIR)/sys_sae.o: sys/$(ERLANG_OSTYPE)/sys.c - $(CC) -DDISABLE_VFORK=1 $(CFLAGS) $(INCLUDES) -c $< -o $@ - $(OBJDIR)/%.kp.o: sys/common/%.c $(CC) -DERTS_KERNEL_POLL_VERSION $(subst -O2, $(GEN_OPT_FLGS), $(CFLAGS)) $(INCLUDES) -c $< -o $@ @@ -689,8 +684,6 @@ endif INIT_OBJS = $(OBJDIR)/erl_main.o $(PRELOAD) -INIT_OBJS_SAE = $(OBJDIR)/erl9_start.o - EMU_OBJS = \ $(OBJDIR)/beam_emu.o $(OBJDIR)/beam_opcodes.o \ $(OBJDIR)/beam_load.o $(OBJDIR)/beam_bif_load.o \ @@ -826,7 +819,6 @@ endif BASE_OBJS = $(RUN_OBJS) $(EMU_OBJS) $(OS_OBJS) $(EXTRA_BASE_OBJS) OBJS = $(BASE_OBJS) $(DRV_OBJS) -OBJS_SAE = $(subst sys.o,sys_sae.o,$(OBJS)) ELIB_C_FILES = beam/elib_malloc.c \ beam/elib_memmove.c \ @@ -933,10 +925,6 @@ $(BINDIR)/$(EMULATOR_EXECUTABLE_ELIB): $(INIT_OBJS) $(OBJS_ELIB) $(DEPLIBS) $(PURIFY) $(LD) -o $(BINDIR)/$(EMULATOR_EXECUTABLE_ELIB) \ $(LDFLAGS) $(DEXPORT) $(INIT_OBJS) $(OBJS_ELIB) $(LIBS) -$(BINDIR)/$(EMULATOR_EXECUTABLE_SAE): $(INIT_OBJS_SAE) $(OBJS_SAE) $(DEPLIBS) - $(PURIFY) $(LD) -o $(BINDIR)/$(EMULATOR_EXECUTABLE_SAE) \ - $(LDFLAGS) $(DEXPORT) $(INIT_OBJS_SAE) $(OBJS_SAE) $(LIBS) - endif # diff --git a/erts/emulator/beam/atom.names b/erts/emulator/beam/atom.names index 1138c0c871..28f69b9460 100644 --- a/erts/emulator/beam/atom.names +++ b/erts/emulator/beam/atom.names @@ -123,6 +123,7 @@ atom busy_dist_port atom busy_port atom call atom call_count +atom call_time atom caller atom capture atom case_clause diff --git a/erts/emulator/beam/beam_bif_load.c b/erts/emulator/beam/beam_bif_load.c index 8462f1c7fd..596ad9a010 100644 --- a/erts/emulator/beam/beam_bif_load.c +++ b/erts/emulator/beam/beam_bif_load.c @@ -368,7 +368,6 @@ BIF_RETTYPE finish_after_on_load_2(BIF_ALIST_2) BIF_RET(am_true); } - static void set_default_trace_pattern(Eterm module) { diff --git a/erts/emulator/beam/beam_bp.c b/erts/emulator/beam/beam_bp.c index 6278ff6bad..682f31b83f 100644 --- a/erts/emulator/beam/beam_bp.c +++ b/erts/emulator/beam/beam_bp.c @@ -30,6 +30,7 @@ #include "error.h" #include "erl_binary.h" #include "beam_bp.h" +#include "erl_term.h" /* ************************************************************************* ** Macros @@ -100,6 +101,11 @@ do { \ (b)->prev = (a); \ } while (0) + +#define BREAK_IS_BIF (1) +#define BREAK_IS_ERL (0) + + /* ************************************************************************* ** Local prototypes */ @@ -114,7 +120,7 @@ static int set_break(Eterm mfa[3], int specified, static int set_module_break(Module *modp, Eterm mfa[3], int specified, Binary *match_spec, BeamInstr break_op, enum erts_break_op count_op, Eterm tracer_pid); -static int set_function_break(Module *modp, BeamInstr *pc, +static int set_function_break(Module *modp, BeamInstr *pc, int bif, Binary *match_spec, BeamInstr break_op, enum erts_break_op count_op, Eterm tracer_pid); @@ -122,11 +128,29 @@ static int clear_break(Eterm mfa[3], int specified, BeamInstr break_op); static int clear_module_break(Module *modp, Eterm mfa[3], int specified, BeamInstr break_op); -static int clear_function_break(Module *modp, BeamInstr *pc, +static int clear_function_break(Module *modp, BeamInstr *pc, int bif, BeamInstr break_op); static BpData *is_break(BeamInstr *pc, BeamInstr break_op); - +static BpData *get_break(Process *p, BeamInstr *pc, BeamInstr break_op); + +/* bp_hash */ +#define BP_TIME_ADD(pi0, pi1) \ + do { \ + Uint r; \ + (pi0)->count += (pi1)->count; \ + (pi0)->s_time += (pi1)->s_time; \ + (pi0)->us_time += (pi1)->us_time; \ + r = (pi0)->us_time / 1000000; \ + (pi0)->s_time += r; \ + (pi0)->us_time = (pi0)->us_time % 1000000; \ + } while(0) + +static void bp_hash_init(bp_time_hash_t *hash, Uint n); +static void bp_hash_rehash(bp_time_hash_t *hash, Uint n); +static ERTS_INLINE bp_data_time_item_t * bp_hash_get(bp_time_hash_t *hash, bp_data_time_item_t *sitem); +static ERTS_INLINE bp_data_time_item_t * bp_hash_put(bp_time_hash_t *hash, bp_data_time_item_t *sitem); +static void bp_hash_delete(bp_time_hash_t *hash); /* ************************************************************************* @@ -156,25 +180,20 @@ erts_set_mtrace_break(Eterm mfa[3], int specified, Binary *match_spec, (BeamInstr) BeamOp(op_i_mtrace_breakpoint), 0, tracer_pid); } +/* set breakpoint data for on exported bif entry */ + void erts_set_mtrace_bif(BeamInstr *pc, Binary *match_spec, Eterm tracer_pid) { - BpDataTrace *bdt; ERTS_SMP_LC_ASSERT(erts_smp_is_system_blocked(0)); + set_function_break(NULL, pc, BREAK_IS_BIF, match_spec, (BeamInstr) BeamOp(op_i_mtrace_breakpoint), 0, tracer_pid); +} - bdt = (BpDataTrace *) pc[-4]; - if (bdt) { - MatchSetUnref(bdt->match_spec); - MatchSetRef(match_spec); - bdt->match_spec = match_spec; - bdt->tracer_pid = tracer_pid; - } else { - bdt = Alloc(sizeof(BpDataTrace)); - BpInit((BpData *) bdt, 0); - MatchSetRef(match_spec); - bdt->match_spec = match_spec; - bdt->tracer_pid = tracer_pid; - pc[-4] = (BeamInstr) bdt; - } +void erts_set_time_trace_bif(BeamInstr *pc, enum erts_break_op count_op) { + set_function_break(NULL, pc, BREAK_IS_BIF, NULL, (BeamInstr) BeamOp(op_i_time_breakpoint), count_op, NIL); +} + +void erts_clear_time_trace_bif(BeamInstr *pc) { + clear_function_break(NULL, pc, BREAK_IS_BIF, (BeamInstr) BeamOp(op_i_time_breakpoint)); } int @@ -191,7 +210,12 @@ erts_set_count_break(Eterm mfa[3], int specified, enum erts_break_op count_op) { (BeamInstr) BeamOp(op_i_count_breakpoint), count_op, NIL); } - +int +erts_set_time_break(Eterm mfa[3], int specified, enum erts_break_op count_op) { + ERTS_SMP_LC_ASSERT(erts_smp_is_system_blocked(0)); + return set_break(mfa, specified, NULL, + (BeamInstr) BeamOp(op_i_time_breakpoint), count_op, NIL); +} int erts_clear_trace_break(Eterm mfa[3], int specified) { @@ -209,17 +233,7 @@ erts_clear_mtrace_break(Eterm mfa[3], int specified) { void erts_clear_mtrace_bif(BeamInstr *pc) { - BpDataTrace *bdt; - ERTS_SMP_LC_ASSERT(erts_smp_is_system_blocked(0)); - - bdt = (BpDataTrace *) pc[-4]; - if (bdt) { - if (bdt->match_spec) { - MatchSetUnref(bdt->match_spec); - } - Free(bdt); - } - pc[-4] = (BeamInstr) NULL; + clear_function_break(NULL, pc, BREAK_IS_BIF, (BeamInstr) BeamOp(op_i_mtrace_breakpoint)); } int @@ -237,6 +251,13 @@ erts_clear_count_break(Eterm mfa[3], int specified) { } int +erts_clear_time_break(Eterm mfa[3], int specified) { + ERTS_SMP_LC_ASSERT(erts_smp_is_system_blocked(0)); + return clear_break(mfa, specified, + (BeamInstr) BeamOp(op_i_time_breakpoint)); +} + +int erts_clear_break(Eterm mfa[3], int specified) { ERTS_SMP_LC_ASSERT(erts_smp_is_system_blocked(0)); return clear_break(mfa, specified, 0); @@ -253,7 +274,7 @@ int erts_clear_function_break(Module *modp, BeamInstr *pc) { ERTS_SMP_LC_ASSERT(erts_smp_is_system_blocked(0)); ASSERT(modp); - return clear_function_break(modp, pc, 0); + return clear_function_break(modp, pc, BREAK_IS_ERL, 0); } @@ -265,9 +286,12 @@ BeamInstr erts_trace_break(Process *p, BeamInstr *pc, Eterm *args, Uint32 *ret_flags, Eterm *tracer_pid) { Eterm tpid1, tpid2; - BpDataTrace *bdt = (BpDataTrace *) pc[-4]; + BpData **bds = (BpData **) (pc)[-4]; + BpDataTrace *bdt = NULL; + ASSERT(bds); ASSERT(pc[-5] == (BeamInstr) BeamOp(op_i_func_info_IaaI)); + bdt = (BpDataTrace *) bds[bp_sched2ix_proc(p)]; ASSERT(bdt); bdt = (BpDataTrace *) bdt->next; ASSERT(bdt); @@ -286,7 +310,7 @@ erts_trace_break(Process *p, BeamInstr *pc, Eterm *args, bdt->tracer_pid = tpid2; ErtsSmpBPUnlock(bdt); } - pc[-4] = (BeamInstr) bdt; + bds[bp_sched2ix_proc(p)] = (BpData *) bdt; return bdt->orig_instr; } @@ -298,12 +322,15 @@ erts_trace_break(Process *p, BeamInstr *pc, Eterm *args, Uint32 erts_bif_mtrace(Process *p, BeamInstr *pc, Eterm *args, int local, Eterm *tracer_pid) { - BpDataTrace *bdt = (BpDataTrace *) pc[-4]; + BpData **bds = (BpData **) (pc)[-4]; + BpDataTrace *bdt = NULL; + ASSERT(tracer_pid); - if (bdt) { + if (bds) { Eterm tpid1, tpid2; Uint32 flags; + bdt = (BpDataTrace *)bds[bp_sched2ix_proc(p)]; ErtsSmpBPLock(bdt); tpid1 = tpid2 = bdt->tracer_pid; @@ -364,24 +391,6 @@ erts_is_mtrace_break(BeamInstr *pc, Binary **match_spec_ret, Eterm *tracer_pid_r } int -erts_is_mtrace_bif(BeamInstr *pc, Binary **match_spec_ret, Eterm *tracer_pid_ret) { - BpDataTrace *bdt = (BpDataTrace *) pc[-4]; - - if (bdt) { - if (match_spec_ret) { - *match_spec_ret = bdt->match_spec; - } - if (tracer_pid_ret) { - ErtsSmpBPLock(bdt); - *tracer_pid_ret = bdt->tracer_pid; - ErtsSmpBPUnlock(bdt); - } - return !0; - } - return 0; -} - -int erts_is_native_break(BeamInstr *pc) { #ifdef HIPE ASSERT(pc[-5] == (BeamInstr) BeamOp(op_i_func_info_IaaI)); @@ -399,15 +408,69 @@ erts_is_count_break(BeamInstr *pc, Sint *count_ret) { if (bdc) { if (count_ret) { - ErtsSmpBPLock(bdc); - *count_ret = bdc->count; - ErtsSmpBPUnlock(bdc); + *count_ret = (Sint) erts_smp_atomic_read(&bdc->acount); } return !0; } return 0; } +int erts_is_time_break(Process *p, BeamInstr *pc, Eterm *retval) { + Uint i, ix; + bp_time_hash_t hash; + Uint size; + Eterm *hp, t; + bp_data_time_item_t *item = NULL; + BpDataTime *bdt = (BpDataTime *) is_break(pc, (BeamInstr) BeamOp(op_i_time_breakpoint)); + + if (bdt) { + if (retval) { + /* collect all hashes to one hash */ + bp_hash_init(&hash, 64); + /* foreach threadspecific hash */ + for (i = 0; i < bdt->n; i++) { + bp_data_time_item_t *sitem; + + /* foreach hash bucket not NIL*/ + for(ix = 0; ix < bdt->hash[i].n; ix++) { + item = &(bdt->hash[i].item[ix]); + if (item->pid != NIL) { + sitem = bp_hash_get(&hash, item); + if (sitem) { + BP_TIME_ADD(sitem, item); + } else { + bp_hash_put(&hash, item); + } + } + } + } + /* *retval should be NIL or term from previous bif in export entry */ + + if (hash.used > 0) { + size = (5 + 2)*hash.used; + hp = HAlloc(p, size); + + for(ix = 0; ix < hash.n; ix++) { + item = &(hash.item[ix]); + if (item->pid != NIL) { + t = TUPLE4(hp, item->pid, + make_small(item->count), + make_small(item->s_time), + make_small(item->us_time)); + hp += 5; + *retval = CONS(hp, t, *retval); hp += 2; + } + } + } + bp_hash_delete(&hash); + } + return !0; + } + + return 0; +} + + BeamInstr * erts_find_local_func(Eterm mfa[3]) { Module *modp; @@ -432,6 +495,351 @@ erts_find_local_func(Eterm mfa[3]) { return NULL; } +/* bp_hash */ +ERTS_INLINE Uint bp_sched2ix() { +#ifdef ERTS_SMP + ErtsSchedulerData *esdp; + esdp = erts_get_scheduler_data(); + return esdp->no - 1; +#else + return 0; +#endif +} +static void bp_hash_init(bp_time_hash_t *hash, Uint n) { + Uint size = sizeof(bp_data_time_item_t)*n; + Uint i; + + hash->n = n; + hash->used = 0; + + hash->item = (bp_data_time_item_t *)Alloc(size); + sys_memzero(hash->item, size); + + for(i = 0; i < n; ++i) { + hash->item[i].pid = NIL; + } +} + +static void bp_hash_rehash(bp_time_hash_t *hash, Uint n) { + bp_data_time_item_t *item = NULL; + Uint size = sizeof(bp_data_time_item_t)*n; + Uint ix; + Uint hval; + + item = (bp_data_time_item_t *)Alloc(size); + sys_memzero(item, size); + + for( ix = 0; ix < n; ++ix) { + item[ix].pid = NIL; + } + + /* rehash, old hash -> new hash */ + + for( ix = 0; ix < hash->n; ix++) { + if (hash->item[ix].pid != NIL) { + + hval = ((hash->item[ix].pid) >> 4) % n; /* new n */ + + while (item[hval].pid != NIL) { + hval = (hval + 1) % n; + } + item[hval].pid = hash->item[ix].pid; + item[hval].count = hash->item[ix].count; + item[hval].s_time = hash->item[ix].s_time; + item[hval].us_time = hash->item[ix].us_time; + } + } + + Free(hash->item); + hash->n = n; + hash->item = item; +} +static ERTS_INLINE bp_data_time_item_t * bp_hash_get(bp_time_hash_t *hash, bp_data_time_item_t *sitem) { + Eterm pid = sitem->pid; + Uint hval = (pid >> 4) % hash->n; + bp_data_time_item_t *item = NULL; + + item = hash->item; + + while (item[hval].pid != pid) { + if (item[hval].pid == NIL) return NULL; + hval = (hval + 1) % hash->n; + } + + return &(item[hval]); +} + +static ERTS_INLINE bp_data_time_item_t * bp_hash_put(bp_time_hash_t *hash, bp_data_time_item_t* sitem) { + Uint hval; + float r = 0.0; + bp_data_time_item_t *item; + + /* make sure that the hash is not saturated */ + /* if saturated, rehash it */ + + r = hash->used / (float) hash->n; + + if (r > 0.7f) { + bp_hash_rehash(hash, hash->n * 2); + } + /* Do hval after rehash */ + hval = (sitem->pid >> 4) % hash->n; + + /* find free slot */ + item = hash->item; + + while (item[hval].pid != NIL) { + hval = (hval + 1) % hash->n; + } + item = &(hash->item[hval]); + + item->pid = sitem->pid; + item->s_time = sitem->s_time; + item->us_time = sitem->us_time; + item->count = sitem->count; + hash->used++; + + return item; +} + +static void bp_hash_delete(bp_time_hash_t *hash) { + hash->n = 0; + hash->used = 0; + Free(hash->item); + hash->item = NULL; +} + +static void bp_time_diff(bp_data_time_item_t *item, /* out */ + process_breakpoint_time_t *pbt, /* in */ + Uint ms, Uint s, Uint us) { + int dms,ds,dus; + + dms = ms - pbt->ms; + ds = s - pbt->s; + dus = us - pbt->us; + + /* get_sys_now may return zero difftime, + * this is ok. + */ + + ASSERT(dms >= 0 || ds >= 0 || dus >= 0); + + if (dus < 0) { + dus += 1000000; + ds -= 1; + } + if (ds < 0) { + ds += 1000000; + } + + item->s_time = ds; + item->us_time = dus; +} + +void erts_schedule_time_break(Process *p, Uint schedule) { + Uint ms, s, us; + process_breakpoint_time_t *pbt = NULL; + bp_data_time_item_t sitem, *item = NULL; + bp_time_hash_t *h = NULL; + BpDataTime *pbdt = NULL; + + ASSERT(p); + + pbt = ERTS_PROC_GET_CALL_TIME(p); + + if (pbt) { + + switch(schedule) { + case ERTS_BP_CALL_TIME_SCHEDULE_EXITING : + break; + case ERTS_BP_CALL_TIME_SCHEDULE_OUT : + /* When a process is scheduled _out_, + * timestamp it and add its delta to + * the previous breakpoint. + */ + + pbdt = (BpDataTime *) get_break(p, pbt->pc, (BeamInstr) BeamOp(op_i_time_breakpoint)); + if (pbdt) { + get_sys_now(&ms,&s,&us); + bp_time_diff(&sitem, pbt, ms, s, us); + sitem.pid = p->id; + sitem.count = 0; + + h = &(pbdt->hash[bp_sched2ix_proc(p)]); + + ASSERT(h); + ASSERT(h->item); + + item = bp_hash_get(h, &sitem); + if (!item) { + item = bp_hash_put(h, &sitem); + } else { + BP_TIME_ADD(item, &sitem); + } + } + break; + case ERTS_BP_CALL_TIME_SCHEDULE_IN : + /* When a process is scheduled _in_, + * timestamp it and remove the previous + * timestamp in the psd. + */ + get_sys_now(&ms,&s,&us); + pbt->ms = ms; + pbt->s = s; + pbt->us = us; + break; + default : + ASSERT(0); + /* will never happen */ + break; + } + } /* pbt */ +} + +/* call_time breakpoint + * Accumulated times are added to the previous bp, + * not the current one. The current one is saved + * for future reference. + * The previous breakpoint is stored in the process it self, the psd. + * We do not need to store in a stack frame. + * There is no need for locking, each thread has its own + * area in each bp to save data. + * Since we need to diffrentiate between processes for each bp, + * every bp has a hash (per thread) to process-bp statistics. + * - egil + */ + +void erts_trace_time_break(Process *p, BeamInstr *pc, BpDataTime *bdt, Uint type) { + Uint ms,s,us; + process_breakpoint_time_t *pbt = NULL; + bp_data_time_item_t sitem, *item = NULL; + bp_time_hash_t *h = NULL; + BpDataTime *pbdt = NULL; + + ASSERT(p); + ASSERT(p->status == P_RUNNING); + + /* get previous timestamp and breakpoint + * from the process psd */ + + pbt = ERTS_PROC_GET_CALL_TIME(p); + get_sys_now(&ms,&s,&us); + + switch(type) { + /* get pbt + * timestamp = t0 + * lookup bdt from code + * set ts0 to pbt + * add call count here? + */ + case ERTS_BP_CALL_TIME_CALL: + case ERTS_BP_CALL_TIME_TAIL_CALL: + + if (pbt) { + ASSERT(pbt->pc); + /* add time to previous code */ + bp_time_diff(&sitem, pbt, ms, s, us); + sitem.pid = p->id; + sitem.count = 0; + + /* previous breakpoint */ + pbdt = (BpDataTime *) get_break(p, pbt->pc, (BeamInstr) BeamOp(op_i_time_breakpoint)); + + /* if null then the breakpoint was removed */ + if (pbdt) { + h = &(pbdt->hash[bp_sched2ix_proc(p)]); + + ASSERT(h); + ASSERT(h->item); + + item = bp_hash_get(h, &sitem); + if (!item) { + item = bp_hash_put(h, &sitem); + } else { + BP_TIME_ADD(item, &sitem); + } + } + + } else { + /* first call of process to instrumented function */ + pbt = Alloc(sizeof(process_breakpoint_time_t)); + (void *) ERTS_PROC_SET_CALL_TIME(p, ERTS_PROC_LOCK_MAIN, pbt); + } + /* add count to this code */ + sitem.pid = p->id; + sitem.count = 1; + sitem.s_time = 0; + sitem.us_time = 0; + + /* this breakpoint */ + ASSERT(bdt); + h = &(bdt->hash[bp_sched2ix_proc(p)]); + + ASSERT(h); + ASSERT(h->item); + + item = bp_hash_get(h, &sitem); + if (!item) { + item = bp_hash_put(h, &sitem); + } else { + BP_TIME_ADD(item, &sitem); + } + + pbt->pc = pc; + pbt->ms = ms; + pbt->s = s; + pbt->us = us; + break; + + case ERTS_BP_CALL_TIME_RETURN: + /* get pbt + * lookup bdt from code + * timestamp = t1 + * get ts0 from pbt + * get item from bdt->hash[bp_hash(p->id)] + * ack diff (t1, t0) to item + */ + + if(pbt) { + /* might have been removed due to + * trace_pattern(false) + */ + ASSERT(pbt->pc); + + bp_time_diff(&sitem, pbt, ms, s, us); + sitem.pid = p->id; + sitem.count = 0; + + /* previous breakpoint */ + pbdt = (BpDataTime *) get_break(p, pbt->pc, (BeamInstr) BeamOp(op_i_time_breakpoint)); + + /* beware, the trace_pattern might have been removed */ + if (pbdt) { + h = &(pbdt->hash[bp_sched2ix_proc(p)]); + + ASSERT(h); + ASSERT(h->item); + + item = bp_hash_get(h, &sitem); + if (!item) { + item = bp_hash_put(h, &sitem); + } else { + BP_TIME_ADD(item, &sitem); + } + } + + pbt->pc = pc; + pbt->ms = ms; + pbt->s = s; + pbt->us = us; + } + break; + default : + ASSERT(0); + /* will never happen */ + break; + } +} /* ************************************************************************* @@ -489,26 +897,35 @@ static int set_module_break(Module *modp, Eterm mfa[3], int specified, ASSERT(code_ptr[0] == (BeamInstr) BeamOp(op_i_func_info_IaaI)); if ((specified < 2 || mfa[1] == ((Eterm) code_ptr[3])) && (specified < 3 || ((int) mfa[2]) == ((int) code_ptr[4]))) { - BeamInstr *pc = code_ptr+5; + BeamInstr *pc = code_ptr+5; num_processed += - set_function_break(modp, pc, match_spec, + set_function_break(modp, pc, BREAK_IS_ERL, match_spec, break_op, count_op, tracer_pid); } } return num_processed; } -static int set_function_break(Module *modp, BeamInstr *pc, +static int set_function_break(Module *modp, BeamInstr *pc, int bif, Binary *match_spec, BeamInstr break_op, enum erts_break_op count_op, Eterm tracer_pid) { - BpData *bd, **r; + + BeamInstr **code_base = NULL; + BpData *bd, **r, ***rs; size_t size; - BeamInstr **code_base = (BeamInstr **)modp->code; + Uint ix = 0; - ASSERT(code_base); - ASSERT(code_base <= (BeamInstr **)pc); - ASSERT((BeamInstr **)pc < code_base + (modp->code_length/sizeof(BeamInstr *))); + if (bif == BREAK_IS_ERL) { + code_base = (BeamInstr **)modp->code; + ASSERT(code_base); + ASSERT(code_base <= (BeamInstr **)pc); + ASSERT((BeamInstr **)pc < code_base + (modp->code_length/sizeof(BeamInstr *))); + } else { + ASSERT(*pc == (BeamInstr) em_apply_bif); + ASSERT(modp == NULL); + } + /* * Currently no trace support for native code. */ @@ -519,6 +936,7 @@ static int set_function_break(Module *modp, BeamInstr *pc, if ( (bd = is_break(pc, break_op))) { if (break_op == (BeamInstr) BeamOp(op_i_trace_breakpoint) || break_op == (BeamInstr) BeamOp(op_i_mtrace_breakpoint)) { + BpDataTrace *bdt = (BpDataTrace *) bd; Binary *old_match_spec; @@ -531,20 +949,44 @@ static int set_function_break(Module *modp, BeamInstr *pc, ErtsSmpBPUnlock(bdt); MatchSetUnref(old_match_spec); } else { + BpDataCount *bdc = (BpDataCount *) bd; + long count = 0; + long res = 0; + ASSERT(! match_spec); ASSERT(is_nil(tracer_pid)); - if (break_op == (BeamInstr) BeamOp(op_i_count_breakpoint)) { - BpDataCount *bdc = (BpDataCount *) bd; - ErtsSmpBPLock(bdc); + if (break_op == (BeamInstr) BeamOp(op_i_count_breakpoint)) { if (count_op == erts_break_stop) { - if (bdc->count >= 0) { - bdc->count = -bdc->count-1; /* Stop call counter */ + count = erts_smp_atomic_read(&bdc->acount); + if (count >= 0) { + while(1) { + res = erts_smp_atomic_cmpxchg(&bdc->acount, -count - 1, count); + if ((res == count) || count < 0) break; + count = res; + } } } else { - bdc->count = 0; /* Reset call counter */ + /* Reset call counter */ + erts_smp_atomic_set(&bdc->acount, 0); } - ErtsSmpBPUnlock(bdc); + + } else if (break_op == (BeamInstr) BeamOp(op_i_time_breakpoint)) { + BpDataTime *bdt = (BpDataTime *) bd; + Uint i = 0; + + ERTS_SMP_LC_ASSERT(erts_smp_is_system_blocked(0)); + + if (count_op == erts_break_stop) { + bdt->pause = 1; + } else { + bdt->pause = 0; + for (i = 0; i < bdt->n; i++) { + bp_hash_delete(&(bdt->hash[i])); + bp_hash_init(&(bdt->hash[i]), 32); + } + } + } else { ASSERT (! count_op); } @@ -558,43 +1000,63 @@ static int set_function_break(Module *modp, BeamInstr *pc, ASSERT(! match_spec); ASSERT(is_nil(tracer_pid)); if (break_op == (BeamInstr) BeamOp(op_i_count_breakpoint)) { - if (count_op == erts_break_reset - || count_op == erts_break_stop) { + if (count_op == erts_break_reset || count_op == erts_break_stop) { /* Do not insert a new breakpoint */ return 1; } size = sizeof(BpDataCount); + } else if (break_op == (BeamInstr) BeamOp(op_i_time_breakpoint)) { + if (count_op == erts_break_reset || count_op == erts_break_stop) { + /* Do not insert a new breakpoint */ + return 1; + } + size = sizeof(BpDataTime); } else { ASSERT(! count_op); ASSERT(break_op == (BeamInstr) BeamOp(op_i_debug_breakpoint)); size = sizeof(BpDataDebug); } } - r = (BpData **) (pc-4); + rs = (BpData ***) (pc-4); + if (! *rs) { + size_t ssize = sizeof(BeamInstr) * erts_no_schedulers; + *rs = (BpData **) Alloc(ssize); + sys_memzero(*rs, ssize); + } + + r = &((*rs)[0]); + if (! *r) { ASSERT(*pc != (BeamInstr) BeamOp(op_i_trace_breakpoint)); ASSERT(*pc != (BeamInstr) BeamOp(op_i_mtrace_breakpoint)); ASSERT(*pc != (BeamInstr) BeamOp(op_i_debug_breakpoint)); ASSERT(*pc != (BeamInstr) BeamOp(op_i_count_breakpoint)); + ASSERT(*pc != (BeamInstr) BeamOp(op_i_time_breakpoint)); /* First breakpoint; create singleton ring */ bd = Alloc(size); BpInit(bd, *pc); - *pc = break_op; *r = bd; + if (bif == BREAK_IS_ERL) { + *pc = break_op; + } } else { ASSERT(*pc == (BeamInstr) BeamOp(op_i_trace_breakpoint) || *pc == (BeamInstr) BeamOp(op_i_mtrace_breakpoint) || *pc == (BeamInstr) BeamOp(op_i_debug_breakpoint) || - *pc == (BeamInstr) BeamOp(op_i_count_breakpoint)); + *pc == (BeamInstr) BeamOp(op_i_time_breakpoint) || + *pc == (BeamInstr) BeamOp(op_i_count_breakpoint) || + *pc == (BeamInstr) em_apply_bif); if (*pc == (BeamInstr) BeamOp(op_i_debug_breakpoint)) { /* Debug bp must be last, so if it is also first; * it must be singleton. */ - ASSERT(BpSingleton(*r)); + ASSERT(BpSingleton(*r)); /* Insert new bp first in the ring, i.e second to last. */ bd = Alloc(size); BpInitAndSpliceNext(bd, *pc, *r); - *pc = break_op; - } else if ((*r)->prev->orig_instr + if (bif == BREAK_IS_ERL) { + *pc = break_op; + } + } else if ((*r)->prev->orig_instr == (BeamInstr) BeamOp(op_i_debug_breakpoint)) { /* Debug bp last in the ring; insert new second to last. */ bd = Alloc(size); @@ -608,6 +1070,11 @@ static int set_function_break(Module *modp, BeamInstr *pc, *r = bd; } } + for (ix = 1; ix < erts_no_schedulers; ++ix) { + (*rs)[ix] = (*rs)[0]; + } + + bd->this_instr = break_op; /* Init the bp type specific data */ if (break_op == (BeamInstr) BeamOp(op_i_trace_breakpoint) || break_op == (BeamInstr) BeamOp(op_i_mtrace_breakpoint)) { @@ -617,12 +1084,25 @@ static int set_function_break(Module *modp, BeamInstr *pc, MatchSetRef(match_spec); bdt->match_spec = match_spec; bdt->tracer_pid = tracer_pid; + } else if (break_op == (BeamInstr) BeamOp(op_i_time_breakpoint)) { + BpDataTime *bdt = (BpDataTime *) bd; + Uint i = 0; + + bdt->pause = 0; + bdt->n = erts_no_schedulers; + bdt->hash = Alloc(sizeof(bp_time_hash_t)*(bdt->n)); + + for (i = 0; i < bdt->n; i++) { + bp_hash_init(&(bdt->hash[i]), 32); + } } else if (break_op == (BeamInstr) BeamOp(op_i_count_breakpoint)) { BpDataCount *bdc = (BpDataCount *) bd; + erts_smp_atomic_init(&bdc->acount, 0); + } - bdc->count = 0; + if (bif == BREAK_IS_ERL) { + ++(*(BeamInstr*)&code_base[MI_NUM_BREAKPOINTS]); } - ++(*(BeamInstr*)&code_base[MI_NUM_BREAKPOINTS]); return 1; } @@ -672,33 +1152,51 @@ static int clear_module_break(Module *m, Eterm mfa[3], int specified, BeamInstr *pc = code_ptr + 5; num_processed += - clear_function_break(m, pc, break_op); + clear_function_break(m, pc, BREAK_IS_ERL, break_op); } } return num_processed; } -static int clear_function_break(Module *m, BeamInstr *pc, BeamInstr break_op) { +static int clear_function_break(Module *m, BeamInstr *pc, int bif, BeamInstr break_op) { BpData *bd; - BeamInstr **code_base = (BeamInstr **)m->code; - - ASSERT(code_base); - ASSERT(code_base <= (BeamInstr **)pc); - ASSERT((BeamInstr **)pc < code_base + (m->code_length/sizeof(BeamInstr *))); + Uint ix = 0; + BeamInstr **code_base = NULL; + + if (bif == BREAK_IS_ERL) { + code_base = (BeamInstr **)m->code; + ASSERT(code_base); + ASSERT(code_base <= (BeamInstr **)pc); + ASSERT((BeamInstr **)pc < code_base + (m->code_length/sizeof(BeamInstr *))); + } else { + ASSERT(*pc == (BeamInstr) em_apply_bif); + ASSERT(m == NULL); + } + /* * Currently no trace support for native code. */ if (erts_is_native_break(pc)) { return 0; } + while ( (bd = is_break(pc, break_op))) { /* Remove all breakpoints of this type. * There should be only one of each type, * but break_op may be 0 which matches any type. */ BeamInstr op; - BpData **r = (BpData **) (pc-4); + BpData ***rs = (BpData ***) (pc - 4); + BpData **r = NULL; + +#ifdef DEBUG + for (ix = 1; ix < erts_no_schedulers; ++ix) { + ASSERT((*rs)[ix] == (*rs)[0]); + } +#endif + r = &((*rs)[0]); + ASSERT(*r); /* Find opcode for this breakpoint */ if (break_op) { @@ -714,8 +1212,11 @@ static int clear_function_break(Module *m, BeamInstr *pc, BeamInstr break_op) { if (BpSingleton(bd)) { ASSERT(*r == bd); /* Only one breakpoint to remove */ - *r = NULL; - *pc = bd->orig_instr; + if (bif == BREAK_IS_ERL) { + *pc = bd->orig_instr; + } + Free(*rs); + *rs = NULL; } else { BpData *bd_prev = bd->prev; @@ -727,7 +1228,9 @@ static int clear_function_break(Module *m, BeamInstr *pc, BeamInstr break_op) { bd_prev->orig_instr = bd->orig_instr; } else if (bd_prev == *r) { /* We removed the first breakpoint in the ring */ - *pc = bd->orig_instr; + if (bif == BREAK_IS_ERL) { + *pc = bd->orig_instr; + } } else { bd_prev->orig_instr = bd->orig_instr; } @@ -736,13 +1239,53 @@ static int clear_function_break(Module *m, BeamInstr *pc, BeamInstr break_op) { op == (BeamInstr) BeamOp(op_i_mtrace_breakpoint)) { BpDataTrace *bdt = (BpDataTrace *) bd; - MatchSetUnref(bdt->match_spec); } + if (op == (BeamInstr) BeamOp(op_i_time_breakpoint)) { + BpDataTime *bdt = (BpDataTime *) bd; + Uint i = 0; + Uint j = 0; + Process *h_p = NULL; + bp_data_time_item_t *item = NULL; + process_breakpoint_time_t *pbt = NULL; + + /* remove all psd associated with the hash + * and then delete the hash. + * ... sigh ... + */ + + for( i = 0; i < bdt->n; ++i) { + if (bdt->hash[i].used) { + for (j = 0; j < bdt->hash[i].n; ++j) { + item = &(bdt->hash[i].item[j]); + if (item->pid != NIL) { + h_p = process_tab[internal_pid_index(item->pid)]; + if (h_p) { + pbt = ERTS_PROC_SET_CALL_TIME(h_p, ERTS_PROC_LOCK_MAIN, NULL); + if (pbt) { + Free(pbt); + } + } + } + } + } + bp_hash_delete(&(bdt->hash[i])); + } + Free(bdt->hash); + bdt->hash = NULL; + bdt->n = 0; + } Free(bd); - ASSERT(((BeamInstr) code_base[MI_NUM_BREAKPOINTS]) > 0); - --(*(BeamInstr*)&code_base[MI_NUM_BREAKPOINTS]); - } + if (bif == BREAK_IS_ERL) { + ASSERT(((BeamInstr) code_base[MI_NUM_BREAKPOINTS]) > 0); + --(*(BeamInstr*)&code_base[MI_NUM_BREAKPOINTS]); + } + if (*rs) { + for (ix = 1; ix < erts_no_schedulers; ++ix) { + (*rs)[ix] = (*rs)[0]; + } + } + } /* while bd != NULL */ return 1; } @@ -755,32 +1298,63 @@ static int clear_function_break(Module *m, BeamInstr *pc, BeamInstr break_op) { ** returned. The program counter must point to the first executable ** (breakpoint) instruction of the function. */ -static BpData *is_break(BeamInstr *pc, BeamInstr break_op) { + +BpData *erts_get_time_break(Process *p, BeamInstr *pc) { + return get_break(p, pc, (BeamInstr) BeamOp(op_i_time_breakpoint)); +} + +static BpData *get_break(Process *p, BeamInstr *pc, BeamInstr break_op) { ASSERT(pc[-5] == (BeamInstr) BeamOp(op_i_func_info_IaaI)); if (! erts_is_native_break(pc)) { - BpData *bd = (BpData *) pc[-4]; - - if (break_op == 0) { - return bd; - } - if (*pc == break_op) { - ASSERT(bd); - return bd->next; - } - if (! bd){ + BpData **rs = (BpData **) pc[-4]; + BpData *bd = NULL, *ebd = NULL; + + if (! rs) { return NULL; } + + bd = ebd = rs[bp_sched2ix_proc(p)]; + ASSERT(bd); + if (bd->this_instr == break_op) { + return bd; + } + bd = bd->next; - while (bd != (BpData *) pc[-4]) { + while (bd != ebd) { ASSERT(bd); - if (bd->orig_instr == break_op) { - bd = bd->next; + if (bd->this_instr == break_op) { ASSERT(bd); return bd; - } else { - bd = bd->next; } + bd = bd->next; } } return NULL; } + +static BpData *is_break(BeamInstr *pc, BeamInstr break_op) { + BpData **rs = (BpData **) pc[-4]; + BpData *bd = NULL, *ebd = NULL; + ASSERT(pc[-5] == (BeamInstr) BeamOp(op_i_func_info_IaaI)); + + if (! rs) { + return NULL; + } + + bd = ebd = rs[bp_sched2ix()]; + ASSERT(bd); + if ( (break_op == 0) || (bd->this_instr == break_op)) { + return bd; + } + + bd = bd->next; + while (bd != ebd) { + ASSERT(bd); + if (bd->this_instr == break_op) { + ASSERT(bd); + return bd; + } + bd = bd->next; + } + return NULL; +} diff --git a/erts/emulator/beam/beam_bp.h b/erts/emulator/beam/beam_bp.h index 786cbbe9d9..b5d5b3c203 100644 --- a/erts/emulator/beam/beam_bp.h +++ b/erts/emulator/beam/beam_bp.h @@ -27,28 +27,46 @@ -/* -** Common struct to all bp_data_* -** -** Two gotchas: -** -** 1) The type of bp_data structure in the ring is deduced from the -** orig_instr field of the structure _before_ in the ring, except for -** the first structure in the ring that has its instruction in -** pc[0] of the code to execute. -** -** 2) pc[-4] points to the _last_ structure in the ring before the -** breakpoints are being executed. -** -** So, as an example, when a breakpointed function starts to execute, -** the first instruction that is a breakpoint instruction at pc[0] finds -** its data at ((BpData *) pc[-4])->next and has to cast that pointer -** to the correct bp_data type. +/* A couple of gotchas: + * + * The breakpoint structure from BeamInstr, + * In beam_emu where the instruction counter pointer, I (or pc), + * points to the *current* instruction. At that time, if the instruction + * is a breakpoint instruction the pc looks like the following, + * + * I[-5] | op_i_func_info_IaaI | scheduler specific entries + * I[-4] | BpData** bpa | --> | BpData * bdas1 | ... | BpData * bdasN | + * I[-3] | Tagged Module | | | + * I[-2] | Tagged Function | V V + * I[-1] | Arity | BpData -> BpData -> BpData -> BpData + * I[0] | The bp instruction | ^ * the bp wheel * | + * |------------------------------ + * + * Common struct to all bp_data_* + * + * 1) The type of bp_data structure in the ring is deduced from the + * orig_instr field of the structure _before_ in the ring, except for + * the first structure in the ring that has its instruction in + * pc[0] of the code to execute. + * This is valid as long as you don't search for the function while it is + * being executed by something else. Or is in the middle of its rotation for + * any other reason. + * A key, the bp beam instruction, is included for this reason. + * + * 2) pc[-4][sched_id - 1] points to the _last_ structure in the ring before the + * breakpoints are being executed. + * + * So, as an example, when a breakpointed function starts to execute, + * the first instruction that is a breakpoint instruction at pc[0] finds + * its data at ((BpData **) pc[-4][sched_id - 1])->next and has to cast that pointer + * to the correct bp_data type. */ + typedef struct bp_data { struct bp_data *next; /* Doubly linked ring pointers */ struct bp_data *prev; /* -"- */ BeamInstr orig_instr; /* The original instruction to execute */ + BeamInstr this_instr; /* key */ } BpData; /* ** All the following bp_data_.. structs must begin the same way @@ -57,26 +75,67 @@ typedef struct bp_data { typedef struct bp_data_trace { struct bp_data *next; struct bp_data *prev; - BeamInstr orig_instr; + BeamInstr orig_instr; + BeamInstr this_instr; /* key */ Binary *match_spec; - Eterm tracer_pid; + Eterm tracer_pid; } BpDataTrace; typedef struct bp_data_debug { struct bp_data *next; struct bp_data *prev; - BeamInstr orig_instr; + BeamInstr orig_instr; + BeamInstr this_instr; /* key */ } BpDataDebug; -typedef struct bp_data_count { /* Call count */ +typedef struct bp_data_count { /* Call count */ struct bp_data *next; struct bp_data *prev; BeamInstr orig_instr; - Sint count; + BeamInstr this_instr; /* key */ + erts_smp_atomic_t acount; } BpDataCount; +typedef struct { + Eterm pid; + Sint count; + Uint s_time; + Uint us_time; +} bp_data_time_item_t; + +typedef struct { + Uint n; + Uint used; + bp_data_time_item_t *item; +} bp_time_hash_t; + +typedef struct bp_data_time { /* Call time */ + struct bp_data *next; + struct bp_data *prev; + BeamInstr orig_instr; + BeamInstr this_instr; /* key */ + Uint pause; + Uint n; + bp_time_hash_t *hash; +} BpDataTime; + +typedef struct { + Uint ms; + Uint s; + Uint us; + Uint *pc; +} process_breakpoint_time_t; /* used within psd */ + extern erts_smp_spinlock_t erts_bp_lock; +#define ERTS_BP_CALL_TIME_SCHEDULE_IN (0) +#define ERTS_BP_CALL_TIME_SCHEDULE_OUT (1) +#define ERTS_BP_CALL_TIME_SCHEDULE_EXITING (2) + +#define ERTS_BP_CALL_TIME_CALL (0) +#define ERTS_BP_CALL_TIME_RETURN (1) +#define ERTS_BP_CALL_TIME_TAIL_CALL (2) + #ifdef ERTS_SMP #define ErtsSmpBPLock(BDC) erts_smp_spin_lock(&erts_bp_lock) #define ErtsSmpBPUnlock(BDC) erts_smp_spin_unlock(&erts_bp_lock) @@ -85,31 +144,46 @@ extern erts_smp_spinlock_t erts_bp_lock; #define ErtsSmpBPUnlock(BDC) #endif -#define ErtsCountBreak(pc,instr_result) \ -do { \ - BpDataCount *bdc = (BpDataCount *) (pc)[-4]; \ - \ +ERTS_INLINE Uint bp_sched2ix(void); + +#ifdef ERTS_SMP +#define bp_sched2ix_proc(p) ((p)->scheduler_data->no - 1) +#else +#define bp_sched2ix_proc(p) (0) +#endif + +#define ErtsCountBreak(p, pc,instr_result) \ +do { \ + BpData **bds = (BpData **) (pc)[-4]; \ + BpDataCount *bdc = NULL; \ + Uint ix = bp_sched2ix_proc( (p) ); \ + long count = 0; \ + \ ASSERT((pc)[-5] == (BeamInstr) BeamOp(op_i_func_info_IaaI)); \ - ASSERT(bdc); \ - bdc = (BpDataCount *) bdc->next; \ - ASSERT(bdc); \ - (pc)[-4] = (BeamInstr) bdc; \ - ErtsSmpBPLock(bdc); \ - if (bdc->count >= 0) bdc->count++; \ - ErtsSmpBPUnlock(bdc); \ - *(instr_result) = bdc->orig_instr; \ + ASSERT(bds); \ + bdc = (BpDataCount *) bds[ix]; \ + bdc = (BpDataCount *) bdc->next; \ + ASSERT(bdc); \ + bds[ix] = (BpData *) bdc; \ + count = erts_smp_atomic_read(&bdc->acount); \ + if (count >= 0) erts_smp_atomic_inc(&bdc->acount); \ + *(instr_result) = bdc->orig_instr; \ } while (0) -#define ErtsBreakSkip(pc,instr_result) \ -do { \ - BpData *bd = (BpData *) (pc)[-4]; \ - \ +#define ErtsBreakSkip(p, pc,instr_result) \ +do { \ + BpData **bds = (BpData **) (pc)[-4]; \ + BpData *bd = NULL; \ + Uint ix = bp_sched2ix_proc( (p) ); \ + \ ASSERT((pc)[-5] == (BeamInstr) BeamOp(op_i_func_info_IaaI)); \ - ASSERT(bd); \ - bd = bd->next; \ - ASSERT(bd); \ - (pc)[-4] = (BeamInstr) bd; \ - *(instr_result) = bd->orig_instr; \ + ASSERT(bds); \ + bd = bds[ix]; \ + ASSERT(bd); \ + bd = bd->next; \ + ASSERT(bd); \ + bds[ix] = bd; \ + *(instr_result) = bd->orig_instr; \ } while (0) enum erts_break_op{ @@ -159,6 +233,17 @@ int erts_is_mtrace_bif(BeamInstr *pc, Binary **match_spec_ret, Eterm *tracer_pid_ret); int erts_is_native_break(BeamInstr *pc); int erts_is_count_break(BeamInstr *pc, Sint *count_ret); +int erts_is_time_break(Process *p, BeamInstr *pc, Eterm *call_time); + +void erts_trace_time_break(Process *p, BeamInstr *pc, BpDataTime *bdt, Uint type); +void erts_schedule_time_break(Process *p, Uint out); +int erts_set_time_break(Eterm mfa[3], int specified, enum erts_break_op); +int erts_clear_time_break(Eterm mfa[3], int specified); + +int erts_is_time_trace_bif(Process *p, BeamInstr *pc, Eterm *call_time); +void erts_set_time_trace_bif(BeamInstr *pc, enum erts_break_op); +void erts_clear_time_trace_bif(BeamInstr *pc); +BpData *erts_get_time_break(Process *p, BeamInstr *pc); BeamInstr *erts_find_local_func(Eterm mfa[3]); diff --git a/erts/emulator/beam/beam_emu.c b/erts/emulator/beam/beam_emu.c index d42e74ccc9..c0680086aa 100644 --- a/erts/emulator/beam/beam_emu.c +++ b/erts/emulator/beam/beam_emu.c @@ -228,9 +228,10 @@ BeamInstr* em_call_traced_function; ** for the refering variable (one of these), and rouge references ** will most likely cause chaos. */ -BeamInstr beam_return_to_trace[1]; /* OpCode(i_return_to_trace) */ -BeamInstr beam_return_trace[1]; /* OpCode(i_return_trace) */ -BeamInstr beam_exception_trace[1]; /* UGLY also OpCode(i_return_trace) */ +BeamInstr beam_return_to_trace[1]; /* OpCode(i_return_to_trace) */ +BeamInstr beam_return_trace[1]; /* OpCode(i_return_trace) */ +BeamInstr beam_exception_trace[1]; /* UGLY also OpCode(i_return_trace) */ +BeamInstr beam_return_time_trace[1]; /* OpCode(i_return_time_trace) */ /* * All Beam instructions in numerical order. @@ -4403,35 +4404,109 @@ apply_bif_or_nif_epilogue: OpCase(i_count_breakpoint): { BeamInstr real_I; - ErtsCountBreak((BeamInstr *) I, &real_I); + ErtsCountBreak(c_p, (BeamInstr *) I, &real_I); ASSERT(VALID_INSTR(real_I)); Goto(real_I); } + /* need to send mfa instead of bdt pointer + * the pointer might be deallocated. + */ + + OpCase(i_time_breakpoint): { + BeamInstr real_I; + BpData **bds = (BpData **) (I)[-4]; + BpDataTime *bdt = NULL; + Uint ix = 0; +#ifdef ERTS_SMP + ix = c_p->scheduler_data->no - 1; +#else + ix = 0; +#endif + bdt = (BpDataTime *)bds[ix]; + + ASSERT((I)[-5] == (BeamInstr) BeamOp(op_i_func_info_IaaI)); + ASSERT(bdt); + bdt = (BpDataTime *) bdt->next; + ASSERT(bdt); + bds[ix] = (BpData *) bdt; + real_I = bdt->orig_instr; + ASSERT(VALID_INSTR(real_I)); + + if (IS_TRACED_FL(c_p, F_TRACE_CALLS) && !(bdt->pause)) { + if ( (*(c_p->cp) == (BeamInstr) OpCode(i_return_time_trace)) || + (*(c_p->cp) == (BeamInstr) OpCode(return_trace)) || + (*(c_p->cp) == (BeamInstr) OpCode(i_return_to_trace))) { + /* This _IS_ a tail recursive call */ + SWAPOUT; + erts_trace_time_break(c_p, I, bdt, ERTS_BP_CALL_TIME_TAIL_CALL); + SWAPIN; + } else { + SWAPOUT; + erts_trace_time_break(c_p, I, bdt, ERTS_BP_CALL_TIME_CALL); + + /* r register needs to be copied to the array + * for the garbage collector + */ + ASSERT(c_p->htop <= E && E <= c_p->hend); + if (E - 2 < HTOP) { + reg[0] = r(0); + PROCESS_MAIN_CHK_LOCKS(c_p); + FCALLS -= erts_garbage_collect(c_p, 2, reg, I[-1]); + PROCESS_MAIN_CHK_LOCKS(c_p); + r(0) = reg[0]; + } + SWAPIN; + + ASSERT(c_p->htop <= E && E <= c_p->hend); + + E -= 2; + E[0] = make_cp(I); + E[1] = make_cp(c_p->cp); /* original return address */ + c_p->cp = (BeamInstr *) make_cp(beam_return_time_trace); + } + } + + Goto(real_I); + } + + OpCase(i_return_time_trace): { + BeamInstr *pc = (BeamInstr *) (UWord) E[0]; + SWAPOUT; + erts_trace_time_break(c_p, pc, NULL, ERTS_BP_CALL_TIME_RETURN); + SWAPIN; + c_p->cp = NULL; + SET_I((BeamInstr *) cp_val(E[1])); + E += 2; + Goto(*I); + } + OpCase(i_trace_breakpoint): if (! IS_TRACED_FL(c_p, F_TRACE_CALLS)) { BeamInstr real_I; - ErtsBreakSkip((BeamInstr *) I, &real_I); + ErtsBreakSkip(c_p, (BeamInstr *) I, &real_I); Goto(real_I); } /* Fall through to next case */ OpCase(i_mtrace_breakpoint): { - Uint real_I; + BeamInstr real_I; Uint32 flags; Eterm tracer_pid; - Uint *cpp; + BeamInstr *cpp; int return_to_trace = 0, need = 0; flags = 0; SWAPOUT; reg[0] = r(0); if (*(c_p->cp) == (BeamInstr) OpCode(return_trace)) { - cpp = (Uint*)&E[2]; - } else if (*(c_p->cp) - == (BeamInstr) OpCode(i_return_to_trace)) { + cpp = (BeamInstr*)&E[2]; + } else if (*(c_p->cp) == (BeamInstr) OpCode(i_return_to_trace)) { return_to_trace = !0; - cpp = (Uint*)&E[0]; + cpp = (BeamInstr*)&E[0]; + } else if (*(c_p->cp) == (BeamInstr) OpCode(i_return_time_trace)) { + return_to_trace = !0; + cpp = (BeamInstr*)&E[0]; } else { cpp = NULL; } @@ -4448,6 +4523,8 @@ apply_bif_or_nif_epilogue: } else if (*cp_val(*cpp) == (BeamInstr) OpCode(i_return_to_trace)) { return_to_trace = !0; cpp += 1; + } else if (*cp_val(*cpp) == (BeamInstr) OpCode(i_return_time_trace)) { + cpp += 2; } else break; } @@ -4957,13 +5034,15 @@ apply_bif_or_nif_epilogue: em_call_error_handler = OpCode(call_error_handler); em_call_traced_function = OpCode(call_traced_function); em_apply_bif = OpCode(apply_bif); - beam_apply[0] = (BeamInstr) OpCode(i_apply); - beam_apply[1] = (BeamInstr) OpCode(normal_exit); - beam_exit[0] = (BeamInstr) OpCode(error_action_code); - beam_continue_exit[0] = (BeamInstr) OpCode(continue_exit); - beam_return_to_trace[0] = (BeamInstr) OpCode(i_return_to_trace); - beam_return_trace[0] = (BeamInstr) OpCode(return_trace); - beam_exception_trace[0] = (BeamInstr) OpCode(return_trace); /* UGLY */ + + beam_apply[0] = (BeamInstr) OpCode(i_apply); + beam_apply[1] = (BeamInstr) OpCode(normal_exit); + beam_exit[0] = (BeamInstr) OpCode(error_action_code); + beam_continue_exit[0] = (BeamInstr) OpCode(continue_exit); + beam_return_to_trace[0] = (BeamInstr) OpCode(i_return_to_trace); + beam_return_trace[0] = (BeamInstr) OpCode(return_trace); + beam_exception_trace[0] = (BeamInstr) OpCode(return_trace); /* UGLY */ + beam_return_time_trace[0] = (BeamInstr) OpCode(i_return_time_trace); /* * Enter all BIFs into the export table. @@ -4975,6 +5054,8 @@ apply_bif_or_nif_epilogue: bif_export[i] = ep; ep->code[3] = (BeamInstr) OpCode(apply_bif); ep->code[4] = (BeamInstr) bif_table[i].f; + /* XXX: set func info for bifs */ + ((BeamInstr*)ep->code + 3)[-5] = (BeamInstr) BeamOp(op_i_func_info_IaaI); } return; @@ -5167,14 +5248,18 @@ next_catch(Process* c_p, Eterm *reg) { int active_catches = c_p->catches > 0; int have_return_to_trace = 0; Eterm *ptr, *prev, *return_to_trace_ptr = NULL; - BeamInstr i_return_trace = beam_return_trace[0]; - BeamInstr i_return_to_trace = beam_return_to_trace[0]; + + BeamInstr i_return_trace = beam_return_trace[0]; + BeamInstr i_return_to_trace = beam_return_to_trace[0]; + BeamInstr i_return_time_trace = beam_return_time_trace[0]; + ptr = prev = c_p->stop; ASSERT(is_CP(*ptr)); ASSERT(ptr <= STACK_START(c_p)); if (ptr == STACK_START(c_p)) return NULL; if ((is_not_CP(*ptr) || (*cp_val(*ptr) != i_return_trace && - *cp_val(*ptr) != i_return_to_trace)) + *cp_val(*ptr) != i_return_to_trace && + *cp_val(*ptr) != i_return_time_trace )) && c_p->cp) { /* Can not follow cp here - code may be unloaded */ BeamInstr *cpp = c_p->cp; @@ -5186,6 +5271,9 @@ next_catch(Process* c_p, Eterm *reg) { } else if (cpp == beam_return_trace) { /* Skip return_trace parameters */ ptr += 2; + } else if (cpp == beam_return_time_trace) { + /* Skip return_trace parameters */ + ptr += 1; } else if (cpp == beam_return_to_trace) { have_return_to_trace = !0; /* Record next cp */ } @@ -5215,6 +5303,13 @@ next_catch(Process* c_p, Eterm *reg) { } have_return_to_trace = !0; /* Record next cp */ return_to_trace_ptr = NULL; + } else if (*cp_val(*prev) == i_return_time_trace) { + /* Skip stack frame variables */ + while (++ptr, ptr < STACK_START(c_p) && is_not_CP(*ptr)) { + if (is_catch(*ptr) && active_catches) goto found_catch; + } + /* Skip return_trace parameters */ + ptr += 1; } else { if (have_return_to_trace) { /* Record this cp as possible return_to trace cp */ diff --git a/erts/emulator/beam/bif.c b/erts/emulator/beam/bif.c index 7a69dc6eff..506bf383ca 100644 --- a/erts/emulator/beam/bif.c +++ b/erts/emulator/beam/bif.c @@ -4171,54 +4171,6 @@ void erts_init_bif(void) await_proc_exit_trap = erts_export_put(am_erlang,am_await_proc_exit,3); } -BIF_RETTYPE blocking_read_file_1(BIF_ALIST_1) -{ - Eterm bin; - Eterm* hp; - byte *buff; - int i, buff_size; - FILE *file; - struct stat file_info; - char *filename = NULL; - size_t size; - - i = list_length(BIF_ARG_1); - if (i < 0) { - BIF_ERROR(BIF_P, BADARG); - } - filename = erts_alloc(ERTS_ALC_T_TMP, i + 1); - if (intlist_to_buf(BIF_ARG_1, filename, i) != i) - erl_exit(1, "%s:%d: Internal error\n", __FILE__, __LINE__); - filename[i] = '\0'; - - hp = HAlloc(BIF_P, 3); - - file = fopen(filename, "r"); - if(file == NULL){ - erts_free(ERTS_ALC_T_TMP, (void *) filename); - BIF_RET(TUPLE2(hp, am_error, am_nofile)); - } - - stat(filename, &file_info); - erts_free(ERTS_ALC_T_TMP, (void *) filename); - - buff_size = file_info.st_size; - buff = (byte *) erts_alloc_fnf(ERTS_ALC_T_TMP, buff_size); - if (!buff) { - fclose(file); - BIF_RET(TUPLE2(hp, am_error, am_allocator)); - } - size = fread(buff, 1, buff_size, file); - fclose(file); - if (size < 0) - size = 0; - else if (size > buff_size) - size = (size_t) buff_size; - bin = new_binary(BIF_P, buff, (int) size); - erts_free(ERTS_ALC_T_TMP, (void *) buff); - - BIF_RET(TUPLE2(hp, am_ok, bin)); -} #ifdef HARDDEBUG /* You'll need this line in bif.tab to be able to use this debug bif diff --git a/erts/emulator/beam/bif.tab b/erts/emulator/beam/bif.tab index e4713567de..0674aae77f 100644 --- a/erts/emulator/beam/bif.tab +++ b/erts/emulator/beam/bif.tab @@ -686,8 +686,6 @@ bif 'erl.system.code':make_stub_module/3 ebif_code_make_stub_module_3 bif code:is_module_native/1 bif 'erl.system.code':is_native/1 ebif_code_is_native_1 code_is_module_native_1 -bif erlang:blocking_read_file/1 - # # New Bifs in R9C. # diff --git a/erts/emulator/beam/break.c b/erts/emulator/beam/break.c index 5cb1481a3a..857cb177c8 100644 --- a/erts/emulator/beam/break.c +++ b/erts/emulator/beam/break.c @@ -258,12 +258,10 @@ print_process_info(int to, void *to_arg, Process *p) } { - long s = 0; int frags = 0; ErlHeapFragment *m = p->mbuf; while (m != NULL) { frags++; - s += m->size; m = m->next; } erts_print(to, to_arg, "Number of heap fragments: %d\n", frags); diff --git a/erts/emulator/beam/copy.c b/erts/emulator/beam/copy.c index 0a5050b1fe..521a1b1788 100644 --- a/erts/emulator/beam/copy.c +++ b/erts/emulator/beam/copy.c @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 1996-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 1996-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ @@ -37,6 +37,8 @@ MA_STACK_DECLARE(dst); MA_STACK_DECLARE(offset); #endif +static void move_one_frag(Eterm** hpp, Eterm* src, Uint src_sz, ErlOffHeap*); + void init_copy(void) { @@ -86,7 +88,7 @@ size_object(Eterm obj) obj = *ptr++; if (!IS_CONST(obj)) { ESTACK_PUSH(s, obj); - } + } obj = *ptr; break; case TAG_PRIMARY_BOXED: @@ -99,7 +101,7 @@ size_object(Eterm obj) arity = header_arity(hdr); sum += arity + 1; if (arity == 0) { /* Empty tuple -- unusual. */ - goto size_common; + goto pop_next; } while (arity-- > 1) { obj = *++ptr; @@ -115,7 +117,6 @@ size_object(Eterm obj) ErlFunThing* funp = (ErlFunThing *) bptr; unsigned eterms = 1 /* creator */ + funp->num_free; unsigned sz = thing_arityval(hdr); - sum += 1 /* header */ + sz + eterms; bptr += 1 /* header */ + sz; while (eterms-- > 1) { @@ -151,7 +152,7 @@ size_object(Eterm obj) } else { sum += heap_bin_size(binary_size(obj)+extra_bytes); } - goto size_common; + goto pop_next; } break; case BIN_MATCHSTATE_SUBTAG: @@ -159,18 +160,12 @@ size_object(Eterm obj) "size_object: matchstate term not allowed"); default: sum += thing_arityval(hdr) + 1; - /* Fall through */ - size_common: - if (ESTACK_ISEMPTY(s)) { - DESTROY_ESTACK(s); - return sum; - } - obj = ESTACK_POP(s); - break; + goto pop_next; } } break; case TAG_PRIMARY_IMMED1: + pop_next: if (ESTACK_ISEMPTY(s)) { DESTROY_ESTACK(s); return sum; @@ -979,3 +974,104 @@ copy_shallow(Eterm* ptr, Uint sz, Eterm** hpp, ErlOffHeap* off_heap) *hpp = hp; return make_tuple(ptr + offs); } + +/* Move all terms in heap fragments into heap. The terms must be guaranteed to + * be contained within the fragments. The source terms are destructed with + * move markers. + * Typically used to copy a multi-fragmented message (from NIF). + */ +void move_multi_frags(Eterm** hpp, ErlOffHeap* off_heap, ErlHeapFragment* first, + Eterm* refs, unsigned nrefs) +{ + ErlHeapFragment* bp; + Eterm* hp_start = *hpp; + Eterm* hp_end; + Eterm* hp; + unsigned i; + + for (bp=first; bp!=NULL; bp=bp->next) { + move_one_frag(hpp, bp->mem, bp->used_size, off_heap); + off_heap->overhead += bp->off_heap.overhead; + } + hp_end = *hpp; + for (hp=hp_start; hp<hp_end; ++hp) { + Eterm* ptr; + Eterm val; + Eterm gval = *hp; + switch (primary_tag(gval)) { + case TAG_PRIMARY_BOXED: + ptr = boxed_val(gval); + val = *ptr; + if (IS_MOVED_BOXED(val)) { + ASSERT(is_boxed(val)); + *hp = val; + } + break; + case TAG_PRIMARY_LIST: + ptr = list_val(gval); + val = *ptr; + if (IS_MOVED_CONS(val)) { + *hp = ptr[1]; + } + break; + case TAG_PRIMARY_HEADER: + if (header_is_thing(gval)) { + hp += thing_arityval(gval); + } + break; + } + } + for (i=0; i<nrefs; ++i) { + refs[i] = follow_moved(refs[i]); + } +} + +static void +move_one_frag(Eterm** hpp, Eterm* src, Uint src_sz, ErlOffHeap* off_heap) +{ + union { + Uint *up; + ProcBin *pbp; + ErlFunThing *efp; + ExternalThing *etp; + } ohe; + Eterm* ptr = src; + Eterm* end = ptr + src_sz; + Eterm dummy_ref; + Eterm* hp = *hpp; + + while (ptr != end) { + Eterm val; + ASSERT(ptr < end); + val = *ptr; + ASSERT(val != ERTS_HOLE_MARKER); + if (is_header(val)) { + ASSERT(ptr + header_arity(val) < end); + ohe.up = hp; + MOVE_BOXED(ptr, val, hp, &dummy_ref); + switch (val & _HEADER_SUBTAG_MASK) { + case REFC_BINARY_SUBTAG: + ohe.pbp->next = off_heap->mso; + off_heap->mso = ohe.pbp; + break; + case FUN_SUBTAG: + ohe.efp->next = off_heap->funs; + off_heap->funs = ohe.efp; + break; + case EXTERNAL_PID_SUBTAG: + case EXTERNAL_PORT_SUBTAG: + case EXTERNAL_REF_SUBTAG: + ohe.etp->next = off_heap->externals; + off_heap->externals = ohe.etp; + break; + } + } + else { /* must be a cons cell */ + ASSERT(ptr+1 < end); + MOVE_CONS(ptr, val, hp, &dummy_ref); + ptr += 2; + } + } + *hpp = hp; +} + diff --git a/erts/emulator/beam/erl_alloc.c b/erts/emulator/beam/erl_alloc.c index f8823b85fe..16ae643ed9 100644 --- a/erts/emulator/beam/erl_alloc.c +++ b/erts/emulator/beam/erl_alloc.c @@ -2897,17 +2897,10 @@ unsigned long erts_alc_test(unsigned long op, } case 0xf10: { ethr_tid *tid = erts_alloc(ERTS_ALC_T_UNDEF, sizeof(ethr_tid)); -#ifdef ERTS_ENABLE_LOCK_COUNT - if (erts_lcnt_thr_create(tid, - (void * (*)(void *)) a1, - (void *) a2, - NULL) != 0) -#else if (ethr_thr_create(tid, (void * (*)(void *)) a1, (void *) a2, NULL) != 0) -#endif ERTS_ALC_TEST_ABORT; return (unsigned long) tid; } diff --git a/erts/emulator/beam/erl_bif_info.c b/erts/emulator/beam/erl_bif_info.c index de60ca49fa..48cda52612 100644 --- a/erts/emulator/beam/erl_bif_info.c +++ b/erts/emulator/beam/erl_bif_info.c @@ -1140,9 +1140,9 @@ process_info_aux(Process *BIF_P, } else { /* Make our copy of the message */ - ASSERT(size_object(msg) == hfp->size); + ASSERT(size_object(msg) == hfp->used_size); msg = copy_struct(msg, - hfp->size, + hfp->used_size, &hp, &MSO(BIF_P)); } @@ -1892,6 +1892,37 @@ c_compiler_used(Eterm **hpp, Uint *szp) } +static int is_snif_term(Eterm module_atom) { + int i; + Atom *a = atom_tab(atom_val(module_atom)); + char *aname = (char *) a->name; + + /* if a->name has a '.' then the bif (snif) is bogus i.e a package */ + for (i = 0; i < a->len; i++) { + if (aname[i] == '.') + return 0; + } + + return 1; +} + +static Eterm build_snif_term(Eterm **hpp, Uint *szp, int ix, Eterm res) { + Eterm tup; + tup = erts_bld_tuple(hpp, szp, 3, bif_table[ix].module, bif_table[ix].name, make_small(bif_table[ix].arity)); + res = erts_bld_cons( hpp, szp, tup, res); + return res; +} + +static Eterm build_snifs_term(Eterm **hpp, Uint *szp, Eterm res) { + int i; + for (i = 0; i < BIF_SIZE; i++) { + if (is_snif_term(bif_table[i].module)) { + res = build_snif_term(hpp, szp, i, res); + } + } + return res; +} + BIF_RETTYPE system_info_1(BIF_ALIST_1) { Eterm res; @@ -1940,6 +1971,15 @@ BIF_RETTYPE system_info_1(BIF_ALIST_1) BIF_RET(db_get_trace_control_word_0(BIF_P)); } else if (ERTS_IS_ATOM_STR("ets_realloc_moves", BIF_ARG_1)) { BIF_RET((erts_ets_realloc_always_moves) ? am_true : am_false); + } else if (ERTS_IS_ATOM_STR("snifs", BIF_ARG_1)) { + Uint size = 0; + Uint *szp; + + szp = &size; + build_snifs_term(NULL, szp, NIL); + hp = HAlloc(BIF_P, size); + res = build_snifs_term(&hp, NULL, NIL); + BIF_RET(res); } else if (BIF_ARG_1 == am_sequential_tracer) { val = erts_get_system_seq_tracer(); ASSERT(is_internal_pid(val) || is_internal_port(val) || val==am_false) @@ -2715,7 +2755,7 @@ BIF_RETTYPE port_info_2(BIF_ALIST_2) erts_doforall_links(prt->nlinks, &one_link_size, &size); for (bp = prt->bp; bp; bp = bp->next) - size += sizeof(ErlHeapFragment) + (bp->size - 1)*sizeof(Eterm); + size += sizeof(ErlHeapFragment) + (bp->alloc_size - 1)*sizeof(Eterm); if (prt->linebuf) size += sizeof(LineBuf) + prt->linebuf->ovsiz; diff --git a/erts/emulator/beam/erl_bif_port.c b/erts/emulator/beam/erl_bif_port.c index 9b56ddd4f8..378c5e73fd 100644 --- a/erts/emulator/beam/erl_bif_port.c +++ b/erts/emulator/beam/erl_bif_port.c @@ -579,8 +579,8 @@ BIF_RETTYPE port_get_data_1(BIF_ALIST_1) if (prt->bp == NULL) { /* MUST be CONST! */ res = prt->data; } else { - Eterm* hp = HAlloc(BIF_P, prt->bp->size); - res = copy_struct(prt->data, prt->bp->size, &hp, &MSO(BIF_P)); + Eterm* hp = HAlloc(BIF_P, prt->bp->used_size); + res = copy_struct(prt->data, prt->bp->used_size, &hp, &MSO(BIF_P)); } erts_smp_port_unlock(prt); BIF_RET(res); diff --git a/erts/emulator/beam/erl_bif_timer.c b/erts/emulator/beam/erl_bif_timer.c index eb40c75110..4ae2f6ebf4 100644 --- a/erts/emulator/beam/erl_bif_timer.c +++ b/erts/emulator/beam/erl_bif_timer.c @@ -357,7 +357,7 @@ bif_timer_timeout(ErtsBifTimer* btm) rp, &rp_locks); } else { - Eterm old_size = bp->size; + Eterm old_size = bp->used_size; bp = erts_resize_message_buffer(bp, old_size + wrap_size, &message, 1); hp = &bp->mem[0] + old_size; diff --git a/erts/emulator/beam/erl_bif_trace.c b/erts/emulator/beam/erl_bif_trace.c index 7fe4410e0d..443cac9033 100644 --- a/erts/emulator/beam/erl_bif_trace.c +++ b/erts/emulator/beam/erl_bif_trace.c @@ -40,8 +40,7 @@ #define DECL_AM(S) Eterm AM_ ## S = am_atom_put(#S, sizeof(#S) - 1) -static erts_smp_mtx_t trace_pattern_mutex; -const struct trace_pattern_flags erts_trace_pattern_flags_off = {0, 0, 0, 0}; +const struct trace_pattern_flags erts_trace_pattern_flags_off = {0, 0, 0, 0, 0}; static int erts_default_trace_pattern_is_on; static Binary *erts_default_match_spec; static Binary *erts_default_meta_match_spec; @@ -65,7 +64,6 @@ static void clear_trace_bif(int bif_index); void erts_bif_trace_init(void) { - erts_smp_mtx_init(&trace_pattern_mutex, "trace_pattern"); erts_default_trace_pattern_is_on = 0; erts_default_match_spec = NULL; erts_default_meta_match_spec = NULL; @@ -186,6 +184,14 @@ trace_pattern_3(Process* p, Eterm MFA, Eterm Pattern, Eterm flaglist) flags.breakpoint = 1; flags.call_count = 1; break; + case am_call_time: + if (is_global) { + goto error; + } + flags.breakpoint = 1; + flags.call_time = 1; + break; + default: goto error; } @@ -195,8 +201,8 @@ trace_pattern_3(Process* p, Eterm MFA, Eterm Pattern, Eterm flaglist) goto error; } - if (match_prog_set && !flags.local && !flags.meta && flags.call_count) { - /* A match prog is not allowed with just call_count */ + if (match_prog_set && !flags.local && !flags.meta && (flags.call_count || flags.call_time)) { + /* A match prog is not allowed with just call_count or call_time*/ goto error; } @@ -235,6 +241,8 @@ trace_pattern_3(Process* p, Eterm MFA, Eterm Pattern, Eterm flaglist) |= flags.meta; erts_default_trace_pattern_flags.call_count |= (on == 1) ? flags.call_count : 0; + erts_default_trace_pattern_flags.call_time + |= (on == 1) ? flags.call_time : 0; } else { erts_default_trace_pattern_flags.local &= ~flags.local; @@ -242,10 +250,13 @@ trace_pattern_3(Process* p, Eterm MFA, Eterm Pattern, Eterm flaglist) &= ~flags.meta; erts_default_trace_pattern_flags.call_count &= ~flags.call_count; + erts_default_trace_pattern_flags.call_time + &= ~flags.call_time; if (! (erts_default_trace_pattern_flags.breakpoint = erts_default_trace_pattern_flags.local | erts_default_trace_pattern_flags.meta | - erts_default_trace_pattern_flags.call_count)) { + erts_default_trace_pattern_flags.call_count | + erts_default_trace_pattern_flags.call_time)) { erts_default_trace_pattern_is_on = !!on; /* i.e off */ } } @@ -267,8 +278,9 @@ trace_pattern_3(Process* p, Eterm MFA, Eterm Pattern, Eterm flaglist) if (on) { if (on != 1) { flags.call_count = 0; + flags.call_time = 0; } - flags.breakpoint = flags.local | flags.meta | flags.call_count; + flags.breakpoint = flags.local | flags.meta | flags.call_count | flags.call_time; erts_default_trace_pattern_flags = flags; /* Struct copy */ erts_default_trace_pattern_is_on = !!flags.breakpoint; } @@ -336,7 +348,6 @@ erts_get_default_trace_pattern(int *trace_pattern_is_on, struct trace_pattern_flags *trace_pattern_flags, Eterm *meta_tracer_pid) { - erts_smp_mtx_lock(&trace_pattern_mutex); if (trace_pattern_is_on) *trace_pattern_is_on = erts_default_trace_pattern_is_on; if (match_spec) @@ -347,12 +358,10 @@ erts_get_default_trace_pattern(int *trace_pattern_is_on, *trace_pattern_flags = erts_default_trace_pattern_flags; if (meta_tracer_pid) *meta_tracer_pid = erts_default_meta_tracer_pid; - erts_smp_mtx_unlock(&trace_pattern_mutex); } - Uint erts_trace_flag2bit(Eterm flag) { @@ -380,7 +389,7 @@ erts_trace_flag2bit(Eterm flag) default: return 0; } } - + /* Scan the argument list and sort out the trace flags. ** ** Returns !0 on success, 0 on failure. @@ -931,6 +940,7 @@ trace_info_pid(Process* p, Eterm pid_spec, Eterm key) #define FUNC_TRACE_LOCAL_TRACE (1<<2) #define FUNC_TRACE_META_TRACE (1<<3) #define FUNC_TRACE_COUNT_TRACE (1<<4) +#define FUNC_TRACE_TIME_TRACE (1<<5) /* * Returns either FUNC_TRACE_NOEXIST, FUNC_TRACE_UNTRACED, * FUNC_TRACE_GLOBAL_TRACE, or, @@ -945,11 +955,13 @@ trace_info_pid(Process* p, Eterm pid_spec, Eterm key) * * If the return value contains FUNC_TRACE_COUNT_TRACE, *count is set. */ -static int function_is_traced(Eterm mfa[3], - Binary **ms, /* out */ - Binary **ms_meta, /* out */ +static int function_is_traced(Process *p, + Eterm mfa[3], + Binary **ms, /* out */ + Binary **ms_meta, /* out */ Eterm *tracer_pid_meta, /* out */ - Sint *count) /* out */ + Sint *count, /* out */ + Eterm *call_time) /* out */ { Export e; Export* ep; @@ -980,10 +992,13 @@ static int function_is_traced(Eterm mfa[3], r |= FUNC_TRACE_LOCAL_TRACE; *ms = ep->match_prog_set; } - if (erts_is_mtrace_bif(ep->code+3, ms_meta, + if (erts_is_mtrace_break(ep->code+3, ms_meta, tracer_pid_meta)) { r |= FUNC_TRACE_META_TRACE; } + if (erts_is_time_break(p, ep->code+3, call_time)) { + r |= FUNC_TRACE_TIME_TRACE; + } } return r ? r : FUNC_TRACE_UNTRACED; } @@ -1001,7 +1016,9 @@ static int function_is_traced(Eterm mfa[3], | (erts_is_mtrace_break(code, ms_meta, tracer_pid_meta) ? FUNC_TRACE_META_TRACE : 0) | (erts_is_count_break(code, count) - ? FUNC_TRACE_COUNT_TRACE : 0); + ? FUNC_TRACE_COUNT_TRACE : 0) + | (erts_is_time_break(p, code, call_time) + ? FUNC_TRACE_TIME_TRACE : 0); return r ? r : FUNC_TRACE_UNTRACED; } @@ -1020,6 +1037,7 @@ trace_info_func(Process* p, Eterm func_spec, Eterm key) Eterm match_spec = am_false; Eterm retval = am_false; Eterm meta = am_false; + Eterm call_time = NIL; int r; @@ -1039,7 +1057,22 @@ trace_info_func(Process* p, Eterm func_spec, Eterm key) mfa[1] = tp[2]; mfa[2] = signed_val(tp[3]); - r = function_is_traced(mfa, &ms, &ms_meta, &meta, &count); +#ifdef ERTS_SMP + if ( (key == am_call_time) || (key == am_all)) { + erts_smp_proc_unlock(p, ERTS_PROC_LOCK_MAIN); + erts_smp_block_system(0); + } +#endif + + r = function_is_traced(p, mfa, &ms, &ms_meta, &meta, &count, &call_time); + +#ifdef ERTS_SMP + if ( (key == am_call_time) || (key == am_all)) { + erts_smp_release_system(); + erts_smp_proc_lock(p, ERTS_PROC_LOCK_MAIN); + } +#endif + switch (r) { case FUNC_TRACE_NOEXIST: UnUseTmpHeap(3,p); @@ -1092,8 +1125,13 @@ trace_info_func(Process* p, Eterm func_spec, Eterm key) erts_make_integer(count, p); } break; + case am_call_time: + if (r & FUNC_TRACE_TIME_TRACE) { + retval = call_time; + } + break; case am_all: { - Eterm match_spec_meta = am_false, c = am_false, t; + Eterm match_spec_meta = am_false, c = am_false, t, ct = am_false; if (ms) { match_spec = MatchSetGetSource(ms); @@ -1111,10 +1149,15 @@ trace_info_func(Process* p, Eterm func_spec, Eterm key) erts_make_integer(-count-1, p) : erts_make_integer(count, p); } - hp = HAlloc(p, (3+2)*5); + if (r & FUNC_TRACE_TIME_TRACE) { + ct = call_time; + } + hp = HAlloc(p, (3+2)*6); retval = NIL; t = TUPLE2(hp, am_call_count, c); hp += 3; retval = CONS(hp, t, retval); hp += 2; + t = TUPLE2(hp, am_call_time, ct); hp += 3; + retval = CONS(hp, t, retval); hp += 2; t = TUPLE2(hp, am_meta_match_spec, match_spec_meta); hp += 3; retval = CONS(hp, t, retval); hp += 2; t = TUPLE2(hp, am_meta, meta); hp += 3; @@ -1210,6 +1253,13 @@ trace_info_on_load(Process* p, Eterm key) } else { return TUPLE2(hp, key, am_false); } + case am_call_time: + hp = HAlloc(p, 3); + if (erts_default_trace_pattern_flags.call_time) { + return TUPLE2(hp, key, am_true); + } else { + return TUPLE2(hp, key, am_false); + } case am_all: { Eterm match_spec = am_false, meta_match_spec = am_false, r = NIL, t; @@ -1284,6 +1334,7 @@ erts_set_trace_pattern(Eterm* mfa, int specified, for (j = 0; j < specified && mfa[j] == ep->code[j]; j++) { /* Empty loop body */ } + if (j == specified) { if (on) { if (! flags.breakpoint) @@ -1356,6 +1407,12 @@ erts_set_trace_pattern(Eterm* mfa, int specified, erts_bif_trace_flags[i] &= ~BIF_TRACE_AS_GLOBAL; m = 1; } + if (flags.call_time) { + erts_set_time_trace_bif(bif_export[i]->code + 3, on); + /* I don't want to remove any other tracers */ + erts_bif_trace_flags[i] |= BIF_TRACE_AS_CALL_TIME; + m = 1; + } if (erts_bif_trace_flags[i]) { setup_bif_trace(i); } @@ -1375,6 +1432,11 @@ erts_set_trace_pattern(Eterm* mfa, int specified, } m = 1; } + if (flags.call_time) { + erts_clear_time_trace_bif(bif_export[i]->code + 3); + erts_bif_trace_flags[i] &= ~BIF_TRACE_AS_CALL_TIME; + m = 1; + } if (! erts_bif_trace_flags[i]) { reset_bif_trace(i); } @@ -1392,6 +1454,7 @@ erts_set_trace_pattern(Eterm* mfa, int specified, erts_clear_trace_break(mfa, specified); erts_clear_mtrace_break(mfa, specified); erts_clear_count_break(mfa, specified); + erts_clear_time_break(mfa, specified); } else { int m = 0; if (flags.local) { @@ -1405,6 +1468,9 @@ erts_set_trace_pattern(Eterm* mfa, int specified, if (flags.call_count) { m = erts_set_count_break(mfa, specified, on); } + if (flags.call_time) { + m = erts_set_time_break(mfa, specified, on); + } /* All assignments to 'm' above should give the same value, * so just use the last */ matches += m; @@ -1420,6 +1486,9 @@ erts_set_trace_pattern(Eterm* mfa, int specified, if (flags.call_count) { m = erts_clear_count_break(mfa, specified); } + if (flags.call_time) { + m = erts_clear_time_break(mfa, specified); + } /* All assignments to 'm' above should give the same value, * so just use the last */ matches += m; @@ -1536,7 +1605,7 @@ static void reset_bif_trace(int bif_index) { ASSERT(ExportIsBuiltIn(ep)); ASSERT(ep->code[4]); ASSERT(! ep->match_prog_set); - ASSERT(! erts_is_mtrace_bif((BeamInstr *)ep->code+3, NULL, NULL)); + ASSERT(! erts_is_mtrace_break((BeamInstr *)ep->code+3, NULL, NULL)); ep->code[4] = (BeamInstr) bif_table[bif_index].f; } diff --git a/erts/emulator/beam/erl_db_util.c b/erts/emulator/beam/erl_db_util.c index fd7de98ac9..93a47eb76f 100644 --- a/erts/emulator/beam/erl_db_util.c +++ b/erts/emulator/beam/erl_db_util.c @@ -1547,10 +1547,9 @@ restart: */ context.save = NULL; error: /* Here is were we land when compilation failed. */ - while (context.save != NULL) { - ErlHeapFragment *ll = context.save->next; + if (context.save != NULL) { free_message_buffer(context.save); - context.save = ll; + context.save = NULL; } DMC_FREE(stack); DMC_FREE(text); @@ -1567,15 +1566,11 @@ error: /* Here is were we land when compilation failed. */ void erts_db_match_prog_destructor(Binary *bprog) { MatchProg *prog; - ErlHeapFragment *tmp, *ll; if (bprog == NULL) return; prog = Binary2MatchProg(bprog); - tmp = prog->term_save; - while (tmp != NULL) { - ll = tmp->next; - free_message_buffer(tmp); - tmp = ll; + if (prog->term_save != NULL) { + free_message_buffer(prog->term_save); } if (prog->saved_program_buf != NULL) free_message_buffer(prog->saved_program_buf); @@ -4125,7 +4120,7 @@ static int match_compact(ErlHeapFragment *expr, DMCErrInfo *err_info) DMC_INIT_STACK(heap); p = expr->mem; - i = expr->size; + i = expr->used_size; while (i--) { if (is_thing(*p)) { a = thing_arityval(*p); @@ -4154,7 +4149,7 @@ static int match_compact(ErlHeapFragment *expr, DMCErrInfo *err_info) } p = expr->mem; - i = expr->size; + i = expr->used_size; while (i--) { if (is_thing(*p)) { a = thing_arityval(*p); diff --git a/erts/emulator/beam/erl_debug.c b/erts/emulator/beam/erl_debug.c index 58d3f92f56..d7d6fcf0a2 100644 --- a/erts/emulator/beam/erl_debug.c +++ b/erts/emulator/beam/erl_debug.c @@ -261,7 +261,7 @@ static int verify_eterm(Process *p,Eterm element) return 1; for (mbuf = p->mbuf; mbuf; mbuf = mbuf->next) { - if (WITHIN(ptr, &mbuf->mem[0], &mbuf->mem[0] + mbuf->size)) { + if (WITHIN(ptr, &mbuf->mem[0], &mbuf->mem[0] + mbuf->used_size)) { return 1; } } @@ -308,7 +308,7 @@ void erts_check_stack(Process *p) if (IN_HEAP(p, ptr)) continue; for (mbuf = p->mbuf; mbuf; mbuf = mbuf->next) - if (WITHIN(ptr, &mbuf->mem[0], &mbuf->mem[0] + mbuf->size)) { + if (WITHIN(ptr, &mbuf->mem[0], &mbuf->mem[0] + mbuf->used_size)) { in_mbuf = 1; break; } @@ -746,7 +746,7 @@ static void print_process_memory(Process *p) PTR_SIZE, "heap fragments", dashes, dashes, dashes, dashes); while (bp) { - print_untagged_memory(bp->mem,bp->mem + bp->size); + print_untagged_memory(bp->mem,bp->mem + bp->used_size); bp = bp->next; } } diff --git a/erts/emulator/beam/erl_drv_thread.c b/erts/emulator/beam/erl_drv_thread.c index 50d8c25c46..aa37edafd1 100644 --- a/erts/emulator/beam/erl_drv_thread.c +++ b/erts/emulator/beam/erl_drv_thread.c @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 2007-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 2007-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ @@ -603,11 +603,7 @@ erl_drv_thread_create(char *name, dtid->name = ((char *) dtid) + sizeof(struct ErlDrvTid_); sys_strcpy(dtid->name, name); } -#ifdef ERTS_ENABLE_LOCK_COUNT - res = erts_lcnt_thr_create(&dtid->tid, erl_drv_thread_wrapper, dtid, use_opts); -#else res = ethr_thr_create(&dtid->tid, erl_drv_thread_wrapper, dtid, use_opts); -#endif if (res != 0) { erts_free(ERTS_ALC_T_DRV_TID, dtid); diff --git a/erts/emulator/beam/erl_gc.c b/erts/emulator/beam/erl_gc.c index 9ed566e66e..a19e090f1e 100644 --- a/erts/emulator/beam/erl_gc.c +++ b/erts/emulator/beam/erl_gc.c @@ -667,7 +667,7 @@ erts_garbage_collect_literals(Process* p, Eterm* literals, Uint lit_size) case TAG_PRIMARY_BOXED: ptr = boxed_val(gval); val = *ptr; - if (IS_MOVED(val)) { + if (IS_MOVED_BOXED(val)) { ASSERT(is_boxed(val)); *g_ptr++ = val; } else if (in_area(ptr, area, area_size)) { @@ -679,7 +679,7 @@ erts_garbage_collect_literals(Process* p, Eterm* literals, Uint lit_size) case TAG_PRIMARY_LIST: ptr = list_val(gval); val = *ptr; - if (is_non_value(val)) { /* Moved */ + if (IS_MOVED_CONS(val)) { /* Moved */ *g_ptr++ = ptr[1]; } else if (in_area(ptr, area, area_size)) { MOVE_CONS(ptr,val,old_htop,g_ptr++); @@ -913,7 +913,7 @@ do_minor(Process *p, int new_sz, Eterm* objv, int nobj) case TAG_PRIMARY_BOXED: { ptr = boxed_val(gval); val = *ptr; - if (IS_MOVED(val)) { + if (IS_MOVED_BOXED(val)) { ASSERT(is_boxed(val)); *g_ptr++ = val; } else if (in_area(ptr, heap, mature_size)) { @@ -929,7 +929,7 @@ do_minor(Process *p, int new_sz, Eterm* objv, int nobj) case TAG_PRIMARY_LIST: { ptr = list_val(gval); val = *ptr; - if (is_non_value(val)) { /* Moved */ + if (IS_MOVED_CONS(val)) { /* Moved */ *g_ptr++ = ptr[1]; } else if (in_area(ptr, heap, mature_size)) { MOVE_CONS(ptr,val,old_htop,g_ptr++); @@ -972,7 +972,7 @@ do_minor(Process *p, int new_sz, Eterm* objv, int nobj) case TAG_PRIMARY_BOXED: { ptr = boxed_val(gval); val = *ptr; - if (IS_MOVED(val)) { + if (IS_MOVED_BOXED(val)) { ASSERT(is_boxed(val)); *n_hp++ = val; } else if (in_area(ptr, heap, mature_size)) { @@ -987,7 +987,7 @@ do_minor(Process *p, int new_sz, Eterm* objv, int nobj) case TAG_PRIMARY_LIST: { ptr = list_val(gval); val = *ptr; - if (is_non_value(val)) { + if (IS_MOVED_CONS(val)) { *n_hp++ = ptr[1]; } else if (in_area(ptr, heap, mature_size)) { MOVE_CONS(ptr,val,old_htop,n_hp++); @@ -1008,7 +1008,7 @@ do_minor(Process *p, int new_sz, Eterm* objv, int nobj) Eterm* origptr = &(mb->orig); ptr = boxed_val(*origptr); val = *ptr; - if (IS_MOVED(val)) { + if (IS_MOVED_BOXED(val)) { *origptr = val; mb->base = binary_bytes(val); } else if (in_area(ptr, heap, mature_size)) { @@ -1161,7 +1161,7 @@ major_collection(Process* p, int need, Eterm* objv, int nobj, Uint *recl) case TAG_PRIMARY_BOXED: { ptr = boxed_val(gval); val = *ptr; - if (IS_MOVED(val)) { + if (IS_MOVED_BOXED(val)) { ASSERT(is_boxed(val)); *g_ptr++ = val; } else if (in_area(ptr, src, src_size) || in_area(ptr, oh, oh_size)) { @@ -1175,7 +1175,7 @@ major_collection(Process* p, int need, Eterm* objv, int nobj, Uint *recl) case TAG_PRIMARY_LIST: { ptr = list_val(gval); val = *ptr; - if (is_non_value(val)) { + if (IS_MOVED_CONS(val)) { *g_ptr++ = ptr[1]; } else if (in_area(ptr, src, src_size) || in_area(ptr, oh, oh_size)) { MOVE_CONS(ptr,val,n_htop,g_ptr++); @@ -1216,7 +1216,7 @@ major_collection(Process* p, int need, Eterm* objv, int nobj, Uint *recl) case TAG_PRIMARY_BOXED: { ptr = boxed_val(gval); val = *ptr; - if (IS_MOVED(val)) { + if (IS_MOVED_BOXED(val)) { ASSERT(is_boxed(val)); *n_hp++ = val; } else if (in_area(ptr, src, src_size) || in_area(ptr, oh, oh_size)) { @@ -1229,7 +1229,7 @@ major_collection(Process* p, int need, Eterm* objv, int nobj, Uint *recl) case TAG_PRIMARY_LIST: { ptr = list_val(gval); val = *ptr; - if (is_non_value(val)) { + if (IS_MOVED_CONS(val)) { *n_hp++ = ptr[1]; } else if (in_area(ptr, src, src_size) || in_area(ptr, oh, oh_size)) { MOVE_CONS(ptr,val,n_htop,n_hp++); @@ -1249,7 +1249,7 @@ major_collection(Process* p, int need, Eterm* objv, int nobj, Uint *recl) origptr = &(mb->orig); ptr = boxed_val(*origptr); val = *ptr; - if (IS_MOVED(val)) { + if (IS_MOVED_BOXED(val)) { *origptr = val; mb->base = binary_bytes(*origptr); } else if (in_area(ptr, src, src_size) || @@ -1392,17 +1392,12 @@ combined_message_size(Process* p) static void remove_message_buffers(Process* p) { - ErlHeapFragment* bp = MBUF(p); - - MBUF(p) = NULL; - MBUF_SIZE(p) = 0; - while (bp != NULL) { - ErlHeapFragment* next_bp = bp->next; - free_message_buffer(bp); - bp = next_bp; - } + if (MBUF(p) != NULL) { + free_message_buffer(MBUF(p)); + MBUF(p) = NULL; + } + MBUF_SIZE(p) = 0; } - #ifdef HARDDEBUG /* @@ -1433,12 +1428,12 @@ disallow_heap_frag_ref(Process* p, Eterm* n_htop, Eterm* objv, int nobj) case TAG_PRIMARY_BOXED: { ptr = _unchecked_boxed_val(gval); val = *ptr; - if (IS_MOVED(val)) { + if (IS_MOVED_BOXED(val)) { ASSERT(is_boxed(val)); objv++; } else { for (qb = mbuf; qb != NULL; qb = qb->next) { - if (in_area(ptr, qb->mem, qb->size*sizeof(Eterm))) { + if (in_area(ptr, qb->mem, qb->alloc_size*sizeof(Eterm))) { abort(); } } @@ -1450,11 +1445,11 @@ disallow_heap_frag_ref(Process* p, Eterm* n_htop, Eterm* objv, int nobj) case TAG_PRIMARY_LIST: { ptr = _unchecked_list_val(gval); val = *ptr; - if (is_non_value(val)) { + if (IS_MOVED_CONS(val)) { objv++; } else { for (qb = mbuf; qb != NULL; qb = qb->next) { - if (in_area(ptr, qb->mem, qb->size*sizeof(Eterm))) { + if (in_area(ptr, qb->mem, qb->alloc_size*sizeof(Eterm))) { abort(); } } @@ -1499,7 +1494,7 @@ disallow_heap_frag_ref_in_heap(Process* p) ptr = _unchecked_boxed_val(val); if (!in_area(ptr, heap, heap_size)) { for (qb = MBUF(p); qb != NULL; qb = qb->next) { - if (in_area(ptr, qb->mem, qb->size*sizeof(Eterm))) { + if (in_area(ptr, qb->mem, qb->alloc_size*sizeof(Eterm))) { abort(); } } @@ -1509,7 +1504,7 @@ disallow_heap_frag_ref_in_heap(Process* p) ptr = _unchecked_list_val(val); if (!in_area(ptr, heap, heap_size)) { for (qb = MBUF(p); qb != NULL; qb = qb->next) { - if (in_area(ptr, qb->mem, qb->size*sizeof(Eterm))) { + if (in_area(ptr, qb->mem, qb->alloc_size*sizeof(Eterm))) { abort(); } } @@ -1557,7 +1552,7 @@ disallow_heap_frag_ref_in_old_heap(Process* p) abort(); } for (qb = MBUF(p); qb != NULL; qb = qb->next) { - if (in_area(ptr, qb->mem, qb->size*sizeof(Eterm))) { + if (in_area(ptr, qb->mem, qb->alloc_size*sizeof(Eterm))) { abort(); } } @@ -1570,7 +1565,7 @@ disallow_heap_frag_ref_in_old_heap(Process* p) abort(); } for (qb = MBUF(p); qb != NULL; qb = qb->next) { - if (in_area(ptr, qb->mem, qb->size*sizeof(Eterm))) { + if (in_area(ptr, qb->mem, qb->alloc_size*sizeof(Eterm))) { abort(); } } @@ -1610,7 +1605,7 @@ sweep_rootset(Rootset* rootset, Eterm* htop, char* src, Uint src_size) case TAG_PRIMARY_BOXED: { ptr = boxed_val(gval); val = *ptr; - if (IS_MOVED(val)) { + if (IS_MOVED_BOXED(val)) { ASSERT(is_boxed(val)); *g_ptr++ = val; } else if (in_area(ptr, src, src_size)) { @@ -1623,7 +1618,7 @@ sweep_rootset(Rootset* rootset, Eterm* htop, char* src, Uint src_size) case TAG_PRIMARY_LIST: { ptr = list_val(gval); val = *ptr; - if (is_non_value(val)) { /* Moved */ + if (IS_MOVED_CONS(val)) { *g_ptr++ = ptr[1]; } else if (in_area(ptr, src, src_size)) { MOVE_CONS(ptr,val,htop,g_ptr++); @@ -1657,7 +1652,7 @@ sweep_one_area(Eterm* n_hp, Eterm* n_htop, char* src, Uint src_size) case TAG_PRIMARY_BOXED: { ptr = boxed_val(gval); val = *ptr; - if (IS_MOVED(val)) { + if (IS_MOVED_BOXED(val)) { ASSERT(is_boxed(val)); *n_hp++ = val; } else if (in_area(ptr, src, src_size)) { @@ -1670,7 +1665,7 @@ sweep_one_area(Eterm* n_hp, Eterm* n_htop, char* src, Uint src_size) case TAG_PRIMARY_LIST: { ptr = list_val(gval); val = *ptr; - if (is_non_value(val)) { + if (IS_MOVED_CONS(val)) { *n_hp++ = ptr[1]; } else if (in_area(ptr, src, src_size)) { MOVE_CONS(ptr,val,n_htop,n_hp++); @@ -1690,7 +1685,7 @@ sweep_one_area(Eterm* n_hp, Eterm* n_htop, char* src, Uint src_size) origptr = &(mb->orig); ptr = boxed_val(*origptr); val = *ptr; - if (IS_MOVED(val)) { + if (IS_MOVED_BOXED(val)) { *origptr = val; mb->base = binary_bytes(*origptr); } else if (in_area(ptr, src, src_size)) { @@ -1722,7 +1717,7 @@ sweep_one_heap(Eterm* heap_ptr, Eterm* heap_end, Eterm* htop, char* src, Uint sr case TAG_PRIMARY_BOXED: { ptr = boxed_val(gval); val = *ptr; - if (IS_MOVED(val)) { + if (IS_MOVED_BOXED(val)) { ASSERT(is_boxed(val)); *heap_ptr++ = val; } else if (in_area(ptr, src, src_size)) { @@ -1735,7 +1730,7 @@ sweep_one_heap(Eterm* heap_ptr, Eterm* heap_end, Eterm* htop, char* src, Uint sr case TAG_PRIMARY_LIST: { ptr = list_val(gval); val = *ptr; - if (is_non_value(val)) { + if (IS_MOVED_CONS(val)) { *heap_ptr++ = ptr[1]; } else if (in_area(ptr, src, src_size)) { MOVE_CONS(ptr,val,htop,heap_ptr++); @@ -1830,28 +1825,6 @@ collect_heap_frags(Process* p, Eterm* n_hstart, Eterm* n_htop, return n_htop; } -#ifdef DEBUG -static Eterm follow_moved(Eterm term) -{ - Eterm* ptr; - switch (primary_tag(term)) { - case TAG_PRIMARY_IMMED1: - break; - case TAG_PRIMARY_BOXED: - ptr = boxed_val(term); - if (IS_MOVED(*ptr)) term = *ptr; - break; - case TAG_PRIMARY_LIST: - ptr = list_val(term); - if (is_non_value(ptr[0])) term = ptr[1]; - break; - default: - abort(); - } - return term; -} -#endif - static Uint setup_rootset(Process *p, Eterm *objv, int nobj, Rootset *rootset) { @@ -2090,7 +2063,7 @@ sweep_proc_externals(Process *p, int fullsweep) while (ptr) { Eterm* ppt = (Eterm *) ptr; - if (IS_MOVED(*ppt)) { /* Object is alive */ + if (IS_MOVED_BOXED(*ppt)) { /* Object is alive */ ExternalThing* ro = external_thing_ptr(*ppt); *prev = ro; /* Patch to moved pos */ @@ -2130,7 +2103,7 @@ sweep_proc_funs(Process *p, int fullsweep) while (ptr) { Eterm* ppt = (Eterm *) ptr; - if (IS_MOVED(*ppt)) { /* Object is alive */ + if (IS_MOVED_BOXED(*ppt)) { /* Object is alive */ ErlFunThing* ro = (ErlFunThing *) fun_val(*ppt); *prev = ro; /* Patch to moved pos */ @@ -2244,7 +2217,7 @@ sweep_proc_bins(Process *p, int fullsweep) while (ptr) { Eterm* ppt = (Eterm *) ptr; - if (IS_MOVED(*ppt)) { /* Object is alive */ + if (IS_MOVED_BOXED(*ppt)) { /* Object is alive */ bin_vheap += ptr->size / sizeof(Eterm); ptr = (ProcBin*) binary_val(*ppt); link_live_proc_bin(&shrink, @@ -2542,7 +2515,7 @@ within2(Eterm *ptr, Process *p, Eterm *real_htop) return 1; } while (bp != NULL) { - if (bp->mem <= ptr && ptr < bp->mem + bp->size) { + if (bp->mem <= ptr && ptr < bp->mem + bp->used_size) { return 1; } bp = bp->next; @@ -2556,7 +2529,7 @@ within2(Eterm *ptr, Process *p, Eterm *real_htop) hfp = erts_dist_ext_trailer(mp->data.dist_ext); else hfp = NULL; - if (hfp && hfp->mem <= ptr && ptr < hfp->mem + hfp->size) + if (hfp && hfp->mem <= ptr && ptr < hfp->mem + hfp->used_size) return 1; } mp = mp->next; diff --git a/erts/emulator/beam/erl_gc.h b/erts/emulator/beam/erl_gc.h index af55b6363f..807ef8ae8d 100644 --- a/erts/emulator/beam/erl_gc.h +++ b/erts/emulator/beam/erl_gc.h @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 2007-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 2007-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ @@ -22,11 +22,12 @@ /* GC declarations shared by beam/erl_gc.c and hipe/hipe_gc.c */ -#ifdef DEBUG +#if defined(DEBUG) && !ERTS_GLB_INLINE_INCL_FUNC_DEF # define HARDDEBUG 1 #endif -#define IS_MOVED(x) (!is_header((x))) +#define IS_MOVED_BOXED(x) (!is_header((x))) +#define IS_MOVED_CONS(x) (is_non_value((x))) #define MOVE_CONS(PTR,CAR,HTOP,ORIG) \ do { \ @@ -69,4 +70,28 @@ extern Uint erts_test_long_gc_sleep; int within(Eterm *ptr, Process *p); #endif +ERTS_GLB_INLINE Eterm follow_moved(Eterm term); + +#if ERTS_GLB_INLINE_INCL_FUNC_DEF +ERTS_GLB_INLINE Eterm follow_moved(Eterm term) +{ + Eterm* ptr; + switch (primary_tag(term)) { + case TAG_PRIMARY_IMMED1: + break; + case TAG_PRIMARY_BOXED: + ptr = boxed_val(term); + if (IS_MOVED_BOXED(*ptr)) term = *ptr; + break; + case TAG_PRIMARY_LIST: + ptr = list_val(term); + if (IS_MOVED_CONS(ptr[0])) term = ptr[1]; + break; + default: + ASSERT(!"strange tag in follow_moved"); + } + return term; +} +#endif + #endif /* __ERL_GC_H__ */ diff --git a/erts/emulator/beam/erl_init.c b/erts/emulator/beam/erl_init.c index f2e71ae98d..4a4507b212 100644 --- a/erts/emulator/beam/erl_init.c +++ b/erts/emulator/beam/erl_init.c @@ -336,59 +336,6 @@ init_shared_memory(int argc, char **argv) #endif } - -/* - * Create the very first process. - */ - -void -erts_first_process(Eterm modname, void* code, unsigned size, int argc, char** argv) -{ - int i; - Eterm args; - Eterm pid; - Eterm* hp; - Process parent; - Process* p; - ErlSpawnOpts so; - - if (erts_find_function(modname, am_start, 1) == NULL) { - char sbuf[256]; - Atom* ap; - - ap = atom_tab(atom_val(modname)); - memcpy(sbuf, ap->name, ap->len); - sbuf[ap->len] = '\0'; - erl_exit(5, "No function %s:start/1\n", sbuf); - } - - /* - * We need a dummy parent process to be able to call erl_create_process(). - */ - erts_init_empty_process(&parent); - hp = HAlloc(&parent, argc*2 + 4); - args = NIL; - for (i = argc-1; i >= 0; i--) { - int len = sys_strlen(argv[i]); - args = CONS(hp, new_binary(&parent, (byte*)argv[i], len), args); - hp += 2; - } - args = CONS(hp, new_binary(&parent, code, size), args); - hp += 2; - args = CONS(hp, args, NIL); - - so.flags = 0; - pid = erl_create_process(&parent, modname, am_start, args, &so); - p = process_tab[internal_pid_index(pid)]; - p->group_leader = pid; - - erts_cleanup_empty_process(&parent); -} - -/* - * XXX Old way of starting. Hopefully soon obsolete. - */ - static void erl_first_process_otp(char* modname, void* code, unsigned size, int argc, char** argv) { @@ -755,6 +702,10 @@ early_init(int *argc, char **argv) /* #ifdef ERTS_ENABLE_LOCK_CHECK erts_lc_late_init(); #endif + +#ifdef ERTS_ENABLE_LOCK_COUNT + erts_lcnt_late_init(); +#endif #if defined(HIPE) hipe_signal_init(); /* must be done very early */ diff --git a/erts/emulator/beam/erl_lock_check.c b/erts/emulator/beam/erl_lock_check.c index 6ff5c1b9da..cee470ae37 100644 --- a/erts/emulator/beam/erl_lock_check.c +++ b/erts/emulator/beam/erl_lock_check.c @@ -105,7 +105,6 @@ static erts_lc_lock_order_t erts_lock_order[] = { { "node_table", NULL }, { "dist_table", NULL }, { "sys_tracers", NULL }, - { "trace_pattern", NULL }, { "module_tab", NULL }, { "export_tab", NULL }, { "fun_tab", NULL }, diff --git a/erts/emulator/beam/erl_lock_count.c b/erts/emulator/beam/erl_lock_count.c index 0d7e1335c1..26028aeefc 100644 --- a/erts/emulator/beam/erl_lock_count.c +++ b/erts/emulator/beam/erl_lock_count.c @@ -257,6 +257,10 @@ void erts_lcnt_init() { erts_lcnt_clear_counters(); } +void erts_lcnt_late_init() { + erts_thr_install_exit_handler(erts_lcnt_thread_exit_handler); +} + /* list operations */ /* BEGIN ASSUMPTION: lcnt_data_lock taken */ @@ -570,36 +574,26 @@ void erts_lcnt_trylock(erts_lcnt_lock_t *lock, int res) { /* thread operations */ -static void *lcnt_thr_init(erts_lcnt_thread_data_t *eltd) { - void *(*function)(void *); - void *argument; - void *res; - function = eltd->function; - argument = eltd->argument; - - ethr_tsd_set(lcnt_thr_data_key, eltd); - - res = (void *)function(argument); - free(eltd); - return (void *)res; -} - - - -int erts_lcnt_thr_create(ethr_tid *tid, void * (*function)(void *), void *arg, ethr_thr_opts *opts) { +void erts_lcnt_thread_setup(void) { erts_lcnt_thread_data_t *eltd; - + lcnt_lock(); /* lock for thread id global update */ eltd = lcnt_thread_data_alloc(); lcnt_unlock(); - - eltd->function = function; - eltd->argument = arg; - - return ethr_thr_create(tid, (void *)lcnt_thr_init, (void *)eltd, opts); + ASSERT(eltd); + ethr_tsd_set(lcnt_thr_data_key, eltd); } +void erts_lcnt_thread_exit_handler() { + erts_lcnt_thread_data_t *eltd; + + eltd = ethr_tsd_get(lcnt_thr_data_key); + + if (eltd) { + free(eltd); + } +} /* bindings for bifs */ diff --git a/erts/emulator/beam/erl_lock_count.h b/erts/emulator/beam/erl_lock_count.h index e3044c371f..6306580ae4 100644 --- a/erts/emulator/beam/erl_lock_count.h +++ b/erts/emulator/beam/erl_lock_count.h @@ -155,11 +155,6 @@ typedef struct { erts_lcnt_time_t timer; /* timer */ int timer_set; /* bool */ int lock_in_conflict; /* bool */ - - /* function pointer */ - void *(*function)(void *); - void *argument; - } erts_lcnt_thread_data_t; /* globals */ @@ -169,6 +164,11 @@ extern Uint16 erts_lcnt_rt_options; /* function declerations */ void erts_lcnt_init(void); +void erts_lcnt_late_init(void); + +/* thread operations */ +void erts_lcnt_thread_setup(void); +void erts_lcnt_thread_exit_handler(void); /* list operations (local) */ erts_lcnt_lock_list_t *erts_lcnt_list_init(void); @@ -194,12 +194,7 @@ void erts_lcnt_unlock_opt(erts_lcnt_lock_t *lock, Uint16 option); void erts_lcnt_trylock_opt(erts_lcnt_lock_t *lock, int res, Uint16 option); void erts_lcnt_trylock(erts_lcnt_lock_t *lock, int res); -/* thread operations */ - -int erts_lcnt_thr_create(ethr_tid *tid, void * (*function)(void *), void *arg, ethr_thr_opts *opts); - /* bif interface */ - Uint16 erts_lcnt_set_rt_opt(Uint16 opt); Uint16 erts_lcnt_clear_rt_opt(Uint16 opt); void erts_lcnt_clear_counters(void); diff --git a/erts/emulator/beam/erl_message.c b/erts/emulator/beam/erl_message.c index a056fce0c5..b63f3df7df 100644 --- a/erts/emulator/beam/erl_message.c +++ b/erts/emulator/beam/erl_message.c @@ -42,6 +42,15 @@ ERTS_SCHED_PREF_QUICK_ALLOC_IMPL(message, #undef HARD_DEBUG #endif + + + +static ERTS_INLINE int in_heapfrag(const Eterm* ptr, const ErlHeapFragment *bp) +{ + return ((unsigned)(ptr - bp->mem) < bp->used_size); +} + + void init_message(void) { @@ -81,9 +90,12 @@ erts_resize_message_buffer(ErlHeapFragment *bp, Uint size, #endif ErlHeapFragment* nbp; + /* ToDo: Make use of 'used_size' to avoid realloc + when shrinking just a few words */ + #ifdef DEBUG { - Uint off_sz = size < bp->size ? size : bp->size; + Uint off_sz = size < bp->used_size ? size : bp->used_size; for (i = 0; i < brefs_size; i++) { Eterm *ptr; if (is_immed(brefs[i])) @@ -95,12 +107,12 @@ erts_resize_message_buffer(ErlHeapFragment *bp, Uint size, } #endif - if (size == bp->size) + if (size == bp->used_size) return bp; #ifdef HARD_DEBUG dbg_brefs = erts_alloc(ERTS_ALC_T_UNDEF, sizeof(Eterm *)*brefs_size); - dbg_bp = new_message_buffer(bp->size); + dbg_bp = new_message_buffer(bp->used_size); dbg_hp = dbg_bp->mem; dbg_tot_size = 0; for (i = 0; i < brefs_size; i++) { @@ -109,15 +121,15 @@ erts_resize_message_buffer(ErlHeapFragment *bp, Uint size, dbg_brefs[i] = copy_struct(brefs[i], dbg_size, &dbg_hp, &dbg_bp->off_heap); } - ASSERT(dbg_tot_size == (size < bp->size ? size : bp->size)); + ASSERT(dbg_tot_size == (size < bp->used_size ? size : bp->used_size)); #endif nbp = (ErlHeapFragment*) ERTS_HEAP_REALLOC(ERTS_ALC_T_HEAP_FRAG, (void *) bp, - ERTS_HEAP_FRAG_SIZE(bp->size), + ERTS_HEAP_FRAG_SIZE(bp->alloc_size), ERTS_HEAP_FRAG_SIZE(size)); if (bp != nbp) { - Uint off_sz = size < nbp->size ? size : nbp->size; + Uint off_sz = size < nbp->used_size ? size : nbp->used_size; Eterm *sp = &bp->mem[0]; Eterm *ep = sp + off_sz; Sint offs = &nbp->mem[0] - sp; @@ -135,7 +147,7 @@ erts_resize_message_buffer(ErlHeapFragment *bp, Uint size, } #endif } - nbp->size = size; + nbp->alloc_size = size; nbp->used_size = size; #ifdef HARD_DEBUG @@ -168,10 +180,15 @@ erts_cleanup_offheap(ErlOffHeap *offheap) void free_message_buffer(ErlHeapFragment* bp) { - erts_cleanup_offheap(&bp->off_heap); - ERTS_HEAP_FREE(ERTS_ALC_T_HEAP_FRAG, - (void *) bp, - ERTS_HEAP_FRAG_SIZE(bp->size)); + ASSERT(bp != NULL); + do { + ErlHeapFragment* next_bp = bp->next; + + erts_cleanup_offheap(&bp->off_heap); + ERTS_HEAP_FREE(ERTS_ALC_T_HEAP_FRAG, (void *) bp, + ERTS_HEAP_FRAG_SIZE(bp->size)); + bp = next_bp; + }while (bp != NULL); } static ERTS_INLINE void @@ -181,7 +198,7 @@ link_mbuf_to_proc(Process *proc, ErlHeapFragment *bp) /* Link the message buffer */ bp->next = MBUF(proc); MBUF(proc) = bp; - MBUF_SIZE(proc) += bp->size; + MBUF_SIZE(proc) += bp->used_size; FLAGS(proc) |= F_FORCE_GC; /* Move any binaries into the process */ @@ -242,7 +259,7 @@ erts_msg_distext2heap(Process *pp, goto decode_error; if (is_not_nil(*tokenp)) { ErlHeapFragment *heap_frag = erts_dist_ext_trailer(dist_extp); - tok_sz = heap_frag->size; + tok_sz = heap_frag->used_size; sz += tok_sz; } if (pp) @@ -283,12 +300,13 @@ erts_msg_distext2heap(Process *pp, erts_cleanup_offheap(&heap_frag->off_heap); } erts_free_dist_ext_copy(dist_extp); - if (*bpp) + if (*bpp) { free_message_buffer(*bpp); + *bpp = NULL; + } else if (hp) { HRelease(pp, hp_end, hp); } - *bpp = NULL; return THE_NON_VALUE; } @@ -436,11 +454,10 @@ erts_queue_message(Process* receiver, ERL_MESSAGE_TERM(mp) = message; ERL_MESSAGE_TOKEN(mp) = seq_trace_token; mp->next = NULL; + mp->data.heap_frag = bp; #ifdef ERTS_SMP if (*receiver_locks & ERTS_PROC_LOCK_MAIN) { - mp->data.heap_frag = bp; - /* * We move 'in queue' to 'private queue' and place * message at the end of 'private queue' in order @@ -453,11 +470,9 @@ erts_queue_message(Process* receiver, LINK_MESSAGE_PRIVQ(receiver, mp); } else { - mp->data.heap_frag = bp; LINK_MESSAGE(receiver, mp); } #else - mp->data.heap_frag = bp; LINK_MESSAGE(receiver, mp); #endif @@ -530,32 +545,27 @@ erts_move_msg_mbuf_to_heap(Eterm** hpp, ErlOffHeap* off_heap, ErlMessage *msg) #ifdef HARD_DEBUG dbg_term_sz = size_object(term); dbg_token_sz = size_object(token); - ASSERT(bp->size == dbg_term_sz + dbg_token_sz); - - dbg_bp = new_message_buffer(bp->size); + /*ASSERT(dbg_term_sz + dbg_token_sz == erts_msg_used_frag_sz(msg)); + Copied size may be smaller due to removed SubBins's or garbage. + Copied size may be larger due to duplicated shared terms. + */ + dbg_bp = new_message_buffer(dbg_term_sz + dbg_token_sz); dbg_hp = dbg_bp->mem; dbg_term = copy_struct(term, dbg_term_sz, &dbg_hp, &dbg_bp->off_heap); dbg_token = copy_struct(token, dbg_token_sz, &dbg_hp, &dbg_bp->off_heap); dbg_thp_start = *hpp; #endif - ASSERT(bp); - msg->data.attached = NULL; + if (bp->next != NULL) { + move_multi_frags(hpp, off_heap, bp, msg->m, 2); + goto copy_done; + } off_heap->overhead += bp->off_heap.overhead; - sz = bp->size; + sz = bp->used_size; -#ifdef DEBUG - if (is_not_immed(term)) { - ASSERT(bp->mem <= ptr_val(term)); - ASSERT(bp->mem + bp->size > ptr_val(term)); - } - - if (is_not_immed(token)) { - ASSERT(bp->mem <= ptr_val(token)); - ASSERT(bp->mem + bp->size > ptr_val(token)); - } -#endif + ASSERT(is_immed(term) || in_heapfrag(ptr_val(term),bp)); + ASSERT(is_immed(token) || in_heapfrag(ptr_val(token),bp)); fhp = bp->mem; hp = *hpp; @@ -574,8 +584,7 @@ erts_move_msg_mbuf_to_heap(Eterm** hpp, ErlOffHeap* off_heap, ErlMessage *msg) break; case TAG_PRIMARY_LIST: case TAG_PRIMARY_BOXED: - ASSERT(bp->mem <= ptr_val(val)); - ASSERT(bp->mem + bp->size > ptr_val(val)); + ASSERT(in_heapfrag(ptr_val(val), bp)); *hp++ = offset_ptr(val, offs); break; case TAG_PRIMARY_HEADER: @@ -609,6 +618,7 @@ erts_move_msg_mbuf_to_heap(Eterm** hpp, ErlOffHeap* off_heap, ErlMessage *msg) cpy_sz = header_arity(val); cpy_words: + ASSERT(sz >= cpy_sz); sz -= cpy_sz; while (cpy_sz >= 8) { cpy_sz -= 8; @@ -676,12 +686,11 @@ erts_move_msg_mbuf_to_heap(Eterm** hpp, ErlOffHeap* off_heap, ErlMessage *msg) } } - ASSERT(bp->size == hp - *hpp); + ASSERT(bp->used_size == hp - *hpp); *hpp = hp; if (is_not_immed(token)) { - ASSERT(bp->mem <= ptr_val(token)); - ASSERT(bp->mem + bp->size > ptr_val(token)); + ASSERT(in_heapfrag(ptr_val(token), bp)); ERL_MESSAGE_TOKEN(msg) = offset_ptr(token, offs); #ifdef HARD_DEBUG ASSERT(dbg_thp_start <= ptr_val(ERL_MESSAGE_TOKEN(msg))); @@ -690,8 +699,7 @@ erts_move_msg_mbuf_to_heap(Eterm** hpp, ErlOffHeap* off_heap, ErlMessage *msg) } if (is_not_immed(term)) { - ASSERT(bp->mem <= ptr_val(term)); - ASSERT(bp->mem + bp->size > ptr_val(term)); + ASSERT(in_heapfrag(ptr_val(term),bp)); ERL_MESSAGE_TERM(msg) = offset_ptr(term, offs); #ifdef HARD_DEBUG ASSERT(dbg_thp_start <= ptr_val(ERL_MESSAGE_TERM(msg))); @@ -699,10 +707,12 @@ erts_move_msg_mbuf_to_heap(Eterm** hpp, ErlOffHeap* off_heap, ErlMessage *msg) #endif } +copy_done: #ifdef HARD_DEBUG { int i, j; + ErlHeapFragment* frag; { ProcBin *mso = off_heap->mso; i = j = 0; @@ -710,10 +720,12 @@ erts_move_msg_mbuf_to_heap(Eterm** hpp, ErlOffHeap* off_heap, ErlMessage *msg) mso = mso->next; i++; } - mso = bp->off_heap.mso; - while (mso) { - mso = mso->next; - j++; + for (frag=bp; frag; frag=frag->next) { + mso = frag->off_heap.mso; + while (mso) { + mso = mso->next; + j++; + } } ASSERT(i == j); } @@ -724,10 +736,12 @@ erts_move_msg_mbuf_to_heap(Eterm** hpp, ErlOffHeap* off_heap, ErlMessage *msg) fun = fun->next; i++; } - fun = bp->off_heap.funs; - while (fun) { - fun = fun->next; - j++; + for (frag=bp; frag; frag=frag->next) { + fun = frag->off_heap.funs; + while (fun) { + fun = fun->next; + j++; + } } ASSERT(i == j); } @@ -738,10 +752,12 @@ erts_move_msg_mbuf_to_heap(Eterm** hpp, ErlOffHeap* off_heap, ErlMessage *msg) external = external->next; i++; } - external = bp->off_heap.externals; - while (external) { - external = external->next; - j++; + for (frag=bp; frag; frag=frag->next) { + external = frag->off_heap.externals; + while (external) { + external = external->next; + j++; + } } ASSERT(i == j); } @@ -755,6 +771,7 @@ erts_move_msg_mbuf_to_heap(Eterm** hpp, ErlOffHeap* off_heap, ErlMessage *msg) #endif bp->off_heap.externals = NULL; free_message_buffer(bp); + msg->data.heap_frag = NULL; #ifdef HARD_DEBUG ASSERT(eq(ERL_MESSAGE_TERM(msg), dbg_term)); @@ -764,6 +781,7 @@ erts_move_msg_mbuf_to_heap(Eterm** hpp, ErlOffHeap* off_heap, ErlMessage *msg) } + Uint erts_msg_attached_data_size_aux(ErlMessage *msg) { @@ -789,7 +807,7 @@ erts_msg_attached_data_size_aux(ErlMessage *msg) if (is_not_nil(msg->m[1])) { ErlHeapFragment *heap_frag; heap_frag = erts_dist_ext_trailer(msg->data.dist_ext); - sz += heap_frag->size; + sz += heap_frag->used_size; } return sz; } @@ -805,7 +823,7 @@ erts_move_msg_attached_data_to_heap(Eterm **hpp, ErlOffHeap *ohp, ErlMessage *ms ErlHeapFragment *heap_frag; heap_frag = erts_dist_ext_trailer(msg->data.dist_ext); ERL_MESSAGE_TOKEN(msg) = copy_struct(ERL_MESSAGE_TOKEN(msg), - heap_frag->size, + heap_frag->used_size, hpp, ohp); erts_cleanup_offheap(&heap_frag->off_heap); @@ -1062,3 +1080,4 @@ erts_deliver_exit_message(Eterm from, Process *to, ErtsProcLocks *to_locksp, erts_queue_message(to, to_locksp, bp, save, NIL); } } + diff --git a/erts/emulator/beam/erl_message.h b/erts/emulator/beam/erl_message.h index 489dee7b37..f478572ac2 100644 --- a/erts/emulator/beam/erl_message.h +++ b/erts/emulator/beam/erl_message.h @@ -49,7 +49,7 @@ typedef struct erl_heap_fragment ErlHeapFragment; struct erl_heap_fragment { ErlHeapFragment* next; /* Next heap fragment */ ErlOffHeap off_heap; /* Offset heap data. */ - unsigned size; /* Size in (half)words of mem */ + unsigned alloc_size; /* Size in (half)words of mem */ unsigned used_size; /* With terms to be moved to heap by GC */ Eterm mem[1]; /* Data */ }; @@ -199,7 +199,7 @@ do { \ #define ERTS_INIT_HEAP_FRAG(HEAP_FRAG_P, DATA_WORDS) \ do { \ (HEAP_FRAG_P)->next = NULL; \ - (HEAP_FRAG_P)->size = (DATA_WORDS); \ + (HEAP_FRAG_P)->alloc_size = (DATA_WORDS); \ (HEAP_FRAG_P)->used_size = (DATA_WORDS); \ (HEAP_FRAG_P)->off_heap.mso = NULL; \ (HEAP_FRAG_P)->off_heap.funs = NULL; \ @@ -227,14 +227,25 @@ void erts_move_msg_attached_data_to_heap(Eterm **, ErlOffHeap *, ErlMessage *); Eterm erts_msg_distext2heap(Process *, ErtsProcLocks *, ErlHeapFragment **, Eterm *, ErtsDistExternal *); +ERTS_GLB_INLINE Uint erts_msg_used_frag_sz(const ErlMessage *msg); ERTS_GLB_INLINE Uint erts_msg_attached_data_size(ErlMessage *msg); #if ERTS_GLB_INLINE_INCL_FUNC_DEF +ERTS_GLB_INLINE Uint erts_msg_used_frag_sz(const ErlMessage *msg) +{ + const ErlHeapFragment *bp; + Uint sz = 0; + for (bp = msg->data.heap_frag; bp!=NULL; bp=bp->next) { + sz += bp->used_size; + } + return sz; +} + ERTS_GLB_INLINE Uint erts_msg_attached_data_size(ErlMessage *msg) { ASSERT(msg->data.attached); if (is_value(ERL_MESSAGE_TERM(msg))) - return msg->data.heap_frag->size; + return erts_msg_used_frag_sz(msg); else if (msg->data.dist_ext->heap_size < 0) return erts_msg_attached_data_size_aux(msg); else { @@ -242,7 +253,7 @@ ERTS_GLB_INLINE Uint erts_msg_attached_data_size(ErlMessage *msg) if (is_not_nil(ERL_MESSAGE_TOKEN(msg))) { ErlHeapFragment *heap_frag; heap_frag = erts_dist_ext_trailer(msg->data.dist_ext); - sz += heap_frag->size; + sz += heap_frag->used_size; } return sz; } diff --git a/erts/emulator/beam/erl_nif.c b/erts/emulator/beam/erl_nif.c index 7095ae03e7..3d63fa1caf 100644 --- a/erts/emulator/beam/erl_nif.c +++ b/erts/emulator/beam/erl_nif.c @@ -48,7 +48,7 @@ struct erl_module_nif { struct enif_entry_t* entry; erts_refc_t rt_cnt; /* number of resource types */ erts_refc_t rt_dtor_cnt; /* number of resource types with destructors */ - int is_orphan; /* if erlang module has been purged */ + Module* mod; /* Can be NULL if orphan with dtor-resources left */ }; #ifdef DEBUG @@ -61,6 +61,10 @@ static void add_readonly_check(ErlNifEnv*, unsigned char* ptr, unsigned sz); # define ADD_READONLY_CHECK(ENV,PTR,SIZE) ((void)0) #endif +#ifdef DEBUG +static int is_offheap(const ErlOffHeap* off_heap); +#endif + #define MIN_HEAP_FRAG_SZ 200 static Eterm* alloc_heap_heavy(ErlNifEnv* env, unsigned need, Eterm* hp); @@ -84,7 +88,8 @@ static Eterm* alloc_heap_heavy(ErlNifEnv* env, unsigned need, Eterm* hp) HEAP_TOP(env->proc) = env->hp; } else { - HRelease(env->proc, env->hp_end, env->hp); + env->heap_frag->used_size = hp - env->heap_frag->mem; + ASSERT(env->heap_frag->used_size <= env->heap_frag->alloc_size); } frag_sz = need + MIN_HEAP_FRAG_SZ; hp = erts_heap_alloc(env->proc, frag_sz); @@ -143,8 +148,9 @@ void erts_post_nif(ErlNifEnv* env) } else { ASSERT(env->hp_end != HEAP_LIMIT(env->proc)); - ASSERT(env->hp_end - env->hp <= env->heap_frag->size); - HRelease(env->proc, env->hp_end, env->hp); + ASSERT(env->hp_end - env->hp <= env->heap_frag->alloc_size); + env->heap_frag->used_size = env->hp - env->heap_frag->mem; + ASSERT(env->heap_frag->used_size <= env->heap_frag->alloc_size); } free_tmp_objs(env); } @@ -158,7 +164,7 @@ static void post_nif_noproc(ErlNifEnv* env) /* Flush out our cached heap pointers to allow an ordinary HAlloc */ -static void enable_halloc(ErlNifEnv* env) +static void flush_env(ErlNifEnv* env) { if (env->heap_frag == NULL) { ASSERT(env->hp_end == HEAP_LIMIT(env->proc)); @@ -168,14 +174,15 @@ static void enable_halloc(ErlNifEnv* env) } else { ASSERT(env->hp_end != HEAP_LIMIT(env->proc)); - ASSERT(env->hp_end - env->hp <= env->heap_frag->size); - HRelease(env->proc, env->hp_end, env->hp); + ASSERT(env->hp_end - env->hp <= env->heap_frag->alloc_size); + env->heap_frag->used_size = env->hp - env->heap_frag->mem; + ASSERT(env->heap_frag->used_size <= env->heap_frag->alloc_size); } } -/* Restore cached heap pointers +/* Restore cached heap pointers to allow alloc_heap again. */ -static void disable_halloc(ErlNifEnv* env) +static void cache_env(ErlNifEnv* env) { if (env->heap_frag == NULL) { ASSERT(env->hp_end == HEAP_LIMIT(env->proc)); @@ -185,34 +192,192 @@ static void disable_halloc(ErlNifEnv* env) } else { ASSERT(env->hp_end != HEAP_LIMIT(env->proc)); - ASSERT(env->hp_end - env->hp <= env->heap_frag->size); + ASSERT(env->hp_end - env->hp <= env->heap_frag->alloc_size); env->heap_frag = MBUF(env->proc); ASSERT(env->heap_frag != NULL); env->hp = env->heap_frag->mem + env->heap_frag->used_size; - env->hp_end = env->heap_frag->mem + env->heap_frag->size; + env->hp_end = env->heap_frag->mem + env->heap_frag->alloc_size; } } - void* enif_priv_data(ErlNifEnv* env) { return env->mod_nif->priv_data; } -void* enif_alloc(ErlNifEnv* env, size_t size) +void* enif_alloc(size_t size) { return erts_alloc_fnf(ERTS_ALC_T_NIF, (Uint) size); } -void* enif_realloc(ErlNifEnv* env, void* ptr, size_t size) +void* enif_realloc(void* ptr, size_t size) { return erts_realloc_fnf(ERTS_ALC_T_NIF, ptr, size); } -void enif_free(ErlNifEnv* env, void* ptr) +void enif_free(void* ptr) { erts_free(ERTS_ALC_T_NIF, ptr); } +struct enif_msg_environment_t +{ + ErlNifEnv env; + Process phony_proc; +}; + +ErlNifEnv* enif_alloc_env(void) +{ + struct enif_msg_environment_t* msg_env = + erts_alloc_fnf(ERTS_ALC_T_NIF, sizeof(struct enif_msg_environment_t)); + Eterm* phony_heap = (Eterm*) msg_env; /* dummy non-NULL ptr */ + + msg_env->env.hp = phony_heap; + msg_env->env.hp_end = phony_heap; + msg_env->env.heap_frag = NULL; + msg_env->env.mod_nif = NULL; + msg_env->env.tmp_obj_list = (struct enif_tmp_obj_t*) 1; /* invalid non-NULL */ + msg_env->env.proc = &msg_env->phony_proc; + memset(&msg_env->phony_proc, 0, sizeof(Process)); + HEAP_START(&msg_env->phony_proc) = phony_heap; + HEAP_TOP(&msg_env->phony_proc) = phony_heap; + HEAP_LIMIT(&msg_env->phony_proc) = phony_heap; + HEAP_END(&msg_env->phony_proc) = phony_heap; + MBUF(&msg_env->phony_proc) = NULL; + msg_env->phony_proc.id = ERTS_INVALID_PID; +#ifdef FORCE_HEAP_FRAGS + msg_env->phony_proc.space_verified = 0; + msg_env->phony_proc.space_verified_from = NULL; +#endif + return &msg_env->env; +} +void enif_free_env(ErlNifEnv* env) +{ + enif_clear_env(env); + erts_free(ERTS_ALC_T_NIF, env); +} + +static ERTS_INLINE void clear_offheap(ErlOffHeap* oh) +{ + oh->mso = NULL; + oh->externals = NULL; + oh->funs = NULL; + oh->overhead = 0; +} + +void enif_clear_env(ErlNifEnv* env) +{ + struct enif_msg_environment_t* menv = (struct enif_msg_environment_t*)env; + Process* p = &menv->phony_proc; + ASSERT(p == menv->env.proc); + ASSERT(p->id == ERTS_INVALID_PID); + ASSERT(MBUF(p) == menv->env.heap_frag); + if (MBUF(p) != NULL) { + erts_cleanup_offheap(&MSO(p)); + clear_offheap(&MSO(p)); + free_message_buffer(MBUF(p)); + MBUF(p) = NULL; + menv->env.heap_frag = NULL; + } + ASSERT(HEAP_TOP(p) == HEAP_END(p)); + menv->env.hp = menv->env.hp_end = HEAP_TOP(p); + + ASSERT(!is_offheap(&MSO(p))); +} +int enif_send(ErlNifEnv* env, const ErlNifPid* to_pid, + ErlNifEnv* msg_env, ERL_NIF_TERM msg) +{ + struct enif_msg_environment_t* menv = (struct enif_msg_environment_t*)msg_env; + ErtsProcLocks rp_locks = 0; + Process* rp; + Process* c_p; + ErlHeapFragment* frags; +#if defined(ERTS_ENABLE_LOCK_CHECK) && defined(ERTS_SMP) + ErtsProcLocks rp_had_locks; +#endif + Eterm receiver = to_pid->pid; + int flush_me = 0; + + if (env != NULL) { + c_p = env->proc; + if (receiver == c_p->id) { + rp_locks = ERTS_PROC_LOCK_MAIN; + flush_me = 1; + } + } + else { +#ifdef ERTS_SMP + c_p = NULL; +#else + erl_exit(ERTS_ABORT_EXIT,"enif_send: env==NULL on non-SMP VM"); +#endif + } + +#if defined(ERTS_ENABLE_LOCK_CHECK) && defined(ERTS_SMP) + rp_had_locks = rp_locks; +#endif + rp = erts_pid2proc_opt(c_p, ERTS_PROC_LOCK_MAIN, + receiver, rp_locks, ERTS_P2P_FLG_SMP_INC_REFC); + if (rp == NULL) { + ASSERT(env == NULL || receiver != c_p->id); + return 0; + } + flush_env(msg_env); + frags = menv->env.heap_frag; + ASSERT(frags == MBUF(&menv->phony_proc)); + if (frags != NULL) { + /* Move all offheap's from phony proc to the first fragment. + Quick and dirty, but erts_move_msg_mbuf_to_heap doesn't care. */ + ASSERT(!is_offheap(&frags->off_heap)); + frags->off_heap = MSO(&menv->phony_proc); + clear_offheap(&MSO(&menv->phony_proc)); + menv->env.heap_frag = NULL; + MBUF(&menv->phony_proc) = NULL; + } + ASSERT(!is_offheap(&MSO(&menv->phony_proc))); + + if (flush_me) { + flush_env(env); /* Needed for ERTS_HOLE_CHECK */ + } + erts_queue_message(rp, &rp_locks, frags, msg, am_undefined); + if (rp_locks) { + ERTS_SMP_LC_ASSERT(rp_locks == (rp_had_locks | (ERTS_PROC_LOCK_MSGQ | + ERTS_PROC_LOCK_STATUS))); + erts_smp_proc_unlock(rp, (ERTS_PROC_LOCK_MSGQ | ERTS_PROC_LOCK_STATUS)); + } + erts_smp_proc_dec_refc(rp); + if (flush_me) { + cache_env(env); + } + return 1; +} + +ERL_NIF_TERM enif_make_copy(ErlNifEnv* dst_env, ERL_NIF_TERM src_term) +{ + Uint sz; + Eterm* hp; + sz = size_object(src_term); + hp = alloc_heap(dst_env, sz); + return copy_struct(src_term, sz, &hp, &MSO(dst_env->proc)); +} + + +#ifdef DEBUG +static int is_offheap(const ErlOffHeap* oh) +{ + return oh->mso != NULL || oh->funs != NULL || oh->externals != NULL; +} +#endif + +ErlNifPid* enif_self(ErlNifEnv* caller_env, ErlNifPid* pid) +{ + pid->pid = caller_env->proc->id; + return pid; +} +int enif_get_local_pid(ErlNifEnv* env, ERL_NIF_TERM term, ErlNifPid* pid) +{ + return is_internal_pid(term) ? (pid->pid=term, 1) : 0; +} + int enif_is_atom(ErlNifEnv* env, ERL_NIF_TERM term) { return is_atom(term); @@ -324,7 +489,7 @@ int enif_inspect_iolist_as_binary(ErlNifEnv* env, Eterm term, ErlNifBinary* bin) return 1; } -int enif_alloc_binary(ErlNifEnv* env, unsigned size, ErlNifBinary* bin) +int enif_alloc_binary(size_t size, ErlNifBinary* bin) { Binary* refbin; @@ -343,7 +508,7 @@ int enif_alloc_binary(ErlNifEnv* env, unsigned size, ErlNifBinary* bin) return 1; } -int enif_realloc_binary(ErlNifEnv* env, ErlNifBinary* bin, unsigned size) +int enif_realloc_binary(ErlNifBinary* bin, size_t size) { if (bin->ref_bin != NULL) { Binary* oldbin; @@ -361,15 +526,15 @@ int enif_realloc_binary(ErlNifEnv* env, ErlNifBinary* bin, unsigned size) } else { unsigned char* old_data = bin->data; - unsigned cpy_sz = (size < bin->size ? size : bin->size); - enif_alloc_binary(env, size, bin); + size_t cpy_sz = (size < bin->size ? size : bin->size); + enif_alloc_binary(size, bin); sys_memcpy(bin->data, old_data, cpy_sz); } return 1; } -void enif_release_binary(ErlNifEnv* env, ErlNifBinary* bin) +void enif_release_binary(ErlNifBinary* bin) { if (bin->ref_bin != NULL) { Binary* refbin = bin->ref_bin; @@ -385,21 +550,21 @@ void enif_release_binary(ErlNifEnv* env, ErlNifBinary* bin) #endif } -unsigned char* enif_make_new_binary(ErlNifEnv* env, unsigned size, +unsigned char* enif_make_new_binary(ErlNifEnv* env, size_t size, ERL_NIF_TERM* termp) { - enable_halloc(env); + flush_env(env); *termp = new_binary(env->proc, NULL, size); - disable_halloc(env); + cache_env(env); return binary_bytes(*termp); } -int enif_is_identical(ErlNifEnv* env, Eterm lhs, Eterm rhs) +int enif_is_identical(Eterm lhs, Eterm rhs) { return EQ(lhs,rhs); } -int enif_compare(ErlNifEnv* env, Eterm lhs, Eterm rhs) +int enif_compare(Eterm lhs, Eterm rhs) { return cmp(lhs,rhs); } @@ -478,15 +643,15 @@ Eterm enif_make_binary(ErlNifEnv* env, ErlNifBinary* bin) return bin_term; } else { - enable_halloc(env); + flush_env(env); bin->bin_term = new_binary(env->proc, bin->data, bin->size); - disable_halloc(env); + cache_env(env); return bin->bin_term; } } Eterm enif_make_sub_binary(ErlNifEnv* env, ERL_NIF_TERM bin_term, - unsigned pos, unsigned size) + size_t pos, size_t size) { ErlSubBin* sb; Eterm orig; @@ -516,9 +681,11 @@ Eterm enif_make_badarg(ErlNifEnv* env) BIF_ERROR(env->proc, BADARG); } -int enif_get_atom(ErlNifEnv* env, Eterm atom, char* buf, unsigned len) +int enif_get_atom(ErlNifEnv* env, Eterm atom, char* buf, unsigned len, + ErlNifCharEncoding encoding) { Atom* ap; + ASSERT(encoding == ERL_NIF_LATIN1); if (is_not_atom(atom)) { return 0; } @@ -566,10 +733,8 @@ int enif_get_long(ErlNifEnv* env, Eterm term, long* ip) #if SIZEOF_LONG == ERTS_SIZEOF_ETERM return term_to_Sint(term, ip); #elif SIZEOF_INT == ERTS_SIZEOF_ETERM - Uint u; - term_to_Sint(term, u); - *ip = (long) u; - return 1; + Sint i; + return term_to_Sint(term, &i) ? (*ip = (long) i, 1) : 0; #else # error Unknown long word size #endif @@ -581,10 +746,7 @@ int enif_get_ulong(ErlNifEnv* env, Eterm term, unsigned long* ip) return term_to_Uint(term, ip); #elif SIZEOF_INT == ERTS_SIZEOF_ETERM Uint u; - int r; - r = term_to_Uint(term, &u); - *ip = (unsigned long) u; - return r; + return term_to_Uint(term, &u) ? (*ip = (unsigned long) u, 1) : 0; #else # error Unknown long word size #endif @@ -601,9 +763,11 @@ int enif_get_double(ErlNifEnv* env, Eterm term, double* dp) return 1; } -int enif_get_atom_length(ErlNifEnv* env, Eterm atom, unsigned* len) +int enif_get_atom_length(ErlNifEnv* env, Eterm atom, unsigned* len, + ErlNifCharEncoding enc) { Atom* ap; + ASSERT(enc == ERL_NIF_LATIN1); if (is_not_atom(atom)) return 0; ap = atom_tab(atom_val(atom)); *len = ap->len; @@ -674,14 +838,16 @@ ERL_NIF_TERM enif_make_atom_len(ErlNifEnv* env, const char* name, size_t len) return am_atom_put(name, len); } -int enif_make_existing_atom(ErlNifEnv* env, const char* name, ERL_NIF_TERM* atom) +int enif_make_existing_atom(ErlNifEnv* env, const char* name, ERL_NIF_TERM* atom, + ErlNifCharEncoding enc) { - return enif_make_existing_atom_len(env, name, sys_strlen(name), atom); + return enif_make_existing_atom_len(env, name, sys_strlen(name), atom, enc); } int enif_make_existing_atom_len(ErlNifEnv* env, const char* name, size_t len, - ERL_NIF_TERM* atom) + ERL_NIF_TERM* atom, ErlNifCharEncoding encoding) { + ASSERT(encoding == ERL_NIF_LATIN1); return erts_atom_get(name, len, atom); } @@ -841,7 +1007,8 @@ struct enif_resource_type_t ErlNifResourceDtor* dtor; /* user destructor function */ erts_refc_t refc; /* num of resources of this type (HOTSPOT warning) +1 for active erl_module_nif */ - char name[1]; + Eterm module; + Eterm name; }; /* dummy node in circular list */ @@ -859,14 +1026,14 @@ typedef struct enif_resource_t #define SIZEOF_ErlNifResource(SIZE) (offsetof(ErlNifResource,data) + (SIZE)) #define DATA_TO_RESOURCE(PTR) ((ErlNifResource*)((char*)(PTR) - offsetof(ErlNifResource,data))) -static ErlNifResourceType* find_resource_type(const char* name) +static ErlNifResourceType* find_resource_type(Eterm module, Eterm name) { ErlNifResourceType* type; for (type = resource_type_list.next; type != &resource_type_list; type = type->next) { - if (sys_strcmp(type->name, name) == 0) { + if (type->module == module && type->name == name) { return type; } } @@ -899,33 +1066,42 @@ static void steal_resource_type(ErlNifResourceType* type) if (type->dtor != NULL && erts_refc_dectest(&lib->rt_dtor_cnt, 0) == 0 - && lib->is_orphan) { + && lib->mod == NULL) { /* last type with destructor gone, close orphan lib */ close_lib(lib); } if (erts_refc_dectest(&lib->rt_cnt, 0) == 0 - && lib->is_orphan) { + && lib->mod == NULL) { erts_free(ERTS_ALC_T_NIF, lib); } } ErlNifResourceType* -enif_open_resource_type(ErlNifEnv* env, const char* type_name, - ErlNifResourceDtor* dtor, - enum ErlNifResourceFlags flags, - enum ErlNifResourceFlags* tried) +enif_open_resource_type(ErlNifEnv* env, + const char* module_str, + const char* name_str, + ErlNifResourceDtor* dtor, + ErlNifResourceFlags flags, + ErlNifResourceFlags* tried) { - ErlNifResourceType* type = find_resource_type(type_name); - enum ErlNifResourceFlags op = flags; + ErlNifResourceType* type = NULL; + ErlNifResourceFlags op = flags; + Eterm module_am, name_am; + ASSERT(erts_smp_is_system_blocked(0)); + ASSERT(module_str == NULL); /* for now... */ + module_am = make_atom(env->mod_nif->mod->module); + name_am = enif_make_atom(env, name_str); + + type = find_resource_type(module_am, name_am); if (type == NULL) { if (flags & ERL_NIF_RT_CREATE) { type = erts_alloc(ERTS_ALC_T_NIF, - sizeof(struct enif_resource_type_t) - + sys_strlen(type_name)); + sizeof(struct enif_resource_type_t)); type->dtor = dtor; - sys_strcpy(type->name, type_name); + type->module = module_am; + type->name = name_am; erts_refc_init(&type->refc, 1); type->owner = env->mod_nif; type->prev = &resource_type_list; @@ -973,13 +1149,13 @@ static void nif_resource_dtor(Binary* bin) if (erts_refc_dectest(&type->refc, 0) == 0) { ASSERT(type->next == NULL); ASSERT(type->owner != NULL); - ASSERT(type->owner->is_orphan); + ASSERT(type->owner->mod == NULL); steal_resource_type(type); erts_free(ERTS_ALC_T_NIF, type); } } -void* enif_alloc_resource(ErlNifEnv* env, ErlNifResourceType* type, unsigned size) +void* enif_alloc_resource(ErlNifResourceType* type, size_t size) { Binary* bin = erts_create_magic_binary(SIZEOF_ErlNifResource(size), &nif_resource_dtor); ErlNifResource* resource = ERTS_MAGIC_BIN_DATA(bin); @@ -992,7 +1168,7 @@ void* enif_alloc_resource(ErlNifEnv* env, ErlNifResourceType* type, unsigned siz return resource->data; } -void enif_release_resource(ErlNifEnv* env, void* obj) +void enif_release_resource(void* obj) { ErlNifResource* resource = DATA_TO_RESOURCE(obj); ErtsBinary* bin = ERTS_MAGIC_BIN_FROM_DATA(resource); @@ -1006,6 +1182,18 @@ void enif_release_resource(ErlNifEnv* env, void* obj) } } +void enif_keep_resource(void* obj) +{ + ErlNifResource* resource = DATA_TO_RESOURCE(obj); + ErtsBinary* bin = ERTS_MAGIC_BIN_FROM_DATA(resource); + + ASSERT(ERTS_MAGIC_BIN_DESTRUCTOR(bin) == &nif_resource_dtor); +#ifdef DEBUG + erts_refc_inc(&resource->nif_refc, 1); +#endif + erts_refc_inc(&bin->binary.refc, 2); +} + ERL_NIF_TERM enif_make_resource(ErlNifEnv* env, void* obj) { ErlNifResource* resource = DATA_TO_RESOURCE(obj); @@ -1014,15 +1202,30 @@ ERL_NIF_TERM enif_make_resource(ErlNifEnv* env, void* obj) return erts_mk_magic_binary_term(&hp, &MSO(env->proc), &bin->binary); } +ERL_NIF_TERM enif_make_resource_binary(ErlNifEnv* env, void* obj, + const void* data, size_t size) +{ + Eterm bin = enif_make_resource(env, obj); + ProcBin* pb = (ProcBin*) binary_val(bin); + pb->bytes = (byte*) data; + pb->size = size; + return bin; +} + int enif_get_resource(ErlNifEnv* env, ERL_NIF_TERM term, ErlNifResourceType* type, void** objp) { + ProcBin* pb; Binary* mbin; ErlNifResource* resource; if (!ERTS_TERM_IS_MAGIC_BINARY(term)) { return 0; } - mbin = ((ProcBin*) binary_val(term))->val; + pb = (ProcBin*) binary_val(term); + /*if (pb->size != 0) { + return 0; / * Or should we allow "resource binaries" as handles? * / + }*/ + mbin = pb->val; resource = (ErlNifResource*) ERTS_MAGIC_BIN_DATA(mbin); if (ERTS_MAGIC_BIN_DESTRUCTOR(mbin) != &nif_resource_dtor || resource->type != type) { @@ -1032,7 +1235,7 @@ int enif_get_resource(ErlNifEnv* env, ERL_NIF_TERM term, ErlNifResourceType* typ return 1; } -unsigned enif_sizeof_resource(ErlNifEnv* env, void* obj) +size_t enif_sizeof_resource(void* obj) { ErlNifResource* resource = DATA_TO_RESOURCE(obj); Binary* bin = &ERTS_MAGIC_BIN_FROM_DATA(resource)->binary; @@ -1262,7 +1465,7 @@ BIF_RETTYPE load_nif_2(BIF_ALIST_2) lib->entry = entry; erts_refc_init(&lib->rt_cnt, 0); erts_refc_init(&lib->rt_dtor_cnt, 0); - lib->is_orphan = 0; + lib->mod = mod; env.mod_nif = lib; if (mod->nif != NULL) { /* Reload */ int k; @@ -1343,7 +1546,8 @@ BIF_RETTYPE load_nif_2(BIF_ALIST_2) code_ptr[5+0] = (BeamInstr) BeamOp(op_call_nif); } else { /* Function traced, patch the original instruction word */ - BpData* bp = (BpData*) code_ptr[1]; + BpData** bps = (BpData**) code_ptr[1]; + BpData* bp = (BpData*) bps[bp_sched2ix()]; bp->orig_instr = (BeamInstr) BeamOp(op_call_nif); } code_ptr[5+1] = (BeamInstr) entry->funcs[i].fptr; @@ -1376,7 +1580,7 @@ erts_unload_nif(struct erl_module_nif* lib) ErlNifResourceType* next; ASSERT(erts_smp_is_system_blocked(0)); ASSERT(lib != NULL); - ASSERT(!lib->is_orphan); + ASSERT(lib->mod != NULL); for (rt = resource_type_list.next; rt != &resource_type_list; rt = next) { @@ -1406,7 +1610,7 @@ erts_unload_nif(struct erl_module_nif* lib) else { ASSERT(erts_refc_read(&lib->rt_cnt, 1) > 0); } - lib->is_orphan = 1; + lib->mod = NULL; /* orphan lib */ } void erl_nif_init() @@ -1415,7 +1619,8 @@ void erl_nif_init() resource_type_list.prev = &resource_type_list; resource_type_list.dtor = NULL; resource_type_list.owner = NULL; - resource_type_list.name[0] = '\0'; + resource_type_list.module = THE_NON_VALUE; + resource_type_list.name = THE_NON_VALUE; } #ifdef READONLY_CHECK diff --git a/erts/emulator/beam/erl_nif.h b/erts/emulator/beam/erl_nif.h index a345837569..936f03bce1 100644 --- a/erts/emulator/beam/erl_nif.h +++ b/erts/emulator/beam/erl_nif.h @@ -23,14 +23,16 @@ #ifndef __ERL_NIF_H__ #define __ERL_NIF_H__ + #include "erl_drv_nif.h" /* Version history: ** 0.1: R13B03 ** 1.0: R13B04 +** 2.0: R14A */ -#define ERL_NIF_MAJOR_VERSION 1 -#define ERL_NIF_MINOR_VERSION 1 +#define ERL_NIF_MAJOR_VERSION 2 +#define ERL_NIF_MINOR_VERSION 0 #include <stdlib.h> @@ -60,6 +62,10 @@ #endif #include "erl_int_sizes_config.h" +#ifdef __cplusplus +extern "C" { +#endif + #ifdef HALFWORD_HEAP_EMULATOR typedef unsigned int ERL_NIF_TERM; #else @@ -93,7 +99,7 @@ typedef struct enif_entry_t typedef struct { - unsigned size; + size_t size; unsigned char* data; /* Internals (avert your eyes) */ @@ -103,17 +109,22 @@ typedef struct typedef struct enif_resource_type_t ErlNifResourceType; typedef void ErlNifResourceDtor(ErlNifEnv*, void*); -enum ErlNifResourceFlags +typedef enum { ERL_NIF_RT_CREATE = 1, ERL_NIF_RT_TAKEOVER = 2 -}; +}ErlNifResourceFlags; typedef enum { ERL_NIF_LATIN1 = 1 }ErlNifCharEncoding; +typedef struct +{ + ERL_NIF_TERM pid; /* internal, may change */ +}ErlNifPid; + typedef ErlDrvSysInfo ErlNifSysInfo; typedef struct ErlDrvTid_ *ErlNifTid; @@ -146,8 +157,6 @@ extern TWinDynNifCallbacks WinDynNifCallbacks; #endif - - #if (defined(__WIN32__) || defined(_WIN32) || defined(_WIN32_)) # define ERL_NIF_INIT_GLOB TWinDynNifCallbacks WinDynNifCallbacks; # define ERL_NIF_INIT_DECL(MODNAME) __declspec(dllexport) ErlNifEntry* nif_init(TWinDynNifCallbacks* callbacks) @@ -163,7 +172,18 @@ extern TWinDynNifCallbacks WinDynNifCallbacks; #endif +#ifdef __cplusplus +} +# define ERL_NIF_INIT_PROLOGUE extern "C" { +# define ERL_NIF_INIT_EPILOGUE } +#else +# define ERL_NIF_INIT_PROLOGUE +# define ERL_NIF_INIT_EPILOGUE +#endif + + #define ERL_NIF_INIT(NAME, FUNCS, LOAD, RELOAD, UPGRADE, UNLOAD) \ +ERL_NIF_INIT_PROLOGUE \ ERL_NIF_INIT_GLOB \ ERL_NIF_INIT_DECL(NAME) \ { \ @@ -178,7 +198,9 @@ ERL_NIF_INIT_DECL(NAME) \ }; \ ERL_NIF_INIT_BODY; \ return &entry; \ -} +} \ +ERL_NIF_INIT_EPILOGUE + #endif /* __ERL_NIF_H__ */ diff --git a/erts/emulator/beam/erl_nif_api_funcs.h b/erts/emulator/beam/erl_nif_api_funcs.h index 44bcca9ca4..ef4e9580b0 100644 --- a/erts/emulator/beam/erl_nif_api_funcs.h +++ b/erts/emulator/beam/erl_nif_api_funcs.h @@ -23,29 +23,29 @@ #ifdef ERL_NIF_API_FUNC_DECL ERL_NIF_API_FUNC_DECL(void*,enif_priv_data,(ErlNifEnv*)); -ERL_NIF_API_FUNC_DECL(void*,enif_alloc,(ErlNifEnv*, size_t size)); -ERL_NIF_API_FUNC_DECL(void,enif_free,(ErlNifEnv*, void* ptr)); +ERL_NIF_API_FUNC_DECL(void*,enif_alloc,(size_t size)); +ERL_NIF_API_FUNC_DECL(void,enif_free,(void* ptr)); ERL_NIF_API_FUNC_DECL(int,enif_is_atom,(ErlNifEnv*, ERL_NIF_TERM term)); ERL_NIF_API_FUNC_DECL(int,enif_is_binary,(ErlNifEnv*, ERL_NIF_TERM term)); ERL_NIF_API_FUNC_DECL(int,enif_is_ref,(ErlNifEnv*, ERL_NIF_TERM term)); ERL_NIF_API_FUNC_DECL(int,enif_inspect_binary,(ErlNifEnv*, ERL_NIF_TERM bin_term, ErlNifBinary* bin)); -ERL_NIF_API_FUNC_DECL(int,enif_alloc_binary,(ErlNifEnv*, unsigned size, ErlNifBinary* bin)); -ERL_NIF_API_FUNC_DECL(int,enif_realloc_binary,(ErlNifEnv*, ErlNifBinary* bin, unsigned size)); -ERL_NIF_API_FUNC_DECL(void,enif_release_binary,(ErlNifEnv*, ErlNifBinary* bin)); +ERL_NIF_API_FUNC_DECL(int,enif_alloc_binary,(size_t size, ErlNifBinary* bin)); +ERL_NIF_API_FUNC_DECL(int,enif_realloc_binary,(ErlNifBinary* bin, size_t size)); +ERL_NIF_API_FUNC_DECL(void,enif_release_binary,(ErlNifBinary* bin)); ERL_NIF_API_FUNC_DECL(int,enif_get_int,(ErlNifEnv*, ERL_NIF_TERM term, int* ip)); ERL_NIF_API_FUNC_DECL(int,enif_get_ulong,(ErlNifEnv*, ERL_NIF_TERM term, unsigned long* ip)); ERL_NIF_API_FUNC_DECL(int,enif_get_double,(ErlNifEnv*, ERL_NIF_TERM term, double* dp)); ERL_NIF_API_FUNC_DECL(int,enif_get_list_cell,(ErlNifEnv* env, ERL_NIF_TERM term, ERL_NIF_TERM* head, ERL_NIF_TERM* tail)); ERL_NIF_API_FUNC_DECL(int,enif_get_tuple,(ErlNifEnv* env, ERL_NIF_TERM tpl, int* arity, const ERL_NIF_TERM** array)); -ERL_NIF_API_FUNC_DECL(int,enif_is_identical,(ErlNifEnv* env, ERL_NIF_TERM lhs, ERL_NIF_TERM rhs)); -ERL_NIF_API_FUNC_DECL(int,enif_compare,(ErlNifEnv* env, ERL_NIF_TERM lhs, ERL_NIF_TERM rhs)); +ERL_NIF_API_FUNC_DECL(int,enif_is_identical,(ERL_NIF_TERM lhs, ERL_NIF_TERM rhs)); +ERL_NIF_API_FUNC_DECL(int,enif_compare,(ERL_NIF_TERM lhs, ERL_NIF_TERM rhs)); ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_make_binary,(ErlNifEnv* env, ErlNifBinary* bin)); ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_make_badarg,(ErlNifEnv* env)); ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_make_int,(ErlNifEnv* env, int i)); ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_make_ulong,(ErlNifEnv* env, unsigned long i)); ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_make_double,(ErlNifEnv* env, double d)); ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_make_atom,(ErlNifEnv* env, const char* name)); -ERL_NIF_API_FUNC_DECL(int,enif_make_existing_atom,(ErlNifEnv* env, const char* name, ERL_NIF_TERM* atom)); +ERL_NIF_API_FUNC_DECL(int,enif_make_existing_atom,(ErlNifEnv* env, const char* name, ERL_NIF_TERM* atom, ErlNifCharEncoding)); ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_make_tuple,(ErlNifEnv* env, unsigned cnt, ...)); ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_make_list,(ErlNifEnv* env, unsigned cnt, ...)); ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_make_list_cell,(ErlNifEnv* env, ERL_NIF_TERM car, ERL_NIF_TERM cdr)); @@ -82,13 +82,13 @@ ERL_NIF_API_FUNC_DECL(int,enif_equal_tids,(ErlNifTid tid1, ErlNifTid tid2)); ERL_NIF_API_FUNC_DECL(void,enif_thread_exit,(void *resp)); ERL_NIF_API_FUNC_DECL(int,enif_thread_join,(ErlNifTid, void **respp)); -ERL_NIF_API_FUNC_DECL(void*,enif_realloc,(ErlNifEnv*, void* ptr, size_t size)); +ERL_NIF_API_FUNC_DECL(void*,enif_realloc,(void* ptr, size_t size)); ERL_NIF_API_FUNC_DECL(void,enif_system_info,(ErlNifSysInfo *sip, size_t si_size)); ERL_NIF_API_FUNC_DECL(int,enif_fprintf,(void/* FILE* */ *filep, const char *format, ...)); ERL_NIF_API_FUNC_DECL(int,enif_inspect_iolist_as_binary,(ErlNifEnv*, ERL_NIF_TERM term, ErlNifBinary* bin)); -ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_make_sub_binary,(ErlNifEnv*, ERL_NIF_TERM bin_term, unsigned pos, unsigned size)); +ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_make_sub_binary,(ErlNifEnv*, ERL_NIF_TERM bin_term, size_t pos, size_t size)); ERL_NIF_API_FUNC_DECL(int,enif_get_string,(ErlNifEnv*, ERL_NIF_TERM list, char* buf, unsigned len, ErlNifCharEncoding)); -ERL_NIF_API_FUNC_DECL(int,enif_get_atom,(ErlNifEnv*, ERL_NIF_TERM atom, char* buf, unsigned len)); +ERL_NIF_API_FUNC_DECL(int,enif_get_atom,(ErlNifEnv*, ERL_NIF_TERM atom, char* buf, unsigned len, ErlNifCharEncoding)); ERL_NIF_API_FUNC_DECL(int,enif_is_fun,(ErlNifEnv*, ERL_NIF_TERM term)); ERL_NIF_API_FUNC_DECL(int,enif_is_pid,(ErlNifEnv*, ERL_NIF_TERM term)); ERL_NIF_API_FUNC_DECL(int,enif_is_port,(ErlNifEnv*, ERL_NIF_TERM term)); @@ -99,20 +99,29 @@ ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_make_long,(ErlNifEnv*, long i)); ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_make_tuple_from_array,(ErlNifEnv*, const ERL_NIF_TERM arr[], unsigned cnt)); ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_make_list_from_array,(ErlNifEnv*, const ERL_NIF_TERM arr[], unsigned cnt)); ERL_NIF_API_FUNC_DECL(int,enif_is_empty_list,(ErlNifEnv*, ERL_NIF_TERM term)); -ERL_NIF_API_FUNC_DECL(ErlNifResourceType*,enif_open_resource_type,(ErlNifEnv*, const char* type_name, void (*dtor)(ErlNifEnv*,void *), enum ErlNifResourceFlags flags, enum ErlNifResourceFlags* tried)); -ERL_NIF_API_FUNC_DECL(void*,enif_alloc_resource,(ErlNifEnv*, ErlNifResourceType* type, unsigned size)); -ERL_NIF_API_FUNC_DECL(void,enif_release_resource,(ErlNifEnv*, void* obj)); +ERL_NIF_API_FUNC_DECL(ErlNifResourceType*,enif_open_resource_type,(ErlNifEnv*, const char* module_str, const char* name_str, void (*dtor)(ErlNifEnv*,void *), ErlNifResourceFlags flags, ErlNifResourceFlags* tried)); +ERL_NIF_API_FUNC_DECL(void*,enif_alloc_resource,(ErlNifResourceType* type, size_t size)); +ERL_NIF_API_FUNC_DECL(void,enif_release_resource,(void* obj)); ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_make_resource,(ErlNifEnv*, void* obj)); ERL_NIF_API_FUNC_DECL(int,enif_get_resource,(ErlNifEnv*, ERL_NIF_TERM term, ErlNifResourceType* type, void** objp)); -ERL_NIF_API_FUNC_DECL(unsigned,enif_sizeof_resource,(ErlNifEnv*, void* obj)); -ERL_NIF_API_FUNC_DECL(unsigned char*,enif_make_new_binary,(ErlNifEnv*,unsigned size,ERL_NIF_TERM* termp)); +ERL_NIF_API_FUNC_DECL(size_t,enif_sizeof_resource,(void* obj)); +ERL_NIF_API_FUNC_DECL(unsigned char*,enif_make_new_binary,(ErlNifEnv*,size_t size,ERL_NIF_TERM* termp)); ERL_NIF_API_FUNC_DECL(int,enif_is_list,(ErlNifEnv*, ERL_NIF_TERM term)); ERL_NIF_API_FUNC_DECL(int,enif_is_tuple,(ErlNifEnv*, ERL_NIF_TERM term)); -ERL_NIF_API_FUNC_DECL(int,enif_get_atom_length,(ErlNifEnv*, ERL_NIF_TERM atom, unsigned* len)); +ERL_NIF_API_FUNC_DECL(int,enif_get_atom_length,(ErlNifEnv*, ERL_NIF_TERM atom, unsigned* len, ErlNifCharEncoding)); ERL_NIF_API_FUNC_DECL(int,enif_get_list_length,(ErlNifEnv* env, ERL_NIF_TERM term, unsigned* len)); ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM, enif_make_atom_len,(ErlNifEnv* env, const char* name, size_t len)); -ERL_NIF_API_FUNC_DECL(int, enif_make_existing_atom_len,(ErlNifEnv* env, const char* name, size_t len, ERL_NIF_TERM* atom)); +ERL_NIF_API_FUNC_DECL(int, enif_make_existing_atom_len,(ErlNifEnv* env, const char* name, size_t len, ERL_NIF_TERM* atom, ErlNifCharEncoding)); ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_make_string_len,(ErlNifEnv* env, const char* string, size_t len, ErlNifCharEncoding)); +ERL_NIF_API_FUNC_DECL(ErlNifEnv*,enif_alloc_env,(void)); +ERL_NIF_API_FUNC_DECL(void,enif_free_env,(ErlNifEnv* env)); +ERL_NIF_API_FUNC_DECL(void,enif_clear_env,(ErlNifEnv* env)); +ERL_NIF_API_FUNC_DECL(int,enif_send,(ErlNifEnv* env, const ErlNifPid* to_pid, ErlNifEnv* msg_env, ERL_NIF_TERM msg)); +ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_make_copy,(ErlNifEnv* dst_env, ERL_NIF_TERM src_term)); +ERL_NIF_API_FUNC_DECL(ErlNifPid*,enif_self,(ErlNifEnv* caller_env, ErlNifPid* pid)); +ERL_NIF_API_FUNC_DECL(int,enif_get_local_pid,(ErlNifEnv* env, ERL_NIF_TERM, ErlNifPid* pid)); +ERL_NIF_API_FUNC_DECL(void,enif_keep_resource,(void* obj)); +ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_make_resource_binary,(ErlNifEnv*,void* obj,const void* data, size_t size)); /* ** Add last to keep compatibility on Windows!!! @@ -212,6 +221,15 @@ ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_make_string_len,(ErlNifEnv* env, const c # define enif_make_atom_len ERL_NIF_API_FUNC_MACRO(enif_make_atom_len) # define enif_make_existing_atom_len ERL_NIF_API_FUNC_MACRO(enif_make_existing_atom_len) # define enif_make_string_len ERL_NIF_API_FUNC_MACRO(enif_make_string_len) +# define enif_alloc_env ERL_NIF_API_FUNC_MACRO(enif_alloc_env) +# define enif_free_env ERL_NIF_API_FUNC_MACRO(enif_free_env) +# define enif_clear_env ERL_NIF_API_FUNC_MACRO(enif_clear_env) +# define enif_send ERL_NIF_API_FUNC_MACRO(enif_send) +# define enif_make_copy ERL_NIF_API_FUNC_MACRO(enif_make_copy) +# define enif_self ERL_NIF_API_FUNC_MACRO(enif_self) +# define enif_get_local_pid ERL_NIF_API_FUNC_MACRO(enif_get_local_pid) +# define enif_keep_resource ERL_NIF_API_FUNC_MACRO(enif_keep_resource) +# define enif_make_resource_binary ERL_NIF_API_FUNC_MACRO(enif_make_resource_binary) #endif #ifndef enif_make_list1 @@ -233,9 +251,7 @@ ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_make_string_len,(ErlNifEnv* env, const c # define enif_make_tuple7(ENV,E1,E2,E3,E4,E5,E6,E7) enif_make_tuple(ENV,7,E1,E2,E3,E4,E5,E6,E7) # define enif_make_tuple8(ENV,E1,E2,E3,E4,E5,E6,E7,E8) enif_make_tuple(ENV,8,E1,E2,E3,E4,E5,E6,E7,E8) # define enif_make_tuple9(ENV,E1,E2,E3,E4,E5,E6,E7,E8,E9) enif_make_tuple(ENV,9,E1,E2,E3,E4,E5,E6,E7,E8,E9) -#endif -#ifndef enif_get_data -# define enif_get_data enif_priv_data /* deprecated */ +# define enif_make_pid(ENV, PID) ((const ERL_NIF_TERM)((PID)->pid)) #endif diff --git a/erts/emulator/beam/erl_port_task.c b/erts/emulator/beam/erl_port_task.c index 0b6bb0d8e9..967a14f0d1 100644 --- a/erts/emulator/beam/erl_port_task.c +++ b/erts/emulator/beam/erl_port_task.c @@ -969,11 +969,11 @@ erts_port_task_execute(ErtsRunQueue *runq, Port **curr_port_pp) erts_port_release(pp); #else { - long refc = erts_smp_atomic_dectest(&pp->refc); + long refc; + erts_smp_mtx_unlock(pp->lock); + refc = erts_smp_atomic_dectest(&pp->refc); ASSERT(refc >= 0); - if (refc > 0) - erts_smp_mtx_unlock(pp->lock); - else { + if (refc == 0) { erts_smp_runq_unlock(runq); erts_port_cleanup(pp); /* Might aquire runq lock */ erts_smp_runq_lock(runq); diff --git a/erts/emulator/beam/erl_process.c b/erts/emulator/beam/erl_process.c index 11ca85a41c..bae17d1569 100644 --- a/erts/emulator/beam/erl_process.c +++ b/erts/emulator/beam/erl_process.c @@ -38,6 +38,7 @@ #include "erl_instrument.h" #include "erl_threads.h" #include "erl_binary.h" +#include "beam_bp.h" #define ERTS_RUNQ_CHECK_BALANCE_REDS_PER_SCHED (2000*CONTEXT_REDS) #define ERTS_RUNQ_CALL_CHECK_BALANCE_REDS \ @@ -180,6 +181,7 @@ static ErtsCpuBindData *scheduler2cpu_map; erts_smp_rwmtx_t erts_cpu_bind_rwmtx; typedef enum { + ERTS_CPU_BIND_UNDEFINED, ERTS_CPU_BIND_SPREAD, ERTS_CPU_BIND_PROCESSOR_SPREAD, ERTS_CPU_BIND_THREAD_SPREAD, @@ -190,6 +192,9 @@ typedef enum { ERTS_CPU_BIND_NONE } ErtsCpuBindOrder; +#define ERTS_CPU_BIND_DEFAULT_BIND \ + ERTS_CPU_BIND_THREAD_NO_NODE_PROCESSOR_SPREAD + ErtsCpuBindOrder cpu_bind_order; static erts_cpu_topology_t *user_cpudata; @@ -388,7 +393,12 @@ erts_pre_init_process(void) erts_psd_required_locks[ERTS_PSD_DIST_ENTRY].get_locks = ERTS_PSD_DIST_ENTRY_GET_LOCKS; erts_psd_required_locks[ERTS_PSD_DIST_ENTRY].set_locks - = ERTS_PSD_DIST_ENTRY_GET_LOCKS; + = ERTS_PSD_DIST_ENTRY_SET_LOCKS; + + erts_psd_required_locks[ERTS_PSD_CALL_TIME_BP].get_locks + = ERTS_PSD_CALL_TIME_BP_GET_LOCKS; + erts_psd_required_locks[ERTS_PSD_CALL_TIME_BP].set_locks + = ERTS_PSD_CALL_TIME_BP_SET_LOCKS; /* Check that we have locks for all entries */ for (ix = 0; ix < ERTS_PSD_SIZE; ix++) { @@ -2078,12 +2088,14 @@ erts_init_scheduling(int mrq, int no_schedulers, int no_schedulers_online) erts_aligned_run_queues = erts_alloc(ERTS_ALC_T_RUNQS, (sizeof(ErtsAlignedRunQueue)*(n+1))); - if ((((UWord) erts_aligned_run_queues) & ERTS_CACHE_LINE_MASK) == 0) + if ((((UWord) erts_aligned_run_queues) & ERTS_CACHE_LINE_MASK) != 0) erts_aligned_run_queues = ((ErtsAlignedRunQueue *) ((((UWord) erts_aligned_run_queues) & ~ERTS_CACHE_LINE_MASK) + ERTS_CACHE_LINE_SIZE)); + ASSERT((((UWord) erts_aligned_run_queues) & ERTS_CACHE_LINE_MASK) == 0); + #ifdef ERTS_SMP erts_smp_atomic_init(&no_empty_run_queues, 0); #endif @@ -2175,11 +2187,14 @@ erts_init_scheduling(int mrq, int no_schedulers, int no_schedulers_online) erts_aligned_scheduler_data = erts_alloc(ERTS_ALC_T_SCHDLR_DATA, (sizeof(ErtsAlignedSchedulerData) *(n+1))); - if ((((UWord) erts_aligned_scheduler_data) & ERTS_CACHE_LINE_MASK) == 0) + if ((((UWord) erts_aligned_scheduler_data) & ERTS_CACHE_LINE_MASK) != 0) erts_aligned_scheduler_data = ((ErtsAlignedSchedulerData *) ((((UWord) erts_aligned_scheduler_data) & ~ERTS_CACHE_LINE_MASK) + ERTS_CACHE_LINE_SIZE)); + + ASSERT((((UWord) erts_aligned_scheduler_data) & ERTS_CACHE_LINE_MASK) == 0); + for (ix = 0; ix < n; ix++) { ErtsSchedulerData *esdp = ERTS_SCHEDULER_IX(ix); #ifdef ERTS_SMP @@ -2769,7 +2784,7 @@ erts_block_multi_scheduling(Process *p, ErtsProcLocks plocks, int on, int all) res = ERTS_SCHDLR_SSPND_YIELD_RESTART; /* Yield */ } else if (on) { /* ------ BLOCK ------ */ - if (erts_is_multi_scheduling_blocked()) { + if (schdlr_sspnd.msb.procs) { plp = proclist_create(p); plp->next = schdlr_sspnd.msb.procs; schdlr_sspnd.msb.procs = plp; @@ -2975,8 +2990,11 @@ erts_dbg_multi_scheduling_return_trap(Process *p, Eterm return_value) int erts_is_multi_scheduling_blocked(void) { - return (erts_smp_atomic_read(&schdlr_sspnd.msb.ongoing) - && erts_smp_atomic_read(&schdlr_sspnd.active) == 1); + int res; + erts_smp_mtx_lock(&schdlr_sspnd.mtx); + res = schdlr_sspnd.msb.procs != NULL; + erts_smp_mtx_unlock(&schdlr_sspnd.mtx); + return res; } Eterm @@ -2985,7 +3003,7 @@ erts_multi_scheduling_blockers(Process *p) Eterm res = NIL; erts_smp_mtx_lock(&schdlr_sspnd.mtx); - if (erts_is_multi_scheduling_blocked()) { + if (schdlr_sspnd.msb.procs) { Eterm *hp, *hp_end; ErtsProcList *plp1, *plp2; Uint max_size; @@ -3096,11 +3114,7 @@ erts_start_schedulers(void) ErtsSchedulerData *esdp = ERTS_SCHEDULER_IX(actual); actual++; ASSERT(actual == esdp->no); -#ifdef ERTS_ENABLE_LOCK_COUNT - res = erts_lcnt_thr_create(&esdp->tid,sched_thread_func,(void*)esdp,&opts); -#else res = ethr_thr_create(&esdp->tid,sched_thread_func,(void*)esdp,&opts); -#endif if (res != 0) { actual--; break; @@ -3503,14 +3517,15 @@ erts_init_scheduler_bind_type(char *how) if (!system_cpudata && !user_cpudata) return ERTS_INIT_SCHED_BIND_TYPE_ERROR_NO_CPU_TOPOLOGY; - if (sys_strcmp(how, "s") == 0) + if (sys_strcmp(how, "db") == 0) + cpu_bind_order = ERTS_CPU_BIND_DEFAULT_BIND; + else if (sys_strcmp(how, "s") == 0) cpu_bind_order = ERTS_CPU_BIND_SPREAD; else if (sys_strcmp(how, "ps") == 0) cpu_bind_order = ERTS_CPU_BIND_PROCESSOR_SPREAD; else if (sys_strcmp(how, "ts") == 0) cpu_bind_order = ERTS_CPU_BIND_THREAD_SPREAD; - else if (sys_strcmp(how, "db") == 0 - || sys_strcmp(how, "tnnps") == 0) + else if (sys_strcmp(how, "tnnps") == 0) cpu_bind_order = ERTS_CPU_BIND_THREAD_NO_NODE_PROCESSOR_SPREAD; else if (sys_strcmp(how, "nnps") == 0) cpu_bind_order = ERTS_CPU_BIND_NO_NODE_PROCESSOR_SPREAD; @@ -4153,14 +4168,15 @@ erts_bind_schedulers(Process *c_p, Eterm how) old_cpu_bind_order = cpu_bind_order; - if (ERTS_IS_ATOM_STR("spread", how)) + if (ERTS_IS_ATOM_STR("default_bind", how)) + cpu_bind_order = ERTS_CPU_BIND_DEFAULT_BIND; + else if (ERTS_IS_ATOM_STR("spread", how)) cpu_bind_order = ERTS_CPU_BIND_SPREAD; else if (ERTS_IS_ATOM_STR("processor_spread", how)) cpu_bind_order = ERTS_CPU_BIND_PROCESSOR_SPREAD; else if (ERTS_IS_ATOM_STR("thread_spread", how)) cpu_bind_order = ERTS_CPU_BIND_THREAD_SPREAD; - else if (ERTS_IS_ATOM_STR("default_bind", how) - || ERTS_IS_ATOM_STR("thread_no_node_processor_spread", how)) + else if (ERTS_IS_ATOM_STR("thread_no_node_processor_spread", how)) cpu_bind_order = ERTS_CPU_BIND_THREAD_NO_NODE_PROCESSOR_SPREAD; else if (ERTS_IS_ATOM_STR("no_node_processor_spread", how)) cpu_bind_order = ERTS_CPU_BIND_NO_NODE_PROCESSOR_SPREAD; @@ -4206,14 +4222,15 @@ erts_fake_scheduler_bindings(Process *p, Eterm how) int cpudata_size; Eterm res; - if (ERTS_IS_ATOM_STR("spread", how)) + if (ERTS_IS_ATOM_STR("default_bind", how)) + fake_cpu_bind_order = ERTS_CPU_BIND_DEFAULT_BIND; + else if (ERTS_IS_ATOM_STR("spread", how)) fake_cpu_bind_order = ERTS_CPU_BIND_SPREAD; else if (ERTS_IS_ATOM_STR("processor_spread", how)) fake_cpu_bind_order = ERTS_CPU_BIND_PROCESSOR_SPREAD; else if (ERTS_IS_ATOM_STR("thread_spread", how)) fake_cpu_bind_order = ERTS_CPU_BIND_THREAD_SPREAD; - else if (ERTS_IS_ATOM_STR("default_bind", how) - || ERTS_IS_ATOM_STR("thread_no_node_processor_spread", how)) + else if (ERTS_IS_ATOM_STR("thread_no_node_processor_spread", how)) fake_cpu_bind_order = ERTS_CPU_BIND_THREAD_NO_NODE_PROCESSOR_SPREAD; else if (ERTS_IS_ATOM_STR("no_node_processor_spread", how)) fake_cpu_bind_order = ERTS_CPU_BIND_NO_NODE_PROCESSOR_SPREAD; @@ -4438,7 +4455,7 @@ early_cpu_bind_init(void) (sizeof(erts_cpu_topology_t) * system_cpudata_size)); - cpu_bind_order = ERTS_CPU_BIND_NONE; + cpu_bind_order = ERTS_CPU_BIND_UNDEFINED; if (!erts_get_cpu_topology(erts_cpuinfo, system_cpudata) || ERTS_INIT_CPU_TOPOLOGY_OK != verify_topology(system_cpudata, @@ -4464,6 +4481,17 @@ late_cpu_bind_init(void) scheduler2cpu_map[ix].bound_id = -1; } + if (cpu_bind_order == ERTS_CPU_BIND_UNDEFINED) { + int ncpus = erts_get_cpu_configured(erts_cpuinfo); + if (ncpus < 1 || erts_no_schedulers < ncpus) + cpu_bind_order = ERTS_CPU_BIND_NONE; + else + cpu_bind_order = ((system_cpudata || user_cpudata) + && (erts_bind_to_cpu(erts_cpuinfo, -1) != -ENOTSUP) + ? ERTS_CPU_BIND_DEFAULT_BIND + : ERTS_CPU_BIND_NONE); + } + if (cpu_bind_order != ERTS_CPU_BIND_NONE) { erts_cpu_topology_t *cpudata; int cpudata_size; @@ -5846,6 +5874,9 @@ Process *schedule(Process *p, int calls) } if (IS_TRACED(p)) { + if (IS_TRACED_FL(p, F_TRACE_CALLS) && p->status != P_FREE) { + erts_schedule_time_break(p, ERTS_BP_CALL_TIME_SCHEDULE_OUT); + } switch (p->status) { case P_EXITING: if (ARE_TRACE_FLAGS_ON(p, F_TRACE_SCHED_EXIT)) @@ -6282,7 +6313,11 @@ Process *schedule(Process *p, int calls) trace_virtual_sched(p, am_in); break; } + if (IS_TRACED_FL(p, F_TRACE_CALLS)) { + erts_schedule_time_break(p, ERTS_BP_CALL_TIME_SCHEDULE_IN); + } } + if (p->status != P_EXITING) p->status = P_RUNNING; @@ -7187,8 +7222,6 @@ erts_debug_verify_clean_empty_process(Process* p) void erts_cleanup_empty_process(Process* p) { - ErlHeapFragment* mbufp; - /* We only check fields that are known to be used... */ erts_cleanup_offheap(&p->off_heap); @@ -7199,13 +7232,10 @@ erts_cleanup_empty_process(Process* p) p->off_heap.externals = NULL; p->off_heap.overhead = 0; - mbufp = p->mbuf; - while (mbufp) { - ErlHeapFragment *next = mbufp->next; - free_message_buffer(mbufp); - mbufp = next; + if (p->mbuf != NULL) { + free_message_buffer(p->mbuf); + p->mbuf = NULL; } - p->mbuf = NULL; #if defined(ERTS_ENABLE_LOCK_COUNT) && defined(ERTS_SMP) erts_lcnt_proc_lock_destroy(p); #endif @@ -7221,7 +7251,6 @@ static void delete_process(Process* p) { ErlMessage* mp; - ErlHeapFragment* bp; VERBOSE(DEBUG_PROCESSES, ("Removing process: %T\n",p->id)); @@ -7271,11 +7300,8 @@ delete_process(Process* p) /* * Free all pending message buffers. */ - bp = p->mbuf; - while (bp != NULL) { - ErlHeapFragment* next_bp = bp->next; - free_message_buffer(bp); - bp = next_bp; + if (p->mbuf != NULL) { + free_message_buffer(p->mbuf); } erts_erase_dicts(p); @@ -8034,8 +8060,13 @@ erts_do_exit_process(Process* p, Eterm reason) ERTS_SMP_MSGQ_MV_INQ2PRIVQ(p); #endif - if (IS_TRACED_FL(p,F_TRACE_PROCS)) - trace_proc(p, p, am_exit, reason); + if (IS_TRACED(p)) { + if (IS_TRACED_FL(p, F_TRACE_CALLS)) + erts_schedule_time_break(p, ERTS_BP_CALL_TIME_SCHEDULE_EXITING); + + if (IS_TRACED_FL(p,F_TRACE_PROCS)) + trace_proc(p, p, am_exit, reason); + } erts_trace_check_exiting(p->id); @@ -8084,6 +8115,8 @@ continue_exit_process(Process *p Eterm reason = p->fvalue; DistEntry *dep; struct saved_calls *scb; + process_breakpoint_time_t *pbt; + #ifdef DEBUG int yield_allowed = 1; #endif @@ -8223,6 +8256,7 @@ continue_exit_process(Process *p ? ERTS_PROC_SET_DIST_ENTRY(p, ERTS_PROC_LOCKS_ALL, NULL) : NULL); scb = ERTS_PROC_SET_SAVED_CALLS_BUF(p, ERTS_PROC_LOCKS_ALL, NULL); + pbt = ERTS_PROC_SET_CALL_TIME(p, ERTS_PROC_LOCKS_ALL, NULL); erts_smp_proc_unlock(p, ERTS_PROC_LOCKS_ALL); processes_busy--; @@ -8265,6 +8299,9 @@ continue_exit_process(Process *p if (scb) erts_free(ERTS_ALC_T_CALLS_BUF, (void *) scb); + if (pbt) + erts_free(ERTS_ALC_T_BPD, (void *) pbt); + delete_process(p); erts_smp_proc_lock(p, ERTS_PROC_LOCK_MAIN); diff --git a/erts/emulator/beam/erl_process.h b/erts/emulator/beam/erl_process.h index cbcdec4ba7..8f9f7f004e 100644 --- a/erts/emulator/beam/erl_process.h +++ b/erts/emulator/beam/erl_process.h @@ -397,8 +397,9 @@ extern ErtsSchedulerData *erts_scheduler_data; #define ERTS_PSD_SAVED_CALLS_BUF 1 #define ERTS_PSD_SCHED_ID 2 #define ERTS_PSD_DIST_ENTRY 3 +#define ERTS_PSD_CALL_TIME_BP 4 -#define ERTS_PSD_SIZE 4 +#define ERTS_PSD_SIZE 5 typedef struct { void *data[ERTS_PSD_SIZE]; @@ -419,6 +420,9 @@ typedef struct { #define ERTS_PSD_DIST_ENTRY_GET_LOCKS ERTS_PROC_LOCK_MAIN #define ERTS_PSD_DIST_ENTRY_SET_LOCKS ERTS_PROC_LOCK_MAIN +#define ERTS_PSD_CALL_TIME_BP_GET_LOCKS ERTS_PROC_LOCK_MAIN +#define ERTS_PSD_CALL_TIME_BP_SET_LOCKS ERTS_PROC_LOCK_MAIN + typedef struct { ErtsProcLocks get_locks; ErtsProcLocks set_locks; @@ -765,7 +769,7 @@ ERTS_GLB_INLINE void erts_heap_frag_shrink(Process* p, Eterm* hp) { ErlHeapFragment* hf = MBUF(p); - ASSERT(hf!=NULL && (hp - hf->mem < (unsigned long)hf->size)); + ASSERT(hf!=NULL && (hp - hf->mem < (unsigned long)hf->alloc_size)); hf->used_size = hp - hf->mem; } @@ -1208,6 +1212,12 @@ erts_psd_set(Process *p, ErtsProcLocks plocks, int ix, void *data) #define ERTS_PROC_SET_SAVED_CALLS_BUF(P, L, SCB) \ ((struct saved_calls *) erts_psd_set((P), (L), ERTS_PSD_SAVED_CALLS_BUF, (void *) (SCB))) +#define ERTS_PROC_GET_CALL_TIME(P) \ + ((process_breakpoint_time_t *) erts_psd_get((P), ERTS_PSD_CALL_TIME_BP)) +#define ERTS_PROC_SET_CALL_TIME(P, L, PBT) \ + ((process_breakpoint_time_t *) erts_psd_set((P), (L), ERTS_PSD_CALL_TIME_BP, (void *) (PBT))) + + ERTS_GLB_INLINE Eterm erts_proc_get_error_handler(Process *p); ERTS_GLB_INLINE Eterm erts_proc_set_error_handler(Process *p, ErtsProcLocks plocks, diff --git a/erts/emulator/beam/erl_threads.h b/erts/emulator/beam/erl_threads.h index d635916dd8..35b338c6eb 100644 --- a/erts/emulator/beam/erl_threads.h +++ b/erts/emulator/beam/erl_threads.h @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 2001-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 2001-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ @@ -34,6 +34,8 @@ #include "erl_lock_count.h" #include "erl_term.h" +#define ERTS_THR_MEMORY_BARRIER ETHR_MEMORY_BARRIER + #ifdef ERTS_ENABLE_LOCK_COUNT #define erts_mtx_lock(L) erts_mtx_lock_x(L, __FILE__, __LINE__) #define erts_spin_lock(L) erts_spin_lock_x(L, __FILE__, __LINE__) @@ -122,6 +124,8 @@ __decl_noreturn void __noreturn erts_thr_fatal_error(int, char *); #else /* #ifdef USE_THREADS */ +#define ERTS_THR_MEMORY_BARRIER + #define ERTS_THR_OPTS_DEFAULT_INITER 0 typedef int erts_thr_opts_t; typedef int erts_thr_init_data_t; @@ -290,11 +294,7 @@ erts_thr_create(erts_tid_t *tid, void * (*func)(void *), void *arg, erts_thr_opts_t *opts) { #ifdef USE_THREADS -#ifdef ERTS_ENABLE_LOCK_COUNT - int res = erts_lcnt_thr_create(tid, func, arg, opts); -#else int res = ethr_thr_create(tid, func, arg, opts); -#endif if (res) erts_thr_fatal_error(res, "create thread"); #endif diff --git a/erts/emulator/beam/erl_trace.c b/erts/emulator/beam/erl_trace.c index 8addfcf5ad..3043bb1e8c 100644 --- a/erts/emulator/beam/erl_trace.c +++ b/erts/emulator/beam/erl_trace.c @@ -43,8 +43,9 @@ #undef DEBUG_PRINTOUTS #endif -extern Eterm beam_return_to_trace[1]; /* OpCode(i_return_to_trace) */ -extern Eterm beam_return_trace[1]; /* OpCode(i_return_trace) */ +extern Eterm beam_return_to_trace[1]; /* OpCode(i_return_to_trace) */ +extern Eterm beam_return_trace[1]; /* OpCode(i_return_trace) */ +extern Eterm beam_return_time_trace[1]; /* OpCode(i_return_time_trace) */ /* Pseudo export entries. Never filled in with data, only used to yield unique pointers of the correct type. */ @@ -1941,11 +1942,13 @@ trace_proc(Process *c_p, Process *t_p, Eterm what, Eterm data) Eterm* hp; int need; + ERTS_SMP_LC_ASSERT((erts_proc_lc_my_proc_locks(t_p) != 0) || erts_is_system_blocked(0)); if (is_internal_port(t_p->tracer_proc)) { #define LOCAL_HEAP_SIZE (5+5) DeclareTmpHeapNoproc(local_heap,LOCAL_HEAP_SIZE); UseTmpHeapNoproc(LOCAL_HEAP_SIZE); + hp = local_heap; mess = TUPLE4(hp, am_trace, t_p->id, what, data); hp += 5; @@ -2114,6 +2117,7 @@ erts_bif_trace(int bif_index, Process* p, Uint32 flags = 0, flags_meta = 0; int global = !!(erts_bif_trace_flags[bif_index] & BIF_TRACE_AS_GLOBAL); int local = !!(erts_bif_trace_flags[bif_index] & BIF_TRACE_AS_LOCAL); + int time = !!(erts_bif_trace_flags[bif_index] & BIF_TRACE_AS_CALL_TIME); Eterm meta_tracer_pid = NIL; int applying = (I == &(ep->code[3])); /* Yup, the apply code for a bif * is actually in the @@ -2137,6 +2141,17 @@ erts_bif_trace(int bif_index, Process* p, flags_meta = erts_bif_mtrace(p, ep->code+3, args, local, &meta_tracer_pid); } + if (time) { + BpDataTime *bdt = NULL; + BeamInstr *pc = (BeamInstr *)ep->code+3; + + bdt = (BpDataTime *) erts_get_time_break(p, pc); + ASSERT(bdt); + + if (!bdt->pause) { + erts_trace_time_break(p, pc, bdt, ERTS_BP_CALL_TIME_CALL); + } + } /* Restore original continuation pointer (if changed). */ p->cp = cp; @@ -2145,8 +2160,9 @@ erts_bif_trace(int bif_index, Process* p, result = func(p, arg1, arg2, arg3, I); if (applying && (flags & MATCH_SET_RETURN_TO_TRACE)) { - Uint i_return_trace = beam_return_trace[0]; - Uint i_return_to_trace = beam_return_to_trace[0]; + BeamInstr i_return_trace = beam_return_trace[0]; + BeamInstr i_return_to_trace = beam_return_to_trace[0]; + BeamInstr i_return_time_trace = beam_return_time_trace[0]; Eterm *cpp; /* Maybe advance cp to skip trace stack frames */ for (cpp = p->stop; ; cp = cp_val(*cpp++)) { @@ -2154,6 +2170,10 @@ erts_bif_trace(int bif_index, Process* p, /* Skip stack frame variables */ while (is_not_CP(*cpp)) cpp++; cpp += 2; /* Skip return_trace parameters */ + } else if (*cp == i_return_time_trace) { + /* Skip stack frame variables */ + while (is_not_CP(*cpp)) cpp++; + cpp += 1; /* Skip return_time_trace parameters */ } else if (*cp == i_return_to_trace) { /* A return_to trace message is going to be generated * by normal means, so we do not have to. @@ -2727,6 +2747,8 @@ trace_port(Port *t_p, Eterm what, Eterm data) { Eterm mess; Eterm* hp; + ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(t_p) || erts_is_system_blocked(0)); + if (is_internal_port(t_p->tracer_proc)) { #define LOCAL_HEAP_SIZE (5+5) DeclareTmpHeapNoproc(local_heap,LOCAL_HEAP_SIZE); diff --git a/erts/emulator/beam/erl_vm.h b/erts/emulator/beam/erl_vm.h index eeeeb7ccfd..cd63401581 100644 --- a/erts/emulator/beam/erl_vm.h +++ b/erts/emulator/beam/erl_vm.h @@ -84,6 +84,7 @@ #define ErtsHAllocLockCheck(P) \ ERTS_SMP_LC_ASSERT((ERTS_PROC_LOCK_MAIN & erts_proc_lc_my_proc_locks((P))) \ + || ((P)->id == ERTS_INVALID_PID) \ || ((P)->scheduler_data \ && (P) == (P)->scheduler_data->match_pseudo_process) \ || erts_is_system_blocked(0)) diff --git a/erts/emulator/beam/export.h b/erts/emulator/beam/export.h index 87c1d483f4..c604fdf7c3 100644 --- a/erts/emulator/beam/export.h +++ b/erts/emulator/beam/export.h @@ -37,7 +37,7 @@ typedef struct export void* address; /* Pointer to code for function. */ struct binary* match_prog_set; /* Match program for tracing. */ - Eterm fake_op_func_info_for_hipe[2]; /* MUST be just before code[] */ + BeamInstr fake_op_func_info_for_hipe[2]; /* MUST be just before code[] */ /* * code[0]: Tagged atom for module. * code[1]: Tagged atom for function. @@ -52,11 +52,7 @@ typedef struct export * on_load function that has not been run yet. * Otherwise: 0. */ -#if HALFWORD_HEAP BeamInstr code[5]; -#else - Eterm code[5]; -#endif } Export; diff --git a/erts/emulator/beam/global.h b/erts/emulator/beam/global.h index a7990e1799..b4a7a22082 100644 --- a/erts/emulator/beam/global.h +++ b/erts/emulator/beam/global.h @@ -881,6 +881,8 @@ Eterm copy_object(Eterm, Process*); Uint size_object(Eterm); Eterm copy_struct(Eterm, Uint, Eterm**, ErlOffHeap*); Eterm copy_shallow(Eterm*, Uint, Eterm**, ErlOffHeap*); +void move_multi_frags(Eterm** hpp, ErlOffHeap*, ErlHeapFragment* first, + Eterm* refs, unsigned nrefs); #ifdef HYBRID #define RRMA_DEFAULT_SIZE 256 @@ -1078,6 +1080,7 @@ Eterm erts_heap_sizes(Process* p); void erts_offset_off_heap(ErlOffHeap *, Sint, Eterm*, Eterm*); void erts_offset_heap_ptr(Eterm*, Uint, Sint, Eterm*, Eterm*); void erts_offset_heap(Eterm*, Uint, Sint, Eterm*, Eterm*); +void erts_free_heap_frags(Process* p); #ifdef HYBRID int erts_global_garbage_collect(Process*, int, Eterm*, int); @@ -1192,12 +1195,11 @@ erts_smp_port_unlock(Port *prt) { #ifdef ERTS_SMP long refc; + erts_smp_mtx_unlock(prt->lock); refc = erts_smp_atomic_dectest(&prt->refc); ASSERT(refc >= 0); if (refc == 0) erts_port_cleanup(prt); - else - erts_smp_mtx_unlock(prt->lock); #endif } @@ -1720,7 +1722,6 @@ int erts_print_system_version(int to, void *arg, Process *c_p); * Interface to erl_init */ void erl_init(void); -void erts_first_process(Eterm modname, void* code, unsigned size, int argc, char** argv); #define seq_trace_output(token, msg, type, receiver, process) \ seq_trace_output_generic((token), (msg), (type), (receiver), (process), NIL) @@ -1740,8 +1741,10 @@ struct trace_pattern_flags { unsigned int local : 1; /* Local call trace breakpoint */ unsigned int meta : 1; /* Metadata trace breakpoint */ unsigned int call_count : 1; /* Fast call count breakpoint */ + unsigned int call_time : 1; /* Fast call time breakpoint */ }; extern const struct trace_pattern_flags erts_trace_pattern_flags_off; +extern int erts_call_time_breakpoint_tracing; int erts_set_trace_pattern(Eterm* mfa, int specified, Binary* match_prog_set, Binary *meta_match_prog_set, int on, struct trace_pattern_flags, @@ -1785,18 +1788,20 @@ extern void erts_match_prog_foreach_offheap(Binary *b, void (*)(ErlOffHeap *, void *), void *); -#define MATCH_SET_RETURN_TRACE 0x1 /* return trace requested */ -#define MATCH_SET_RETURN_TO_TRACE 0x2 /* Misleading name, it is not actually - set by the match program, but by the - breakpoint functions */ -#define MATCH_SET_EXCEPTION_TRACE 0x4 /* exception trace requested */ +#define MATCH_SET_RETURN_TRACE (0x1) /* return trace requested */ +#define MATCH_SET_RETURN_TO_TRACE (0x2) /* Misleading name, it is not actually + set by the match program, but by the + breakpoint functions */ +#define MATCH_SET_EXCEPTION_TRACE (0x4) /* exception trace requested */ #define MATCH_SET_RX_TRACE (MATCH_SET_RETURN_TRACE|MATCH_SET_EXCEPTION_TRACE) /* * Flag values when tracing bif + * Future note: flag field is 8 bits */ -#define BIF_TRACE_AS_LOCAL 0x1 -#define BIF_TRACE_AS_GLOBAL 0x2 -#define BIF_TRACE_AS_META 0x4 +#define BIF_TRACE_AS_LOCAL (0x1) +#define BIF_TRACE_AS_GLOBAL (0x2) +#define BIF_TRACE_AS_META (0x4) +#define BIF_TRACE_AS_CALL_TIME (0x8) extern erts_driver_t vanilla_driver; extern erts_driver_t spawn_driver; diff --git a/erts/emulator/beam/io.c b/erts/emulator/beam/io.c index 10f1082039..68625801cf 100644 --- a/erts/emulator/beam/io.c +++ b/erts/emulator/beam/io.c @@ -280,10 +280,36 @@ erts_test_next_port(int set, Uint next) return res; } + +static void port_cleanup(Port *prt); + +#ifdef ERTS_SMP + +static void +sched_port_cleanup(void *vprt) +{ + Port *prt = (Port *) vprt; + erts_smp_mtx_lock(prt->lock); + port_cleanup(prt); +} + +#endif + void erts_port_cleanup(Port *prt) { #ifdef ERTS_SMP + if (erts_smp_mtx_trylock(prt->lock) == EBUSY) + erts_schedule_misc_op(sched_port_cleanup, (void *) prt); + else +#endif + port_cleanup(prt); +} + +void +port_cleanup(Port *prt) +{ +#ifdef ERTS_SMP Uint32 port_specific; erts_smp_mtx_t *mtx; #endif diff --git a/erts/emulator/beam/ops.tab b/erts/emulator/beam/ops.tab index 42c84989c6..a2439d5582 100644 --- a/erts/emulator/beam/ops.tab +++ b/erts/emulator/beam/ops.tab @@ -84,6 +84,8 @@ i_trace_breakpoint i_mtrace_breakpoint i_debug_breakpoint i_count_breakpoint +i_time_breakpoint +i_return_time_trace i_return_to_trace i_yield i_global_cons diff --git a/erts/emulator/beam/register.c b/erts/emulator/beam/register.c index 964c10a380..900ebcbbf7 100644 --- a/erts/emulator/beam/register.c +++ b/erts/emulator/beam/register.c @@ -476,8 +476,9 @@ int erts_unregister_name(Process *c_p, * on c_prt. */ - if (!c_p) + if (!c_p) { c_p_locks = 0; + } current_c_p_locks = c_p_locks; restart: @@ -489,9 +490,15 @@ int erts_unregister_name(Process *c_p, if (is_non_value(name)) { /* Unregister current process name */ ASSERT(c_p); - if (c_p->reg) +#ifdef ERTS_SMP + if (current_c_p_locks != c_p_locks) { + erts_smp_proc_lock(c_p, c_p_locks); + current_c_p_locks = c_p_locks; + } +#endif + if (c_p->reg) { r.name = c_p->reg->name; - else { + } else { /* Name got unregistered while main lock was released */ res = 0; goto done; @@ -533,24 +540,25 @@ int erts_unregister_name(Process *c_p, } } else if (rp->p) { - Process* p = rp->p; + #ifdef ERTS_SMP erts_proc_safelock(c_p, current_c_p_locks, c_p_locks, rp->p, - 0, + (c_p == rp->p) ? current_c_p_locks : 0, ERTS_PROC_LOCK_MAIN); current_c_p_locks = c_p_locks; #endif - p->reg = NULL; + rp->p->reg = NULL; + if (IS_TRACED_FL(rp->p, F_TRACE_PROCS)) { + trace_proc(c_p, rp->p, am_unregister, r.name); + } #ifdef ERTS_SMP - if (rp->p != c_p) + if (rp->p != c_p) { erts_smp_proc_unlock(rp->p, ERTS_PROC_LOCK_MAIN); -#endif - if (IS_TRACED_FL(p, F_TRACE_PROCS)) { - trace_proc(c_p, p, am_unregister, r.name); } +#endif } hash_erase(&process_reg, (void*) &r); res = 1; @@ -560,14 +568,17 @@ int erts_unregister_name(Process *c_p, reg_write_unlock(); if (c_prt != port) { - if (port) + if (port) { erts_smp_port_unlock(port); - if (c_prt) + } + if (c_prt) { erts_smp_port_lock(c_prt); + } } #ifdef ERTS_SMP - if (c_p && !current_c_p_locks) + if (c_p && !current_c_p_locks) { erts_smp_proc_lock(c_p, c_p_locks); + } #endif return res; } diff --git a/erts/emulator/beam/sys.h b/erts/emulator/beam/sys.h index 0f20d36167..ca87d3d70f 100644 --- a/erts/emulator/beam/sys.h +++ b/erts/emulator/beam/sys.h @@ -648,7 +648,7 @@ extern char *erts_sys_ddll_error(int code); /* - * System interfaces for startup/sae code (functions found in respective sys.c) + * System interfaces for startup. */ diff --git a/erts/emulator/beam/utils.c b/erts/emulator/beam/utils.c index 51c12a0b69..da6f9ed12f 100644 --- a/erts/emulator/beam/utils.c +++ b/erts/emulator/beam/utils.c @@ -32,6 +32,7 @@ #include "erl_binary.h" #include "erl_bits.h" #include "packet_parser.h" +#include "erl_gc.h" #define ERTS_WANT_DB_INTERNAL__ #include "erl_db.h" #include "erl_threads.h" @@ -125,7 +126,7 @@ erts_heap_alloc(Process* p, Uint need) n = need; bp = MBUF(p); - if (bp != NULL && need <= (bp->size - bp->used_size)) { + if (bp != NULL && need <= (bp->alloc_size - bp->used_size)) { Eterm* ret = bp->mem + bp->used_size; bp->used_size += need; return ret; @@ -158,7 +159,7 @@ erts_heap_alloc(Process* p, Uint need) bp->next = MBUF(p); MBUF(p) = bp; - bp->size = n; + bp->alloc_size = n; bp->used_size = n; MBUF_SIZE(p) += n; bp->off_heap.mso = NULL; @@ -509,7 +510,7 @@ erts_bld_string_n(Uint **hpp, Uint *szp, const char *str, Sint len) if (hpp) { res = NIL; while (--i >= 0) { - res = CONS(*hpp, make_small(str[i]), res); + res = CONS(*hpp, make_small((byte) str[i]), res); *hpp += 2; } } diff --git a/erts/emulator/drivers/common/erl_efile.h b/erts/emulator/drivers/common/erl_efile.h index bbc973d58b..ac95c1f949 100644 --- a/erts/emulator/drivers/common/erl_efile.h +++ b/erts/emulator/drivers/common/erl_efile.h @@ -32,7 +32,8 @@ #define EFILE_MODE_READ_WRITE 3 #define EFILE_MODE_APPEND 4 #define EFILE_COMPRESSED 8 -#define EFILE_NO_TRUNCATE 16 /* Special for reopening on VxWorks */ +#define EFILE_MODE_EXCL 16 +#define EFILE_NO_TRUNCATE 32 /* Special for reopening on VxWorks */ /* * Seek modes for efile_seek(). diff --git a/erts/emulator/drivers/unix/unix_efile.c b/erts/emulator/drivers/unix/unix_efile.c index ea016526ef..0052ac0739 100644 --- a/erts/emulator/drivers/unix/unix_efile.c +++ b/erts/emulator/drivers/unix/unix_efile.c @@ -706,6 +706,9 @@ efile_openfile(Efile_error* errInfo, /* Where to return error codes. */ #endif } + if (flags & EFILE_MODE_EXCL) { + mode |= O_EXCL; + } #ifdef VXWORKS if (*name != '/') { diff --git a/erts/emulator/drivers/win32/win_efile.c b/erts/emulator/drivers/win32/win_efile.c index d5f2b79706..04bd1139f5 100644 --- a/erts/emulator/drivers/win32/win_efile.c +++ b/erts/emulator/drivers/win32/win_efile.c @@ -689,7 +689,11 @@ Sint64* pSize; /* Where to store the size of the file. */ if (flags & EFILE_MODE_APPEND) { crFlags = OPEN_ALWAYS; } - fd = CreateFile(name, access, FILE_SHARE_READ | FILE_SHARE_WRITE, + if (flags & EFILE_MODE_EXCL) { + crFlags = CREATE_NEW; + } + fd = CreateFile(name, access, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, crFlags, FILE_ATTRIBUTE_NORMAL, NULL); /* diff --git a/erts/emulator/hipe/hipe_gc.c b/erts/emulator/hipe/hipe_gc.c index e57e293547..6c9e1d9ba7 100644 --- a/erts/emulator/hipe/hipe_gc.c +++ b/erts/emulator/hipe/hipe_gc.c @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 2004-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 2004-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ /* $Id$ @@ -86,7 +86,7 @@ Eterm *fullsweep_nstack(Process *p, Eterm *n_htop) if (is_boxed(gval)) { Eterm *ptr = boxed_val(gval); Eterm val = *ptr; - if (IS_MOVED(val)) { + if (IS_MOVED_BOXED(val)) { ASSERT(is_boxed(val)); *nsp_i = val; } else if (in_area(ptr, src, src_size) || @@ -96,7 +96,7 @@ Eterm *fullsweep_nstack(Process *p, Eterm *n_htop) } else if (is_list(gval)) { Eterm *ptr = list_val(gval); Eterm val = *ptr; - if (is_non_value(val)) { + if (IS_MOVED_CONS(val)) { *nsp_i = ptr[1]; } else if (in_area(ptr, src, src_size) || in_area(ptr, oh, oh_size)) { @@ -193,7 +193,7 @@ void gensweep_nstack(Process *p, Eterm **ptr_old_htop, Eterm **ptr_n_htop) if (is_boxed(gval)) { Eterm *ptr = boxed_val(gval); Eterm val = *ptr; - if (IS_MOVED(val)) { + if (IS_MOVED_BOXED(val)) { ASSERT(is_boxed(val)); *nsp_i = val; } else if (in_area(ptr, heap, mature_size)) { @@ -205,7 +205,7 @@ void gensweep_nstack(Process *p, Eterm **ptr_old_htop, Eterm **ptr_n_htop) } else if (is_list(gval)) { Eterm *ptr = list_val(gval); Eterm val = *ptr; - if (is_non_value(val)) { + if (IS_MOVED_CONS(val)) { *nsp_i = ptr[1]; } else if (in_area(ptr, heap, mature_size)) { MOVE_CONS(ptr, val, old_htop, nsp_i); diff --git a/erts/emulator/sys/common/erl_poll.c b/erts/emulator/sys/common/erl_poll.c index d268547e1a..09fb6337f7 100644 --- a/erts/emulator/sys/common/erl_poll.c +++ b/erts/emulator/sys/common/erl_poll.c @@ -130,13 +130,18 @@ #define ERTS_POLLSET_IS_POLLED(PS) \ ((int) erts_smp_atomic_read(&(PS)->polled)) -#define ERTS_POLLSET_SET_POLLER_WOKEN_CHK(PS) \ - ((int) erts_smp_atomic_xchg(&(PS)->woken, (long) 1)) -#define ERTS_POLLSET_SET_POLLER_WOKEN(PS) \ - erts_smp_atomic_set(&(PS)->woken, (long) 1) -#define ERTS_POLLSET_UNSET_POLLER_WOKEN(PS) \ - erts_smp_atomic_set(&(PS)->woken, (long) 0) -#define ERTS_POLLSET_IS_POLLER_WOKEN(PS) \ +#define ERTS_POLLSET_SET_POLLER_WOKEN_CHK(PS) set_poller_woken_chk((PS)) +#define ERTS_POLLSET_SET_POLLER_WOKEN(PS) \ +do { \ + ERTS_THR_MEMORY_BARRIER; \ + erts_smp_atomic_set(&(PS)->woken, (long) 1); \ +} while (0) +#define ERTS_POLLSET_UNSET_POLLER_WOKEN(PS) \ +do { \ + erts_smp_atomic_set(&(PS)->woken, (long) 0); \ + ERTS_THR_MEMORY_BARRIER; \ +} while (0) +#define ERTS_POLLSET_IS_POLLER_WOKEN(PS) \ ((int) erts_smp_atomic_read(&(PS)->woken)) #else @@ -194,13 +199,18 @@ #else -#define ERTS_POLLSET_UNSET_INTERRUPTED_CHK(PS) \ - ((int) erts_smp_atomic_xchg(&(PS)->interrupt, (long) 0)) -#define ERTS_POLLSET_UNSET_INTERRUPTED(PS) \ - erts_smp_atomic_set(&(PS)->interrupt, (long) 0) -#define ERTS_POLLSET_SET_INTERRUPTED(PS) \ - erts_smp_atomic_set(&(PS)->interrupt, (long) 1) -#define ERTS_POLLSET_IS_INTERRUPTED(PS) \ +#define ERTS_POLLSET_UNSET_INTERRUPTED_CHK(PS) unset_interrupted_chk((PS)) +#define ERTS_POLLSET_UNSET_INTERRUPTED(PS) \ +do { \ + erts_smp_atomic_set(&(PS)->interrupt, (long) 0); \ + ERTS_THR_MEMORY_BARRIER; \ +} while (0) +#define ERTS_POLLSET_SET_INTERRUPTED(PS) \ +do { \ + ERTS_THR_MEMORY_BARRIER; \ + erts_smp_atomic_set(&(PS)->interrupt, (long) 1); \ +} while (0) +#define ERTS_POLLSET_IS_INTERRUPTED(PS) \ ((int) erts_smp_atomic_read(&(PS)->interrupt)) #endif @@ -336,16 +346,30 @@ struct ErtsPollSet_ { #endif }; -#if ERTS_POLL_ASYNC_INTERRUPT_SUPPORT && !defined(ERTS_SMP) - static ERTS_INLINE int unset_interrupted_chk(ErtsPollSet ps) { + int res; +#if ERTS_POLL_ASYNC_INTERRUPT_SUPPORT && !defined(ERTS_SMP) /* This operation isn't atomic, but we have no need at all for an atomic operation here... */ - int res = ps->interrupt; + res = ps->interrupt; ps->interrupt = 0; +#else + res = (int) erts_smp_atomic_xchg(&ps->interrupt, (long) 0); + ERTS_THR_MEMORY_BARRIER; +#endif return res; + +} + +#ifdef ERTS_SMP + +static ERTS_INLINE int +set_poller_woken_chk(ErtsPollSet ps) +{ + ERTS_THR_MEMORY_BARRIER; + return (int) erts_smp_atomic_xchg(&ps->woken, (long) 1); } #endif diff --git a/erts/emulator/sys/unix/erl9_start.c b/erts/emulator/sys/unix/erl9_start.c deleted file mode 100644 index 578062d7e2..0000000000 --- a/erts/emulator/sys/unix/erl9_start.c +++ /dev/null @@ -1,130 +0,0 @@ -/* - * %CopyrightBegin% - * - * Copyright Ericsson AB 2002-2009. All Rights Reserved. - * - * The contents of this file are subject to the Erlang Public License, - * Version 1.1, (the "License"); you may not use this file except in - * compliance with the License. You should have received a copy of the - * Erlang Public License along with this software. If not, it can be - * retrieved online at http://www.erlang.org/. - * - * Software distributed under the License is distributed on an "AS IS" - * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See - * the License for the specific language governing rights and limitations - * under the License. - * - * %CopyrightEnd% - */ -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif -#include "sys.h" -#include "erl_vm.h" -#include "global.h" -#include <stdio.h> -#include <string.h> -#include <stdlib.h> - -/* - * XXX This is a temporary dummy to make sys.c happy until we'll rewrite it. - */ -unsigned preloaded_size_ring0 = 1; -unsigned char preloaded_ring0[1] = {0}; - -Preload pre_loaded[] = { - {"ring0", 1, preloaded_ring0}, - {0, 0, 0} -}; - -int -main(int argc, char** argv) -{ - char sbuf[1024]; - struct { - void* p; - int sz; - } bins[2]; - int bin_num = 0; - FILE* fp; - char* progname = argv[0]; - char* eq; - - argv++, argc--; - - if (argc > 0 && argv[0][0] == '-') { - argv++, argc--; - } - if (argc < 1) { - abort(); - } - if ((fp = fopen(argv[0], "r")) == NULL) { - abort(); - } - - /* Needs to be called before any memory allocation */ - erts_short_init(); - - while (fgets(sbuf, sizeof sbuf, fp)) { - if (sbuf[0] == '#') { - continue; /* Comment */ - } else if (sbuf[0] == 'e' && strncmp("exec", sbuf, 4) == 0) { - continue; /* Comment ;-) */ - } else if ((eq = strchr(sbuf, '=')) != NULL) { - char* val; - char* p = strchr(sbuf, '\n'); - if (p) { - *p = '\0'; - } - *eq = '\0'; - val = erts_read_env(sbuf); - if (val == NULL) { - *eq = '='; - erts_sys_putenv(sbuf, eq - &sbuf[0]); - } - erts_free_read_env(val); - } else if (sbuf[0] == ':' && '0' <= sbuf[1] && sbuf[1] <= '9') { - int load_size = atoi(sbuf+1); - void* bin; - - bin = malloc(load_size); - if (fread(bin, 1, load_size, fp) != load_size) { - abort(); - } - bins[bin_num].p = bin; - bins[bin_num].sz = load_size; - bin_num++; - } else if (strcmp(sbuf, "--end--\n") == 0) { - int rval; - Eterm mod = NIL; - char *val; - - fclose(fp); - - if (bin_num != 2) { - abort(); - } - - val = erts_read_env("ERLBREAKHANDLER"); - if (val) { - init_break_handler(); - } - erts_free_read_env(val); - - if ((rval = erts_load_module(NULL, 0, NIL, &mod, bins[0].p, bins[0].sz)) < 0) { - fprintf(stderr, "%s: Load of initial module failed: %d\n", - progname, rval); - abort(); - } - erts_first_process(mod, bins[1].p, bins[1].sz, argc, argv); - free(bins[0].p); - free(bins[1].p); - process_main(); - abort(); - } else { - fprintf(stderr, "%s: bad line: %s\n", progname, sbuf); - abort(); - } - } - abort(); -} diff --git a/erts/emulator/sys/unix/sys.c b/erts/emulator/sys/unix/sys.c index 50b208848f..151fa06e8e 100644 --- a/erts/emulator/sys/unix/sys.c +++ b/erts/emulator/sys/unix/sys.c @@ -457,6 +457,10 @@ thr_create_prepare_child(void *vtcdp) { erts_thr_create_data_t *tcdp = (erts_thr_create_data_t *) vtcdp; +#ifdef ERTS_ENABLE_LOCK_COUNT + erts_lcnt_thread_setup(); +#endif + #ifndef NO_FPE_SIGNALS /* * We do not want fp exeptions in other threads than the diff --git a/erts/emulator/sys/win32/erl_poll.c b/erts/emulator/sys/win32/erl_poll.c index d816cc2c07..a766fe9575 100644 --- a/erts/emulator/sys/win32/erl_poll.c +++ b/erts/emulator/sys/win32/erl_poll.c @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 2007-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 2007-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ #ifdef HAVE_CONFIG_H @@ -304,24 +304,51 @@ struct ErtsPollSet_ { erts_smp_atomic_set(&(PS)->polled, (long) 0) #define ERTS_POLLSET_IS_POLLED(PS) \ ((int) erts_smp_atomic_read(&(PS)->polled)) -#define ERTS_POLLSET_SET_POLLER_WOKEN_CHK(PS) \ - ((int) erts_smp_atomic_xchg(&(PS)->woken, (long) 1)) -#define ERTS_POLLSET_SET_POLLER_WOKEN(PS) \ - erts_smp_atomic_set(&(PS)->woken, (long) 1) -#define ERTS_POLLSET_UNSET_POLLER_WOKEN(PS) \ - erts_smp_atomic_set(&(PS)->woken, (long) 0) -#define ERTS_POLLSET_IS_POLLER_WOKEN(PS) \ + +#define ERTS_POLLSET_SET_POLLER_WOKEN_CHK(PS) set_poller_woken_chk((PS)) +#define ERTS_POLLSET_SET_POLLER_WOKEN(PS) \ +do { \ + ERTS_THR_MEMORY_BARRIER; \ + erts_smp_atomic_set(&(PS)->woken, (long) 1); \ +} while (0) +#define ERTS_POLLSET_UNSET_POLLER_WOKEN(PS) \ +do { \ + erts_smp_atomic_set(&(PS)->woken, (long) 0); \ + ERTS_THR_MEMORY_BARRIER; \ +} while (0) +#define ERTS_POLLSET_IS_POLLER_WOKEN(PS) \ ((int) erts_smp_atomic_read(&(PS)->woken)) -#define ERTS_POLLSET_UNSET_INTERRUPTED_CHK(PS) \ - ((int) erts_smp_atomic_xchg(&(PS)->interrupt, (long) 0)) -#define ERTS_POLLSET_UNSET_INTERRUPTED(PS) \ - erts_smp_atomic_set(&(PS)->interrupt, (long) 0) -#define ERTS_POLLSET_SET_INTERRUPTED(PS) \ - erts_smp_atomic_set(&(PS)->interrupt, (long) 1) -#define ERTS_POLLSET_IS_INTERRUPTED(PS) \ +#define ERTS_POLLSET_UNSET_INTERRUPTED_CHK(PS) unset_interrupted_chk((PS)) +#define ERTS_POLLSET_UNSET_INTERRUPTED(PS) \ +do { \ + erts_smp_atomic_set(&(PS)->interrupt, (long) 0); \ + ERTS_THR_MEMORY_BARRIER; \ +} while (0) +#define ERTS_POLLSET_SET_INTERRUPTED(PS) \ +do { \ + ERTS_THR_MEMORY_BARRIER; \ + erts_smp_atomic_set(&(PS)->interrupt, (long) 1); \ +} while (0) +#define ERTS_POLLSET_IS_INTERRUPTED(PS) \ ((int) erts_smp_atomic_read(&(PS)->interrupt)) +static ERTS_INLINE int +unset_interrupted_chk(ErtsPollSet ps) +{ + int res = (int) erts_smp_atomic_xchg(&ps->interrupt, (long) 0); + ERTS_THR_MEMORY_BARRIER; + return res; + +} + +static ERTS_INLINE int +set_poller_woken_chk(ErtsPollSet ps) +{ + ERTS_THR_MEMORY_BARRIER; + return (int) erts_smp_atomic_xchg(&ps->woken, (long) 1); +} + #else #define ERTS_POLLSET_LOCK(PS) diff --git a/erts/emulator/sys/win32/sys.c b/erts/emulator/sys/win32/sys.c index 65719a91d6..bd02cf85eb 100644 --- a/erts/emulator/sys/win32/sys.c +++ b/erts/emulator/sys/win32/sys.c @@ -2986,7 +2986,15 @@ static void ethr_internal_free(void *ptr) { erts_free(ERTS_ALC_T_ETHR_INTERNAL, ptr); } -#endif + +#ifdef ERTS_ENABLE_LOCK_COUNT +static void +thr_create_prepare_child(void *vtcdp) +{ + erts_lcnt_thread_setup(); +} +#endif /* ERTS_ENABLE_LOCK_COUNT */ +#endif /* USE_THREADS */ void erts_sys_pre_init(void) @@ -3000,6 +3008,11 @@ erts_sys_pre_init(void) eid.alloc = ethr_internal_alloc; eid.realloc = ethr_internal_realloc; eid.free = ethr_internal_free; + +#ifdef ERTS_ENABLE_LOCK_COUNT + eid.thread_create_child_func = thr_create_prepare_child; +#endif + erts_thr_init(&eid); #ifdef ERTS_ENABLE_LOCK_COUNT erts_lcnt_init(); @@ -3010,12 +3023,6 @@ erts_sys_pre_init(void) erts_sys_env_init(); } -/* - * the last two only used for standalone erlang - * they should are used by sae_main in beam dll to - * enable standalone execution via erl_api-routines - */ - void noinherit_std_handle(DWORD type) { HANDLE h = GetStdHandle(type); diff --git a/erts/emulator/test/Makefile b/erts/emulator/test/Makefile index 97b94c466f..5ec17a5e2a 100644 --- a/erts/emulator/test/Makefile +++ b/erts/emulator/test/Makefile @@ -101,6 +101,7 @@ MODULES= \ trace_local_SUITE \ trace_meta_SUITE \ trace_call_count_SUITE \ + trace_call_time_SUITE \ scheduler_SUITE \ old_scheduler_SUITE \ z_SUITE \ diff --git a/erts/emulator/test/bif_SUITE.erl b/erts/emulator/test/bif_SUITE.erl index cfbc5dfe81..15c09d9158 100644 --- a/erts/emulator/test/bif_SUITE.erl +++ b/erts/emulator/test/bif_SUITE.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2005-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2005-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -308,6 +308,18 @@ min_max(Config) when is_list(Config) -> ?line 42.0 = erlang:min(42.0, 42), ?line 42.0 = erlang:max(42.0, 42), + %% And now (R14) they are also autoimported! + ?line a = min(id(a), a), + ?line a = min(id(a), b), + ?line a = min(id(b), a), + ?line b = min(id(b), b), + ?line a = max(id(a), a), + ?line b = max(id(a), b), + ?line b = max(id(b), a), + ?line b = max(id(b), b), + + ?line 42.0 = min(42.0, 42), + ?line 42.0 = max(42.0, 42), ok. diff --git a/erts/emulator/test/binary_SUITE.erl b/erts/emulator/test/binary_SUITE.erl index 7ecc31aa29..77d2579848 100644 --- a/erts/emulator/test/binary_SUITE.erl +++ b/erts/emulator/test/binary_SUITE.erl @@ -438,11 +438,11 @@ terms(Config) when is_list(Config) -> ok end, Term = binary_to_term(Bin), - Term = erlang:binary_to_term(Bin, [safe]), + Term = binary_to_term(Bin, [safe]), Unaligned = make_unaligned_sub_binary(Bin), Term = binary_to_term(Unaligned), - Term = erlang:binary_to_term(Unaligned, []), - Term = erlang:binary_to_term(Bin, [safe]), + Term = binary_to_term(Unaligned, []), + Term = binary_to_term(Bin, [safe]), BinC = erlang:term_to_binary(Term, [compressed]), Term = binary_to_term(BinC), true = size(BinC) =< size(Bin), @@ -542,7 +542,7 @@ bad_bin_to_term(BadBin) -> {'EXIT',{badarg,_}} = (catch binary_to_term(BadBin)). bad_bin_to_term(BadBin,Opts) -> - {'EXIT',{badarg,_}} = (catch erlang:binary_to_term(BadBin,Opts)). + {'EXIT',{badarg,_}} = (catch binary_to_term(BadBin,Opts)). safe_binary_to_term2(doc) -> "Test safety options for binary_to_term/2"; safe_binary_to_term2(Config) when is_list(Config) -> @@ -553,7 +553,7 @@ safe_binary_to_term2(Config) when is_list(Config) -> BadRef = <<131,114,0,3,BadHostAtom/binary,0,<<0,0,0,255>>/binary, Empty/binary,Empty/binary>>, ?line bad_bin_to_term(BadRef, [safe]), % good ref, with a bad atom - ?line fullsweep_after = erlang:binary_to_term(<<131,100,0,15,"fullsweep_after">>, [safe]), % should be a good atom + ?line fullsweep_after = binary_to_term(<<131,100,0,15,"fullsweep_after">>, [safe]), % should be a good atom BadExtFun = <<131,113,100,0,4,98,108,117,101,100,0,4,109,111,111,110,97,3>>, ?line bad_bin_to_term(BadExtFun, [safe]), ok. diff --git a/erts/emulator/test/busy_port_SUITE.erl b/erts/emulator/test/busy_port_SUITE.erl index 9b16170293..7350aef4ec 100644 --- a/erts/emulator/test/busy_port_SUITE.erl +++ b/erts/emulator/test/busy_port_SUITE.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1997-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -182,7 +182,7 @@ system_monitor(Config) when is_list(Config) -> ?line Master ! {Owner, {command, "u"}}, ?line {Busy,beta} = rec(Void), ?line Void = rec(Void), - ?line NewMonitor = erlang:system_monitor(OldMonitor), + ?line _NewMonitor = erlang:system_monitor(OldMonitor), ?line OldMonitor = erlang:system_monitor(), ?line OldMonitor = erlang:system_monitor(OldMonitor), %% @@ -361,7 +361,6 @@ soft_busy_driver(Config) when is_list(Config) -> hs_test(Config, false). hs_test(Config, HardBusy) when is_list(Config) -> - ?line Me = self(), ?line DrvName = case HardBusy of true -> 'hard_busy_drv'; false -> 'soft_busy_drv' @@ -479,7 +478,7 @@ hs_busy_pcmd(Prt, Opts, StartFun, EndFun) -> Tester ! {self(), doing_port_command}, Start = os:timestamp(), Res = try {return, - erlang:port_command(Prt, [], Opts)} + port_command(Prt, [], Opts)} catch Exception:Error -> {Exception, Error} end, End = os:timestamp(), diff --git a/erts/emulator/test/nif_SUITE.erl b/erts/emulator/test/nif_SUITE.erl index 161e38c68a..888bf582d8 100644 --- a/erts/emulator/test/nif_SUITE.erl +++ b/erts/emulator/test/nif_SUITE.erl @@ -20,24 +20,39 @@ -module(nif_SUITE). %%-define(line_trace,true). -%%-define(CHECK(Exp,Got), ?line check(Exp,Got,?LINE)). --define(CHECK(Exp,Got), ?line Exp = Got). +-define(CHECK(Exp,Got), check(Exp,Got,?LINE)). +%%-define(CHECK(Exp,Got), ?line Exp = Got). -include("test_server.hrl"). --export([all/1, fin_per_testcase/2, basic/1, reload/1, upgrade/1, heap_frag/1, +-export([all/1, + %%init_per_testcase/2, + fin_per_testcase/2, basic/1, reload/1, upgrade/1, heap_frag/1, types/1, many_args/1, binaries/1, get_string/1, get_atom/1, api_macros/1, - from_array/1, iolist_as_binary/1, resource/1, resource_takeover/1, - threading/1, neg/1, is_checks/1, get_length/1, make_atom/1, make_string/1]). + from_array/1, iolist_as_binary/1, resource/1, resource_binary/1, resource_takeover/1, + threading/1, send/1, send2/1, send_threaded/1, neg/1, is_checks/1, + get_length/1, make_atom/1, make_string/1]). -export([many_args_100/100]). + + +%% -export([lib_version/0,call_history/0,hold_nif_mod_priv_data/1,nif_mod_call_history/0, +%% list_seq/1,type_test/0,tuple_2_list/1,is_identical/2,compare/2, +%% clone_bin/1,make_sub_bin/3,string_to_bin/2,atom_to_bin/2,macros/1, +%% tuple_2_list_and_tuple/1,iolist_2_bin/1,get_resource_type/1,alloc_resource/2, +%% make_resource/1,get_resource/2,release_resource/1,last_resource_dtor_call/0, +%% make_new_resource/2,make_new_resource_binary/1,send_list_seq/2,send_new_blob/2, +%% alloc_msgenv/0,clear_msgenv/1,grow_blob/2,send_blob/2,send_blob_thread/3, +%% join_send_thread/1]). + + -define(nif_stub,nif_stub_error(?LINE)). all(suite) -> [basic, reload, upgrade, heap_frag, types, many_args, binaries, get_string, - get_atom, api_macros, from_array, iolist_as_binary, resource, - resource_takeover, threading, neg, is_checks, get_length, make_atom, - make_string]. + get_atom, api_macros, from_array, iolist_as_binary, resource, resource_binary, + resource_takeover, threading, send, send2, send_threaded, neg, is_checks, + get_length, make_atom, make_string]. %%init_per_testcase(_Case, Config) -> %% ?line Dog = ?t:timetrap(?t:seconds(60*60*24)), @@ -474,12 +489,51 @@ resource_new_do2(Type) -> {{PtrA,BinA}, {ResB,PtrB,BinB}}. resource_neg(TypeA) -> + resource_neg_do(TypeA), + + catch exit(42), % dummy exception to purge saved stacktraces from earlier exception + erlang:garbage_collect(), + ?line {_,_,2} = last_resource_dtor_call(), + ok. + +resource_neg_do(TypeA) -> TypeB = get_resource_type(1), - Aptr = alloc_resource(TypeA, <<"Arnold">>), - Bptr = alloc_resource(TypeB, <<"Bobo">>), - ?line {'EXIT',{badarg,_}} = (catch get_resource(TypeA, Bptr)), - ?line {'EXIT',{badarg,_}} = (catch get_resource(TypeB, Aptr)), + ResA = make_new_resource(TypeA, <<"Arnold">>), + ResB= make_new_resource(TypeB, <<"Bobo">>), + ?line {'EXIT',{badarg,_}} = (catch get_resource(TypeA, ResB)), + ?line {'EXIT',{badarg,_}} = (catch get_resource(TypeB, ResA)), + ok. + +resource_binary(doc) -> ["Test enif_make_resource_binary"]; +resource_binary(suite) -> []; +resource_binary(Config) when is_list(Config) -> + ?line ensure_lib_loaded(Config, 1), + ?line {Ptr,Bin} = resource_binary_do(), + erlang:garbage_collect(), + Last = last_resource_dtor_call(), + ?CHECK({Ptr,Bin,1}, Last), ok. + +resource_binary_do() -> + Bin = <<"Hej Hopp i lingonskogen">>, + ?line {Ptr,ResBin1} = make_new_resource_binary(Bin), + ?line ResBin1 = Bin, + ?line ResInfo = {Ptr,_} = get_resource(binary_resource_type,ResBin1), + + Papa = self(), + Forwarder = spawn_link(fun() -> forwarder(Papa) end), + io:format("sending to forwarder pid=~p\n",[Forwarder]), + Forwarder ! ResBin1, + ResBin2 = receive_any(), + ?line ResBin2 = ResBin1, + ?line ResInfo = get_resource(binary_resource_type,ResBin2), + Forwarder ! terminate, + ?line {Forwarder, 1} = receive_any(), + erlang:garbage_collect(), + ?line ResInfo = get_resource(binary_resource_type,ResBin1), + ?line ResInfo = get_resource(binary_resource_type,ResBin2), + ResInfo. + -define(RT_CREATE,1). -define(RT_TAKEOVER,2). @@ -744,6 +798,124 @@ threading(Config) when is_list(Config) -> ?line ok = tester:load_nif_lib(Config, "tsd"), ?line ok = tester:run(). + +send(doc) -> ["Test NIF message sending"]; +send(Config) when is_list(Config) -> + ensure_lib_loaded(Config), + + N = 1500, + List = lists:seq(1,N), + ?line {ok,1} = send_list_seq(N, self), + ?line {ok,1} = send_list_seq(N, self()), + ?line List = receive_any(), + ?line List = receive_any(), + Papa = self(), + spawn_link(fun() -> ?line {ok,1} = send_list_seq(N, Papa) end), + ?line List = receive_any(), + + ?line {ok, 1, BlobS} = send_new_blob(self(), other_term()), + ?line BlobR = receive_any(), + io:format("Sent ~p\nGot ~p\n", [BlobS, BlobR]), + ?line BlobR = BlobS, + + %% send to dead pid + {DeadPid, DeadMon} = spawn_monitor(fun() -> void end), + ?line {'DOWN', DeadMon, process, DeadPid, normal} = receive_any(), + {ok,0} = send_list_seq(7, DeadPid), + ok. + +send2(doc) -> ["More NIF message sending"]; +send2(Config) when is_list(Config) -> + ensure_lib_loaded(Config), + + send2_do1(fun send_blob_dbg/2), + ok. + +send_threaded(doc) -> ["Send msg from user thread"]; +send_threaded(Config) when is_list(Config) -> + case erlang:system_info(smp_support) of + true -> + send2_do1(fun(ME,To) -> send_blob_thread_dbg(ME,To,join) end), + send2_do1(fun(ME,To) -> send_blob_thread_and_join(ME,To) end), + ok; + false -> + {skipped,"No threaded send on non-SMP"} + end. + + +send2_do1(SendBlobF) -> + io:format("sending to self=~p\n",[self()]), + send2_do2(SendBlobF, self()), + + Papa = self(), + Forwarder = spawn_link(fun() -> forwarder(Papa) end), + io:format("sending to forwarder pid=~p\n",[Forwarder]), + send2_do2(SendBlobF, Forwarder), + Forwarder ! terminate, + ?line {Forwarder, 4} = receive_any(), + ok. + +send2_do2(SendBlobF, To) -> + MsgEnv = alloc_msgenv(), + repeat(50, fun(_) -> grow_blob(MsgEnv,other_term()) end, []), + ?line {ok,1,Blob0} = SendBlobF(MsgEnv, To), + ?line Blob1 = receive_any(), + ?line Blob1 = Blob0, + + clear_msgenv(MsgEnv), + repeat(50, fun(_) -> grow_blob(MsgEnv,other_term()) end, []), + ?line {ok,1,Blob2} = SendBlobF(MsgEnv, To), + ?line Blob3 = receive_any(), + ?line Blob3 = Blob2, + + clear_msgenv(MsgEnv), + repeat(50, fun(_) -> grow_blob(MsgEnv,other_term()) end, []), + + clear_msgenv(MsgEnv), + repeat(50, fun(_) -> grow_blob(MsgEnv,other_term()) end, []), + ?line {ok,1,Blob4} = SendBlobF(MsgEnv, To), + ?line Blob5 = receive_any(), + ?line Blob5 = Blob4, + + clear_msgenv(MsgEnv), + clear_msgenv(MsgEnv), + repeat(50, fun(_) -> grow_blob(MsgEnv,other_term()) end, []), + ?line {ok,1,Blob6} = SendBlobF(MsgEnv, To), + ?line Blob7 = receive_any(), + ?line Blob7 = Blob6, + + ok. + + +send_blob_thread_and_join(MsgEnv, To) -> + ?line {ok,Blob} = send_blob_thread_dbg(MsgEnv, To, no_join), + ?line {ok,SendRes} = join_send_thread(MsgEnv), + {ok,SendRes,Blob}. + +send_blob_dbg(MsgEnv, To) -> + Ret = send_blob(MsgEnv, To), + %%io:format("send_blob to ~p returned ~p\n",[To,Ret]), + Ret. + +send_blob_thread_dbg(MsgEnv, To, Join) -> + Ret = send_blob_thread(MsgEnv, To, Join), + %%io:format("send_blob_thread to ~p Join=~p returned ~p\n",[To,Join,Ret]), + Ret. + + +forwarder(To) -> + forwarder(To, 0). +forwarder(To, N) -> + case receive_any() of + terminate -> + To ! {self(), N}; + Msg -> + To ! Msg, + forwarder(To, N+1) + end. + +other_term() -> + {fun(X,Y) -> X*Y end, make_ref()}. neg(doc) -> ["Negative testing of load_nif"]; neg(Config) when is_list(Config) -> @@ -795,10 +967,11 @@ make_atom(Config) when is_list(Config) -> make_string(Config) when is_list(Config) -> ?line ensure_lib_loaded(Config, 1), ?line Strings = make_strings(), - ?line 4 = size(Strings), + ?line 5 = size(Strings), A0String = "a0string", A0String0 = [$a,0,$s,$t,$r,$i,$n,$g,0], - ?line Strings = {A0String,A0String,A0String,A0String0}. + AStringWithAccents = [$E,$r,$l,$a,$n,$g,$ ,16#e4,$r,$ ,$e,$t,$t,$ ,$g,$e,$n,$e,$r,$e,$l,$l,$t,$ ,$p,$r,$o,$g,$r,$a,$m,$s,$p,$r,16#e5,$k], + ?line Strings = {A0String,A0String,A0String,A0String0, AStringWithAccents}. tmpmem() -> case erlang:system_info({allocator,temp_alloc}) of @@ -848,13 +1021,18 @@ call(Pid,Cmd) -> receive_any() -> receive M -> M end. -%% check(Exp,Got,Line) -> -%% case Got of -%% Exp -> Exp; -%% _ -> -%% io:format("CHECK at ~p: Expected ~p but got ~p\n",[Line,Exp,Got]), -%% Got -%% end. +repeat(0, _, Arg) -> + Arg; +repeat(N, Fun, Arg0) -> + repeat(N-1, Fun, Fun(Arg0)). + +check(Exp,Got,Line) -> + case Got of + Exp -> Exp; + _ -> + io:format("CHECK at ~p: Expected ~p but got ~p\n",[Line,Exp,Got]), + Got + end. %% The NIFs: @@ -886,6 +1064,16 @@ check_is(_,_,_,_,_,_,_,_,_,_) -> ?nif_stub. length_test(_,_,_,_,_) -> ?nif_stub. make_atoms() -> ?nif_stub. make_strings() -> ?nif_stub. +make_new_resource_binary(_) -> ?nif_stub. +send_list_seq(_,_) -> ?nif_stub. +send_new_blob(_,_) -> ?nif_stub. +alloc_msgenv() -> ?nif_stub. +clear_msgenv(_) -> ?nif_stub. +grow_blob(_,_) -> ?nif_stub. +send_blob(_,_) -> ?nif_stub. +send_blob_thread(_,_,_) -> ?nif_stub. +join_send_thread(_) -> ?nif_stub. + nif_stub_error(Line) -> exit({nif_not_loaded,module,?MODULE,line,Line}). diff --git a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c index 73226a09cb..c8cd323b7e 100644 --- a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c +++ b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c @@ -28,6 +28,12 @@ static int static_cntA; /* zero by default */ static int static_cntB = NIF_SUITE_LIB_VER * 100; +static ERL_NIF_TERM atom_self; +static ERL_NIF_TERM atom_ok; +static ERL_NIF_TERM atom_join; +static ERL_NIF_TERM atom_binary_resource_type; + + typedef struct { int ref_cnt; @@ -38,7 +44,7 @@ typedef struct void add_call(ErlNifEnv* env, PrivData* data, const char* func_name) { - CallInfo* call = enif_alloc(env, sizeof(CallInfo)+strlen(func_name)); + CallInfo* call = enif_alloc(sizeof(CallInfo)+strlen(func_name)); strcpy(call->func_name, func_name); call->lib_ver = NIF_SUITE_LIB_VER; call->next = data->call_history; @@ -49,7 +55,7 @@ void add_call(ErlNifEnv* env, PrivData* data, const char* func_name) call->arg_sz = 0; } -#define ADD_CALL(FUNC_NAME) add_call(env, enif_get_data(env),FUNC_NAME) +#define ADD_CALL(FUNC_NAME) add_call(env, enif_priv_data(env),FUNC_NAME) static void* resource_dtor_last = NULL; static unsigned resource_dtor_last_sz = 0; @@ -60,15 +66,24 @@ static void resource_dtor(ErlNifEnv* env, void* obj) { resource_dtor_last = obj; resource_dtor_cnt++; - resource_dtor_last_sz = enif_sizeof_resource(env, obj); + resource_dtor_last_sz = enif_sizeof_resource(obj); assert(resource_dtor_last_sz <= sizeof(resource_dtor_last_data)); memcpy(resource_dtor_last_data, obj, resource_dtor_last_sz); } +static ErlNifResourceType* msgenv_resource_type; +static void msgenv_dtor(ErlNifEnv* env, void* obj); + +static ErlNifResourceType* binary_resource_type; +static void binary_resource_dtor(ErlNifEnv* env, void* obj); +struct binary_resource { + unsigned char* data; + unsigned size; +}; + static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) { - /*ERL_NIF_TERM head, tail;*/ - PrivData* data = enif_alloc(env, sizeof(PrivData)); + PrivData* data = enif_alloc(sizeof(PrivData)); assert(data != NULL); data->ref_cnt = 1; data->call_history = NULL; @@ -76,41 +91,71 @@ static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) add_call(env, data, "load"); - /* - head = load_info; - data->rt_cnt = 0; - for (head=load_info; enif_get_list_cell(env,load_info,&head,&tail); - head=tail) { - char buf[20]; - int n = enif_get_string(env,head,buf,sizeof(buf)); - assert(n > 0); - assert(i < sizeof(data->rt_arr)/sizeof(*data->rt_arr)); - data->rt_arr[data->rt_cnt++].t = enif_create_resource_type(env,buf,resource_dtor, - ERL_NIF_RT_CREATE,NULL); - } - assert(enif_is_empty_list(env,head)); - */ - data->rt_arr[0].t = enif_open_resource_type(env,"Gold",resource_dtor, + data->rt_arr[0].t = enif_open_resource_type(env,NULL,"Gold",resource_dtor, ERL_NIF_RT_CREATE,NULL); - data->rt_arr[1].t = enif_open_resource_type(env,"Silver",resource_dtor, + data->rt_arr[1].t = enif_open_resource_type(env,NULL,"Silver",resource_dtor, ERL_NIF_RT_CREATE,NULL); + binary_resource_type = enif_open_resource_type(env,NULL,"nif_SUITE.binary", + binary_resource_dtor, + ERL_NIF_RT_CREATE, NULL); + + msgenv_resource_type = enif_open_resource_type(env,NULL,"nif_SUITE.msgenv", + msgenv_dtor, + ERL_NIF_RT_CREATE, NULL); + + atom_self = enif_make_atom(env,"self"); + atom_ok = enif_make_atom(env,"ok"); + atom_join = enif_make_atom(env,"join"); + atom_binary_resource_type = enif_make_atom(env,"binary_resource_type"); + *priv_data = data; return 0; } +static void resource_takeover(ErlNifEnv* env, PrivData* priv) +{ + ErlNifResourceFlags tried; + ErlNifResourceType* rt; + rt = enif_open_resource_type(env, NULL, "Gold", resource_dtor, + ERL_NIF_RT_TAKEOVER, &tried); + assert(rt == priv->rt_arr[0].t); + assert(tried == ERL_NIF_RT_TAKEOVER); + rt = enif_open_resource_type(env, NULL, "Silver", resource_dtor, + ERL_NIF_RT_TAKEOVER, &tried); + assert(rt == priv->rt_arr[1].t); + assert(tried == ERL_NIF_RT_TAKEOVER); + + rt = enif_open_resource_type(env, NULL, "nif_SUITE.binary", binary_resource_dtor, + ERL_NIF_RT_TAKEOVER, &tried); + assert(rt != NULL); + assert(tried == ERL_NIF_RT_TAKEOVER); + assert(binary_resource_type==NULL || binary_resource_type == rt); + binary_resource_type = rt; + + rt = enif_open_resource_type(env, NULL, "nif_SUITE.msgenv", msgenv_dtor, + ERL_NIF_RT_TAKEOVER, &tried); + assert(rt != NULL); + assert(tried == ERL_NIF_RT_TAKEOVER); + assert(msgenv_resource_type==NULL || msgenv_resource_type == rt); + msgenv_resource_type = rt; +} + static int reload(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) { - add_call(env, *priv_data, "reload"); + PrivData* priv = (PrivData*) *priv_data; + add_call(env, priv, "reload"); + resource_takeover(env,priv); return 0; } static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info) { - PrivData* data = *old_priv_data; - add_call(env, data, "upgrade"); - data->ref_cnt++; - *priv_data = *old_priv_data; + PrivData* priv = (PrivData*) *old_priv_data; + add_call(env, priv, "upgrade"); + priv->ref_cnt++; + *priv_data = *old_priv_data; + resource_takeover(env,priv); return 0; } @@ -119,7 +164,10 @@ static void unload(ErlNifEnv* env, void* priv_data) PrivData* data = priv_data; add_call(env, data, "unload"); if (--data->ref_cnt == 0) { - enif_free(env, priv_data); + if (data->nif_mod != NULL) { + NifModPrivData_release(data->nif_mod); + } + enif_free(priv_data); } } @@ -149,28 +197,28 @@ static ERL_NIF_TERM make_call_history(ErlNifEnv* env, CallInfo** headp) enif_make_int(env,call->static_cntB)); list = enif_make_list_cell(env, tpl, list); *headp = call->next; - enif_free(env,call); + enif_free(call); } return list; } static ERL_NIF_TERM call_history(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - PrivData* data = (PrivData*) enif_get_data(env); + PrivData* data = (PrivData*) enif_priv_data(env); return make_call_history(env,&data->call_history); } static ERL_NIF_TERM hold_nif_mod_priv_data(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - PrivData* data = (PrivData*) enif_get_data(env); + PrivData* data = (PrivData*) enif_priv_data(env); unsigned long ptr_as_ulong; if (!enif_get_ulong(env,argv[0],&ptr_as_ulong)) { return enif_make_badarg(env); } - if (data->nif_mod != NULL && --(data->nif_mod->ref_cnt) == 0) { - enif_free(env,data->nif_mod); + if (data->nif_mod != NULL) { + NifModPrivData_release(data->nif_mod); } data->nif_mod = (NifModPrivData*) ptr_as_ulong; return enif_make_int(env,++(data->nif_mod->ref_cnt)); @@ -178,7 +226,7 @@ static ERL_NIF_TERM hold_nif_mod_priv_data(ErlNifEnv* env, int argc, const ERL_N static ERL_NIF_TERM nif_mod_call_history(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - PrivData* data = (PrivData*) enif_get_data(env); + PrivData* data = (PrivData*) enif_priv_data(env); ERL_NIF_TERM ret; if (data->nif_mod == NULL) { return enif_make_string(env,"nif_mod pointer is NULL", ERL_NIF_LATIN1); @@ -354,13 +402,13 @@ static ERL_NIF_TERM type_test(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[ } } - if (!enif_make_existing_atom(env,"nif_SUITE", &atom) - || !enif_is_identical(env,atom,enif_make_atom(env,"nif_SUITE"))) { + if (!enif_make_existing_atom(env,"nif_SUITE", &atom, ERL_NIF_LATIN1) + || !enif_is_identical(atom,enif_make_atom(env,"nif_SUITE"))) { fprintf(stderr, "nif_SUITE not an atom?\r\n"); goto error; } for (i=2; i; i--) { - if (enif_make_existing_atom(env,"nif_SUITE_pink_unicorn", &atom)) { + if (enif_make_existing_atom(env,"nif_SUITE_pink_unicorn", &atom, ERL_NIF_LATIN1)) { fprintf(stderr, "pink unicorn exist?\r\n"); goto error; } @@ -368,7 +416,7 @@ static ERL_NIF_TERM type_test(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[ ref1 = enif_make_ref(env); ref2 = enif_make_ref(env); if (!enif_is_ref(env,ref1) || !enif_is_ref(env,ref2) - || enif_is_identical(env,ref1,ref2) || enif_compare(env,ref1,ref2)==0) { + || enif_is_identical(ref1,ref2) || enif_compare(ref1,ref2)==0) { fprintf(stderr, "strange refs?\r\n"); goto error; } @@ -398,7 +446,7 @@ static ERL_NIF_TERM is_identical(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar if (argc != 2) { return enif_make_badarg(env); } - return enif_make_atom(env, (enif_is_identical(env,argv[0],argv[1]) ? + return enif_make_atom(env, (enif_is_identical(argv[0],argv[1]) ? "true" : "false")); } @@ -407,7 +455,7 @@ static ERL_NIF_TERM compare(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) if (argc != 2) { return enif_make_badarg(env); } - return enif_make_int(env, enif_compare(env,argv[0],argv[1])); + return enif_make_int(env, enif_compare(argv[0],argv[1])); } static ERL_NIF_TERM many_args_100(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) @@ -454,7 +502,7 @@ static ERL_NIF_TERM string_to_bin(ErlNifEnv* env, int argc, const ERL_NIF_TERM a unsigned size; int n; if (!enif_get_int(env,argv[1],(int*)&size) - || !enif_alloc_binary(env,size,&obin)) { + || !enif_alloc_binary(size,&obin)) { return enif_make_badarg(env); } n = enif_get_string(env, argv[0], (char*)obin.data, size, ERL_NIF_LATIN1); @@ -468,10 +516,10 @@ static ERL_NIF_TERM atom_to_bin(ErlNifEnv* env, int argc, const ERL_NIF_TERM arg unsigned size; int n; if (!enif_get_int(env,argv[1],(int*)&size) - || !enif_alloc_binary(env,size,&obin)) { + || !enif_alloc_binary(size,&obin)) { return enif_make_badarg(env); } - n = enif_get_atom(env, argv[0], (char*)obin.data, size); + n = enif_get_atom(env, argv[0], (char*)obin.data, size, ERL_NIF_LATIN1); return enif_make_tuple(env, 2, enif_make_int(env,n), enif_make_binary(env,&obin)); } @@ -552,7 +600,7 @@ static ERL_NIF_TERM last_resource_dtor_call(ErlNifEnv* env, int argc, const ERL_ static ERL_NIF_TERM get_resource_type(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - PrivData* data = (PrivData*) enif_get_data(env); + PrivData* data = (PrivData*) enif_priv_data(env); int ix; if (!enif_get_int(env, argv[0], &ix) || ix >= 2) { @@ -568,7 +616,7 @@ static ERL_NIF_TERM alloc_resource(ErlNifEnv* env, int argc, const ERL_NIF_TERM union { void* p; long l;} data; if (!enif_get_long(env, argv[0], &type.l) || !enif_inspect_binary(env, argv[1], &data_bin) - || (data.p = enif_alloc_resource(env, type.t, data_bin.size))==NULL) { + || (data.p = enif_alloc_resource(type.t, data_bin.size))==NULL) { return enif_make_badarg(env); } @@ -593,28 +641,65 @@ static ERL_NIF_TERM make_new_resource(ErlNifEnv* env, int argc, const ERL_NIF_TE ERL_NIF_TERM ret; if (!enif_get_long(env, argv[0], &type.l) || !enif_inspect_binary(env, argv[1], &data_bin) - || (data = enif_alloc_resource(env, type.t, data_bin.size))==NULL) { + || (data = enif_alloc_resource(type.t, data_bin.size))==NULL) { return enif_make_badarg(env); } ret = enif_make_resource(env, data); memcpy(data, data_bin.data, data_bin.size); - enif_release_resource(env, data); + enif_release_resource(data); return ret; } +static ERL_NIF_TERM make_new_resource_binary(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + ErlNifBinary data_bin; + union { struct binary_resource* p; void* vp; long l;} br; + void* buf; + ERL_NIF_TERM ret; + if (!enif_inspect_binary(env, argv[0], &data_bin) + || (br.vp = enif_alloc_resource(binary_resource_type, + sizeof(struct binary_resource)))==NULL + || (buf = enif_alloc(data_bin.size)) == NULL) { + + return enif_make_badarg(env); + } + memset(br.vp,0xba,sizeof(struct binary_resource)); /* avoid valgrind warning */ + br.p->data = buf; + br.p->size = data_bin.size; + memcpy(br.p->data, data_bin.data, data_bin.size); + ret = enif_make_resource_binary(env, br.vp, br.p->data, br.p->size); + enif_release_resource(br.p); + return enif_make_tuple2(env, enif_make_long(env,br.l), ret); +} + +static void binary_resource_dtor(ErlNifEnv* env, void* obj) +{ + struct binary_resource* br = (struct binary_resource*) obj; + resource_dtor(env,obj); + assert(br->data != NULL); + enif_free(br->data); + br->data = NULL; +} + static ERL_NIF_TERM get_resource(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { ErlNifBinary data_bin; union { ErlNifResourceType* t; long l; } type; union { void* p; long l; } data; - if (!enif_get_long(env, argv[0], &type.l) + type.t = NULL; + if (enif_is_identical(argv[0], atom_binary_resource_type)) { + type.t = binary_resource_type; + } + else { + enif_get_long(env, argv[0], &type.l); + } + if (type.t == NULL || !enif_get_resource(env, argv[1], type.t, &data.p)) { return enif_make_badarg(env); } - - enif_alloc_binary(env, enif_sizeof_resource(env,data.p), &data_bin); + enif_alloc_binary(enif_sizeof_resource(data.p), &data_bin); memcpy(data_bin.data, data.p, data_bin.size); return enif_make_tuple2(env, enif_make_long(env,data.l), enif_make_binary(env, &data_bin)); @@ -626,7 +711,7 @@ static ERL_NIF_TERM release_resource(ErlNifEnv* env, int argc, const ERL_NIF_TER if (!enif_get_long(env, argv[0], &data.l)) { return enif_make_badarg(env); } - enif_release_resource(env, data.p); + enif_release_resource(data.p); return enif_make_atom(env,"ok"); } @@ -649,7 +734,7 @@ static ERL_NIF_TERM check_is(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[] if (!enif_is_atom(env, argv[0])) return enif_make_badarg(env); if (!enif_is_binary(env, argv[1])) return enif_make_badarg(env); if (!enif_is_ref(env, argv[2])) return enif_make_badarg(env); - if (!enif_is_identical(env, argv[3], ok_atom)) return enif_make_badarg(env); + if (!enif_is_identical(argv[3], ok_atom)) return enif_make_badarg(env); if (!enif_is_fun(env, argv[4])) return enif_make_badarg(env); if (!enif_is_pid(env, argv[5])) return enif_make_badarg(env); if (!enif_is_port(env, argv[6])) return enif_make_badarg(env); @@ -672,7 +757,7 @@ static ERL_NIF_TERM length_test(ErlNifEnv* env, int argc, const ERL_NIF_TERM arg { unsigned len; - if (!enif_get_atom_length(env, argv[0], &len) || len != 6) + if (!enif_get_atom_length(env, argv[0], &len, ERL_NIF_LATIN1) || len != 6) return enif_make_badarg(env); if (!enif_get_list_length(env, argv[1], &len) || len != 6) @@ -681,7 +766,7 @@ static ERL_NIF_TERM length_test(ErlNifEnv* env, int argc, const ERL_NIF_TERM arg if (!enif_get_list_length(env, argv[2], &len) || len != 0) return enif_make_badarg(env); - if (enif_get_atom_length(env, argv[3], &len)) + if (enif_get_atom_length(env, argv[3], &len, ERL_NIF_LATIN1)) return enif_make_badarg(env); if (enif_get_list_length(env, argv[4], &len)) @@ -703,15 +788,15 @@ static ERL_NIF_TERM make_atoms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv arr[2] = enif_make_atom_len(env, an0atom, 7); arr[3] = enif_make_atom_len(env, an0atom0, 8); - if (!enif_make_existing_atom(env, "an0atom", &existingatom0a)) + if (!enif_make_existing_atom(env, "an0atom", &existingatom0a, ERL_NIF_LATIN1)) return enif_make_atom(env, "error"); arr[4] = existingatom0a; - if (!enif_make_existing_atom_len(env, an0atom, 7, &existingatom0b)) + if (!enif_make_existing_atom_len(env, an0atom, 7, &existingatom0b, ERL_NIF_LATIN1)) return enif_make_atom(env, "error"); arr[5] = existingatom0b; - if (!enif_make_existing_atom_len(env, an0atom0, 8, &existing0atom0)) + if (!enif_make_existing_atom_len(env, an0atom0, 8, &existing0atom0, ERL_NIF_LATIN1)) return enif_make_atom(env, "error"); arr[6] = existing0atom0; @@ -723,13 +808,451 @@ static ERL_NIF_TERM make_strings(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar { const char a0string[8] = {'a','0','s','t','r','i','n','g'}; const char a0string0[9] = {'a','\0','s','t','r','i','n','g',0}; + const char astringwith8bits[37] = {'E','r','l','a','n','g',' ',0xE4 /* 'ä' */,'r',' ','e','t','t',' ','g','e','n','e','r','e','l','l','t',' ','p','r','o','g','r','a','m','s','p','r', 0xE5 /* 'å' */,'k',0}; - return enif_make_tuple4(env, + return enif_make_tuple5(env, enif_make_string(env, "a0string", ERL_NIF_LATIN1), enif_make_string_len(env, "a0string", 8, ERL_NIF_LATIN1), enif_make_string_len(env, a0string, 8, ERL_NIF_LATIN1), - enif_make_string_len(env, a0string0, 9, ERL_NIF_LATIN1)); + enif_make_string_len(env, a0string0, 9, ERL_NIF_LATIN1), + enif_make_string(env, astringwith8bits, ERL_NIF_LATIN1)); +} +static ERL_NIF_TERM send_list_seq(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + ErlNifPid to; + ERL_NIF_TERM msg; + ErlNifEnv* msg_env; + int i, res; + + if (!enif_get_int(env, argv[0], &i)) { + return enif_make_badarg(env); + } + if (argv[1] == atom_self) { + enif_self(env, &to); + } + else if (!enif_get_local_pid(env, argv[1], &to)) { + return enif_make_badarg(env); + } + msg_env = enif_alloc_env(); + msg = enif_make_list(msg_env,0); + for ( ; i>0 ; i--) { + msg = enif_make_list_cell(msg_env, enif_make_int(msg_env, i), msg); + } + res = enif_send(env, &to, msg_env, msg); + enif_free_env(msg_env); + return enif_make_tuple2(env, atom_ok, enif_make_int(env,res)); +} + +static void fill(void* dst, unsigned bytes, int seed) +{ + unsigned char* ptr = dst; + int i; + for (i=bytes; i>0; i--) { + *ptr++ = seed; + seed += 7; + } +} + +#define MAKE_TERM_REUSE_LEN 16 +struct make_term_info +{ + ErlNifEnv* caller_env; + ErlNifEnv* dst_env; + ERL_NIF_TERM reuse[MAKE_TERM_REUSE_LEN]; + unsigned reuse_push; + unsigned reuse_pull; + ErlNifResourceType* resource_type; + ERL_NIF_TERM other_term; + ERL_NIF_TERM blob; + ErlNifPid to_pid; + ErlNifTid tid; + ErlNifCond* cond; + ErlNifMutex* mtx; + int send_it; + int send_res; + unsigned n; +}; + + +static void push_term(struct make_term_info* mti, ERL_NIF_TERM term) +{ + unsigned ix = (mti->reuse_push++) % MAKE_TERM_REUSE_LEN; + mti->reuse[ix] = term; + //enif_fprintf(stderr, "push at %u: %T\r\n", ix, term); +} +static ERL_NIF_TERM pull_term(struct make_term_info* mti) +{ + unsigned ix; + if (mti->reuse_pull >= mti->reuse_push && + mti->reuse_push < MAKE_TERM_REUSE_LEN) { + mti->reuse_pull = 0; + if (mti->reuse_push == 0) { + mti->reuse[0] = enif_make_list(mti->dst_env, 0); + } + } + ix = (mti->reuse_pull++) % MAKE_TERM_REUSE_LEN; + //enif_fprintf(stderr, "pull from %u: %T\r\n", ix, mti->reuse[ix]); + return mti->reuse[ix]; +} + +static int make_term_n(struct make_term_info* mti, int n, ERL_NIF_TERM* res); + +static ERL_NIF_TERM make_term_binary(struct make_term_info* mti, int n) +{ + ErlNifBinary bin; + enif_alloc_binary(100, &bin); + fill(bin.data, bin.size, n); + return enif_make_binary(mti->dst_env, &bin); +} + +static ERL_NIF_TERM make_term_int(struct make_term_info* mti, int n) +{ + int i; + fill(&i, sizeof(i), n); + return enif_make_int(mti->dst_env, i); +} + +static ERL_NIF_TERM make_term_ulong(struct make_term_info* mti, int n) +{ + unsigned long ul; + fill(&ul, sizeof(ul), n); + return enif_make_ulong(mti->dst_env, ul); +} + +static ERL_NIF_TERM make_term_double(struct make_term_info* mti, int n) +{ + double d = 3.141592; + return enif_make_double(mti->dst_env, d); +} +static ERL_NIF_TERM make_term_atom(struct make_term_info* mti, int n) +{ + return enif_make_atom(mti->dst_env, "make_term_n"); +} +static ERL_NIF_TERM make_term_existing_atom(struct make_term_info* mti, int n) +{ + ERL_NIF_TERM res; + int exist = enif_make_existing_atom(mti->dst_env, "nif_SUITE", &res, + ERL_NIF_LATIN1); + assert(exist); + return res; +} +static ERL_NIF_TERM make_term_string(struct make_term_info* mti, int n) +{ + return enif_make_string(mti->dst_env, "Hello!", ERL_NIF_LATIN1); +} +static ERL_NIF_TERM make_term_ref(struct make_term_info* mti, int n) +{ + return enif_make_ref(mti->dst_env); +} +static ERL_NIF_TERM make_term_sub_binary(struct make_term_info* mti, int n) +{ + ERL_NIF_TERM orig; + unsigned char* ptr = enif_make_new_binary(mti->dst_env, 10, &orig); + fill(ptr, 10, n); + return enif_make_sub_binary(mti->dst_env, orig, 3, 5); +} +static ERL_NIF_TERM make_term_uint(struct make_term_info* mti, int n) +{ + unsigned int ui; + fill(&ui, sizeof(ui), n); + return enif_make_uint(mti->dst_env, ui); +} +static ERL_NIF_TERM make_term_long(struct make_term_info* mti, int n) +{ + long l; + fill(&l, sizeof(l), n); + return enif_make_long(mti->dst_env, l); +} +static ERL_NIF_TERM make_term_tuple0(struct make_term_info* mti, int n) +{ + return enif_make_tuple(mti->dst_env, 0); +} +static ERL_NIF_TERM make_term_list0(struct make_term_info* mti, int n) +{ + return enif_make_list(mti->dst_env, 0); +} +static ERL_NIF_TERM make_term_resource(struct make_term_info* mti, int n) +{ + void* resource = enif_alloc_resource(mti->resource_type, 10); + fill(resource, 10, n); + return enif_make_resource(mti->dst_env, resource); } +static ERL_NIF_TERM make_term_new_binary(struct make_term_info* mti, int n) +{ + ERL_NIF_TERM res; + unsigned char* ptr = enif_make_new_binary(mti->dst_env,20,&res); + fill(ptr, 20, n); + return res; +} +static ERL_NIF_TERM make_term_caller_pid(struct make_term_info* mti, int n) +{ + ErlNifPid pid; + return enif_make_pid(mti->dst_env, enif_self(mti->caller_env, &pid)); +} + +static ERL_NIF_TERM make_term_tuple(struct make_term_info* mti, int n) +{ + ERL_NIF_TERM t[3]; + t[0] = pull_term(mti); + t[1] = pull_term(mti); + t[2] = pull_term(mti); + return enif_make_tuple3(mti->dst_env, t[0], t[1], t[2]); +} +static ERL_NIF_TERM make_term_list(struct make_term_info* mti, int n) +{ + ERL_NIF_TERM t[3]; + t[0] = pull_term(mti); + t[1] = pull_term(mti); + t[2] = pull_term(mti); + return enif_make_list3(mti->dst_env, t[0], t[1], t[2]); +} +static ERL_NIF_TERM make_term_list_cell(struct make_term_info* mti, int n) +{ + ERL_NIF_TERM t[2]; + t[0] = pull_term(mti); + t[1] = pull_term(mti); + return enif_make_list_cell(mti->dst_env, t[0], t[1]); +} +static ERL_NIF_TERM make_term_tuple_from_array(struct make_term_info* mti, int n) +{ + ERL_NIF_TERM t[3]; + t[0] = pull_term(mti); + t[1] = pull_term(mti); + t[2] = pull_term(mti); + return enif_make_tuple_from_array(mti->dst_env, t, 3); +} +static ERL_NIF_TERM make_term_list_from_array(struct make_term_info* mti, int n) +{ + ERL_NIF_TERM t[3]; + t[0] = pull_term(mti); + t[1] = pull_term(mti); + t[2] = pull_term(mti); + return enif_make_list_from_array(mti->dst_env, t, 3); +} +static ERL_NIF_TERM make_term_garbage(struct make_term_info* mti, int n) +{ + (void) enif_make_string(mti->dst_env, "garbage string", ERL_NIF_LATIN1); + return pull_term(mti); +} +static ERL_NIF_TERM make_term_copy(struct make_term_info* mti, int n) +{ + return enif_make_copy(mti->dst_env, mti->other_term); +} +static int make_term_n(struct make_term_info* mti, int n, ERL_NIF_TERM* res) +{ + typedef ERL_NIF_TERM Make_term_Func(struct make_term_info*, int); + static Make_term_Func* funcs[] = { + make_term_binary, + make_term_int, + make_term_ulong, + make_term_double, + make_term_atom, + make_term_existing_atom, + make_term_string, + //make_term_ref, + make_term_sub_binary, + make_term_uint, + make_term_long, + make_term_tuple0, + make_term_list0, + make_term_resource, + make_term_new_binary, + make_term_caller_pid, + make_term_tuple, + make_term_list, + make_term_list_cell, + make_term_tuple_from_array, + make_term_list_from_array, + make_term_garbage, + make_term_copy + }; + if (n < sizeof(funcs)/sizeof(*funcs)) { + *res = funcs[n](mti, n); + push_term(mti, *res); + return 1; + } + return 0; +} + +static ERL_NIF_TERM make_blob(ErlNifEnv* caller_env, ErlNifEnv* dst_env, + ERL_NIF_TERM other_term) +{ + PrivData* priv = (PrivData*) enif_priv_data(caller_env); + ERL_NIF_TERM term, list; + int n = 0; + struct make_term_info mti; + mti.caller_env = caller_env; + mti.dst_env = dst_env; + mti.reuse_push = 0; + mti.reuse_pull = 0; + mti.resource_type = priv->rt_arr[0].t; + mti.other_term = other_term; + + list = enif_make_list(dst_env, 0); + while (make_term_n(&mti, n++, &term)) { + list = enif_make_list_cell(dst_env, term, list); + } + return list; +} + +static ERL_NIF_TERM send_new_blob(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + ErlNifPid to; + ERL_NIF_TERM msg, copy; + ErlNifEnv* msg_env; + int res; + + if (!enif_get_local_pid(env, argv[0], &to)) { + return enif_make_badarg(env); + } + msg_env = enif_alloc_env(); + msg = make_blob(env,msg_env, argv[1]); + copy = make_blob(env,env, argv[1]); + res = enif_send(env, &to, msg_env, msg); + enif_free_env(msg_env); + return enif_make_tuple3(env, atom_ok, enif_make_int(env,res), copy); +} + +static ERL_NIF_TERM alloc_msgenv(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + PrivData* priv = (PrivData*) enif_priv_data(env); + struct make_term_info* mti; + ERL_NIF_TERM ret; + + mti = (struct make_term_info*) enif_alloc_resource(msgenv_resource_type, + sizeof(*mti)); + mti->caller_env = NULL; + mti->dst_env = enif_alloc_env(); + mti->reuse_push = 0; + mti->reuse_pull = 0; + mti->resource_type = priv->rt_arr[0].t; + mti->other_term = enif_make_list(mti->dst_env, 0); + mti->blob = enif_make_list(mti->dst_env, 0); + mti->mtx = enif_mutex_create("nif_SUITE:mtx"); + mti->cond = enif_cond_create("nif_SUITE:cond"); + mti->send_res = 0xcafebabe; + mti->n = 0; + ret = enif_make_resource(env, mti); + enif_release_resource(mti); + return ret; +} + +static void msgenv_dtor(ErlNifEnv* env, void* obj) +{ + struct make_term_info* mti = (struct make_term_info*) obj; + if (mti->dst_env != NULL) { + enif_free_env(mti->dst_env); + } + enif_mutex_destroy(mti->mtx); + enif_cond_destroy(mti->cond); +} + +static ERL_NIF_TERM clear_msgenv(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + union { + void* vp; + struct make_term_info* p; + }mti; + if (!enif_get_resource(env, argv[0], msgenv_resource_type, &mti.vp)) { + return enif_make_badarg(env); + } + enif_clear_env(mti.p->dst_env); + mti.p->reuse_pull = 0; + mti.p->reuse_push = 0; + mti.p->blob = enif_make_list(mti.p->dst_env, 0); + return atom_ok; +} + +static ERL_NIF_TERM grow_blob(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + union { void* vp; struct make_term_info* p; }mti; + ERL_NIF_TERM term; + if (!enif_get_resource(env, argv[0], msgenv_resource_type, &mti.vp)) { + return enif_make_badarg(env); + } + mti.p->caller_env = env; + mti.p->other_term = argv[1]; + while (!make_term_n(mti.p, mti.p->n++, &term)) { + mti.p->n = 0; + } + mti.p->blob = enif_make_list_cell(mti.p->dst_env, term, mti.p->blob); + return atom_ok; +} + +static ERL_NIF_TERM send_blob(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + union { void* vp; struct make_term_info* p; }mti; + ErlNifPid to; + ERL_NIF_TERM copy; + int res; + if (!enif_get_resource(env, argv[0], msgenv_resource_type, &mti.vp) + || !enif_get_local_pid(env, argv[1], &to)) { + return enif_make_badarg(env); + } + copy = enif_make_copy(env, mti.p->blob); + res = enif_send(env, &to, mti.p->dst_env, mti.p->blob); + return enif_make_tuple3(env, atom_ok, enif_make_int(env,res), copy); +} + +void* threaded_sender(void *arg) +{ + + union { void* vp; struct make_term_info* p; }mti; + mti.vp = arg; + + enif_mutex_lock(mti.p->mtx); + while (!mti.p->send_it) { + enif_cond_wait(mti.p->cond, mti.p->mtx); + } + mti.p->send_it = 0; + enif_mutex_unlock(mti.p->mtx); + mti.p->send_res = enif_send(NULL, &mti.p->to_pid, mti.p->dst_env, mti.p->blob); + return NULL; +} + +static ERL_NIF_TERM send_blob_thread(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + union { void* vp; struct make_term_info* p; }mti; + ERL_NIF_TERM copy; + if (!enif_get_resource(env, argv[0], msgenv_resource_type, &mti.vp) + || !enif_get_local_pid(env,argv[1], &mti.p->to_pid)) { + return enif_make_badarg(env); + } + copy = enif_make_copy(env, mti.p->blob); + + mti.p->send_it = enif_is_identical(argv[2],atom_join); + if (enif_thread_create("nif_SUITE:send_from_thread", &mti.p->tid, + threaded_sender, mti.p, NULL) != 0) { + return enif_make_badarg(env); + } + if (enif_is_identical(argv[2],atom_join)) { + int err = enif_thread_join(mti.p->tid, NULL); + assert(err == 0); + return enif_make_tuple3(env, atom_ok, enif_make_int(env, mti.p->send_res), copy); + } + else { + enif_keep_resource(mti.vp); + return enif_make_tuple2(env, atom_ok, copy); + } +} + +static ERL_NIF_TERM join_send_thread(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + union { void* vp; struct make_term_info* p; }mti; + int err; + if (!enif_get_resource(env, argv[0], msgenv_resource_type, &mti.vp)) { + return enif_make_badarg(env); + } + enif_mutex_lock(mti.p->mtx); + mti.p->send_it = 1; + enif_cond_signal(mti.p->cond); + enif_mutex_unlock(mti.p->mtx); + err = enif_thread_join(mti.p->tid, NULL); + assert(err == 0); + enif_release_resource(mti.vp); + return enif_make_tuple2(env, atom_ok, enif_make_int(env, mti.p->send_res)); +} + static ErlNifFunc nif_funcs[] = { @@ -760,8 +1283,17 @@ static ErlNifFunc nif_funcs[] = {"check_is", 10, check_is}, {"length_test", 5, length_test}, {"make_atoms", 0, make_atoms}, - {"make_strings", 0, make_strings} - + {"make_strings", 0, make_strings}, + {"make_new_resource", 2, make_new_resource}, + {"make_new_resource_binary", 1, make_new_resource_binary}, + {"send_list_seq", 2, send_list_seq}, + {"send_new_blob", 2, send_new_blob}, + {"alloc_msgenv", 0, alloc_msgenv}, + {"clear_msgenv", 1, clear_msgenv}, + {"grow_blob", 2, grow_blob}, + {"send_blob", 2, send_blob}, + {"send_blob_thread", 3, send_blob_thread}, + {"join_send_thread", 1, join_send_thread} }; ERL_NIF_INIT(nif_SUITE,nif_funcs,load,reload,upgrade,unload) diff --git a/erts/emulator/test/nif_SUITE_data/nif_mod.c b/erts/emulator/test/nif_SUITE_data/nif_mod.c index 75df9d56d5..e32d10057c 100644 --- a/erts/emulator/test/nif_SUITE_data/nif_mod.c +++ b/erts/emulator/test/nif_SUITE_data/nif_mod.c @@ -42,6 +42,11 @@ static ERL_NIF_TERM am_resource_type; static ERL_NIF_TERM am_resource_dtor_A; static ERL_NIF_TERM am_resource_dtor_B; +static NifModPrivData* priv_data(ErlNifEnv* env) +{ + return (NifModPrivData*) enif_priv_data(env); +} + static void init(ErlNifEnv* env) { am_true = enif_make_atom(env, "true"); @@ -54,7 +59,7 @@ static void init(ErlNifEnv* env) static void add_call_with_arg(ErlNifEnv* env, NifModPrivData* data, const char* func_name, const char* arg, int arg_sz) { - CallInfo* call = enif_alloc(env, sizeof(CallInfo)+strlen(func_name) + arg_sz); + CallInfo* call = (CallInfo*)enif_alloc(sizeof(CallInfo)+strlen(func_name) + arg_sz); strcpy(call->func_name, func_name); call->lib_ver = NIF_LIB_VER; call->static_cntA = ++static_cntA; @@ -78,7 +83,7 @@ static void add_call(ErlNifEnv* env, NifModPrivData* data,const char* func_name) add_call_with_arg(env, data, func_name, NULL, 0); } -#define ADD_CALL(FUNC_NAME) add_call(env, enif_priv_data(env),FUNC_NAME) +#define ADD_CALL(FUNC_NAME) add_call(env, priv_data(env),FUNC_NAME) #define STRINGIFY_(X) #X #define STRINGIFY(X) STRINGIFY_(X) @@ -87,56 +92,56 @@ static void resource_dtor_A(ErlNifEnv* env, void* a) { const char dtor_name[] = "resource_dtor_A_v" STRINGIFY(NIF_LIB_VER); - add_call_with_arg(env, enif_priv_data(env), dtor_name, - a, enif_sizeof_resource(env, a)); + add_call_with_arg(env, priv_data(env), dtor_name, (const char*)a, + enif_sizeof_resource(a)); } static void resource_dtor_B(ErlNifEnv* env, void* a) { - const char dtor_name[] = "resource_dtor_B_v" STRINGIFY(NIF_LIB_VER); + const char dtor_name[] = "resource_dtor_B_v" STRINGIFY(NIF_LIB_VER); - add_call_with_arg(env, enif_priv_data(env), dtor_name, - a, enif_sizeof_resource(env, a)); + add_call_with_arg(env, priv_data(env), dtor_name, (const char*)a, + enif_sizeof_resource(a)); } /* {resource_type, Ix|null, ErlNifResourceFlags in, "TypeName", dtor(A|B|null), ErlNifResourceFlags out}*/ static void open_resource_type(ErlNifEnv* env, ERL_NIF_TERM op_tpl) { - NifModPrivData* data = enif_priv_data(env); + NifModPrivData* data = priv_data(env); const ERL_NIF_TERM* arr; int arity; char rt_name[30]; - union { enum ErlNifResourceFlags e; int i; } flags, exp_res, got_res; + union { ErlNifResourceFlags e; int i; } flags, exp_res, got_res; unsigned ix; ErlNifResourceDtor* dtor; ErlNifResourceType* got_ptr; CHECK(enif_get_tuple(env, op_tpl, &arity, &arr)); CHECK(arity == 6); - CHECK(enif_is_identical(env, arr[0], am_resource_type)); + CHECK(enif_is_identical(arr[0], am_resource_type)); CHECK(enif_get_int(env, arr[2], &flags.i)); CHECK(enif_get_string(env, arr[3], rt_name, sizeof(rt_name), ERL_NIF_LATIN1) > 0); CHECK(enif_get_int(env, arr[5], &exp_res.i)); - if (enif_is_identical(env, arr[4], am_null)) { + if (enif_is_identical(arr[4], am_null)) { dtor = NULL; } - else if (enif_is_identical(env, arr[4], am_resource_dtor_A)) { + else if (enif_is_identical(arr[4], am_resource_dtor_A)) { dtor = resource_dtor_A; } else { - CHECK(enif_is_identical(env, arr[4], am_resource_dtor_B)); + CHECK(enif_is_identical(arr[4], am_resource_dtor_B)); dtor = resource_dtor_B; } - got_ptr = enif_open_resource_type(env, rt_name, dtor, + got_ptr = enif_open_resource_type(env, NULL, rt_name, dtor, flags.e, &got_res.e); if (enif_get_uint(env, arr[1], &ix) && ix < RT_MAX && got_ptr != NULL) { data->rt_arr[ix] = got_ptr; } else { - CHECK(enif_is_identical(env, arr[1], am_null)); + CHECK(enif_is_identical(arr[1], am_null)); CHECK(got_ptr == NULL); } CHECK(got_res.e == exp_res.e); @@ -144,7 +149,7 @@ static void open_resource_type(ErlNifEnv* env, ERL_NIF_TERM op_tpl) static void do_load_info(ErlNifEnv* env, ERL_NIF_TERM load_info) { - NifModPrivData* data = enif_priv_data(env); + NifModPrivData* data = priv_data(env); ERL_NIF_TERM head, tail; unsigned ix; for (ix=0; ix<RT_MAX; ix++) { @@ -158,17 +163,18 @@ static void do_load_info(ErlNifEnv* env, ERL_NIF_TERM load_info) CHECK(enif_is_empty_list(env, head)); } -static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) +static int load(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info) { NifModPrivData* data; init(env); - data = enif_alloc(env, sizeof(NifModPrivData)); + data = (NifModPrivData*) enif_alloc(sizeof(NifModPrivData)); CHECK(data != NULL); - *priv_data = data; + *priv = data; data->mtx = enif_mutex_create("nif_mod_priv_data"); data->ref_cnt = 1; data->call_history = NULL; + add_call(env, data, "load"); do_load_info(env, load_info); @@ -176,39 +182,35 @@ static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) return 0; } -static int reload(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) +static int reload(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info) { + NifModPrivData* data = (NifModPrivData*) *priv; init(env); - add_call(env, *priv_data, "reload"); + add_call(env, data, "reload"); do_load_info(env, load_info); return 0; } -static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info) +static int upgrade(ErlNifEnv* env, void** priv, void** old_priv_data, ERL_NIF_TERM load_info) { - NifModPrivData* data = *old_priv_data; + NifModPrivData* data = (NifModPrivData*) *old_priv_data; init(env); add_call(env, data, "upgrade"); data->ref_cnt++; - *priv_data = *old_priv_data; + *priv = *old_priv_data; do_load_info(env, load_info); return 0; } -static void unload(ErlNifEnv* env, void* priv_data) +static void unload(ErlNifEnv* env, void* priv) { - NifModPrivData* data = priv_data; + NifModPrivData* data = (NifModPrivData*) priv; + int is_last; add_call(env, data, "unload"); - enif_mutex_lock(data->mtx); - if (--data->ref_cnt == 0) { - enif_mutex_unlock(data->mtx); - enif_mutex_destroy(data->mtx); - enif_free(env, data); - } - enif_mutex_unlock(data->mtx); + NifModPrivData_release(data); } static ERL_NIF_TERM lib_version(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) @@ -220,12 +222,12 @@ static ERL_NIF_TERM lib_version(ErlNifEnv* env, int argc, const ERL_NIF_TERM arg static ERL_NIF_TERM get_priv_data_ptr(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { ADD_CALL("get_priv_data_ptr"); - return enif_make_ulong(env, (unsigned long)enif_priv_data(env)); + return enif_make_ulong(env, (unsigned long)priv_data(env)); } static ERL_NIF_TERM make_new_resource(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - NifModPrivData* data = (NifModPrivData*) enif_priv_data(env); + NifModPrivData* data = priv_data(env); ErlNifBinary ibin; char* a; ERL_NIF_TERM ret; @@ -234,22 +236,22 @@ static ERL_NIF_TERM make_new_resource(ErlNifEnv* env, int argc, const ERL_NIF_TE || !enif_inspect_binary(env, argv[1], &ibin)) { return enif_make_badarg(env); } - a = enif_alloc_resource(env, data->rt_arr[ix], ibin.size); + a = (char*) enif_alloc_resource(data->rt_arr[ix], ibin.size); memcpy(a, ibin.data, ibin.size); ret = enif_make_resource(env, a); - enif_release_resource(env, a); + enif_release_resource(a); return ret; } static ERL_NIF_TERM get_resource(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - NifModPrivData* data = (NifModPrivData*) enif_priv_data(env); + NifModPrivData* data = priv_data(env); ErlNifBinary obin; unsigned ix; void* a; if (!enif_get_uint(env, argv[0], &ix) || ix >= RT_MAX || !enif_get_resource(env, argv[1], data->rt_arr[ix], &a) - || !enif_alloc_binary(env, enif_sizeof_resource(env, a), &obin)) { + || !enif_alloc_binary(enif_sizeof_resource(a), &obin)) { return enif_make_badarg(env); } memcpy(obin.data, a, obin.size); diff --git a/erts/emulator/test/nif_SUITE_data/nif_mod.h b/erts/emulator/test/nif_SUITE_data/nif_mod.h index 0eaf91d6e1..cd0ecf4b54 100644 --- a/erts/emulator/test/nif_SUITE_data/nif_mod.h +++ b/erts/emulator/test/nif_SUITE_data/nif_mod.h @@ -20,3 +20,15 @@ typedef struct ErlNifResourceType* rt_arr[RT_MAX]; }NifModPrivData; +#define NifModPrivData_release(NMPD) \ + do { \ + int is_last; \ + enif_mutex_lock((NMPD)->mtx); \ + is_last = (--(NMPD)->ref_cnt == 0); \ + enif_mutex_unlock((NMPD)->mtx); \ + if (is_last) { \ + enif_mutex_destroy((NMPD)->mtx); \ + enif_free((NMPD)); \ + } \ + }while (0) + diff --git a/erts/emulator/test/scheduler_SUITE.erl b/erts/emulator/test/scheduler_SUITE.erl index c9101b77c2..ab727b7cb1 100644 --- a/erts/emulator/test/scheduler_SUITE.erl +++ b/erts/emulator/test/scheduler_SUITE.erl @@ -902,7 +902,8 @@ scheduler_suspend_test(Config, Schedulers) -> ?line [SState] = mcall(Node, [fun () -> erlang:system_info(schedulers_state) end]), - ?line {Sched, _, _} = SState, + ?line ?t:format("SState=~p~n", [SState]), + ?line {Sched, SchedOnln, _SchedAvail} = SState, ?line true = is_integer(Sched), ?line [ok] = mcall(Node, [fun () -> sst0_loop(300) end]), ?line [ok] = mcall(Node, [fun () -> sst1_loop(300) end]), @@ -914,6 +915,14 @@ scheduler_suspend_test(Config, Schedulers) -> fun () -> sst2_loop(200) end, fun () -> sst3_loop(Sched, 200) end]), ?line [SState] = mcall(Node, [fun () -> + case Sched == SchedOnln of + false -> + Sched = erlang:system_flag( + schedulers_online, + SchedOnln); + true -> + ok + end, erlang:system_info(schedulers_state) end]), ?line stop_node(Node), diff --git a/erts/emulator/test/trace_call_time_SUITE.erl b/erts/emulator/test/trace_call_time_SUITE.erl new file mode 100644 index 0000000000..7bc91addde --- /dev/null +++ b/erts/emulator/test/trace_call_time_SUITE.erl @@ -0,0 +1,614 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% +%%% Define to run outside of test server +%%% +%%% -define(STANDALONE,1). +%%% +%%% +%%% Define for debug output +%%% +%%% -define(debug,1). + +-module(trace_call_time_SUITE). + +%% Exported end user tests + +-export([seq/3, seq_r/3]). +-export([loaded/1, a_function/1, a_called_function/1, dec/1, nif_dec/1]). + +-define(US_ERROR, 10000). +-define(R_ERROR, 0.8). +-define(SINGLE_CALL_US_TIME, 10). + +%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Result examination macros + +-define(CT(P,MFA),{trace,P,call,MFA}). +-define(CTT(P, MFA),{trace_ts,P,call,MFA,{_,_,_}}). +-define(RF(P,MFA,V),{trace,P,return_from,MFA,V}). +-define(RFT(P,MFA,V),{trace_ts,P,return_from,MFA,V,{_,_,_}}). +-define(RT(P,MFA),{trace,P,return_to,MFA}). +-define(RTT(P,MFA),{trace_ts,P,return_to,MFA,{_,_,_}}). + +-ifdef(debug). +-define(dbgformat(A,B),io:format(A,B)). +-else. +-define(dbgformat(A,B),noop). +-endif. + +%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-include("test_server.hrl"). + +%% When run in test server. +-export([all/1, init_per_testcase/2, fin_per_testcase/2, not_run/1]). +-export([basic/1, on_and_off/1, info/1, + pause_and_restart/1, scheduling/1, called_function/1, combo/1, bif/1, nif/1]). + +init_per_testcase(_Case, Config) -> + ?line Dog=test_server:timetrap(test_server:seconds(400)), + erlang:trace_pattern({'_','_','_'}, false, [local,meta,call_time,call_count]), + erlang:trace_pattern(on_load, false, [local,meta,call_time,call_count]), + timer:now_diff(now(),now()), + [{watchdog, Dog}|Config]. + +fin_per_testcase(_Case, Config) -> + erlang:trace_pattern({'_','_','_'}, false, [local,meta,call_time,call_count]), + erlang:trace_pattern(on_load, false, [local,meta,call_time,call_count]), + erlang:trace(all, false, [all]), + Dog=?config(watchdog, Config), + test_server:timetrap_cancel(Dog), + ok. + +all(doc) -> + ["Test call count tracing of local function calls."]; +all(suite) -> + case test_server:is_native(?MODULE) of + true -> [not_run]; + false -> [basic, on_and_off, info, + pause_and_restart, scheduling, combo, bif, nif, called_function] + end. + +not_run(Config) when is_list(Config) -> + {skipped,"Native code"}. + +basic(suite) -> + []; +basic(doc) -> + ["Tests basic call count trace"]; +basic(Config) when is_list(Config) -> + ?line P = erlang:trace_pattern({'_','_','_'}, false, [call_time]), + ?line M = 1000, + %% + ?line 1 = erlang:trace_pattern({?MODULE,seq, '_'}, true, [call_time]), + ?line 2 = erlang:trace_pattern({?MODULE,seq_r,'_'}, true, [call_time]), + ?line Pid = setup(), + ?line {L, T1} = execute(Pid, fun() -> seq(1, M, fun(X) -> (X+1) end) end), + ?line ok = check_trace_info({?MODULE, seq, 3}, [{Pid, M, 0, 0}], T1), + ?line ok = check_trace_info({?MODULE, seq_r, 3}, [], none), + + ?line {Lr, T2} = execute(Pid, fun() -> seq_r(1, M, fun(X) -> (X+1) end) end), + ?line ok = check_trace_info({?MODULE, seq, 3}, [{Pid, M, 0, 0}], T1), + ?line ok = check_trace_info({?MODULE, seq_r, 3}, [{Pid, 1, 0, 0}], T2/M), + ?line ok = check_trace_info({?MODULE, seq_r, 4}, [{Pid, M, 0, 0}], T2), + ?line L = lists:reverse(Lr), + + %% + ?line P = erlang:trace_pattern({'_','_','_'}, false, [call_time]), + ?line Pid ! quit, + ok. + +%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +on_and_off(suite) -> + []; +on_and_off(doc) -> + ["Tests turning trace parameters on and off"]; +on_and_off(Config) when is_list(Config) -> + ?line P = erlang:trace_pattern({'_','_','_'}, false, [call_time]), + ?line M = 100, + %% + ?line 1 = erlang:trace_pattern({?MODULE,seq,'_'}, true, [call_time]), + ?line Pid = setup(), + ?line {L, T1} = execute(Pid, {?MODULE, seq, [1, M, fun(X) -> X+1 end]}), + ?line ok = check_trace_info({?MODULE, seq, 3}, [{Pid, M, 0, 0}], T1), + + ?line N = erlang:trace_pattern({?MODULE,'_','_'}, true, [call_time]), + ?line {L, T2} = execute(Pid, fun() -> seq(1, M, fun(X) -> X+1 end) end), + ?line ok = check_trace_info({?MODULE, seq, 3}, [{Pid, M, 0, 0}], T2), + + ?line P = erlang:trace_pattern({'_','_','_'}, true, [call_time]), + ?line {L, T3} = execute(Pid, fun() -> seq(1, M, fun(X) -> X+1 end) end), + ?line ok = check_trace_info({?MODULE, seq, 3}, [{Pid, M, 0, 0}], T3), + + ?line 1 = erlang:trace_pattern({?MODULE,seq,'_'}, false, [call_time]), + ?line ok = check_trace_info({?MODULE, seq, 3}, false, none), + ?line {L, _T4} = execute(Pid, fun() -> seq(1, M, fun(X) -> X+1 end) end), + ?line ok = check_trace_info({?MODULE, seq, 3}, false, none), + ?line ok = check_trace_info({?MODULE, seq_r, 4}, [], none), + ?line {Lr, T5} = execute(Pid, fun() -> seq_r(1, M, fun(X) -> X+1 end) end), + ?line ok = check_trace_info({?MODULE, seq_r, 4}, [{Pid,M,0,0}], T5), + + ?line N = erlang:trace_pattern({?MODULE,'_','_'}, false, [call_time]), + ?line ok = check_trace_info({?MODULE, seq_r, 4}, false, none), + ?line {Lr, _T6} = execute(Pid, fun() -> seq_r(1, M, fun(X) -> X+1 end) end), + ?line ok = check_trace_info({?MODULE, seq_r, 4}, false, none), + ?line L = lists:reverse(Lr), + %% + ?line Pid ! quit, + ?line P = erlang:trace_pattern({'_','_','_'}, false, [call_time]), + ok. + +%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +info(suite) -> + []; +info(doc) -> + ["Tests the trace_info BIF"]; +info(Config) when is_list(Config) -> + ?line P = erlang:trace_pattern({'_','_','_'}, false, [call_time]), + %% + ?line 1 = erlang:trace_pattern({?MODULE,seq,3}, true, [call_time]), + ?line {call_time,[]} = erlang:trace_info({?MODULE,seq,3}, call_time), + ?line 1 = erlang:trace_pattern({?MODULE,seq,'_'}, pause, [call_time]), + ?line {call_time,[]} = erlang:trace_info({?MODULE,seq,3}, call_time), + ?line {all,[_|_]=L} = erlang:trace_info({?MODULE,seq,3}, all), + ?line {value,{call_time,[]}} = lists:keysearch(call_time, 1, L), + ?line 1 = erlang:trace_pattern({?MODULE,seq,'_'}, restart, [call_time]), + ?line {call_time,[]} = erlang:trace_info({?MODULE,seq,3}, call_time), + ?line 1 = erlang:trace_pattern({?MODULE,seq,'_'}, false, [call_time]), + ?line {call_time,false} = erlang:trace_info({?MODULE,seq,3}, call_time), + ?line {all,false} = erlang:trace_info({?MODULE,seq,3}, all), + %% + ?line P = erlang:trace_pattern({'_','_','_'}, false, [call_time]), + ok. + +%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +pause_and_restart(suite) -> + []; +pause_and_restart(doc) -> + ["Tests pausing and restarting call time counters"]; +pause_and_restart(Config) when is_list(Config) -> + ?line P = erlang:trace_pattern({'_','_','_'}, false, [call_time]), + ?line M = 100, + ?line Pid = setup(), + %% + ?line 1 = erlang:trace_pattern({?MODULE,seq,'_'}, true, [call_time]), + ?line ok = check_trace_info({?MODULE, seq, 3}, [], none), + ?line {L, T1} = execute(Pid, fun() -> seq(1, M, fun(X) -> X+1 end) end), + ?line ok = check_trace_info({?MODULE, seq, 3}, [{Pid,M,0,0}], T1), + ?line 1 = erlang:trace_pattern({?MODULE,seq,'_'}, pause, [call_time]), + ?line ok = check_trace_info({?MODULE, seq, 3}, [{Pid,M,0,0}], T1), + ?line {L, T2} = execute(Pid, fun() -> seq(1, M, fun(X) -> X+1 end) end), + ?line ok = check_trace_info({?MODULE, seq, 3}, [{Pid,M,0,0}], T2), + ?line 1 = erlang:trace_pattern({?MODULE,seq,'_'}, restart, [call_time]), + ?line ok = check_trace_info({?MODULE, seq, 3}, [], none), + ?line {L, T3} = execute(Pid, fun() -> seq(1, M, fun(X) -> X+1 end) end), + ?line ok = check_trace_info({?MODULE, seq, 3}, [{Pid,M,0,0}], T3), + %% + ?line Pid ! quit, + ?line P = erlang:trace_pattern({'_','_','_'}, false, [call_time]), + ok. + +%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +scheduling(suite) -> + []; +scheduling(doc) -> + ["Tests in/out scheduling of call time counters"]; +scheduling(Config) when is_list(Config) -> + ?line P = erlang:trace_pattern({'_','_','_'}, false, [call_time]), + ?line M = 1000000, + ?line Np = erlang:system_info(schedulers_online), + ?line F = 12, + + %% setup load processes + %% (single, no internal calls) + + ?line erlang:trace_pattern({?MODULE,loaded,1}, true, [call_time]), + + ?line Pids = [setup() || _ <- lists:seq(1, F*Np)], + ?line {_Ls,T1} = execute(Pids, {?MODULE,loaded,[M]}), + ?line [Pid ! quit || Pid <- Pids], + + %% logic dictates that each process will get ~ 1/F of the schedulers time + + ?line {call_time, CT} = erlang:trace_info({?MODULE,loaded,1}, call_time), + + ?line lists:foreach(fun (Pid) -> + ?line ok = case check_process_time(lists:keysearch(Pid, 1, CT), M, F, T1) of + schedule_time_error -> + test_server:comment("Warning: Failed time ratio"), + ok; + Other -> Other + end + end, Pids), + ?line P = erlang:trace_pattern({'_','_','_'}, false, [call_time]), + ok. + +%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +combo(suite) -> + []; +combo(doc) -> + ["Tests combining local call trace and meta trace with call time trace"]; +combo(Config) when is_list(Config) -> + ?line Self = self(), + ?line Nbc = 3, + ?line MetaMs = [{'_',[],[{return_trace}]}], + ?line Flags = lists:sort([call, return_to]), + ?line LocalTracer = spawn_link(fun () -> relay_n(5 + Nbc + 3, Self) end), + ?line MetaTracer = spawn_link(fun () -> relay_n(9 + Nbc + 3, Self) end), + ?line 2 = erlang:trace_pattern({?MODULE,seq_r,'_'}, [], [local]), + ?line 2 = erlang:trace_pattern({?MODULE,seq_r,'_'}, true, [call_time]), + ?line 2 = erlang:trace_pattern({?MODULE,seq_r,'_'}, MetaMs, [{meta,MetaTracer}]), + ?line 2 = erlang:trace_pattern({?MODULE,seq_r,'_'}, true, [call_count]), + + % bifs + ?line 2 = erlang:trace_pattern({erlang, term_to_binary, '_'}, [], [local]), + ?line 2 = erlang:trace_pattern({erlang, term_to_binary, '_'}, true, [call_time]), + ?line 2 = erlang:trace_pattern({erlang, term_to_binary, '_'}, MetaMs, [{meta,MetaTracer}]), + %% not implemented + %?line 2 = erlang:trace_pattern({erlang, term_to_binary, '_'}, true, [call_count]), + + ?line 1 = erlang:trace(Self, true, [{tracer,LocalTracer} | Flags]), + %% + ?line {traced,local} = + erlang:trace_info({?MODULE,seq_r,3}, traced), + ?line {match_spec,[]} = + erlang:trace_info({?MODULE,seq_r,3}, match_spec), + ?line {meta,MetaTracer} = + erlang:trace_info({?MODULE,seq_r,3}, meta), + ?line {meta_match_spec,MetaMs} = + erlang:trace_info({?MODULE,seq_r,3}, meta_match_spec), + ?line ok = check_trace_info({?MODULE, seq_r, 3}, [], none), + + %% check empty trace_info for ?MODULE:seq_r/3 + ?line {all,[_|_]=TraceInfo} = erlang:trace_info({?MODULE,seq_r,3}, all), + ?line {value,{traced,local}} = lists:keysearch(traced, 1, TraceInfo), + ?line {value,{match_spec,[]}} = lists:keysearch(match_spec, 1, TraceInfo), + ?line {value,{meta,MetaTracer}} = lists:keysearch(meta, 1, TraceInfo), + ?line {value,{meta_match_spec,MetaMs}} = lists:keysearch(meta_match_spec, 1, TraceInfo), + ?line {value,{call_count,0}} = lists:keysearch(call_count, 1, TraceInfo), + ?line {value,{call_time,[]}} = lists:keysearch(call_time, 1, TraceInfo), + + %% check empty trace_info for erlang:term_to_binary/1 + ?line {all, [_|_] = TraceInfoBif} = erlang:trace_info({erlang, term_to_binary, 1}, all), + ?line {value,{traced,local}} = lists:keysearch(traced, 1, TraceInfoBif), + ?line {value,{match_spec,[]}} = lists:keysearch(match_spec, 1, TraceInfoBif), + ?line {value,{meta, MetaTracer}} = lists:keysearch(meta, 1, TraceInfoBif), + ?line {value,{meta_match_spec,MetaMs}} = lists:keysearch(meta_match_spec, 1, TraceInfoBif), + %% not implemented + ?line {value,{call_count,false}} = lists:keysearch(call_count, 1, TraceInfoBif), + %?line {value,{call_count,0}} = lists:keysearch(call_count, 1, TraceInfoBif), + ?line {value,{call_time,[]}} = lists:keysearch(call_time, 1, TraceInfoBif), + + %% + ?line [3,2,1] = seq_r(1, 3, fun(X) -> X+1 end), + ?line T0 = now(), + ?line with_bif(Nbc), + ?line T1 = now(), + ?line TimeB = timer:now_diff(T1,T0), + %% + + ?line List = collect(100), + ?line {MetaR, LocalR} = + lists:foldl( + fun ({P,X}, {M,L}) when P == MetaTracer -> + {[X|M],L}; + ({P,X}, {M,L}) when P == LocalTracer -> + {M,[X|L]} + end, + {[],[]}, + List), + ?line Meta = lists:reverse(MetaR), + ?line Local = lists:reverse(LocalR), + + ?line [?CTT(Self,{?MODULE,seq_r,[1,3,_]}), + ?CTT(Self,{?MODULE,seq_r,[1,3,_,[]]}), + ?CTT(Self,{?MODULE,seq_r,[2,3,_,[1]]}), + ?CTT(Self,{?MODULE,seq_r,[3,3,_,[2,1]]}), + ?RFT(Self,{?MODULE,seq_r,4},[3,2,1]), + ?RFT(Self,{?MODULE,seq_r,4},[3,2,1]), + ?RFT(Self,{?MODULE,seq_r,4},[3,2,1]), + ?RFT(Self,{?MODULE,seq_r,3},[3,2,1]), + ?CTT(Self,{erlang,term_to_binary,[3]}), % bif + ?RFT(Self,{erlang,term_to_binary,1},<<131,97,3>>), + ?CTT(Self,{erlang,term_to_binary,[2]}), + ?RFT(Self,{erlang,term_to_binary,1},<<131,97,2>>) + ] = Meta, + + ?line [?CT(Self,{?MODULE,seq_r,[1,3,_]}), + ?CT(Self,{?MODULE,seq_r,[1,3,_,[]]}), + ?CT(Self,{?MODULE,seq_r,[2,3,_,[1]]}), + ?CT(Self,{?MODULE,seq_r,[3,3,_,[2,1]]}), + ?RT(Self,{?MODULE,combo,1}), + ?CT(Self,{erlang,term_to_binary,[3]}), % bif + ?RT(Self,{?MODULE,with_bif,1}), + ?CT(Self,{erlang,term_to_binary,[2]}), + ?RT(Self,{?MODULE,with_bif,1}) + ] = Local, + + ?line ok = check_trace_info({?MODULE, seq_r, 3}, [{Self,1,0,0}], 1), + ?line ok = check_trace_info({?MODULE, seq_r, 4}, [{Self,3,0,0}], 1), + ?line ok = check_trace_info({?MODULE, seq_r, 3}, [{Self,1,0,0}], 1), + ?line ok = check_trace_info({?MODULE, seq_r, 4}, [{Self,3,0,0}], 1), + ?line ok = check_trace_info({erlang, term_to_binary, 1}, [{self(), Nbc - 1, 0, 0}], TimeB), + %% + ?line erlang:trace_pattern({'_','_','_'}, false, [local,meta,call_time]), + ?line erlang:trace_pattern(on_load, false, [local,meta,call_time]), + ?line erlang:trace(all, false, [all]), + ok. + +%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +bif(suite) -> + []; +bif(doc) -> + ["Tests tracing of bifs"]; +bif(Config) when is_list(Config) -> + ?line P = erlang:trace_pattern({'_','_','_'}, false, [call_time]), + ?line M = 1000000, + %% + ?line 2 = erlang:trace_pattern({erlang, binary_to_term, '_'}, true, [call_time]), + ?line 2 = erlang:trace_pattern({erlang, term_to_binary, '_'}, true, [call_time]), + ?line Pid = setup(), + ?line {L, T1} = execute(Pid, fun() -> with_bif(M) end), + + ?line ok = check_trace_info({erlang, binary_to_term, 1}, [{Pid, M - 1, 0, 0}], T1/2), + ?line ok = check_trace_info({erlang, term_to_binary, 1}, [{Pid, M - 1, 0, 0}], T1/2), + + % disable term2binary + + ?line 2 = erlang:trace_pattern({erlang, term_to_binary, '_'}, false, [call_time]), + + ?line {L, T2} = execute(Pid, fun() -> with_bif(M) end), + + ?line ok = check_trace_info({erlang, binary_to_term, 1}, [{Pid, M*2 - 2, 0, 0}], T1/2 + T2), + ?line ok = check_trace_info({erlang, term_to_binary, 1}, false, none), + + %% + ?line P = erlang:trace_pattern({'_','_','_'}, false, [call_time]), + ?line Pid ! quit, + ok. + +%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +nif(suite) -> + []; +nif(doc) -> + ["Tests tracing of nifs"]; +nif(Config) when is_list(Config) -> + load_nif(Config), + ?line P = erlang:trace_pattern({'_','_','_'}, false, [call_time]), + ?line M = 1000000, + %% + ?line 1 = erlang:trace_pattern({?MODULE, nif_dec, '_'}, true, [call_time]), + ?line 1 = erlang:trace_pattern({?MODULE, with_nif, '_'}, true, [call_time]), + ?line Pid = setup(), + ?line {L, T1} = execute(Pid, fun() -> with_nif(M) end), + + % the nif is called M - 1 times, the last time the function with 'with_nif' + % returns ok and does not call the nif. + ?line ok = check_trace_info({?MODULE, nif_dec, 1}, [{Pid, M-1, 0, 0}], T1/5*4), + ?line ok = check_trace_info({?MODULE, with_nif, 1}, [{Pid, M, 0, 0}], T1/5), + + %% + ?line P = erlang:trace_pattern({'_','_','_'}, false, [call_time]), + ?line Pid ! quit, + ok. + +%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +called_function(suite) -> + []; +called_function(doc) -> + ["Tests combining nested function calls and that the time accumulates to the right function"]; +called_function(Config) when is_list(Config) -> + ?line P = erlang:trace_pattern({'_','_','_'}, false, [call_time]), + ?line M = 2100, + ?line Pid = setup(), + %% + ?line 1 = erlang:trace_pattern({?MODULE,a_function,'_'}, true, [call_time]), + ?line {L, T1} = execute(Pid, {?MODULE, a_function, [M]}), + ?line ok = check_trace_info({?MODULE, a_function, 1}, [{Pid, M, 0, 0}], T1), + + ?line 1 = erlang:trace_pattern({?MODULE,a_called_function,'_'}, true, [call_time]), + ?line {L, T2} = execute(Pid, {?MODULE, a_function, [M]}), + ?line ok = check_trace_info({?MODULE, a_function, 1}, [{Pid, M+M, 0, 0}], T1 + M*?SINGLE_CALL_US_TIME), + ?line ok = check_trace_info({?MODULE, a_called_function, 1}, [{Pid, M, 0, 0}], T2), + + + ?line 1 = erlang:trace_pattern({?MODULE,dec,'_'}, true, [call_time]), + ?line {L, T3} = execute(Pid, {?MODULE, a_function, [M]}), + ?line ok = check_trace_info({?MODULE, a_function, 1}, [{Pid, M+M+M, 0, 0}], T1 + (M+M)*?SINGLE_CALL_US_TIME), + ?line ok = check_trace_info({?MODULE, a_called_function, 1}, [{Pid, M+M, 0, 0}], T2 + M*?SINGLE_CALL_US_TIME ), + ?line ok = check_trace_info({?MODULE, dec, 1}, [{Pid, M, 0, 0}], T3), + + ?line Pid ! quit, + ?line P = erlang:trace_pattern({'_','_','_'}, false, [call_time]), + ok. + +%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% The Tests +%%% + +%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Local helpers + + +load_nif(Config) -> + ?line Path = ?config(data_dir, Config), + ?line ok = erlang:load_nif(filename:join(Path,"trace_nif"), 0). + + +%% Stack recursive seq +seq(Stop, Stop, Succ) when is_function(Succ) -> + [Stop]; +seq(Start, Stop, Succ) when is_function(Succ) -> + [Start | seq(Succ(Start), Stop, Succ)]. + + +a_function(1) -> a_called_function(1); +a_function(N) when N > 1 -> a_function(a_called_function(N)). + +a_called_function(N) -> dec(N). + +with_bif(1) -> ok; +with_bif(N) -> + with_bif(erlang:binary_to_term(erlang:term_to_binary(N)) - 1). + +with_nif(0) -> error; +with_nif(1) -> ok; +with_nif(N) -> + with_nif(?MODULE:nif_dec(N)). + + +nif_dec(N) -> 0. + +dec(N) -> + loaded(10000), + N - 1. + +loaded(N) when N > 1 -> loaded(N - 1); +loaded(_) -> 5. + + +%% Tail recursive seq, result list is reversed +seq_r(Start, Stop, Succ) when is_function(Succ) -> + seq_r(Start, Stop, Succ, []). + +seq_r(Stop, Stop, _, R) -> + [Stop | R]; +seq_r(Start, Stop, Succ, R) -> + seq_r(Succ(Start), Stop, Succ, [Start | R]). + +% Check call time tracing data and print mismatches +check_trace_info(Mfa, [{Pid, C,_,_}] = Expect, Time) -> + case erlang:trace_info(Mfa, call_time) of + % Time tests are somewhat problematic. We want to know if Time (EXPECTED_TIME) and S*1000000 + Us (ACTUAL_TIME) + % is the same. + % If the ratio EXPECTED_TIME/ACTUAL_TIME is ~ 1 or if EXPECTED_TIME - ACTUAL_TIME is near zero, the test is ok. + {call_time,[{Pid,C,S,Us}]} when S >= 0, Us >= 0, abs(1 - Time/(S*1000000 + Us)) < ?R_ERROR; abs(Time - S*1000000 - Us) < ?US_ERROR -> + ok; + {call_time,[{Pid,C,S,Us}]} -> + Sum = S*1000000 + Us, + io:format("Expected ~p -> {call_time, ~p (Time ~p us)}~n - got ~w s. ~w us. = ~w us. - ~w -> delta ~w (ratio ~.2f, should be 1.0)~n", + [Mfa, Expect, Time, S, Us, Sum, Time, Sum - Time, Time/Sum]), + time_error; + Other -> + io:format("Expected ~p -> {call_time, ~p (Time ~p us)}~n - got ~p~n", [ Mfa, Expect, Time, Other]), + time_count_error + end; +check_trace_info(Mfa, Expect, _) -> + case erlang:trace_info(Mfa, call_time) of + {call_time, Expect} -> + ok; + Other -> + io:format("Expected ~p -> {call_time, ~p}~n - got ~p~n", [Mfa, Expect, Other]), + result_not_expected_error + end. + + +%check process time +check_process_time({value,{Pid, M, S, Us}}, M, F, Time) -> + ?line Sum = S*1000000 + Us, + if + abs(1 - (F/(Time/Sum))) < ?R_ERROR -> + ok; + true -> + io:format("- Pid ~p, Got ratio ~.2f, expected ratio ~w~n", [Pid, Time/Sum,F]), + schedule_time_error + end; +check_process_time(Other, M, _, _) -> + io:format(" - Got ~p, expected count ~w~n", [Other, M]), + error. + + + +%% Message relay process +relay_n(0, _) -> + ok; +relay_n(N, Dest) -> + receive Msg -> + Dest ! {self(), Msg}, + relay_n(N-1, Dest) + end. + + + +%% Collect received messages +collect(Time) -> + Ref = erlang:start_timer(Time, self(), done), + L = lists:reverse(collect([], Ref)), + ?dbgformat("Got: ~p~n",[L]), + L. + +collect(A, 0) -> + receive + Mess -> + collect([Mess | A], 0) + after 0 -> + A + end; +collect(A, Ref) -> + receive + {timeout, Ref, done} -> + collect(A, 0); + Mess -> + collect([Mess | A], Ref) + end. + +setup() -> + Pid = spawn_link(fun() -> loop() end), + ?line 1 = erlang:trace(Pid, true, [call]), + Pid. + +execute(Pids, Mfa) when is_list(Pids) -> + T0 = now(), + [P ! {self(), execute, Mfa} || P <- Pids], + As = [receive {P, answer, Answer} -> Answer end || P <- Pids], + T1 = now(), + {As, timer:now_diff(T1,T0)}; +execute(P, Mfa) -> + T0 = now(), + P ! {self(), execute, Mfa}, + A = receive {P, answer, Answer} -> Answer end, + T1 = now(), + {A, timer:now_diff(T1,T0)}. + + + +loop() -> + receive + quit -> + ok; + {Pid, execute, Fun } when is_function(Fun) -> + Pid ! {self(), answer, erlang:apply(Fun, [])}, + loop(); + {Pid, execute, {M, F, A}} -> + Pid ! {self(), answer, erlang:apply(M, F, A)}, + loop() + end. diff --git a/erts/emulator/test/trace_call_time_SUITE_data/Makefile.src b/erts/emulator/test/trace_call_time_SUITE_data/Makefile.src new file mode 100644 index 0000000000..2b2a35bd2c --- /dev/null +++ b/erts/emulator/test/trace_call_time_SUITE_data/Makefile.src @@ -0,0 +1,6 @@ + +NIF_LIBS = trace_nif@dll@ + +all: $(NIF_LIBS) + +@SHLIB_RULES@ diff --git a/erts/emulator/test/trace_call_time_SUITE_data/trace_nif.c b/erts/emulator/test/trace_call_time_SUITE_data/trace_nif.c new file mode 100644 index 0000000000..33b346aab7 --- /dev/null +++ b/erts/emulator/test/trace_call_time_SUITE_data/trace_nif.c @@ -0,0 +1,37 @@ +#include "erl_nif.h" + + +static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) +{ + return 0; +} + +static int reload(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) +{ + return 0; +} + +static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info) +{ + return 0; +} + +static void unload(ErlNifEnv* env, void* priv_data) +{ +} + +static ERL_NIF_TERM nif_dec_1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + int x = 0; + enif_get_uint(env, argv[0], &x); + return enif_make_int(env, x - 1); +} + + + +static ErlNifFunc nif_funcs[] = +{ + {"nif_dec", 1, nif_dec_1} +}; + +ERL_NIF_INIT(trace_call_time_SUITE,nif_funcs,load,reload,upgrade,unload) diff --git a/erts/include/internal/ethread.h b/erts/include/internal/ethread.h index 934a79c6f9..4e7a38cd5c 100644 --- a/erts/include/internal/ethread.h +++ b/erts/include/internal/ethread.h @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 2004-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 2004-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ @@ -375,6 +375,12 @@ ETHR_INLINE_FUNC_NAME_(ethr_mutex_unlock)(ethr_mutex *mtx) #define ETHR_HAVE_OPTIMIZED_ATOMIC_OPS 1 #define ETHR_HAVE_OPTIMIZED_LOCKS 1 +#define ETHR_MEMORY_BARRIER \ +do { \ + volatile LONG x___ = 0; \ + (void) _InterlockedCompareExchange(&x___, (LONG) 1, (LONG) 0); \ +} while (0) + typedef struct { volatile LONG value; } ethr_atomic_t; @@ -688,25 +694,33 @@ ETHR_INLINE_FUNC_NAME_(ethr_write_lock)(ethr_rwlock_t *lock) #endif /* For CPU-optimised atomics, spinlocks, and rwlocks. */ -#if !defined(ETHR_DISABLE_NATIVE_IMPLS) && defined(__GNUC__) -# if ETHR_SIZEOF_PTR == 4 -# if defined(__i386__) -# include "i386/ethread.h" -# elif (defined(__powerpc__) || defined(__ppc__)) && !defined(__powerpc64__) -# include "ppc32/ethread.h" -# elif defined(__sparc__) -# include "sparc32/ethread.h" -# elif defined(__tile__) -# include "tile/ethread.h" +#if !defined(ETHR_DISABLE_NATIVE_IMPLS) +# if defined(__GNUC__) +# if defined(ETHR_PREFER_GCC_NATIVE_IMPLS) +# include "gcc/ethread.h" # endif -# elif ETHR_SIZEOF_PTR == 8 -# if defined(__x86_64__) -# include "x86_64/ethread.h" -# elif defined(__sparc__) && defined(__arch64__) -# include "sparc64/ethread.h" +# ifndef ETHR_HAVE_NATIVE_ATOMICS +# if ETHR_SIZEOF_PTR == 4 +# if defined(__i386__) +# include "i386/ethread.h" +# elif (defined(__powerpc__)||defined(__ppc__))&&!defined(__powerpc64__) +# include "ppc32/ethread.h" +# elif defined(__sparc__) +# include "sparc32/ethread.h" +# elif defined(__tile__) +# include "tile/ethread.h" +# endif +# elif ETHR_SIZEOF_PTR == 8 +# if defined(__x86_64__) +# include "x86_64/ethread.h" +# elif defined(__sparc__) && defined(__arch64__) +# include "sparc64/ethread.h" +# endif +# endif +# include "gcc/ethread.h" # endif # endif -#endif /* !defined(ETHR_DISABLE_NATIVE_IMPLS) && defined(__GNUC__) */ +#endif /* !defined(ETHR_DISABLE_NATIVE_IMPLS) */ #ifdef ETHR_HAVE_OPTIMIZED_ATOMIC_OPS # undef ETHR_HAVE_NATIVE_ATOMICS @@ -1124,8 +1138,14 @@ ETHR_INLINE_FUNC_NAME_(ethr_write_lock)(ethr_rwlock_t *lock) */ #ifndef ETHR_HAVE_OPTIMIZED_ATOMIC_OPS -#define ETHR_ATOMIC_ADDR_BITS 4 -#define ETHR_ATOMIC_ADDR_SHIFT 3 +/* + * ETHR_MEMORY_BARRIER orders between locked and atomic accesses only, + * i.e. when this atomic fallback is used a noop is sufficient. + */ +#define ETHR_MEMORY_BARRIER + +#define ETHR_ATOMIC_ADDR_BITS 10 +#define ETHR_ATOMIC_ADDR_SHIFT 6 typedef struct { union { diff --git a/erts/include/internal/ethread_header_config.h.in b/erts/include/internal/ethread_header_config.h.in index e5b4946a53..c9fd87c2f6 100644 --- a/erts/include/internal/ethread_header_config.h.in +++ b/erts/include/internal/ethread_header_config.h.in @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 2004-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 2004-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ @@ -50,6 +50,19 @@ /* Define if you have a pthread_rwlock implementation that can be used */ #undef ETHR_HAVE_PTHREAD_RWLOCK_INIT +/* Define if you have the pthread_rwlockattr_setkind_np() function. */ +#undef ETHR_HAVE_PTHREAD_RWLOCKATTR_SETKIND_NP + +/* Define if you have the PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP rwlock + attribute. */ +#undef ETHR_HAVE_PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP + +/* Define if you have gcc atomic operations */ +#undef ETHR_HAVE_GCC_ATOMIC_OPS + +/* Define if you prefer gcc native ethread implementations */ +#undef ETHR_PREFER_GCC_NATIVE_IMPLS + /* Define if you want to turn on extra sanity checking in the ethread library */ #undef ETHR_XCHK diff --git a/erts/include/internal/gcc/ethr_atomic.h b/erts/include/internal/gcc/ethr_atomic.h new file mode 100644 index 0000000000..775030c8d5 --- /dev/null +++ b/erts/include/internal/gcc/ethr_atomic.h @@ -0,0 +1,164 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +/* + * Description: Native atomics ethread support using gcc's builtins + * Author: Rickard Green + */ + +#ifndef ETHR_GCC_ATOMIC_H__ +#define ETHR_GCC_ATOMIC_H__ + +#if !defined(ETHR_HAVE_NATIVE_ATOMICS) && defined(ETHR_HAVE_GCC_ATOMIC_OPS) +#define ETHR_HAVE_NATIVE_ATOMICS 1 + +#define ETHR_IMMED_ATOMIC_SET_GET_SAFE__ 0 +/* Enable immediate read/write on platforms where we know it is safe */ +#if defined(__i386__) || defined(__x86_64__) || defined(__sparc__) \ + || defined(__powerpc__) || defined(__ppc__) +# undef ETHR_IMMED_ATOMIC_SET_GET_SAFE__ +# define ETHR_IMMED_ATOMIC_SET_GET_SAFE__ 1 +#endif + +typedef struct { + volatile long counter; +} ethr_native_atomic_t; + + +/* + * According to the documentation this is what we want: + * #define ETHR_MEMORY_BARRIER __sync_synchronize() + * However, __sync_synchronize() is known to erroneously be + * a noop on at least some platforms with some gcc versions. + * This has suposedly been fixed in some gcc version, but we + * don't know from which version. Therefore, we use the + * workaround implemented below on all gcc versions. + */ +#define ETHR_MEMORY_BARRIER \ +do { \ + volatile long x___ = 0; \ + (void) __sync_val_compare_and_swap(&x___, (long) 0, (long) 1); \ +} while (0) + +#if defined(ETHR_TRY_INLINE_FUNCS) || defined(ETHR_AUX_IMPL__) + +static ETHR_INLINE void +ethr_native_atomic_set(ethr_native_atomic_t *var, long value) +{ +#if ETHR_IMMED_ATOMIC_SET_GET_SAFE__ + var->counter = value; +#else + /* + * Unfortunately no __sync_store() or similar exist in the gcc atomic + * op interface. We therefore have to simulate it this way... + */ + long act = 0, exp; + do { + exp = act; + act = __sync_val_compare_and_swap(&var->counter, exp, value); + } while (act != exp); +#endif +} + +#define ethr_native_atomic_init ethr_native_atomic_set + +static ETHR_INLINE long +ethr_native_atomic_read(ethr_native_atomic_t *var) +{ +#if ETHR_IMMED_ATOMIC_SET_GET_SAFE__ + return var->counter; +#else + /* + * Unfortunately no __sync_fetch() or similar exist in the gcc atomic + * op interface. We therefore have to simulate it this way... + */ + return __sync_add_and_fetch(&var->counter, (long) 0); +#endif +} + +static ETHR_INLINE void +ethr_native_atomic_add(ethr_native_atomic_t *var, long incr) +{ + (void) __sync_add_and_fetch(&var->counter, incr); +} + +static ETHR_INLINE long +ethr_native_atomic_add_return(ethr_native_atomic_t *var, long incr) +{ + return __sync_add_and_fetch(&var->counter, incr); +} + +static ETHR_INLINE void +ethr_native_atomic_inc(ethr_native_atomic_t *var) +{ + (void) __sync_add_and_fetch(&var->counter, (long) 1); +} + +static ETHR_INLINE void +ethr_native_atomic_dec(ethr_native_atomic_t *var) +{ + (void) __sync_sub_and_fetch(&var->counter, (long) 1); +} + +static ETHR_INLINE long +ethr_native_atomic_inc_return(ethr_native_atomic_t *var) +{ + return __sync_add_and_fetch(&var->counter, (long) 1); +} + +static ETHR_INLINE long +ethr_native_atomic_dec_return(ethr_native_atomic_t *var) +{ + return __sync_sub_and_fetch(&var->counter, (long) 1); +} + +static ETHR_INLINE long +ethr_native_atomic_and_retold(ethr_native_atomic_t *var, long mask) +{ + return __sync_fetch_and_and(&var->counter, mask); +} + +static ETHR_INLINE long +ethr_native_atomic_or_retold(ethr_native_atomic_t *var, long mask) +{ + return (long) __sync_fetch_and_or(&var->counter, mask); +} + +static ETHR_INLINE long +ethr_native_atomic_cmpxchg(ethr_native_atomic_t *var, long new, long old) +{ + return __sync_val_compare_and_swap(&var->counter, old, new); +} + +static ETHR_INLINE long +ethr_native_atomic_xchg(ethr_native_atomic_t *var, long new) +{ + long exp, act = 0; + do { + exp = act; + act = __sync_val_compare_and_swap(&var->counter, exp, new); + } while (act != exp); + return act; +} + +#endif + +#endif + +#endif diff --git a/erts/include/internal/gcc/ethread.h b/erts/include/internal/gcc/ethread.h new file mode 100644 index 0000000000..bb378e31e0 --- /dev/null +++ b/erts/include/internal/gcc/ethread.h @@ -0,0 +1,30 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +/* + * Description: Native atomic ethread support when using gcc + * Author: Rickard Green + */ + +#ifndef ETHREAD_GCC_H__ +#define ETHREAD_GCC_H__ + +#include "ethr_atomic.h" + +#endif diff --git a/erts/include/internal/i386/atomic.h b/erts/include/internal/i386/atomic.h index 3291ad38e5..90b4c5f773 100644 --- a/erts/include/internal/i386/atomic.h +++ b/erts/include/internal/i386/atomic.h @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 2005-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 2005-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ @@ -32,6 +32,16 @@ typedef struct { volatile long counter; } ethr_native_atomic_t; +#ifdef __x86_64__ +#define ETHR_MEMORY_BARRIER __asm__ __volatile__("mfence" : : : "memory") +#else +#define ETHR_MEMORY_BARRIER \ +do { \ + volatile long x___ = 0; \ + __asm__ __volatile__("lock; incl %0" : "=m"(x___) : "m"(x___) : "memory"); \ +} while (0) +#endif + #ifdef ETHR_TRY_INLINE_FUNCS #ifdef __x86_64__ diff --git a/erts/include/internal/ppc32/atomic.h b/erts/include/internal/ppc32/atomic.h index fa701c6a92..105d874995 100644 --- a/erts/include/internal/ppc32/atomic.h +++ b/erts/include/internal/ppc32/atomic.h @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 2005-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 2005-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ @@ -32,6 +32,7 @@ typedef struct { volatile int counter; } ethr_native_atomic_t; +#define ETHR_MEMORY_BARRIER __asm__ __volatile__("sync" : : : "memory") #ifdef ETHR_TRY_INLINE_FUNCS diff --git a/erts/include/internal/sparc32/atomic.h b/erts/include/internal/sparc32/atomic.h index d6fdc6b2a4..8fde449a52 100644 --- a/erts/include/internal/sparc32/atomic.h +++ b/erts/include/internal/sparc32/atomic.h @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 2005-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 2005-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ @@ -28,6 +28,10 @@ typedef struct { volatile long counter; } ethr_native_atomic_t; +#define ETHR_MEMORY_BARRIER \ + __asm__ __volatile__("membar #LoadLoad|#LoadStore|#StoreLoad|#StoreStore\n" \ + : : : "memory") + #ifdef ETHR_TRY_INLINE_FUNCS #if defined(__arch64__) diff --git a/erts/include/internal/tile/atomic.h b/erts/include/internal/tile/atomic.h index 59a9250e7c..5e4c7ac9fe 100644 --- a/erts/include/internal/tile/atomic.h +++ b/erts/include/internal/tile/atomic.h @@ -32,6 +32,8 @@ typedef struct { volatile long counter; } ethr_native_atomic_t; +#define ETHR_MEMORY_BARRIER __insn_mf() + #ifdef ETHR_TRY_INLINE_FUNCS static ETHR_INLINE void diff --git a/erts/lib_src/Makefile.in b/erts/lib_src/Makefile.in index 49f5b1f048..e7caac8072 100644 --- a/erts/lib_src/Makefile.in +++ b/erts/lib_src/Makefile.in @@ -447,7 +447,7 @@ INTERNAL_RELEASE_INCLUDES= \ $(ERTS_INCL_INT)/erl_misc_utils.h \ $(ERTS_INCL_INT)/erl_errno.h -INTERNAL_X_RELEASE_INCLUDE_DIRS= i386 x86_64 ppc32 sparc32 sparc64 tile +INTERNAL_X_RELEASE_INCLUDE_DIRS= i386 x86_64 ppc32 sparc32 sparc64 tile gcc INTERNAL_RELEASE_LIBS= \ ../lib/internal/README \ diff --git a/erts/lib_src/common/ethread.c b/erts/lib_src/common/ethread.c index eb4d0cad20..9c88233934 100644 --- a/erts/lib_src/common/ethread.c +++ b/erts/lib_src/common/ethread.c @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 2004-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 2004-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ @@ -269,6 +269,11 @@ static ethr_mutex no_ethrs_mtx; #warning "Cannot enforce fork-safety" #endif +#ifdef ETHR_HAVE_PTHREAD_RWLOCK_INIT +static pthread_rwlockattr_t write_pref_attr_data; +static pthread_rwlockattr_t *write_pref_attr; +#endif + /* * ---------------------------------------------------------------------------- * Static functions @@ -525,6 +530,24 @@ ethr_init(ethr_init_data *id) } #endif +#ifdef ETHR_HAVE_PTHREAD_RWLOCK_INIT +#if defined(ETHR_HAVE_PTHREAD_RWLOCKATTR_SETKIND_NP) \ + && defined(ETHR_HAVE_PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP) + res = pthread_rwlockattr_init(&write_pref_attr_data); + if (res != 0) + goto error; + res = pthread_rwlockattr_setkind_np( + &write_pref_attr_data, + PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP); + if (res != 0) + goto error; + write_pref_attr = &write_pref_attr_data; +#else + write_pref_attr = NULL; +#endif +#endif + + return 0; error: @@ -1060,7 +1083,7 @@ ethr_rwmutex_init(ethr_rwmutex *rwmtx) } rwmtx->initialized = ETHR_RWMUTEX_INITIALIZED; #endif - return pthread_rwlock_init(&rwmtx->pt_rwlock, NULL); + return pthread_rwlock_init(&rwmtx->pt_rwlock, write_pref_attr); } int @@ -3193,7 +3216,7 @@ ethr_rwmutex_tryrlock(ethr_rwmutex *rwmtx) res = ethr_mutex_trylock__(&rwmtx->mtx); if (res != 0) return res; - if (!rwmtx->waiting_writers) { + if (rwmtx->waiting_writers) { res = ethr_mutex_unlock__(&rwmtx->mtx); if (res == 0) return EBUSY; diff --git a/erts/preloaded/ebin/erl_prim_loader.beam b/erts/preloaded/ebin/erl_prim_loader.beam Binary files differindex fe3cee1c56..0d2be9e1ea 100644 --- a/erts/preloaded/ebin/erl_prim_loader.beam +++ b/erts/preloaded/ebin/erl_prim_loader.beam diff --git a/erts/preloaded/ebin/erlang.beam b/erts/preloaded/ebin/erlang.beam Binary files differindex 5a4c5e9d1e..a33a570891 100644 --- a/erts/preloaded/ebin/erlang.beam +++ b/erts/preloaded/ebin/erlang.beam diff --git a/erts/preloaded/ebin/init.beam b/erts/preloaded/ebin/init.beam Binary files differindex cfe2c36cee..2cb7d3bd25 100644 --- a/erts/preloaded/ebin/init.beam +++ b/erts/preloaded/ebin/init.beam diff --git a/erts/preloaded/ebin/otp_ring0.beam b/erts/preloaded/ebin/otp_ring0.beam Binary files differindex 74587de26b..4c1e8aa63f 100644 --- a/erts/preloaded/ebin/otp_ring0.beam +++ b/erts/preloaded/ebin/otp_ring0.beam diff --git a/erts/preloaded/ebin/prim_file.beam b/erts/preloaded/ebin/prim_file.beam Binary files differindex c6610b71e6..7e99523527 100644 --- a/erts/preloaded/ebin/prim_file.beam +++ b/erts/preloaded/ebin/prim_file.beam diff --git a/erts/preloaded/ebin/prim_inet.beam b/erts/preloaded/ebin/prim_inet.beam Binary files differindex 8d19923281..52d1d9215f 100644 --- a/erts/preloaded/ebin/prim_inet.beam +++ b/erts/preloaded/ebin/prim_inet.beam diff --git a/erts/preloaded/ebin/prim_zip.beam b/erts/preloaded/ebin/prim_zip.beam Binary files differindex cd41f36413..d31de6cfd3 100644 --- a/erts/preloaded/ebin/prim_zip.beam +++ b/erts/preloaded/ebin/prim_zip.beam diff --git a/erts/preloaded/ebin/zlib.beam b/erts/preloaded/ebin/zlib.beam Binary files differindex ce1163d260..ac4537662b 100644 --- a/erts/preloaded/ebin/zlib.beam +++ b/erts/preloaded/ebin/zlib.beam diff --git a/erts/preloaded/src/erl_prim_loader.erl b/erts/preloaded/src/erl_prim_loader.erl index a13292d5ab..024b20eadb 100644 --- a/erts/preloaded/src/erl_prim_loader.erl +++ b/erts/preloaded/src/erl_prim_loader.erl @@ -405,7 +405,7 @@ handle_timeout(State = #state{loader = inet}, Parent) -> efile_multi_get_file_from_port(State, ModFiles, Paths, Fun) -> Ref = make_ref(), %% More than 200 processes is no gain. - Max = min(200, erlang:system_info(thread_pool_size)), + Max = erlang:min(200, erlang:system_info(thread_pool_size)), efile_multi_get_file_from_port2(ModFiles, 0, Max, State, Paths, Fun, Ref, ok). efile_multi_get_file_from_port2([MF | MFs], Out, Max, State, Paths, Fun, Ref, Ret) when Out < Max -> @@ -1189,9 +1189,6 @@ keyins(X, I, [Y | T]) when X < element(I,Y) -> [X,Y|T]; keyins(X, I, [Y | T]) -> [Y | keyins(X, I, T)]; keyins(X, _I, []) -> [X]. -min(X, Y) when X < Y -> X; -min(_X, Y) -> Y. - to_strs([P|Paths]) when is_atom(P) -> [atom_to_list(P)|to_strs(Paths)]; to_strs([P|Paths]) when is_list(P) -> diff --git a/erts/preloaded/src/erlang.erl b/erts/preloaded/src/erlang.erl index 1edb5e72db..935c2de253 100644 --- a/erts/preloaded/src/erlang.erl +++ b/erts/preloaded/src/erlang.erl @@ -44,7 +44,14 @@ -deprecated([hash/2]). -deprecated([concat_binary/1]). --compile(nowarn_bif_clash). +% Get rid of autoimports of spawn to avoid clashes with ourselves. +-compile({no_auto_import,[spawn/1]}). +-compile({no_auto_import,[spawn/4]}). +-compile({no_auto_import,[spawn_link/1]}). +-compile({no_auto_import,[spawn_link/4]}). +-compile({no_auto_import,[spawn_opt/2]}). +-compile({no_auto_import,[spawn_opt/4]}). +-compile({no_auto_import,[spawn_opt/5]}). %%-------------------------------------------------------------------------- @@ -55,10 +62,10 @@ %%-------------------------------------------------------------------------- apply(Fun, Args) -> - apply(Fun, Args). + erlang:apply(Fun, Args). apply(Mod, Name, Args) -> - apply(Mod, Name, Args). + erlang:apply(Mod, Name, Args). %% Spawns with a fun diff --git a/erts/preloaded/src/prim_file.erl b/erts/preloaded/src/prim_file.erl index 2d177bf80e..7f24889bb2 100644 --- a/erts/preloaded/src/prim_file.erl +++ b/erts/preloaded/src/prim_file.erl @@ -116,9 +116,10 @@ -define(EFILE_MODE_READ_WRITE, 3). -define(EFILE_MODE_APPEND, 4). -define(EFILE_COMPRESSED, 8). +-define(EFILE_MODE_EXCL, 16). %% Use this mask to get just the mode bits to be passed to the driver. --define(EFILE_MODE_MASK, 15). +-define(EFILE_MODE_MASK, 31). %% Seek modes for the driver's seek function. -define(EFILE_SEEK_SET, 0). @@ -958,6 +959,8 @@ open_mode([compressed|Rest], Mode, Portopts, Setopts) -> open_mode([append|Rest], Mode, Portopts, Setopts) -> open_mode(Rest, Mode bor ?EFILE_MODE_APPEND bor ?EFILE_MODE_WRITE, Portopts, Setopts); +open_mode([exclusive|Rest], Mode, Portopts, Setopts) -> + open_mode(Rest, Mode bor ?EFILE_MODE_EXCL, Portopts, Setopts); open_mode([delayed_write|Rest], Mode, Portopts, Setopts) -> open_mode([{delayed_write, 64*1024, 2000}|Rest], Mode, Portopts, Setopts); diff --git a/lib/appmon/src/appmon.erl b/lib/appmon/src/appmon.erl index 6f5d2824d2..2b982cddf0 100644 --- a/lib/appmon/src/appmon.erl +++ b/lib/appmon/src/appmon.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1996-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% -module(appmon). -behaviour(gen_server). @@ -838,7 +838,7 @@ draw_apps(GUI, [App | Apps], X, Lx0, N, GSObjs) -> %% Some necessary data {_Pid, AppName, _Descr} = App, Text = atom_to_list(AppName), - Width = max(8*length(Text)+10, ?wBTN), + Width = erlang:max(8*length(Text)+10, ?wBTN), %% Connect the application to the node label with a line %% Lx0 = leftmost X coordinate (above previous application button) @@ -1009,9 +1009,6 @@ bcast(MNodes, Msg) -> end, MNodes). -max(X, Y) when X>Y -> X; -max(_, Y) -> Y. - %% parse_nodes(MNodes) -> NodeApps %% MNodes -> [#mnode{}] %% NodeApps -> [{Node, Status, Apps}] diff --git a/lib/appmon/src/appmon_info.erl b/lib/appmon/src/appmon_info.erl index 4e36d3a13f..332140f69d 100644 --- a/lib/appmon/src/appmon_info.erl +++ b/lib/appmon/src/appmon_info.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1996-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %%---------------------------------------------------------------------- @@ -807,24 +807,21 @@ load(Opts) -> case get_opt(load_scale, Opts) of linear -> - min(trunc(load_range()*(Td/Tot+Q/6)), + erlang:min(trunc(load_range()*(Td/Tot+Q/6)), load_range()); prog -> - min(trunc(load_range()*prog(Td/Tot+Q/6)), + erlang:min(trunc(load_range()*prog(Td/Tot+Q/6)), load_range()) end; queue -> case get_opt(load_scale, Opts) of linear -> - min(trunc(load_range()*Q/6), load_range()); + erlang:min(trunc(load_range()*Q/6), load_range()); prog -> - min(trunc(load_range()*prog(Q/6)), load_range()) + erlang:min(trunc(load_range()*prog(Q/6)), load_range()) end end. -min(X,Y) when X<Y -> X; -min(_,Y)->Y. - %% %% T shall be within 0 and 0.9 for this to work correctly diff --git a/lib/appmon/src/appmon_place.erl b/lib/appmon/src/appmon_place.erl index 5a6ae6aa48..fe1e909d7c 100644 --- a/lib/appmon/src/appmon_place.erl +++ b/lib/appmon/src/appmon_place.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1996-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %%------------------------------------------------------------ %% @@ -155,10 +155,8 @@ move2(DG, V, LastX, DeltaX) -> ChLX = foldl(fun(C, LX) -> move2(DG, C, LX, DeltaX) end, tll(LastX), appmon_dg:get(out, DG, V)), - [max(NewX+appmon_dg:get(w, DG, V), hdd(LastX)) | ChLX]. + [erlang:max(NewX+appmon_dg:get(w, DG, V), hdd(LastX)) | ChLX]. -max(A, B) when A>B -> A; -max(_, B) -> B. %%------------------------------------------------------------ %% diff --git a/lib/compiler/doc/src/compile.xml b/lib/compiler/doc/src/compile.xml index bbd3f1043d..e1f24b602d 100644 --- a/lib/compiler/doc/src/compile.xml +++ b/lib/compiler/doc/src/compile.xml @@ -310,6 +310,23 @@ (there will not even be a warning if there is a mismatch).</p> </item> + <tag><c>{no_auto_import,[F/A, ...]}</c></tag> + <item> + <p>Makes the function <c>F/A</c> no longer beeing + auto-imported from the module <c>erlang</c>, which resolves + BIF name clashes. This option has to be used to resolve name + clashes with BIFs auto-imported before R14A, if one wants to + call the local function with the same name as an + auto-imported BIF without module prefix.</p> + <note> + <p>From R14A and forward, the compiler resolves calls + without module prefix to local or imported functions before + trying auto-imported BIFs. If the BIF is to be + called, use the <c>erlang</c> module prefix in the call, not + <c>{ no_auto_import,[F/A, ...]}</c></p> + </note> + </item> + </taglist> <p>If warnings are turned on (the <c>report_warnings</c> option @@ -338,31 +355,35 @@ <tag><c>nowarn_bif_clash</c></tag> <item> - <p>By default, there will be a compilation error if a - module contains an exported function with the same name - as an auto-imported BIF (such as <c>size/1</c>) AND - there is a call to it without a qualifying module name. - The reason is that the BIF will be called, not - the function in the same module. The recommended way to - eliminate that warning is to use a call with a module - name - either <c>erlang</c> to call the BIF or - <c>?MODULE</c> to call the function in the same module. - The warning can also be turned off using this option, - but that is not recommended.</p> + <p>This option is removed, it will generate a fatal error if used.</p> + + <warning> + <p>Beginning with R14A, the compiler no longer calls the + auto-imported BIF if the name clashes with a local or + explicitly imported function and a call without explicit + module name is issued. Instead the local or imported + function is called. Still accepting <c>nowarn_bif_clash</c> would makes a + module calling functions clashing with autoimported BIFs + compile with both the old and new compilers, but with + completely different semantics, why the option was removed.</p> - <p><em>The use of this option is strongly discouraged, - as code that uses it will probably break in a future - major release (R14 or R15).</em></p> + <p>The use of this option has always been strongly discouraged. + From OTP R14A and forward it's an error to use it.</p> + <p>To resolve BIF clashes, use explicit module names or the + <c>{no_auto_import,[F/A]}</c> compiler directive.</p> + </warning> </item> <tag><c>{nowarn_bif_clash, FAs}</c></tag> <item> - <p>Turns off warnings as <c>nowarn_bif_clash</c> but only - for the mentioned local functions. <c>FAs</c> is a tuple - <c>{Name,Arity}</c> or a list of such tuples.</p> - <p><em>The use of this option is strongly discouraged, - as code that uses it will probably break in a future - major release (R14 or R15).</em></p> + <p>This option is removed, it will generate a fatal error if used.</p> + + <warning> + <p>The use of this option has always been strongly discouraged. + From OTP R14A and forward it's an error to use it.</p> + <p>To resolve BIF clashes, use explicit module names or the + <c>{no_auto_import,[F/A]}</c> compiler directive.</p> + </warning> </item> <tag><c>warn_export_all</c></tag> diff --git a/lib/compiler/src/beam_dead.erl b/lib/compiler/src/beam_dead.erl index 7b4cd814a2..bb93110176 100644 --- a/lib/compiler/src/beam_dead.erl +++ b/lib/compiler/src/beam_dead.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2002-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2002-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -281,12 +281,12 @@ forward([{test,is_eq_exact,_,[Dst,Src]}=I,{move,Src,Dst}|Is], D, Lc, Acc) -> forward([I|Is], D, Lc, Acc); forward([{test,is_nil,_,[Dst]}=I,{move,nil,Dst}|Is], D, Lc, Acc) -> forward([I|Is], D, Lc, Acc); -forward([{test,is_eq_exact,_,[_,{atom,_}]}=I|Is], D, Lc, [{label,_}|_]=Acc) -> +forward([{test,is_eq_exact,_,_}=I|Is], D, Lc, Acc) -> case Is of [{label,_}|_] -> forward(Is, D, Lc, [I|Acc]); _ -> forward(Is, D, Lc+1, [{label,Lc},I|Acc]) end; -forward([{test,is_ne_exact,_,[_,{atom,_}]}=I|Is], D, Lc, [{label,_}|_]=Acc) -> +forward([{test,is_ne_exact,_,_}=I|Is], D, Lc, Acc) -> case Is of [{label,_}|_] -> forward(Is, D, Lc, [I|Acc]); _ -> forward(Is, D, Lc+1, [{label,Lc},I|Acc]) @@ -371,10 +371,10 @@ backward([{test,bs_start_match2,{f,To0},Live,[Src|_]=Info,Dst}|Is], D, Acc) -> To = shortcut_bs_start_match(To0, Src, D), I = {test,bs_start_match2,{f,To},Live,Info,Dst}, backward(Is, D, [I|Acc]); -backward([{test,is_eq_exact=Op,{f,To0},[Reg,{atom,Val}]=Ops}|Is], D, Acc) -> +backward([{test,is_eq_exact,{f,To0},[Reg,{atom,Val}]=Ops}|Is], D, Acc) -> To1 = shortcut_bs_test(To0, Is, D), To = shortcut_fail_label(To1, Reg, Val, D), - I = {test,Op,{f,To},Ops}, + I = combine_eqs(To, Ops, D, Acc), backward(Is, D, [I|Acc]); backward([{test,Op,{f,To0},Ops0}|Is], D, Acc) -> To1 = shortcut_bs_test(To0, Is, D), @@ -394,7 +394,10 @@ backward([{test,Op,{f,To0},Ops0}|Is], D, Acc) -> _Code -> To2 end, - I = {test,Op,{f,To},Ops0}, + I = case Op of + is_eq_exact -> combine_eqs(To, Ops0, D, Acc); + _ -> {test,Op,{f,To},Ops0} + end, backward(Is, D, [I|Acc]); backward([{test,Op,{f,To0},Live,Ops0,Dst}|Is], D, Acc) -> To1 = shortcut_bs_test(To0, Is, D), @@ -519,6 +522,41 @@ bif_to_test(Name, Args, Fail) -> not_possible() -> throw(not_possible). +%% combine_eqs(To, Operands, Acc) -> Instruction. +%% Combine two is_eq_exact instructions or (an is_eq_exact +%% instruction and a select_val instruction) to a select_val +%% instruction if possible. +%% +%% Example: +%% +%% is_eq_exact F1 Reg Lit1 select_val Reg F2 [ Lit1 L1 +%% L1: . Lit2 L2 ] +%% . +%% . ==> +%% . +%% F1: is_eq_exact F2 Reg Lit2 F1: is_eq_exact F2 Reg Lit2 +%% L2: .... L2: +%% +combine_eqs(To, [Reg,{Type,_}=Lit1]=Ops, D, [{label,L1}|_]) + when Type =:= atom; Type =:= integer -> + case beam_utils:code_at(To, D) of + [{test,is_eq_exact,{f,F2},[Reg,{Type,_}=Lit2]}, + {label,L2}|_] when Lit1 =/= Lit2 -> + {select_val,Reg,{f,F2},{list,[Lit1,{f,L1},Lit2,{f,L2}]}}; + [{select_val,Reg,{f,F2},{list,[{Type,_}|_]=List0}}|_] -> + List = remove_from_list(Lit1, List0), + {select_val,Reg,{f,F2},{list,[Lit1,{f,L1}|List]}}; + _Is -> + {test,is_eq_exact,{f,To},Ops} + end; +combine_eqs(To, Ops, _D, _Acc) -> + {test,is_eq_exact,{f,To},Ops}. + +remove_from_list(Lit, [Lit,{f,_}|T]) -> + T; +remove_from_list(Lit, [Val,{f,_}=Fail|T]) -> + [Val,Fail|remove_from_list(Lit, T)]; +remove_from_list(_, []) -> []. %% shortcut_bs_test(TargetLabel, [Instruction], D) -> TargetLabel' %% Try to shortcut the failure label for a bit syntax matching. diff --git a/lib/compiler/src/beam_peep.erl b/lib/compiler/src/beam_peep.erl index d03ac4b1f4..f39fc50b95 100644 --- a/lib/compiler/src/beam_peep.erl +++ b/lib/compiler/src/beam_peep.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2008-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2008-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -64,22 +64,7 @@ function({function,Name,Arity,CLabel,Is0}) -> %% InEncoding =:= latin1, OutEncoding =:= unicode; %% InEncoding =:= latin1, OutEncoding =:= utf8 -> %% -%% (2) Code like -%% -%% is_ne_exact Fail Reg Literal1 -%% is_ne_exact Fail Reg Literal2 -%% is_ne_exact Fail Reg Literal3 -%% is_eq_exact UltimateFail Reg Literal4 -%% Fail: .... -%% -%% can be rewritten to -%% -%% select_val Reg UltimateFail [ Literal1 Fail -%% Literal2 Fail -%% Literal3 Fail -%% Literal4 Fail ] -%% -%% (3) A select_val/4 instruction that only verifies that +%% (2) A select_val/4 instruction that only verifies that %% its argument is either 'true' or 'false' can be %% be replaced with an is_boolean/2 instruction. That is: %% @@ -132,7 +117,7 @@ peep([{test,Op,_,Ops}=I|Is], SeenTests0, Acc) -> false -> %% Remember that we have seen this test. SeenTests = gb_sets:insert(Test, SeenTests0), - make_select_val(I, Is, SeenTests, Acc) + peep(Is, SeenTests, [I|Acc]) end end; peep([{select_val,Src,Fail, @@ -151,33 +136,6 @@ peep([I|Is], _, Acc) -> peep(Is, gb_sets:empty(), [I|Acc]); peep([], _, Acc) -> reverse(Acc). -make_select_val({test,is_ne_exact,{f,Fail},[Val,Lit]}=I0, - Is0, SeenTests, Acc) -> - try - Type = case Lit of - {atom,_} -> atom; - {integer,_} -> integer; - _ -> throw(impossible) - end, - {I,Is} = make_select_val_1(Is0, Fail, Val, Type, [Lit,{f,Fail}]), - peep([I|Is], SeenTests, Acc) - catch - impossible -> - peep(Is0, SeenTests, [I0|Acc]) - end; -make_select_val(I, Is, SeenTests, Acc) -> - peep(Is, SeenTests, [I|Acc]). - -make_select_val_1([{test,is_ne_exact,{f,Fail},[Val,{Type,_}=Lit]}|Is], - Fail, Val, Type, Acc) -> - make_select_val_1(Is, Fail, Val, Type, [Lit,{f,Fail}|Acc]); -make_select_val_1([{test,is_eq_exact,{f,UltimateFail},[Val,{Type,_}=Lit]} | - [{label,Fail}|_]=Is], Fail, Val, Type, Acc) -> - Choices = [Lit,{f,Fail}|Acc], - I = {select_val,Val,{f,UltimateFail},{list,Choices}}, - {I,Is}; -make_select_val_1(_Is, _Fail, _Val, _Type, _Acc) -> throw(impossible). - kill_seen(Dst, Seen0) -> gb_sets:from_ordset(kill_seen_1(gb_sets:to_list(Seen0), Dst)). @@ -187,5 +145,3 @@ kill_seen_1([{_,Ops}=Test|T], Dst) -> false -> [Test|kill_seen_1(T, Dst)] end; kill_seen_1([], _) -> []. - - diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl index dc5a1068db..f3a2b01e04 100644 --- a/lib/compiler/src/beam_validator.erl +++ b/lib/compiler/src/beam_validator.erl @@ -18,6 +18,8 @@ -module(beam_validator). +-compile({no_auto_import,[min/2]}). + -export([file/1, files/1]). %% Interface for compiler. diff --git a/lib/compiler/src/cerl.erl b/lib/compiler/src/cerl.erl index 74fc0878cf..d1fd9d40e2 100644 --- a/lib/compiler/src/cerl.erl +++ b/lib/compiler/src/cerl.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2001-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2001-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% ===================================================================== @@ -122,6 +122,9 @@ bitstr_bitsize/1, bitstr_unit/1, bitstr_type/1, bitstr_flags/1]). +-export_type([c_binary/0, c_call/0, c_clause/0, c_cons/0, c_fun/0, c_literal/0, + c_module/0, c_tuple/0, c_values/0, c_var/0, cerl/0, var_name/0]). + %% %% needed by the include file below -- do not move %% diff --git a/lib/compiler/src/cerl_inline.erl b/lib/compiler/src/cerl_inline.erl index 6d7eca0113..c15103999f 100644 --- a/lib/compiler/src/cerl_inline.erl +++ b/lib/compiler/src/cerl_inline.erl @@ -65,7 +65,6 @@ try_evars/1, try_handler/1, tuple_es/1, tuple_arity/1, type/1, values_es/1, var_name/1]). --import(erlang, [max/2]). -import(lists, [foldl/3, foldr/3, mapfoldl/3, reverse/1]). %% @@ -201,9 +200,9 @@ start(Reply, Tree, Ctxt, Opts) -> false -> ok end, - Size = max(1, proplists:get_value(inline_size, Opts)), - Effort = max(1, proplists:get_value(inline_effort, Opts)), - Unroll = max(1, proplists:get_value(inline_unroll, Opts)), + Size = erlang:max(1, proplists:get_value(inline_size, Opts)), + Effort = erlang:max(1, proplists:get_value(inline_effort, Opts)), + Unroll = erlang:max(1, proplists:get_value(inline_unroll, Opts)), case proplists:get_bool(verbose, Opts) of true -> io:fwrite("Inlining: inline_size=~w inline_effort=~w\n", diff --git a/lib/compiler/src/cerl_trees.erl b/lib/compiler/src/cerl_trees.erl index 7a2057713e..1e3755025f 100644 --- a/lib/compiler/src/cerl_trees.erl +++ b/lib/compiler/src/cerl_trees.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2001-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2001-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @doc Basic functions on Core Erlang abstract syntax trees. @@ -73,14 +73,12 @@ depth(T) -> [] -> 0; Gs -> - 1 + lists:foldl(fun (G, A) -> max(depth_1(G), A) end, 0, Gs) + 1 + lists:foldl(fun (G, A) -> erlang:max(depth_1(G), A) end, 0, Gs) end. depth_1(Ts) -> - lists:foldl(fun (T, A) -> max(depth(T), A) end, 0, Ts). + lists:foldl(fun (T, A) -> erlang:max(depth(T), A) end, 0, Ts). -max(X, Y) when X > Y -> X; -max(_, Y) -> Y. %% @spec size(Tree::cerl()) -> integer() diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl index d5dfde6514..4642fb68b3 100644 --- a/lib/compiler/src/compile.erl +++ b/lib/compiler/src/compile.erl @@ -29,6 +29,8 @@ %% Erlc interface. -export([compile/3,compile_beam/3,compile_asm/3,compile_core/3]). +-export_type([option/0]). + -include("erl_compile.hrl"). -include("core_parse.hrl"). diff --git a/lib/compiler/src/rec_env.erl b/lib/compiler/src/rec_env.erl index 9b73e08ad8..77005a6f9d 100644 --- a/lib/compiler/src/rec_env.erl +++ b/lib/compiler/src/rec_env.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2001-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2001-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %% @author Richard Carlsson <[email protected]> @@ -32,6 +32,8 @@ get/2, is_defined/2, is_empty/1, keys/1, lookup/2, new_key/1, new_key/2, new_keys/2, new_keys/3, size/1, to_list/1]). +-export_type([environment/0]). + -import(erlang, [max/2]). -ifdef(DEBUG). diff --git a/lib/compiler/src/sys_core_fold.erl b/lib/compiler/src/sys_core_fold.erl index 6202f07479..96015fbe58 100644 --- a/lib/compiler/src/sys_core_fold.erl +++ b/lib/compiler/src/sys_core_fold.erl @@ -1038,6 +1038,8 @@ fold_non_lit_args(Call, lists, append, [Arg1,Arg2], _) -> eval_append(Call, Arg1, Arg2); fold_non_lit_args(Call, erlang, setelement, [Arg1,Arg2,Arg3], _) -> eval_setelement(Call, Arg1, Arg2, Arg3); +fold_non_lit_args(Call, erlang, is_record, [Arg1,Arg2,Arg3], Sub) -> + eval_is_record(Call, Arg1, Arg2, Arg3, Sub); fold_non_lit_args(Call, erlang, N, Args, Sub) -> NumArgs = length(Args), case erl_internal:comp_op(N, NumArgs) of @@ -1194,19 +1196,22 @@ eval_element(Call, #c_literal{val=Pos}, #c_tuple{es=Es}, _Types) when is_integer true -> eval_failure(Call, badarg) end; -%% eval_element(Call, #c_literal{val=Pos}, #c_var{name=V}, Types) -%% when is_integer(Pos) -> -%% case orddict:find(V, Types#sub.t) of -%% {ok,#c_tuple{es=Elements}} -> -%% if -%% 1 =< Pos, Pos =< length(Elements) -> -%% lists:nth(Pos, Elements); -%% true -> -%% eval_failure(Call, badarg) -%% end; -%% error -> -%% Call -%% end; +eval_element(Call, #c_literal{val=Pos}, #c_var{name=V}, Types) + when is_integer(Pos) -> + case orddict:find(V, Types#sub.t) of + {ok,#c_tuple{es=Elements}} -> + if + 1 =< Pos, Pos =< length(Elements) -> + case lists:nth(Pos, Elements) of + #c_alias{var=Alias} -> Alias; + Res -> Res + end; + true -> + eval_failure(Call, badarg) + end; + error -> + Call + end; eval_element(Call, Pos, Tuple, _Types) -> case is_not_integer(Pos) orelse is_not_tuple(Tuple) of true -> @@ -1215,6 +1220,20 @@ eval_element(Call, Pos, Tuple, _Types) -> Call end. +%% eval_is_record(Call, Var, Tag, Size, Types) -> Val. +%% Evaluates is_record/3 using type information. +%% +eval_is_record(Call, #c_var{name=V}, #c_literal{val=NeededTag}=Lit, + #c_literal{val=Size}, Types) -> + case orddict:find(V, Types#sub.t) of + {ok,#c_tuple{es=[#c_literal{val=Tag}|_]=Es}} -> + Lit#c_literal{val=Tag =:= NeededTag andalso + length(Es) =:= Size}; + _ -> + Call + end; +eval_is_record(Call, _, _, _, _) -> Call. + %% is_not_integer(Core) -> true | false. %% Returns true if Core is definitely not an integer. diff --git a/lib/compiler/src/sys_pre_expand.erl b/lib/compiler/src/sys_pre_expand.erl index f80d03dfac..480954adac 100644 --- a/lib/compiler/src/sys_pre_expand.erl +++ b/lib/compiler/src/sys_pre_expand.erl @@ -403,16 +403,21 @@ expr({'fun',Line,Body}, St) -> expr({call,Line,{atom,La,N}=Atom,As0}, St0) -> {As,St1} = expr_list(As0, St0), Ar = length(As), - case erl_internal:bif(N, Ar) of - true -> - {{call,Line,{remote,La,{atom,La,erlang},Atom},As},St1}; - false -> - case imported(N, Ar, St1) of - {yes,Mod} -> - {{call,Line,{remote,La,{atom,La,Mod},Atom},As},St1}; - no -> - {{call,Line,Atom,As},St1} - end + case defined(N,Ar,St1) of + true -> + {{call,Line,Atom,As},St1}; + _ -> + case imported(N, Ar, St1) of + {yes,Mod} -> + {{call,Line,{remote,La,{atom,La,Mod},Atom},As},St1}; + no -> + case erl_internal:bif(N, Ar) of + true -> + {{call,Line,{remote,La,{atom,La,erlang},Atom},As},St1}; + false -> %% This should have been handled by erl_lint + {{call,Line,Atom,As},St1} + end + end end; expr({call,Line,{record_field,_,_,_}=M,As0}, St0) -> expr({call,Line,expand_package(M, St0),As0}, St0); @@ -685,3 +690,6 @@ imported(F, A, St) -> {ok,Mod} -> {yes,Mod}; error -> no end. + +defined(F, A, St) -> + ordsets:is_element({F,A}, St#expand.defined). diff --git a/lib/compiler/test/error_SUITE.erl b/lib/compiler/test/error_SUITE.erl index 4530313bb0..0874225a62 100644 --- a/lib/compiler/test/error_SUITE.erl +++ b/lib/compiler/test/error_SUITE.erl @@ -21,11 +21,133 @@ -include("test_server.hrl"). -export([all/1, - head_mismatch_line/1,warnings_as_errors/1]). + head_mismatch_line/1,warnings_as_errors/1, bif_clashes/1]). all(suite) -> test_lib:recompile(?MODULE), - [head_mismatch_line,warnings_as_errors]. + [head_mismatch_line,warnings_as_errors,bif_clashes]. + + +bif_clashes(Config) when is_list(Config) -> + Ts = [{bif_clashes1, + <<" + -export([t/0]). + t() -> + length([a,b,c]). + + length(X) -> + erlang:length(X). + ">>, + [return_warnings], + {error, + [{4, erl_lint,{call_to_redefined_old_bif,{length,1}}}], []} }], + ?line [] = run(Config, Ts), + Ts1 = [{bif_clashes2, + <<" + -export([t/0]). + -import(x,[length/1]). + t() -> + length([a,b,c]). + ">>, + [return_warnings], + {error, + [{3, erl_lint,{redefine_old_bif_import,{length,1}}}], []} }], + ?line [] = run(Config, Ts1), + Ts00 = [{bif_clashes3, + <<" + -export([t/0]). + -compile({no_auto_import,[length/1]}). + t() -> + length([a,b,c]). + + length(X) -> + erlang:length(X). + ">>, + [return_warnings], + []}], + ?line [] = run(Config, Ts00), + Ts11 = [{bif_clashes4, + <<" + -export([t/0]). + -compile({no_auto_import,[length/1]}). + -import(x,[length/1]). + t() -> + length([a,b,c]). + ">>, + [return_warnings], + []}], + ?line [] = run(Config, Ts11), + Ts000 = [{bif_clashes5, + <<" + -export([t/0]). + t() -> + binary_part(<<1,2,3,4>>,1,2). + + binary_part(X,Y,Z) -> + erlang:binary_part(X,Y,Z). + ">>, + [return_warnings], + {warning, + [{4, erl_lint,{call_to_redefined_bif,{binary_part,3}}}]} }], + ?line [] = run(Config, Ts000), + Ts111 = [{bif_clashes6, + <<" + -export([t/0]). + -import(x,[binary_part/3]). + t() -> + binary_part(<<1,2,3,4>>,1,2). + ">>, + [return_warnings], + {warning, + [{3, erl_lint,{redefine_bif_import,{binary_part,3}}}]} }], + ?line [] = run(Config, Ts111), + Ts2 = [{bif_clashes7, + <<" + -export([t/0]). + -compile({no_auto_import,[length/1]}). + -import(x,[length/1]). + t() -> + length([a,b,c]). + length(X) -> + erlang:length(X). + ">>, + [], + {error, + [{7,erl_lint,{define_import,{length,1}}}], + []} }], + ?line [] = run2(Config, Ts2), + Ts3 = [{bif_clashes8, + <<" + -export([t/1]). + -compile({no_auto_import,[length/1]}). + t(X) when length(X) > 3 -> + length([a,b,c]). + length(X) -> + erlang:length(X). + ">>, + [], + {error, + [{4,erl_lint,illegal_guard_expr}], + []} }], + ?line [] = run2(Config, Ts3), + Ts4 = [{bif_clashes9, + <<" + -export([t/1]). + -compile({no_auto_import,[length/1]}). + -import(x,[length/1]). + t(X) when length(X) > 3 -> + length([a,b,c]). + ">>, + [], + {error, + [{5,erl_lint,illegal_guard_expr}], + []} }], + ?line [] = run2(Config, Ts4), + + ok. + + + %% Tests that a head mismatch is reported on the correct line (OTP-2125). head_mismatch_line(Config) when is_list(Config) -> @@ -49,7 +171,7 @@ warnings_as_errors(Config) when is_list(Config) -> A = unused, ok. ">>, - [warnings_as_errors], + [export_all,warnings_as_errors], {error, [], [{3,erl_lint,{unused_var,'A'}}]} }], @@ -70,6 +192,24 @@ run(Config, Tests) -> end, lists:foldl(F, [], Tests). +run2(Config, Tests) -> + F = fun({N,P,Ws,E}, BadL) -> + case catch filter(run_test(Config, P, Ws)) of + E -> + BadL; + Bad -> + ?t:format("~nTest ~p failed. Expected~n ~p~n" + "but got~n ~p~n", [N, E, Bad]), + fail() + end + end, + lists:foldl(F, [], Tests). + +filter({error,Es,_Ws}) -> + {error,Es,[]}; +filter(X) -> + X. + %% Compiles a test module and returns the list of errors and warnings. @@ -78,17 +218,29 @@ run_test(Conf, Test0, Warnings) -> ?line DataDir = ?config(priv_dir, Conf), ?line Test = ["-module(errors_test). ", Test0], ?line File = filename:join(DataDir, Filename), - ?line Opts = [binary,export_all,return|Warnings], + ?line Opts = [binary,return_errors|Warnings], ?line ok = file:write_file(File, Test), %% Compile once just to print all errors and warnings. - ?line compile:file(File, [binary,export_all,report|Warnings]), + ?line compile:file(File, [binary,report|Warnings]), %% Test result of compilation. ?line Res = case compile:file(File, Opts) of - {error,[{_File,Es}],Ws} -> + {ok,errors_test,_,[{_File,Ws}]} -> + %io:format("compile:file(~s,~p) ->~n~p~n", + % [File,Opts,Ws]), + {warning,Ws}; + {ok,errors_test,_,[]} -> + %io:format("compile:file(~s,~p) ->~n~p~n", + % [File,Opts,Ws]), + []; + {error,[{XFile,Es}],Ws} = _ZZ when is_list(XFile) -> + %io:format("compile:file(~s,~p) ->~n~p~n", + % [File,Opts,_ZZ]), {error,Es,Ws}; - {error,Es,[{_File,Ws}]} -> + {error,Es,[{_File,Ws}]} = _ZZ-> + %io:format("compile:file(~s,~p) ->~n~p~n", + % [File,Opts,_ZZ]), {error,Es,Ws} end, file:delete(File), diff --git a/lib/compiler/test/guard_SUITE.erl b/lib/compiler/test/guard_SUITE.erl index aa1b3b16dc..8f23bd2e5a 100644 --- a/lib/compiler/test/guard_SUITE.erl +++ b/lib/compiler/test/guard_SUITE.erl @@ -31,7 +31,7 @@ t_is_boolean/1,is_function_2/1, tricky/1,rel_ops/1,literal_type_tests/1, basic_andalso_orelse/1,traverse_dcd/1, - check_qlc_hrl/1,andalso_semi/1,tuple_size/1,binary_part/1]). + check_qlc_hrl/1,andalso_semi/1,t_tuple_size/1,binary_part/1]). all(suite) -> test_lib:recompile(?MODULE), @@ -43,7 +43,7 @@ all(suite) -> build_in_guard,old_guard_tests,gbif, t_is_boolean,is_function_2,tricky,rel_ops,literal_type_tests, basic_andalso_orelse,traverse_dcd,check_qlc_hrl,andalso_semi, - tuple_size,binary_part]. + t_tuple_size,binary_part]. misc(Config) when is_list(Config) -> ?line 42 = case id(42) of @@ -1330,7 +1330,7 @@ andalso_semi_bar(Bar) when is_list(Bar) andalso length(Bar) =:= 3; Bar =:= 1 -> ok. -tuple_size(Config) when is_list(Config) -> +t_tuple_size(Config) when is_list(Config) -> ?line 10 = do_tuple_size({1,2,3,4}), ?line fc(catch do_tuple_size({1,2,3})), ?line fc(catch do_tuple_size(42)), diff --git a/lib/compiler/test/misc_SUITE.erl b/lib/compiler/test/misc_SUITE.erl index 126a679724..450a4e279d 100644 --- a/lib/compiler/test/misc_SUITE.erl +++ b/lib/compiler/test/misc_SUITE.erl @@ -20,10 +20,23 @@ -export([all/1,init_per_testcase/2,fin_per_testcase/2, tobias/1,empty_string/1,md5/1,silly_coverage/1, - confused_literals/1,integer_encoding/1]). + confused_literals/1,integer_encoding/1,override_bif/1]). -include("test_server.hrl"). +%% For the override_bif testcase. +%% NB, no other testcases in this testsuite can use these without erlang:prefix! +-compile({no_auto_import,[abs/1]}). +-compile({no_auto_import,[binary_part/3]}). +-compile({no_auto_import,[binary_part/2]}). +-import(test_lib,[binary_part/2]). + +%% This should do no harm (except for fun byte_size/1 which does not, by design, work with import +-compile({no_auto_import,[byte_size/1]}). +-import(erlang,[byte_size/1]). + + + %% Include an opaque declaration to cover the stripping of %% opaque types from attributes in v3_kernel. -opaque misc_SUITE_test_cases() :: [atom()]. @@ -42,7 +55,39 @@ fin_per_testcase(Case, Config) when is_atom(Case), is_list(Config) -> all(suite) -> test_lib:recompile(?MODULE), [tobias,empty_string,md5,silly_coverage,confused_literals, - integer_encoding]. + integer_encoding, override_bif]. + + +%% +%% Functions that override new and old bif's +%% +abs(_N) -> + dummy_abs. + +binary_part(_,_,_) -> + dummy_bp. + +% Make sure that auto-imported BIF's are overridden correctly + +override_bif(suite) -> + []; +override_bif(doc) -> + ["Test dat local functions and imports override auto-imported BIFs."]; +override_bif(Config) when is_list(Config) -> + ?line dummy_abs = abs(1), + ?line dummy_bp = binary_part(<<"hello">>,1,1), + ?line dummy = binary_part(<<"hello">>,{1,1}), + ?line 1 = erlang:abs(1), + ?line <<"e">> = erlang:binary_part(<<"hello">>,1,1), + ?line <<"e">> = erlang:binary_part(<<"hello">>,{1,1}), + F = fun(X) when byte_size(X) =:= 4 -> + four; + (X) -> + byte_size(X) + end, + ?line four = F(<<1,2,3,4>>), + ?line 5 = F(<<1,2,3,4,5>>), + ok. %% A bug reported by Tobias Lindahl for a development version of R11B. diff --git a/lib/compiler/test/test_lib.erl b/lib/compiler/test/test_lib.erl index 05236ee010..d8799952a9 100644 --- a/lib/compiler/test/test_lib.erl +++ b/lib/compiler/test/test_lib.erl @@ -19,8 +19,8 @@ -module(test_lib). -include("test_server.hrl"). - --export([recompile/1,opt_opts/1,get_data_dir/1,smoke_disasm/1,p_run/2]). +-compile({no_auto_import,[binary_part/2]}). +-export([recompile/1,opt_opts/1,get_data_dir/1,smoke_disasm/1,p_run/2,binary_part/2]). recompile(Mod) when is_atom(Mod) -> case whereis(cover_server) of @@ -104,3 +104,7 @@ p_run_loop(Test, List, N, Refs0, Errors0, Ws0) -> Refs = Refs0 -- [Ref], p_run_loop(Test, List, N, Refs, Errors, Ws) end. + +%% This is for the misc_SUITE:override_bif testcase +binary_part(_A,_B) -> + dummy. diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c index a71df1d7fd..bb639054a6 100644 --- a/lib/crypto/c_src/crypto.c +++ b/lib/crypto/c_src/crypto.c @@ -264,15 +264,15 @@ static int is_ok_load_info(ErlNifEnv* env, ERL_NIF_TERM load_info) } static void* crypto_alloc(size_t size) { - return enif_alloc(NULL, size); + return enif_alloc(size); } static void* crypto_realloc(void* ptr, size_t size) { - return enif_realloc(NULL, ptr, size); + return enif_realloc(ptr, size); } static void crypto_free(void* ptr) { - enif_free(NULL, ptr); + enif_free(ptr); } static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) @@ -289,7 +289,7 @@ static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) if (sys_info.scheduler_threads > 1) { int i; - lock_vec = enif_alloc(env,CRYPTO_num_locks()*sizeof(*lock_vec)); + lock_vec = enif_alloc(CRYPTO_num_locks()*sizeof(*lock_vec)); if (lock_vec==NULL) return -1; memset(lock_vec,0,CRYPTO_num_locks()*sizeof(*lock_vec)); @@ -371,7 +371,7 @@ static void unload(ErlNifEnv* env, void* priv_data) enif_rwlock_destroy(lock_vec[i]); } } - enif_free(env,lock_vec); + enif_free(lock_vec); } } /*else NIF library still used by other (new) module code */ @@ -994,7 +994,7 @@ static ERL_NIF_TERM rsa_sign_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar RSA_free(rsa); return enif_make_badarg(env); } - enif_alloc_binary(env, RSA_size(rsa), &ret_bin); + enif_alloc_binary(RSA_size(rsa), &ret_bin); if (is_sha) { SHA1(data_bin.data+4, data_bin.size-4, hmacbuf); ERL_VALGRIND_ASSERT_MEM_DEFINED(hmacbuf, SHA_DIGEST_LENGTH); @@ -1011,13 +1011,13 @@ static ERL_NIF_TERM rsa_sign_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar if (i) { ERL_VALGRIND_MAKE_MEM_DEFINED(ret_bin.data, rsa_s_len); if (rsa_s_len != data_bin.size) { - enif_realloc_binary(env, &ret_bin, rsa_s_len); + enif_realloc_binary(&ret_bin, rsa_s_len); ERL_VALGRIND_ASSERT_MEM_DEFINED(ret_bin.data, rsa_s_len); } return enif_make_binary(env,&ret_bin); } else { - enif_release_binary(env, &ret_bin); + enif_release_binary(&ret_bin); return atom_error; } } @@ -1049,13 +1049,13 @@ static ERL_NIF_TERM dss_sign_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar SHA1(data_bin.data+4, data_bin.size-4, hmacbuf); - enif_alloc_binary(env, DSA_size(dsa), &ret_bin); + enif_alloc_binary(DSA_size(dsa), &ret_bin); i = DSA_sign(NID_sha1, hmacbuf, SHA_DIGEST_LENGTH, ret_bin.data, &dsa_s_len, dsa); DSA_free(dsa); if (i) { if (dsa_s_len != ret_bin.size) { - enif_realloc_binary(env, &ret_bin, dsa_s_len); + enif_realloc_binary(&ret_bin, dsa_s_len); } return enif_make_binary(env, &ret_bin); } @@ -1100,7 +1100,7 @@ static ERL_NIF_TERM rsa_public_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TER return enif_make_badarg(env); } - enif_alloc_binary(env, RSA_size(rsa), &ret_bin); + enif_alloc_binary(RSA_size(rsa), &ret_bin); if (argv[3] == atom_true) { ERL_VALGRIND_ASSERT_MEM_DEFINED(buf+i,data_len); @@ -1115,7 +1115,7 @@ static ERL_NIF_TERM rsa_public_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TER ret_bin.data, rsa, padding); if (i > 0) { ERL_VALGRIND_MAKE_MEM_DEFINED(ret_bin.data, i); - enif_realloc_binary(env, &ret_bin, i); + enif_realloc_binary(&ret_bin, i); } } RSA_free(rsa); @@ -1148,7 +1148,7 @@ static ERL_NIF_TERM rsa_private_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TE return enif_make_badarg(env); } - enif_alloc_binary(env, RSA_size(rsa), &ret_bin); + enif_alloc_binary(RSA_size(rsa), &ret_bin); if (argv[3] == atom_true) { ERL_VALGRIND_ASSERT_MEM_DEFINED(buf+i,data_len); @@ -1163,7 +1163,7 @@ static ERL_NIF_TERM rsa_private_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TE ret_bin.data, rsa, padding); if (i > 0) { ERL_VALGRIND_MAKE_MEM_DEFINED(ret_bin.data, i); - enif_realloc_binary(env, &ret_bin, i); + enif_realloc_binary(&ret_bin, i); } } RSA_free(rsa); @@ -1293,11 +1293,11 @@ static ERL_NIF_TERM dh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_T ret = enif_make_badarg(env); } else { - enif_alloc_binary(env, DH_size(dh_params), &ret_bin); + enif_alloc_binary(DH_size(dh_params), &ret_bin); i = DH_compute_key(ret_bin.data, pubkey, dh_params); if (i > 0) { if (i != ret_bin.size) { - enif_realloc_binary(env, &ret_bin, i); + enif_realloc_binary(&ret_bin, i); } ret = enif_make_binary(env, &ret_bin); } diff --git a/lib/debugger/src/dbg_iload.erl b/lib/debugger/src/dbg_iload.erl index 1216338006..ec54c646c8 100644 --- a/lib/debugger/src/dbg_iload.erl +++ b/lib/debugger/src/dbg_iload.erl @@ -1,29 +1,25 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1998-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1998-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% -module(dbg_iload). -%% External exports -export([load_mod/4]). -%% Internal exports --export([load_mod1/4]). - %%==================================================================== %% External exports %%==================================================================== @@ -36,29 +32,29 @@ %% Db = ETS identifier %% Load a new module into the database. %% -%% We want the loading of a module to be syncronous so no other +%% We want the loading of a module to be synchronous so that no other %% process tries to interpret code in a module not being completely %% loaded. This is achieved as this function is called from %% dbg_iserver. We are suspended until the module has been loaded. %%-------------------------------------------------------------------- +-spec load_mod(Mod, file:filename(), binary(), ets:tid()) -> + {'ok', Mod} when is_subtype(Mod, atom()). + load_mod(Mod, File, Binary, Db) -> Flag = process_flag(trap_exit, true), - Pid = spawn_link(?MODULE, load_mod1, [Mod, File, Binary, Db]), + Pid = spawn_link(fun () -> load_mod1(Mod, File, Binary, Db) end), receive {'EXIT', Pid, What} -> process_flag(trap_exit, Flag), What end. -%%==================================================================== -%% Internal exports -%%==================================================================== +-spec load_mod1(atom(), file:filename(), binary(), ets:tid()) -> no_return(). load_mod1(Mod, File, Binary, Db) -> store_module(Mod, File, Binary, Db), exit({ok, Mod}). - %%==================================================================== %% Internal functions %%==================================================================== @@ -84,7 +80,7 @@ store_module(Mod, File, Binary, Db) -> Attr = store_forms(Forms, Mod, Db, Exp, []), erase(mod_md5), erase(current_function), -% store_funs(Db, Mod), + %% store_funs(Db, Mod), erase(vcount), erase(funs), erase(fun_count), diff --git a/lib/debugger/src/dbg_ui_trace_win.erl b/lib/debugger/src/dbg_ui_trace_win.erl index dbf93c7f45..c6f041a63d 100644 --- a/lib/debugger/src/dbg_ui_trace_win.erl +++ b/lib/debugger/src/dbg_ui_trace_win.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1997-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% -module(dbg_ui_trace_win). @@ -106,7 +106,7 @@ create_win(GS, Title, TraceWin, Menus) -> gs:read('CodeArea', height) + gs:read('RB1', height) + gs:read('ButtonArea', height) + - max(gs:read('EvalArea', height), + erlang:max(gs:read('EvalArea', height), gs:read('BindArea', height)) + gs:read('RB2', height) + gs:read('TraceArea', height)}), @@ -1032,7 +1032,7 @@ config_v() -> gs:config('RB3', {y,Y3}), gs:config('BindArea', {y,Y3}), - Y4 = Y3 + max(gs:read('EvalArea', height), + Y4 = Y3 + erlang:max(gs:read('EvalArea', height), gs:read('BindArea', height)), gs:config('RB2', {y,Y4}), @@ -1061,7 +1061,7 @@ configure(WinInfo, NewW, NewH) -> OldH = 25+gs:read('CodeArea', height)+ gs:read('RB1', height)+ gs:read('ButtonArea', height)+ - max(gs:read('EvalArea', height), gs:read('BindArea', height))+ + erlang:max(gs:read('EvalArea', height), gs:read('BindArea', height))+ gs:read('RB2', height)+ gs:read('TraceArea', height), @@ -1112,7 +1112,7 @@ configure_widths(OldW, NewW, Flags) -> {_Bu,Ev,Bi,_Tr} = Flags, %% Difference between old and new width, considering min window width - Diff = abs(max(OldW,330)-max(NewW,330)), + Diff = abs(erlang:max(OldW,330)-erlang:max(NewW,330)), %% Check how much the frames can be resized in reality Limits = if @@ -1166,7 +1166,7 @@ configure_heights(OldH, NewH, Flags) -> %% Difference between old and new height, considering min win height MinH = min_height(Flags), - Diff = abs(max(OldH,MinH)-max(NewH,MinH)), + Diff = abs(erlang:max(OldH,MinH)-erlang:max(NewH,MinH)), %% Check how much the frames can be resized in reality {T,Sf,Ff} = if @@ -1392,7 +1392,7 @@ rblimits('RB1',_W,H) -> H-112; _ -> Y = gs:read('RB2',y), - max(Min,Y-140) + erlang:max(Min,Y-140) end, {Min,Max}; @@ -1403,7 +1403,7 @@ rblimits('RB2',_W,H) -> %% Min is decided by a minimum distance to 'RB1' Y = gs:read('RB1',y), - Min = min(Max,Y+140), + Min = erlang:min(Max,Y+140), {Min,Max}; @@ -1412,13 +1412,7 @@ rblimits('RB3',W,_H) -> %% Neither CodeArea nor BindArea should occupy %% less than 1/3 of the total window width and EvalFrame should %% be at least 289 pixels wide - {max(round(W/3),289),round(2*W/3)}. - -max(A, B) when A>B -> A; -max(_A, B) -> B. - -min(A, B) when A<B -> A; -min(_A, B) -> B. + {erlang:max(round(W/3),289),round(2*W/3)}. %%==================================================================== diff --git a/lib/debugger/src/dbg_ui_win.erl b/lib/debugger/src/dbg_ui_win.erl index 9840aa54da..74ff2503ab 100644 --- a/lib/debugger/src/dbg_ui_win.erl +++ b/lib/debugger/src/dbg_ui_win.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2002-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2002-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% -module(dbg_ui_win). @@ -76,13 +76,10 @@ min_size(Font, Strings, MinW, MinH) -> min_size(GS, Font, [String|Strings], MinW, MinH) -> {W, H} = gs:read(GS, {font_wh, {Font, String}}), - min_size(GS, Font, Strings, max(MinW, W), max(MinH, H)); + min_size(GS, Font, Strings, erlang:max(MinW, W), erlang:max(MinH, H)); min_size(_GS, _Font, [], W, H) -> {W, H}. -max(X, Y) when X>Y -> X; -max(_X, Y) -> Y. - %%-------------------------------------------------------------------- %% create_menus(MenuBar, [Menu]) %% MenuBar = gsobj() diff --git a/lib/debugger/src/dbg_wx_trace_win.erl b/lib/debugger/src/dbg_wx_trace_win.erl index 3799acdc1b..2b4a1164ad 100755 --- a/lib/debugger/src/dbg_wx_trace_win.erl +++ b/lib/debugger/src/dbg_wx_trace_win.erl @@ -632,7 +632,7 @@ handle_event(#wx{id=?SASH_CODE, event=#wxSash{dragRect={_X,_Y,_W,H}}}, Wi) -> Change = CH - H, ChangeH = fun(Item) -> {ItemW, ItemH} = wxSizerItem:getMinSize(Item), - wxSizerItem:setInitSize(Item, ItemW, max(ItemH+Change,-1)) + wxSizerItem:setInitSize(Item, ItemW, erlang:max(ItemH+Change,-1)) end, if Enable -> {IW, IH} = wxSizer:getMinSize(InfoSzr), @@ -694,7 +694,7 @@ handle_event(#wx{id=?SASH_TRACE, event=#wxSash{dragRect={_X,_Y,_W,H}}}, Wi) -> true -> %% Change the Eval and Bindings area ChangeH = fun(Item) -> {ItemW, ItemH} = wxSizerItem:getMinSize(Item), - wxSizerItem:setInitSize(Item, ItemW, max(ItemH+Change,-1)) + wxSizerItem:setInitSize(Item, ItemW, erlang:max(ItemH+Change,-1)) end, {IW, IH} = wxSizer:getMinSize(InfoSzr), [ChangeH(Child) || Child <- wxSizer:getChildren(InfoSzr)], @@ -1021,9 +1021,3 @@ helpwin(Type, WinInfo = #winInfo{sg=Sg =#sub{in=Sa}}) -> search -> wxWindow:setFocus(Sa#sa.search) end, Wi. - -max(X,Y) when X > Y -> X; -max(_,Y) -> Y. - - - diff --git a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl index ab1bbe5ade..e3dd690470 100644 --- a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl +++ b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl @@ -96,6 +96,9 @@ loop(#server_state{parent = Parent, legal_warnings = LegalWarnings} = State, end; {AnalPid, ext_calls, NewExtCalls} -> loop(State, Analysis, NewExtCalls); + {AnalPid, ext_types, ExtTypes} -> + send_ext_types(Parent, ExtTypes), + loop(State, Analysis, ExtCalls); {AnalPid, unknown_behaviours, UnknownBehaviour} -> send_unknown_behaviours(Parent, UnknownBehaviour), loop(State, Analysis, ExtCalls); @@ -123,8 +126,7 @@ analysis_start(Parent, Analysis) -> parent = Parent, start_from = Analysis#analysis.start_from, use_contracts = Analysis#analysis.use_contracts, - behaviours = {Analysis#analysis.behaviours_chk, - []} + behaviours = {Analysis#analysis.behaviours_chk, []} }, Files = ordsets:from_list(Analysis#analysis.files), {Callgraph, NoWarn, TmpCServer0} = compile_and_store(Files, State), @@ -132,22 +134,36 @@ analysis_start(Parent, Analysis) -> NewCServer = try NewRecords = dialyzer_codeserver:get_temp_records(TmpCServer0), - OldRecords = dialyzer_plt:get_types(State#analysis_state.plt), + NewExpTypes = dialyzer_codeserver:get_temp_exported_types(TmpCServer0), + OldRecords = dialyzer_plt:get_types(Plt), + OldExpTypes0 = dialyzer_plt:get_exported_types(Plt), MergedRecords = dialyzer_utils:merge_records(NewRecords, OldRecords), + RemMods = + [case Analysis#analysis.start_from of + byte_code -> list_to_atom(filename:basename(F, ".beam")); + src_code -> list_to_atom(filename:basename(F, ".erl")) + end || F <- Files], + OldExpTypes1 = dialyzer_utils:sets_filter(RemMods, OldExpTypes0), + MergedExpTypes = sets:union(NewExpTypes, OldExpTypes1), TmpCServer1 = dialyzer_codeserver:set_temp_records(MergedRecords, TmpCServer0), - TmpCServer2 = dialyzer_utils:process_record_remote_types(TmpCServer1), - dialyzer_contracts:process_contract_remote_types(TmpCServer2) + TmpCServer2 = + dialyzer_codeserver:insert_temp_exported_types(MergedExpTypes, + TmpCServer1), + TmpCServer3 = dialyzer_utils:process_record_remote_types(TmpCServer2), + dialyzer_contracts:process_contract_remote_types(TmpCServer3) catch throw:{error, _ErrorMsg} = Error -> exit(Error) end, - NewPlt = dialyzer_plt:insert_types(Plt, dialyzer_codeserver:get_records(NewCServer)), - State0 = State#analysis_state{plt = NewPlt}, + NewPlt0 = dialyzer_plt:insert_types(Plt, dialyzer_codeserver:get_records(NewCServer)), + ExpTypes = dialyzer_codeserver:get_exported_types(NewCServer), + NewPlt1 = dialyzer_plt:insert_exported_types(NewPlt0, ExpTypes), + State0 = State#analysis_state{plt = NewPlt1}, dump_callgraph(Callgraph, State0, Analysis), State1 = State0#analysis_state{codeserver = NewCServer}, State2 = State1#analysis_state{no_warn_unused = NoWarn}, %% Remove all old versions of the files being analyzed AllNodes = dialyzer_callgraph:all_nodes(Callgraph), - Plt1 = dialyzer_plt:delete_list(NewPlt, AllNodes), + Plt1 = dialyzer_plt:delete_list(NewPlt1, AllNodes), Exports = dialyzer_codeserver:get_exports(NewCServer), NewCallgraph = case Analysis#analysis.race_detection of @@ -155,6 +171,7 @@ analysis_start(Parent, Analysis) -> false -> Callgraph end, State3 = analyze_callgraph(NewCallgraph, State2#analysis_state{plt = Plt1}), + rcv_and_send_ext_types(Parent), NonExports = sets:subtract(sets:from_list(AllNodes), Exports), NonExportsList = sets:to_list(NonExports), Plt3 = dialyzer_plt:delete_list(State3#analysis_state.plt, NonExportsList), @@ -371,14 +388,28 @@ compile_byte(File, Callgraph, CServer, UseContracts) -> store_core(Mod, Core, NoWarn, Callgraph, CServer) -> Exp = get_exports_from_core(Core), + OldExpTypes = dialyzer_codeserver:get_temp_exported_types(CServer), + NewExpTypes = get_exported_types_from_core(Core), + MergedExpTypes = sets:union(NewExpTypes, OldExpTypes), CServer1 = dialyzer_codeserver:insert_exports(Exp, CServer), - {LabeledCore, CServer2} = label_core(Core, CServer1), - store_code_and_build_callgraph(Mod, LabeledCore, Callgraph, CServer2, NoWarn). + CServer2 = dialyzer_codeserver:insert_temp_exported_types(MergedExpTypes, + CServer1), + {LabeledCore, CServer3} = label_core(Core, CServer2), + store_code_and_build_callgraph(Mod, LabeledCore, Callgraph, CServer3, NoWarn). abs_get_nowarn(Abs, M) -> [{M, F, A} || {attribute, _, compile, {nowarn_unused_function, {F, A}}} <- Abs]. +get_exported_types_from_core(Core) -> + Attrs = cerl:module_attrs(Core), + ExpTypes1 = [cerl:concrete(L2) || {L1, L2} <- Attrs, cerl:is_literal(L1), + cerl:is_literal(L2), + cerl:concrete(L1) =:= 'export_type'], + ExpTypes2 = lists:flatten(ExpTypes1), + M = cerl:atom_val(cerl:module_name(Core)), + sets:from_list([{M, F, A} || {F, A} <- ExpTypes2]). + get_exports_from_core(Core) -> Tree = cerl:from_records(Core), Exports1 = cerl:module_exports(Tree), @@ -454,6 +485,19 @@ default_includes(Dir) -> %% Handle Messages %%------------------------------------------------------------------- +rcv_and_send_ext_types(Parent) -> + Self = self(), + Self ! {Self, done}, + ExtTypes = rcv_ext_types(Self, []), + Parent ! {Self, ext_types, ExtTypes}. + +rcv_ext_types(Self, ExtTypes) -> + receive + {Self, ext_types, ExtType} -> + rcv_ext_types(Self, [ExtType|ExtTypes]); + {Self, done} -> lists:usort(ExtTypes) + end. + send_log(Parent, Msg) -> Parent ! {self(), log, Msg}, ok. @@ -476,6 +520,10 @@ send_ext_calls(Parent, ExtCalls) -> Parent ! {self(), ext_calls, ExtCalls}, ok. +send_ext_types(Parent, ExtTypes) -> + Parent ! {self(), ext_types, ExtTypes}, + ok. + send_unknown_behaviours(Parent, UnknownBehaviours) -> Parent ! {self(), unknown_behaviours, UnknownBehaviours}, ok. diff --git a/lib/dialyzer/src/dialyzer_behaviours.erl b/lib/dialyzer/src/dialyzer_behaviours.erl index 4e8dceaa8e..3fae816cfe 100644 --- a/lib/dialyzer/src/dialyzer_behaviours.erl +++ b/lib/dialyzer/src/dialyzer_behaviours.erl @@ -156,9 +156,11 @@ check_all_callbacks(Module, Behaviour, Callbacks, State) -> check_all_callbacks(_Module, _Behaviour, [], _State, Acc) -> Acc; -check_all_callbacks(Module, Behaviour, [{Fun, Arity, Spec}|Rest], State, Acc) -> - Records = dialyzer_codeserver:get_records(State#state.codeserver), - case parse_spec(Spec, Records) of +check_all_callbacks(Module, Behaviour, [{Fun, Arity, Spec}|Rest], + #state{codeserver = CServer} = State, Acc) -> + Records = dialyzer_codeserver:get_records(CServer), + ExpTypes = dialyzer_codeserver:get_exported_types(CServer), + case parse_spec(Spec, ExpTypes, Records) of {ok, Fun, Type} -> RetType = erl_types:t_fun_range(Type), ArgTypes = erl_types:t_fun_args(Type), @@ -172,7 +174,7 @@ check_all_callbacks(Module, Behaviour, [{Fun, Arity}|Rest], State, Acc) -> Warns = {spec_missing, [Behaviour, Fun, Arity]}, check_all_callbacks(Module, Behaviour, Rest, State, [Warns|Acc]). -parse_spec(String, Records) -> +parse_spec(String, ExpTypes, Records) -> case erl_scan:string(String) of {ok, Tokens, _} -> case erl_parse:parse(Tokens) of @@ -181,7 +183,8 @@ parse_spec(String, Records) -> {attribute, _, 'spec', {{Fun, _}, [TypeForm|_Constraint]}} -> MaybeRemoteType = erl_types:t_from_form(TypeForm), try - Type = erl_types:t_solve_remote(MaybeRemoteType, Records), + Type = erl_types:t_solve_remote(MaybeRemoteType, ExpTypes, + Records), {ok, Fun, Type} catch throw:{error,Msg} -> {spec_remote_error, Msg} diff --git a/lib/dialyzer/src/dialyzer_callgraph.erl b/lib/dialyzer/src/dialyzer_callgraph.erl index f932f43548..d3de5aaf45 100644 --- a/lib/dialyzer/src/dialyzer_callgraph.erl +++ b/lib/dialyzer/src/dialyzer_callgraph.erl @@ -59,6 +59,8 @@ put_named_tables/2, put_public_tables/2, put_behaviour_api_calls/2, get_behaviour_api_calls/1]). +-export_type([callgraph/0]). + -include("dialyzer.hrl"). %%---------------------------------------------------------------------- diff --git a/lib/dialyzer/src/dialyzer_cl.erl b/lib/dialyzer/src/dialyzer_cl.erl index d533e734db..1d02c4f0dc 100644 --- a/lib/dialyzer/src/dialyzer_cl.erl +++ b/lib/dialyzer/src/dialyzer_cl.erl @@ -38,6 +38,7 @@ {backend_pid :: pid(), erlang_mode = false :: boolean(), external_calls = [] :: [mfa()], + external_types = [] :: [mfa()], legal_warnings = ordsets:new() :: [dial_warn_tag()], mod_deps = dict:new() :: dict(), output = standard_io :: io:device(), @@ -538,6 +539,8 @@ cl_loop(State, LogCache) -> return_value(State, NewPlt); {BackendPid, ext_calls, ExtCalls} -> cl_loop(State#cl_state{external_calls = ExtCalls}, LogCache); + {BackendPid, ext_types, ExtTypes} -> + cl_loop(State#cl_state{external_types = ExtTypes}, LogCache); {BackendPid, mod_deps, ModDeps} -> NewState = State#cl_state{mod_deps = ModDeps}, cl_loop(NewState, LogCache); @@ -613,6 +616,7 @@ return_value(State = #cl_state{erlang_mode = ErlangMode, false -> print_warnings(State), print_ext_calls(State), + print_ext_types(State), print_unknown_behaviours(State), maybe_close_output_file(State), {RetValue, []}; @@ -649,10 +653,41 @@ do_print_ext_calls(Output, [{M,F,A}|T], Before) -> do_print_ext_calls(_, [], _) -> ok. +print_ext_types(#cl_state{report_mode = quiet}) -> + ok; +print_ext_types(#cl_state{output = Output, + external_calls = Calls, + external_types = Types, + stored_warnings = Warnings, + output_format = Format}) -> + case Types =:= [] of + true -> ok; + false -> + case Warnings =:= [] andalso Calls =:= [] of + true -> io:nl(Output); %% Need to do a newline first + false -> ok + end, + case Format of + formatted -> + io:put_chars(Output, "Unknown types:\n"), + do_print_ext_types(Output, Types, " "); + raw -> + io:put_chars(Output, "%% Unknown types:\n"), + do_print_ext_types(Output, Types, "%% ") + end + end. + +do_print_ext_types(Output, [{M,F,A}|T], Before) -> + io:format(Output, "~s~p:~p/~p\n", [Before,M,F,A]), + do_print_ext_types(Output, T, Before); +do_print_ext_types(_, [], _) -> + ok. + %%print_unknown_behaviours(#cl_state{report_mode = quiet}) -> %% ok; print_unknown_behaviours(#cl_state{output = Output, external_calls = Calls, + external_types = Types, stored_warnings = Warnings, unknown_behaviours = DupBehaviours, legal_warnings = LegalWarnings, @@ -662,7 +697,7 @@ print_unknown_behaviours(#cl_state{output = Output, false -> ok; true -> Behaviours = lists:usort(DupBehaviours), - case Warnings =:= [] andalso Calls =:= [] of + case Warnings =:= [] andalso Calls =:= [] andalso Types =:= [] of true -> io:nl(Output); %% Need to do a newline first false -> ok end, diff --git a/lib/dialyzer/src/dialyzer_codeserver.erl b/lib/dialyzer/src/dialyzer_codeserver.erl index 3bc5fadc21..3cf090712c 100644 --- a/lib/dialyzer/src/dialyzer_codeserver.erl +++ b/lib/dialyzer/src/dialyzer_codeserver.erl @@ -29,15 +29,19 @@ -export([delete/1, finalize_contracts/2, + finalize_exported_types/2, finalize_records/2, get_contracts/1, + get_exported_types/1, get_exports/1, get_records/1, get_next_core_label/1, get_temp_contracts/1, + get_temp_exported_types/1, get_temp_records/1, - insert/3, - insert_exports/2, + insert/3, + insert_exports/2, + insert_temp_exported_types/2, is_exported/2, lookup_mod_code/2, lookup_mfa_code/2, @@ -52,17 +56,21 @@ store_contracts/3, store_temp_contracts/3]). +-export_type([codeserver/0]). + -include("dialyzer.hrl"). %%-------------------------------------------------------------------- --record(codeserver, {table_pid :: pid(), - exports = sets:new() :: set(), % set(mfa()) - next_core_label = 0 :: label(), - records = dict:new() :: dict(), - temp_records = dict:new() :: dict(), - contracts = dict:new() :: dict(), - temp_contracts = dict:new() :: dict()}). +-record(codeserver, {table_pid :: pid(), + exported_types = sets:new() :: set(), % set(mfa()) + temp_exported_types = sets:new() :: set(), % set(mfa()) + exports = sets:new() :: set(), % set(mfa()) + next_core_label = 0 :: label(), + records = dict:new() :: dict(), + temp_records = dict:new() :: dict(), + contracts = dict:new() :: dict(), + temp_contracts = dict:new() :: dict()}). -opaque codeserver() :: #codeserver{}. @@ -84,6 +92,11 @@ insert(Mod, ModCode, CS) -> NewTablePid = table__insert(CS#codeserver.table_pid, Mod, ModCode), CS#codeserver{table_pid = NewTablePid}. +-spec insert_temp_exported_types(set(), codeserver()) -> codeserver(). + +insert_temp_exported_types(Set, CS) -> + CS#codeserver{temp_exported_types = Set}. + -spec insert_exports([mfa()], codeserver()) -> codeserver(). insert_exports(List, #codeserver{exports = Exports} = CS) -> @@ -96,11 +109,26 @@ insert_exports(List, #codeserver{exports = Exports} = CS) -> is_exported(MFA, #codeserver{exports = Exports}) -> sets:is_element(MFA, Exports). +-spec get_exported_types(codeserver()) -> set(). % set(mfa()) + +get_exported_types(#codeserver{exported_types = ExpTypes}) -> + ExpTypes. + +-spec get_temp_exported_types(codeserver()) -> set(). + +get_temp_exported_types(#codeserver{temp_exported_types = TempExpTypes}) -> + TempExpTypes. + -spec get_exports(codeserver()) -> set(). % set(mfa()) get_exports(#codeserver{exports = Exports}) -> Exports. +-spec finalize_exported_types(set(), codeserver()) -> codeserver(). + +finalize_exported_types(Set, CS) -> + CS#codeserver{exported_types = Set, temp_exported_types = sets:new()}. + -spec lookup_mod_code(module(), codeserver()) -> cerl:c_module(). lookup_mod_code(Mod, CS) when is_atom(Mod) -> diff --git a/lib/dialyzer/src/dialyzer_contracts.erl b/lib/dialyzer/src/dialyzer_contracts.erl index 3486c72748..2bedf99e42 100644 --- a/lib/dialyzer/src/dialyzer_contracts.erl +++ b/lib/dialyzer/src/dialyzer_contracts.erl @@ -33,6 +33,8 @@ process_contract_remote_types/1, store_tmp_contract/5]). +-export_type([file_contract/0, plt_contracts/0]). + %%----------------------------------------------------------------------- -include("dialyzer.hrl"). @@ -50,7 +52,7 @@ %% to expand records and/or remote types that they might contain. %%----------------------------------------------------------------------- --type tmp_contract_fun() :: fun((dict()) -> contract_pair()). +-type tmp_contract_fun() :: fun((set(), dict()) -> contract_pair()). -record(tmp_contract, {contract_funs = [] :: [tmp_contract_fun()], forms = [] :: [{_, _}]}). @@ -140,10 +142,11 @@ sequence([H|T], Delimiter) -> H ++ Delimiter ++ sequence(T, Delimiter). process_contract_remote_types(CodeServer) -> TmpContractDict = dialyzer_codeserver:get_temp_contracts(CodeServer), + ExpTypes = dialyzer_codeserver:get_exported_types(CodeServer), RecordDict = dialyzer_codeserver:get_records(CodeServer), ContractFun = fun({_M, _F, _A}, {File, #tmp_contract{contract_funs = CFuns, forms = Forms}}) -> - NewCs = [CFun(RecordDict) || CFun <- CFuns], + NewCs = [CFun(ExpTypes, RecordDict) || CFun <- CFuns], Args = general_domain(NewCs), {File, #contract{contracts = NewCs, args = Args, forms = Forms}} end, @@ -354,9 +357,9 @@ contract_from_form(Forms, RecDict) -> contract_from_form([{type, _, 'fun', [_, _]} = Form | Left], RecDict, TypeAcc, FormAcc) -> TypeFun = - fun(AllRecords) -> + fun(ExpTypes, AllRecords) -> Type = erl_types:t_from_form(Form, RecDict), - NewType = erl_types:t_solve_remote(Type, AllRecords), + NewType = erl_types:t_solve_remote(Type, ExpTypes, AllRecords), {NewType, []} end, NewTypeAcc = [TypeFun | TypeAcc], @@ -366,11 +369,12 @@ contract_from_form([{type, _L1, bounded_fun, [{type, _L2, 'fun', [_, _]} = Form, Constr]}| Left], RecDict, TypeAcc, FormAcc) -> TypeFun = - fun(AllRecords) -> - Constr1 = [constraint_from_form(C, RecDict, AllRecords) || C <- Constr], + fun(ExpTypes, AllRecords) -> + Constr1 = [constraint_from_form(C, RecDict, ExpTypes, AllRecords) + || C <- Constr], VarDict = insert_constraints(Constr1, dict:new()), Type = erl_types:t_from_form(Form, RecDict, VarDict), - NewType = erl_types:t_solve_remote(Type, AllRecords), + NewType = erl_types:t_solve_remote(Type, ExpTypes, AllRecords), {NewType, Constr1} end, NewTypeAcc = [TypeFun | TypeAcc], @@ -380,13 +384,15 @@ contract_from_form([], _RecDict, TypeAcc, FormAcc) -> {lists:reverse(TypeAcc), lists:reverse(FormAcc)}. constraint_from_form({type, _, constraint, [{atom, _, is_subtype}, - [Type1, Type2]]}, RecDict, AllRecords) -> + [Type1, Type2]]}, RecDict, + ExpTypes, AllRecords) -> T1 = erl_types:t_from_form(Type1, RecDict), T2 = erl_types:t_from_form(Type2, RecDict), - T3 = erl_types:t_solve_remote(T1, AllRecords), - T4 = erl_types:t_solve_remote(T2, AllRecords), + T3 = erl_types:t_solve_remote(T1, ExpTypes, AllRecords), + T4 = erl_types:t_solve_remote(T2, ExpTypes, AllRecords), {subtype, T3, T4}; -constraint_from_form({type, _, constraint, [{atom,_,Name}, List]}, _RecDict, _) -> +constraint_from_form({type, _, constraint, [{atom,_,Name}, List]}, _RecDict, + _ExpTypes, _AllRecords) -> N = length(List), throw({error, io_lib:format("Unsupported type guard ~w/~w\n", [Name, N])}). diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl index 1ccfaaa52f..a3c7114ee1 100644 --- a/lib/dialyzer/src/dialyzer_dataflow.erl +++ b/lib/dialyzer/src/dialyzer_dataflow.erl @@ -38,6 +38,8 @@ %% Debug and test interfaces. -export([get_top_level_signatures/2, pp/1]). +-export_type([state/0]). + -include("dialyzer.hrl"). -import(erl_types, diff --git a/lib/dialyzer/src/dialyzer_plt.erl b/lib/dialyzer/src/dialyzer_plt.erl index e387077a46..c10375eea2 100644 --- a/lib/dialyzer/src/dialyzer_plt.erl +++ b/lib/dialyzer/src/dialyzer_plt.erl @@ -39,10 +39,12 @@ from_file/1, get_default_plt/0, get_types/1, + get_exported_types/1, %% insert/3, insert_list/2, insert_contract_list/2, insert_types/2, + insert_exported_types/2, lookup/2, lookup_contract/2, lookup_module/2, @@ -57,6 +59,8 @@ %% Debug utilities -export([pp_non_returning/0, pp_mod/1]). +-export_type([plt/0, plt_info/0]). + %%---------------------------------------------------------------------- -type mod_deps() :: dict(). @@ -70,9 +74,10 @@ %%---------------------------------------------------------------------- --record(plt, {info = table_new() :: dict(), - types = table_new() :: dict(), - contracts = table_new() :: dict()}). +-record(plt, {info = table_new() :: dict(), + types = table_new() :: dict(), + contracts = table_new() :: dict(), + exported_types = sets:new() :: set()}). -opaque plt() :: #plt{}. -include("dialyzer.hrl"). @@ -80,13 +85,14 @@ -type file_md5() :: {file:filename(), binary()}. -type plt_info() :: {[file_md5()], dict()}. --record(file_plt, {version = "" :: string(), - file_md5_list = [] :: [file_md5()], - info = dict:new() :: dict(), - contracts = dict:new() :: dict(), - types = dict:new() :: dict(), - mod_deps :: mod_deps(), - implementation_md5 = [] :: [file_md5()]}). +-record(file_plt, {version = "" :: string(), + file_md5_list = [] :: [file_md5()], + info = dict:new() :: dict(), + contracts = dict:new() :: dict(), + types = dict:new() :: dict(), + exported_types = sets:new() :: set(), + mod_deps :: mod_deps(), + implementation_md5 = [] :: [file_md5()]}). %%---------------------------------------------------------------------- @@ -97,17 +103,21 @@ new() -> -spec delete_module(plt(), module()) -> plt(). -delete_module(#plt{info = Info, types = Types, contracts = Contracts}, Mod) -> +delete_module(#plt{info = Info, types = Types, contracts = Contracts, + exported_types = ExpTypes}, Mod) -> #plt{info = table_delete_module(Info, Mod), types = table_delete_module2(Types, Mod), - contracts = table_delete_module(Contracts, Mod)}. + contracts = table_delete_module(Contracts, Mod), + exported_types = table_delete_module1(ExpTypes, Mod)}. -spec delete_list(plt(), [mfa() | integer()]) -> plt(). -delete_list(#plt{info = Info, types = Types, contracts = Contracts}, List) -> +delete_list(#plt{info = Info, types = Types, contracts = Contracts, + exported_types = ExpTypes}, List) -> #plt{info = table_delete_list(Info, List), types = Types, - contracts = table_delete_list(Contracts, List)}. + contracts = table_delete_list(Contracts, List), + exported_types = ExpTypes}. -spec insert_contract_list(plt(), dialyzer_contracts:plt_contracts()) -> plt(). @@ -150,11 +160,21 @@ lookup(#plt{info = Info}, Label) when is_integer(Label) -> insert_types(PLT, Rec) -> PLT#plt{types = Rec}. +-spec insert_exported_types(plt(), set()) -> plt(). + +insert_exported_types(PLT, Set) -> + PLT#plt{exported_types = Set}. + -spec get_types(plt()) -> dict(). get_types(#plt{types = Types}) -> Types. +-spec get_exported_types(plt()) -> set(). + +get_exported_types(#plt{exported_types = ExpTypes}) -> + ExpTypes. + -type mfa_types() :: {mfa(), erl_types:erl_type(), [erl_types:erl_type()]}. -spec lookup_module(plt(), module()) -> 'none' | {'value', [mfa_types()]}. @@ -207,7 +227,8 @@ from_file(FileName, ReturnInfo) -> ok -> Plt = #plt{info = Rec#file_plt.info, types = Rec#file_plt.types, - contracts = Rec#file_plt.contracts}, + contracts = Rec#file_plt.contracts, + exported_types = Rec#file_plt.exported_types}, case ReturnInfo of false -> Plt; true -> @@ -261,15 +282,18 @@ get_record_from_file(FileName) -> merge_plts(List) -> InfoList = [Info || #plt{info = Info} <- List], TypesList = [Types || #plt{types = Types} <- List], + ExpTypesList = [ExpTypes || #plt{exported_types = ExpTypes} <- List], ContractsList = [Contracts || #plt{contracts = Contracts} <- List], #plt{info = table_merge(InfoList), types = table_merge(TypesList), + exported_types = sets_merge(ExpTypesList), contracts = table_merge(ContractsList)}. -spec to_file(file:filename(), plt(), mod_deps(), {[file_md5()], mod_deps()}) -> 'ok'. to_file(FileName, - #plt{info = Info, types = Types, contracts = Contracts}, + #plt{info = Info, types = Types, contracts = Contracts, + exported_types = ExpTypes}, ModDeps, {MD5, OldModDeps}) -> NewModDeps = dict:merge(fun(_Key, OldVal, NewVal) -> ordsets:union(OldVal, NewVal) @@ -281,6 +305,7 @@ to_file(FileName, info = Info, contracts = Contracts, types = Types, + exported_types = ExpTypes, mod_deps = NewModDeps, implementation_md5 = ImplMd5}, Bin = term_to_binary(Record, [compressed]), @@ -475,6 +500,9 @@ table_delete_module(Plt, Mod) -> (_, _) -> true end, Plt). +table_delete_module1(Plt, Mod) -> + sets:filter(fun({M, _F, _A}) -> M =/= Mod end, Plt). + table_delete_module2(Plt, Mod) -> dict:filter(fun(M, _Val) -> M =/= Mod end, Plt). @@ -526,6 +554,15 @@ table_merge([Plt|Plts], Acc) -> NewAcc = dict:merge(fun(_Key, Val, Val) -> Val end, Plt, Acc), table_merge(Plts, NewAcc). +sets_merge([H|T]) -> + sets_merge(T, H). + +sets_merge([], Acc) -> + Acc; +sets_merge([Plt|Plts], Acc) -> + NewAcc = sets:union(Plt, Acc), + sets_merge(Plts, NewAcc). + %%--------------------------------------------------------------------------- %% Debug utilities. diff --git a/lib/dialyzer/src/dialyzer_races.erl b/lib/dialyzer/src/dialyzer_races.erl index 4972967960..fb16e6a75f 100644 --- a/lib/dialyzer/src/dialyzer_races.erl +++ b/lib/dialyzer/src/dialyzer_races.erl @@ -39,6 +39,8 @@ let_tag_new/2, new/0, put_curr_fun/3, put_fun_args/2, put_race_analysis/2, put_race_list/3]). +-export_type([races/0]). + -include("dialyzer.hrl"). %%% =========================================================================== @@ -1704,7 +1706,6 @@ compare_types(VarArgs, WarnVarArgs, RaceWarnTag, RaceVarMap) -> false -> compare_var_list(VA1, WVA1, RaceVarMap) orelse compare_argtypes(VA2, WVA2) - end end; ?WARN_ETS_LOOKUP_INSERT -> diff --git a/lib/dialyzer/src/dialyzer_utils.erl b/lib/dialyzer/src/dialyzer_utils.erl index 6ea243c26f..338027c5ab 100644 --- a/lib/dialyzer/src/dialyzer_utils.erl +++ b/lib/dialyzer/src/dialyzer_utils.erl @@ -42,6 +42,7 @@ merge_records/2, pp_hook/0, process_record_remote_types/1, + sets_filter/2, src_compiler_opts/0 ]). @@ -78,7 +79,7 @@ print_types1([{record, _Name} = Key|T], RecDict) -> %% -type abstract_code() :: [tuple()]. %% XXX: refine --type comp_options() :: [atom()]. %% XXX: only a resticted set of options used +-type comp_options() :: [atom()]. %% XXX: a restricted set of options is used %% ============================================================================ %% @@ -169,7 +170,7 @@ get_record_and_type_info(AbstractCode) -> Module = get_module(AbstractCode), get_record_and_type_info(AbstractCode, Module, dict:new()). --spec get_record_and_type_info(abstract_code(), atom(), dict()) -> +-spec get_record_and_type_info(abstract_code(), module(), dict()) -> {'ok', dict()} | {'error', string()}. get_record_and_type_info(AbstractCode, Module, RecDict) -> @@ -278,13 +279,16 @@ type_record_fields([RecKey|Recs], RecDict) -> process_record_remote_types(CServer) -> TempRecords = dialyzer_codeserver:get_temp_records(CServer), + TempExpTypes = dialyzer_codeserver:get_temp_exported_types(CServer), RecordFun = fun(Key, Value) -> case Key of {record, _Name} -> FieldFun = fun(_Arity, Fields) -> - [{Name, erl_types:t_solve_remote(Field, TempRecords)} || {Name, Field} <- Fields] + [{Name, erl_types:t_solve_remote(Field, TempExpTypes, + TempRecords)} + || {Name, Field} <- Fields] end, orddict:map(FieldFun, Value); _Other -> Value @@ -295,7 +299,8 @@ process_record_remote_types(CServer) -> dict:map(RecordFun, Record) end, NewRecords = dict:map(ModuleFun, TempRecords), - dialyzer_codeserver:finalize_records(NewRecords, CServer). + CServer1 = dialyzer_codeserver:finalize_records(NewRecords, CServer), + dialyzer_codeserver:finalize_exported_types(TempExpTypes, CServer1). -spec merge_records(dict(), dict()) -> dict(). @@ -353,6 +358,20 @@ get_spec_info([], SpecDict, _RecordsDict, _ModName, _File) -> %% ============================================================================ %% +%% Exported types +%% +%% ============================================================================ + +-spec sets_filter([module()], set()) -> set(). + +sets_filter([], ExpTypes) -> + ExpTypes; +sets_filter([Mod|Mods], ExpTypes) -> + NewExpTypes = sets:filter(fun({M, _F, _A}) -> M =/= Mod end, ExpTypes), + sets_filter(Mods, NewExpTypes). + +%% ============================================================================ +%% %% Util utils %% %% ============================================================================ @@ -361,7 +380,8 @@ get_spec_info([], SpecDict, _RecordsDict, _ModName, _File) -> src_compiler_opts() -> [no_copt, to_core, binary, return_errors, - no_inline, strict_record_tests, strict_record_updates]. + no_inline, strict_record_tests, strict_record_updates, + no_is_record_optimization]. -spec get_module(abstract_code()) -> module(). diff --git a/lib/erl_interface/doc/src/ei.xml b/lib/erl_interface/doc/src/ei.xml index 9083ae02b0..d7af7a1b67 100644 --- a/lib/erl_interface/doc/src/ei.xml +++ b/lib/erl_interface/doc/src/ei.xml @@ -581,7 +581,7 @@ ei_x_encode_empty_list(&x); <c><![CDATA[term]]></c> union, it is decoded, and the appropriate field in <c><![CDATA[term->value]]></c> is set, and <c><![CDATA[*index]]></c> is incremented by the term size.</p> - <p>The function returns 0 on successful encoding, -1 on error, + <p>The function returns 0 on successful decoding, -1 on error, and 1 if the term seems alright, but does not fit in the <c><![CDATA[term]]></c> structure. If it returns 0, the <c><![CDATA[index]]></c> will be incremented, and the <c><![CDATA[term]]></c> contains the diff --git a/lib/erl_interface/include/ei.h b/lib/erl_interface/include/ei.h index d1a697615a..729b9fc367 100644 --- a/lib/erl_interface/include/ei.h +++ b/lib/erl_interface/include/ei.h @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 1998-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 1998-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ #ifndef EI_H @@ -110,6 +110,7 @@ #define ERL_SMALL_INTEGER_EXT 'a' #define ERL_INTEGER_EXT 'b' #define ERL_FLOAT_EXT 'c' +#define NEW_FLOAT_EXT 'F' #define ERL_ATOM_EXT 'd' #define ERL_REFERENCE_EXT 'e' #define ERL_NEW_REFERENCE_EXT 'r' diff --git a/lib/erl_interface/src/connect/ei_connect.c b/lib/erl_interface/src/connect/ei_connect.c index d2d0a7e7c1..b1b79aa0e5 100644 --- a/lib/erl_interface/src/connect/ei_connect.c +++ b/lib/erl_interface/src/connect/ei_connect.c @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 2000-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 2000-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ /* @@ -1323,7 +1323,8 @@ static int send_name_or_challenge(int fd, char *nodename, put32be(s, (DFLAG_EXTENDED_REFERENCES | DFLAG_EXTENDED_PIDS_PORTS | DFLAG_FUN_TAGS - | DFLAG_NEW_FUN_TAGS)); + | DFLAG_NEW_FUN_TAGS + | DFLAG_NEW_FLOATS)); if (f_chall) put32be(s, challenge); memcpy(s, nodename, strlen(nodename)); @@ -1393,6 +1394,11 @@ static int recv_challenge(int fd, unsigned *challenge, goto error; } + if (!(*flags & DFLAG_NEW_FLOATS)) { + EI_TRACE_ERR0("recv_challenge","<- RECV_CHALLENGE peer cannot " + "handle binary float encoding"); + goto error; + } if (getpeername(fd, (struct sockaddr *) &sin, &sin_len) < 0) { EI_TRACE_ERR0("recv_challenge","<- RECV_CHALLENGE can't get peername"); diff --git a/lib/erl_interface/src/connect/ei_connect_int.h b/lib/erl_interface/src/connect/ei_connect_int.h index 9926f799df..3c42b49b82 100644 --- a/lib/erl_interface/src/connect/ei_connect_int.h +++ b/lib/erl_interface/src/connect/ei_connect_int.h @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 2001-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 2001-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ /* @@ -101,6 +101,7 @@ extern int h_errno; #define DFLAG_FUN_TAGS 16 #define DFLAG_NEW_FUN_TAGS 0x80 #define DFLAG_EXTENDED_PIDS_PORTS 0x100 +#define DFLAG_NEW_FLOATS 0x800 ei_cnode *ei_fd_to_cnode(int fd); int ei_distversion(int fd); diff --git a/lib/erl_interface/src/decode/decode_double.c b/lib/erl_interface/src/decode/decode_double.c index 66dbe474ec..ed6e39655e 100644 --- a/lib/erl_interface/src/decode/decode_double.c +++ b/lib/erl_interface/src/decode/decode_double.c @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 1998-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 1998-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ #include <stdio.h> @@ -26,14 +26,22 @@ int ei_decode_double(const char *buf, int *index, double *p) { const char *s = buf + *index; const char *s0 = s; - double f; + FloatExt f; - if (get8(s) != ERL_FLOAT_EXT) return -1; - - if (sscanf(s, "%lf", &f) != 1) return -1; + switch (get8(s)) { + case ERL_FLOAT_EXT: + if (sscanf(s, "%lf", &f.d) != 1) return -1; + s += 31; + break; + case NEW_FLOAT_EXT: + /* IEEE 754 format */ + f.val = get64be(s); + break; + default: + return -1; + } - s += 31; - if (p) *p = f; + if (p) *p = f.d; *index += s-s0; return 0; } diff --git a/lib/erl_interface/src/decode/decode_skip.c b/lib/erl_interface/src/decode/decode_skip.c index 316b5bee98..f6c5d861ab 100644 --- a/lib/erl_interface/src/decode/decode_skip.c +++ b/lib/erl_interface/src/decode/decode_skip.c @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 2002-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 2002-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ #include "eidef.h" @@ -77,6 +77,7 @@ int ei_skip_term(const char* buf, int* index) if (ei_decode_big(buf, index, NULL) < 0) return -1; break; case ERL_FLOAT_EXT: + case NEW_FLOAT_EXT: if (ei_decode_double(buf, index, NULL) < 0) return -1; break; case ERL_FUN_EXT: diff --git a/lib/erl_interface/src/encode/encode_double.c b/lib/erl_interface/src/encode/encode_double.c index 53f3d52ba6..148a49f73a 100644 --- a/lib/erl_interface/src/encode/encode_double.c +++ b/lib/erl_interface/src/encode/encode_double.c @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 1998-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 1998-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ #include <stdio.h> @@ -27,13 +27,13 @@ int ei_encode_double(char *buf, int *index, double p) char *s = buf + *index; char *s0 = s; - if (!buf) s ++; + if (!buf) + s += 9; else { - put8(s,ERL_FLOAT_EXT); - memset(s, 0, 31); - sprintf(s, "%.20e", p); + /* IEEE 754 format */ + put8(s, NEW_FLOAT_EXT); + put64be(s, ((FloatExt*)&p)->val); } - s += 31; *index += s-s0; diff --git a/lib/erl_interface/src/legacy/decode_term.c b/lib/erl_interface/src/legacy/decode_term.c index ef29d6f57d..796cebdfef 100644 --- a/lib/erl_interface/src/legacy/decode_term.c +++ b/lib/erl_interface/src/legacy/decode_term.c @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 1998-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 1998-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ #include "eidef.h" @@ -59,6 +59,7 @@ int ei_decode_term(const char *buf, int *index, void *t) return ei_decode_long(buf,index,NULL); case ERL_FLOAT_EXT: + case NEW_FLOAT_EXT: return ei_decode_double(buf,index,NULL); case ERL_ATOM_EXT: diff --git a/lib/erl_interface/src/legacy/erl_marshal.c b/lib/erl_interface/src/legacy/erl_marshal.c index 4b5f28178f..c57c552b90 100644 --- a/lib/erl_interface/src/legacy/erl_marshal.c +++ b/lib/erl_interface/src/legacy/erl_marshal.c @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 1996-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 1996-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ /* @@ -102,6 +102,7 @@ void erl_init_marshal(void) cmp_array[ERL_SMALL_INTEGER_EXT] = 1; cmp_array[ERL_INTEGER_EXT] = 1; cmp_array[ERL_FLOAT_EXT] = 1; + cmp_array[NEW_FLOAT_EXT] = 1; cmp_array[ERL_SMALL_BIG_EXT] = 1; cmp_array[ERL_LARGE_BIG_EXT] = 1; cmp_array[ERL_ATOM_EXT] = 2; @@ -124,6 +125,7 @@ void erl_init_marshal(void) cmp_num_class[ERL_SMALL_INTEGER_EXT] = SMALL; cmp_num_class[ERL_INTEGER_EXT] = SMALL; cmp_num_class[ERL_FLOAT_EXT] = FLOAT; + cmp_num_class[NEW_FLOAT_EXT] = FLOAT; cmp_num_class[ERL_SMALL_BIG_EXT] = BIG; cmp_num_class[ERL_LARGE_BIG_EXT] = BIG; init_cmp_num_class_p = 0; @@ -1008,10 +1010,13 @@ static ETERM *erl_decode_it(unsigned char **ext) return ep; case ERL_FLOAT_EXT: + case NEW_FLOAT_EXT: ERL_TYPE(ep) = ERL_FLOAT; - if (sscanf((char *) *ext, "%lf", &ff) != 1) + cp = (char *) *ext; + i = -1; + if (ei_decode_double(cp, &i, &ff) == -1) goto failure; - *ext += 31; + *ext += i; ep->uval.fval.f = ff; return ep; @@ -1176,6 +1181,7 @@ unsigned char erl_ext_type(unsigned char *ext) case ERL_LARGE_TUPLE_EXT: return ERL_TUPLE; case ERL_FLOAT_EXT: + case NEW_FLOAT_EXT: return ERL_FLOAT; case ERL_BINARY_EXT: return ERL_BINARY; @@ -1218,6 +1224,7 @@ int erl_ext_size(unsigned char *t) case ERL_BINARY_EXT: case ERL_STRING_EXT: case ERL_FLOAT_EXT: + case NEW_FLOAT_EXT: case ERL_SMALL_BIG_EXT: case ERL_LARGE_BIG_EXT: return 0; @@ -1332,6 +1339,9 @@ static int jump(unsigned char **ext) case ERL_FLOAT_EXT: *ext += 31; break; + case NEW_FLOAT_EXT: + *ext += 8; + break; case ERL_BINARY_EXT: i = (**ext << 24) | ((*ext)[1] << 16) |((*ext)[2] << 8) | (*ext)[3]; *ext += 4+i; @@ -1696,12 +1706,15 @@ static int cmp_exe2(unsigned char **e1, unsigned char **e2) } return 0; case ERL_FLOAT_EXT: - if (sscanf((char *) *e1, "%lf", &ff1) != 1) - return -1; - *e1 += 31; - if (sscanf((char *) *e2, "%lf", &ff2) != 1) - return -1; - *e2 += 31; + case NEW_FLOAT_EXT: + i = -1; + if (ei_decode_double((char *) *e1, &i, &ff1) != 0) + return -1; + *e1 += i; + j = -1; + if (ei_decode_double((char *) *e2, &j, &ff2) != 0) + return -1; + *e2 += j; return cmp_floats(ff1,ff2); case ERL_BINARY_EXT: diff --git a/lib/erl_interface/src/misc/ei_decode_term.c b/lib/erl_interface/src/misc/ei_decode_term.c index 7b95ff232f..ddcbfa5a9a 100644 --- a/lib/erl_interface/src/misc/ei_decode_term.c +++ b/lib/erl_interface/src/misc/ei_decode_term.c @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 2001-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 2001-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% * @@ -25,9 +25,9 @@ #include "ei_decode_term.h" #include "putget.h" -/* Returns 1 if term is decoded, 0 if term is OK, but not decoded here - and -1 if something is wrong. - ONLY changes index if term is decoded (return value 1)! */ +/* Returns 0 on successful encoding, -1 on error, and 1 if the term seems + alright, but does not fit in the term structure. If it returns 0, the + index will be incremented, and the term contains the decoded term. */ int ei_decode_ei_term(const char* buf, int* index, ei_term* term) { @@ -46,11 +46,8 @@ int ei_decode_ei_term(const char* buf, int* index, ei_term* term) term->value.i_val = get32be(s); break; case ERL_FLOAT_EXT: - if (s[30]) return -1; - if (sscanf(s, "%lf", &f) != 1) return -1; - s += 31; - term->value.d_val = f; - break; + case NEW_FLOAT_EXT: + return ei_decode_double(buf, index, &term->value.d_val); case ERL_ATOM_EXT: len = get16be(s); memcpy(term->value.atom_name, s, len); diff --git a/lib/erl_interface/src/misc/ei_printterm.c b/lib/erl_interface/src/misc/ei_printterm.c index 8d0eef5e79..98473f780e 100644 --- a/lib/erl_interface/src/misc/ei_printterm.c +++ b/lib/erl_interface/src/misc/ei_printterm.c @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 2001-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 2001-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% * @@ -272,6 +272,7 @@ static int print_term(FILE* fp, ei_x_buff* x, break; case ERL_FLOAT_EXT: + case NEW_FLOAT_EXT: if (ei_decode_double(buf, index, &d) < 0) goto err; ch_written += xprintf(fp, x, "%f", d); break; diff --git a/lib/erl_interface/src/misc/get_type.c b/lib/erl_interface/src/misc/get_type.c index d67a6a80d3..2a680d0f94 100644 --- a/lib/erl_interface/src/misc/get_type.c +++ b/lib/erl_interface/src/misc/get_type.c @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 1998-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 1998-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% * @@ -122,7 +122,12 @@ int ei_get_type_internal(const char *buf, const int *index, case ERL_STRING_EXT: *len = get16be(s); break; - + + case ERL_FLOAT_EXT: + case NEW_FLOAT_EXT: + *type = ERL_FLOAT_EXT; + break; + case ERL_LARGE_TUPLE_EXT: case ERL_LIST_EXT: case ERL_BINARY_EXT: diff --git a/lib/erl_interface/src/misc/putget.h b/lib/erl_interface/src/misc/putget.h index 98d9ebb64c..7a43de324b 100644 --- a/lib/erl_interface/src/misc/putget.h +++ b/lib/erl_interface/src/misc/putget.h @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 1998-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 1998-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% * @@ -54,6 +54,18 @@ (s) += 4; \ } while (0) +#define put64be(s,n) do { \ + (s)[0] = ((n) >> 56) & 0xff; \ + (s)[1] = ((n) >> 48) & 0xff; \ + (s)[2] = ((n) >> 40) & 0xff; \ + (s)[3] = ((n) >> 32) & 0xff; \ + (s)[4] = ((n) >> 24) & 0xff; \ + (s)[5] = ((n) >> 16) & 0xff; \ + (s)[6] = ((n) >> 8) & 0xff; \ + (s)[7] = (n) & 0xff; \ + (s) += 8; \ +} while (0) + #define get8(s) \ ((s) += 1, \ ((unsigned char *)(s))[-1] & 0xff) @@ -82,4 +94,20 @@ (((unsigned char *)(s))[-2] << 8) | \ ((unsigned char *)(s))[-1])) +#define get64be(s) \ + ((s) += 8, \ + (((EI_ULONGLONG)((unsigned char *)(s))[-8] << 56) | \ + ((EI_ULONGLONG)((unsigned char *)(s))[-7] << 48) | \ + ((EI_ULONGLONG)((unsigned char *)(s))[-6] << 40) | \ + ((EI_ULONGLONG)((unsigned char *)(s))[-5] << 32) | \ + ((EI_ULONGLONG)((unsigned char *)(s))[-4] << 24) | \ + ((EI_ULONGLONG)((unsigned char *)(s))[-3] << 16) | \ + ((EI_ULONGLONG)((unsigned char *)(s))[-2] << 8) | \ + (EI_ULONGLONG)((unsigned char *)(s))[-1])) + +typedef union float_ext { + double d; + EI_ULONGLONG val; +} FloatExt; + #endif /* _PUTGET_H */ diff --git a/lib/erl_interface/src/misc/show_msg.c b/lib/erl_interface/src/misc/show_msg.c index 25865d6f8e..14bea5e01f 100644 --- a/lib/erl_interface/src/misc/show_msg.c +++ b/lib/erl_interface/src/misc/show_msg.c @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 1998-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 1998-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% * @@ -400,6 +400,7 @@ static void show_term(const char *termbuf, int *index, FILE *stream) break; case ERL_FLOAT_EXT: + case NEW_FLOAT_EXT: ei_decode_double(termbuf,index,&fnum); fprintf(stream,"%f",fnum); break; diff --git a/lib/erl_interface/test/ei_decode_SUITE.erl b/lib/erl_interface/test/ei_decode_SUITE.erl index ea528728ab..c6858b45ad 100644 --- a/lib/erl_interface/test/ei_decode_SUITE.erl +++ b/lib/erl_interface/test/ei_decode_SUITE.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2004-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2004-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -181,22 +181,9 @@ test_ei_decode_misc(suite) -> []; test_ei_decode_misc(Config) when is_list(Config) -> ?line P = runner:start(?test_ei_decode_misc), -% ?line <<131>> = get_binaries(P), - -% ?line {term,F} = get_term(P), -% ?line match_float(F, 0.0), -% ?line {term,F} = get_term(P), -% ?line match_float(F, 0.0), - -% ?line {term,F} = get_term(P), -% ?line true = match_float(F, -1.0), -% ?line {term,F} = get_term(P), -% ?line true = match_float(F, -1.0), - -% ?line {term,F} = get_term(P), -% ?line true = match_float(F, 1.0), -% ?line {term,F} = get_term(P), -% ?line true = match_float(F, 1.0), + ?line send_term_as_binary(P,0.0), + ?line send_term_as_binary(P,-1.0), + ?line send_term_as_binary(P,1.0), ?line send_term_as_binary(P,false), ?line send_term_as_binary(P,true), diff --git a/lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c b/lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c index d81ea88437..5447e2deb3 100644 --- a/lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c +++ b/lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 2004-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 2004-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ @@ -515,11 +515,10 @@ TESTCASE(test_ei_decode_misc) /* EI_DECODE_0(decode_version); */ -/* - EI_DECODE_2(decode_double, 0.0); - EI_DECODE_2(decode_double, -1.0); - EI_DECODE_2(decode_double, 1.0); -*/ + EI_DECODE_2(decode_double, 32, double, 0.0); + EI_DECODE_2(decode_double, 32, double, -1.0); + EI_DECODE_2(decode_double, 32, double, 1.0); + EI_DECODE_2(decode_boolean, 8, int, 0); EI_DECODE_2(decode_boolean, 7, int, 1); diff --git a/lib/erl_interface/test/ei_encode_SUITE.erl b/lib/erl_interface/test/ei_encode_SUITE.erl index fb790eb7c3..6b9de4f093 100644 --- a/lib/erl_interface/test/ei_encode_SUITE.erl +++ b/lib/erl_interface/test/ei_encode_SUITE.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2004-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2004-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -181,20 +181,14 @@ test_ei_encode_misc(Config) when is_list(Config) -> ?line <<131>> = get_binaries(P), -% ?line {term,F} = get_term(P), -% ?line match_float(F, 0.0), -% ?line {term,F} = get_term(P), -% ?line match_float(F, 0.0), + ?line {<<70,_:8/binary>>,F0} = get_buf_and_term(P), + ?line true = match_float(F0, 0.0), -% ?line {term,F} = get_term(P), -% ?line true = match_float(F, -1.0), -% ?line {term,F} = get_term(P), -% ?line true = match_float(F, -1.0), + ?line {<<70,_:8/binary>>,Fn1} = get_buf_and_term(P), + ?line true = match_float(Fn1, -1.0), -% ?line {term,F} = get_term(P), -% ?line true = match_float(F, 1.0), -% ?line {term,F} = get_term(P), -% ?line true = match_float(F, 1.0), + ?line {<<70,_:8/binary>>,Fp1} = get_buf_and_term(P), + ?line true = match_float(Fp1, 1.0), ?line {<<100,0,5,"false">>,false} = get_buf_and_term(P), ?line {<<100,0,4,"true">> ,true} = get_buf_and_term(P), @@ -310,6 +304,8 @@ get_term(P) -> %% +match_float(F, Match) when is_float(F), is_float(Match), F == Match -> + true; match_float(F, Match) when is_float(F), F > Match*0.99, F < Match*1.01 -> true. diff --git a/lib/erl_interface/test/ei_encode_SUITE_data/ei_encode_test.c b/lib/erl_interface/test/ei_encode_SUITE_data/ei_encode_test.c index f8de0b7878..c373658152 100644 --- a/lib/erl_interface/test/ei_encode_SUITE_data/ei_encode_test.c +++ b/lib/erl_interface/test/ei_encode_SUITE_data/ei_encode_test.c @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 2004-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 2004-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ @@ -350,13 +350,13 @@ TESTCASE(test_ei_encode_char) TESTCASE(test_ei_encode_misc) { EI_ENCODE_0(encode_version); -/* + EI_ENCODE_1(encode_double, 0.0); EI_ENCODE_1(encode_double, -1.0); EI_ENCODE_1(encode_double, 1.0); -*/ + EI_ENCODE_1(encode_boolean, 0) /* Only case it should be false */; EI_ENCODE_1(encode_boolean, 1); diff --git a/lib/erl_interface/test/ei_tmo_SUITE.erl b/lib/erl_interface/test/ei_tmo_SUITE.erl index 0c211aa148..e7a2465421 100644 --- a/lib/erl_interface/test/ei_tmo_SUITE.erl +++ b/lib/erl_interface/test/ei_tmo_SUITE.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2003-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2003-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -349,10 +349,12 @@ make_and_check_dummy() -> -define(DFLAG_ATOM_CACHE,2). -define(DFLAG_EXTENDED_REFERENCES,4). -define(DFLAG_EXTENDED_PIDS_PORTS,16#100). +-define(DFLAG_NEW_FLOATS,16#800). -define(DFLAG_DIST_MONITOR,8). %% From R9 and forward extended references is compulsory --define(COMPULSORY_DFLAGS, (?DFLAG_EXTENDED_REFERENCES bor ?DFLAG_EXTENDED_PIDS_PORTS)). +%% From 14 and forward new float is compulsory +-define(COMPULSORY_DFLAGS, (?DFLAG_EXTENDED_REFERENCES bor ?DFLAG_EXTENDED_PIDS_PORTS bor ?DFLAG_NEW_FLOATS)). -define(shutdown(X), exit(X)). -define(int16(X), [((X) bsr 8) band 16#ff, (X) band 16#ff]). diff --git a/lib/gs/contribs/bonk/bonk.erl b/lib/gs/contribs/bonk/bonk.erl index 12d94f6c5e..79f01bf659 100644 --- a/lib/gs/contribs/bonk/bonk.erl +++ b/lib/gs/contribs/bonk/bonk.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1996-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -33,10 +33,10 @@ run() -> run([ColorMode]) -> % This is for the start script... run(ColorMode); -run(ColorMode) when atom(ColorMode) -> +run(ColorMode) when is_atom(ColorMode) -> GS = gs:start(), - SoundPid=spawn_link(bonk_sound,start,[]), - {H,M,S}=time(), + SoundPid = spawn_link(bonk_sound,start,[]), + {H,M,S} = time(), random:seed(H*13,M*7,S*3), {SqrPids, Bmps, Colors} = create_board(GS, ColorMode), {ScoreL,_File} = get_highscore(), @@ -96,7 +96,7 @@ init(SoundPid, SqrPids, Bmps, Colors) -> game(SoundPid, SqrPids, Bmps, Colors, Scores) -> receive - {gs, _Square, buttonpress, SqrPid, [1 | _Rest]} when pid(SqrPid) -> + {gs, _Square, buttonpress, SqrPid, [1 | _Rest]} when is_pid(SqrPid) -> SqrPid ! bonk, game(SoundPid, SqrPids, Bmps, Colors, Scores); {gs, _Id, buttonpress, _Data, [Butt | _Rest]} when Butt =/= 1 -> @@ -224,11 +224,9 @@ update_score(SoundPid, SqrPids, Scores) -> send_to_all([], _Msg) -> true; - -send_to_all([Pid|Rest],Msg) when pid(Pid) -> +send_to_all([Pid|Rest],Msg) when is_pid(Pid) -> Pid ! Msg, send_to_all(Rest,Msg); - send_to_all([_Else|Rest],Msg) -> send_to_all(Rest,Msg). @@ -460,7 +458,7 @@ update_scorelist(SoundPid, Scores) -> {ScoreL,FileName} = get_highscore(), New_scorelist=update_scorelist_2(ScoreL, Score, 0, SoundPid), display_highscore(New_scorelist), - case file:open(FileName, write) of + case file:open(FileName, [write]) of {error,_} -> true; {ok,FD} -> @@ -559,7 +557,7 @@ display_about() -> {activebg, BGColor}]), gs:create(text, aboutText, aboutCan, [{width, Wid-30}, {coords, [{15, 0}]}, {fg, TextColor}, {justify, center}]), - case file:open(lists:append(bonk_dir(),"bonk.txt"), read) of + case file:open(lists:append(bonk_dir(),"bonk.txt"), [read]) of {ok, Fd} -> write_text(Fd, "", io:get_line(Fd, "")), file:close(Fd); diff --git a/lib/gs/contribs/othello/othello_adt.erl b/lib/gs/contribs/othello/othello_adt.erl index d1d3ec950b..fb60c30b89 100644 --- a/lib/gs/contribs/othello/othello_adt.erl +++ b/lib/gs/contribs/othello/othello_adt.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1996-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -375,29 +375,29 @@ is_good(Colour,H,Board) -> false. is_good_0(_,_,false,_) -> false; -is_good_0(_,H,D,_) when integer(H), integer(D), H+D<0 -> false; -is_good_0(_,H,D,_) when integer(H), integer(D), H+D>63 -> false; -is_good_0(black,H,D,Board) when integer(H), integer(D) -> +is_good_0(_,H,D,_) when is_integer(H), is_integer(D), H+D<0 -> false; +is_good_0(_,H,D,_) when is_integer(H), is_integer(D), H+D>63 -> false; +is_good_0(black,H,D,Board) when is_integer(H), is_integer(D) -> case element((H+D)+1,Board) of white -> is_good_1(black,H+D,dir(H+D,D),Board); _ -> false end; -is_good_0(white,H,D,Board) when integer(H), integer(D) -> +is_good_0(white,H,D,Board) when is_integer(H), is_integer(D) -> case element((H+D)+1,Board) of black -> is_good_1(white,H+D,dir(H+D,D),Board); _ -> false end. is_good_1(_,_,false,_) -> false; -is_good_1(_,H,D,_) when integer(H), integer(D), H+D<0 -> false; -is_good_1(_,H,D,_) when integer(H), integer(D), H+D>63 -> false; -is_good_1(black,H,D,Board) when integer(H), integer(D) -> +is_good_1(_,H,D,_) when is_integer(H), is_integer(D), H+D<0 -> false; +is_good_1(_,H,D,_) when is_integer(H), is_integer(D), H+D>63 -> false; +is_good_1(black,H,D,Board) when is_integer(H), is_integer(D) -> case element((H+D)+1,Board) of white -> is_good_1(black,H+D,dir(H+D,D),Board); black -> throw(true); _ -> false end; -is_good_1(white,H,D,Board) when integer(H), integer(D) -> +is_good_1(white,H,D,Board) when is_integer(H), is_integer(D) -> case element((H+D)+1,Board) of black -> is_good_1(white,H+D,dir(H+D,D),Board); white -> throw(true); @@ -429,15 +429,15 @@ turn(Colour,H,D,Board) -> Board end. -turn_0(_,H,D,B) when integer(H), integer(D), H+D<0 -> B; -turn_0(_,H,D,B) when integer(H), integer(D), H+D>63 -> B; -turn_0(black,H,D,Board) when integer(H), integer(D) -> +turn_0(_,H,D,B) when is_integer(H), is_integer(D), H+D<0 -> B; +turn_0(_,H,D,B) when is_integer(H), is_integer(D), H+D>63 -> B; +turn_0(black,H,D,Board) when is_integer(H), is_integer(D) -> E = H+D, case element(E+1,Board) of white -> turn_0(black,H+D,D,swap(black,E,Board)); _ -> Board end; -turn_0(white,H,D,Board) when integer(H), integer(D) -> +turn_0(white,H,D,Board) when is_integer(H), is_integer(D) -> E = H+D, case element(E+1,Board) of black -> turn_0(white,H+D,D,swap(white,E,Board)); @@ -450,7 +450,7 @@ turn_0(white,H,D,Board) when integer(H), integer(D) -> %% Neighbours are not changed !! %%------------------------------------------------------- -swap(Colour,Pos,Board) when integer(Pos) -> +swap(Colour,Pos,Board) when is_integer(Pos) -> setelement(Pos+1,Board,Colour). score(Pos) -> score1({col(Pos),row(Pos)}). diff --git a/lib/gs/src/tool_utils.erl b/lib/gs/src/tool_utils.erl index 697dd07151..b07e92c4f0 100644 --- a/lib/gs/src/tool_utils.erl +++ b/lib/gs/src/tool_utils.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1997-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -224,11 +224,11 @@ help_win(Type, Parent, Strings) -> {Wbtn0,Hbtn0} = gs:read(Lbl, {font_wh,{Font,"Cancel"}}), %% Compute size of the objects and adjust the graphics accordingly - Wbtn = max(Wbtn0+10, ?Wbtn), - Hbtn = max(Hbtn0+10, ?Hbtn), - Hent = max(Hent0+10, ?Hent), - Wlbl = max(Wlbl0, max(Nbtn*Wbtn+(Nbtn-1)*?PAD, ?Wlbl)), - Hlbl = max(Hlbl0, ?Hlbl), + Wbtn = erlang:max(Wbtn0+10, ?Wbtn), + Hbtn = erlang:max(Hbtn0+10, ?Hbtn), + Hent = erlang:max(Hent0+10, ?Hent), + Wlbl = erlang:max(Wlbl0, erlang:max(Nbtn*Wbtn+(Nbtn-1)*?PAD, ?Wlbl)), + Hlbl = erlang:max(Hlbl0, ?Hlbl), Wwin = ?PAD+Wlbl+?PAD, @@ -297,9 +297,6 @@ data("Yes") -> {helpwin,yes}; data("No") -> {helpwin,no}; data("Cancel") -> {helpwin,cancel}. -max(X, Y) when X>Y -> X; -max(_X, Y) -> Y. - get_coords(Parent, W, H) -> case gs:read(Parent, x) of X when is_integer(X) -> diff --git a/lib/hipe/cerl/erl_types.erl b/lib/hipe/cerl/erl_types.erl index f3b91b3953..758914ff9e 100644 --- a/lib/hipe/cerl/erl_types.erl +++ b/lib/hipe/cerl/erl_types.erl @@ -178,7 +178,7 @@ t_remote/3, t_string/0, t_struct_from_opaque/2, - t_solve_remote/2, + t_solve_remote/3, t_subst/2, t_subtract/2, t_subtract_list/2, @@ -210,6 +210,7 @@ ]). %%-define(DO_ERL_TYPES_TEST, true). +-compile({no_auto_import,[min/2,max/2]}). -ifdef(DO_ERL_TYPES_TEST). -export([test/0]). @@ -221,6 +222,8 @@ -export([t_is_identifier/1]). -endif. +-export_type([erl_type/0]). + %%============================================================================= %% %% Definition of the type structure @@ -398,7 +401,8 @@ t_is_none(_) -> false. -spec t_opaque(module(), atom(), [_], erl_type()) -> erl_type(). t_opaque(Mod, Name, Args, Struct) -> - ?opaque(set_singleton(#opaque{mod=Mod, name=Name, args=Args, struct=Struct})). + O = #opaque{mod = Mod, name = Name, args = Args, struct = Struct}, + ?opaque(set_singleton(O)). -spec t_is_opaque(erl_type()) -> boolean(). @@ -427,7 +431,7 @@ t_opaque_structure(?opaque(Elements)) -> t_opaque_module(?opaque(Elements)) -> case ordsets:size(Elements) of 1 -> - [#opaque{mod=Module}] = ordsets:to_list(Elements), + [#opaque{mod = Module}] = ordsets:to_list(Elements), Module; _ -> throw({error, "Unexpected multiple opaque types"}) end. @@ -631,7 +635,7 @@ t_unopaque_on_mismatch(GenType, Type, Opaques) -> case t_inf(GenType, Type) of ?none -> Unopaqued = t_unopaque(Type, Opaques), - %% Unions might be a problem, must investigate. + %% XXX: Unions might be a problem, must investigate. case t_inf(GenType, Unopaqued) of ?none -> Type; _ -> Unopaqued @@ -643,10 +647,10 @@ t_unopaque_on_mismatch(GenType, Type, Opaques) -> module_builtin_opaques(Module) -> [O || O <- all_opaque_builtins(), t_opaque_module(O) =:= Module]. - + %%----------------------------------------------------------------------------- -%% Remote types -%% These types are used for preprocessing they should never reach the analysis stage +%% Remote types: these types are used for preprocessing; +%% they should never reach the analysis stage. -spec t_remote(module(), atom(), [_]) -> erl_type(). @@ -658,126 +662,133 @@ t_remote(Mod, Name, Args) -> t_is_remote(?remote(_)) -> true; t_is_remote(_) -> false. --spec t_solve_remote(erl_type(), dict()) -> erl_type(). +-spec t_solve_remote(erl_type(), set(), dict()) -> erl_type(). -t_solve_remote(Type , Records) -> - {RT, _RR} = t_solve_remote(Type, Records, []), +t_solve_remote(Type, ExpTypes, Records) -> + {RT, _RR} = t_solve_remote(Type, ExpTypes, Records, []), RT. -t_solve_remote(?function(Domain, Range), R, C) -> - {RT1, RR1} = t_solve_remote(Domain, R, C), - {RT2, RR2} = t_solve_remote(Range, R, C), +t_solve_remote(?function(Domain, Range), ET, R, C) -> + {RT1, RR1} = t_solve_remote(Domain, ET, R, C), + {RT2, RR2} = t_solve_remote(Range, ET, R, C), {?function(RT1, RT2), RR1 ++ RR2}; -t_solve_remote(?list(Types, Term, Size), R, C) -> - {RT, RR} = t_solve_remote(Types, R, C), +t_solve_remote(?list(Types, Term, Size), ET, R, C) -> + {RT, RR} = t_solve_remote(Types, ET, R, C), {?list(RT, Term, Size), RR}; -t_solve_remote(?product(Types), R, C) -> - {RL, RR} = list_solve_remote(Types, R, C), +t_solve_remote(?product(Types), ET, R, C) -> + {RL, RR} = list_solve_remote(Types, ET, R, C), {?product(RL), RR}; -t_solve_remote(?opaque(Set), R, C) -> +t_solve_remote(?opaque(Set), ET, R, C) -> List = ordsets:to_list(Set), - {NewList, RR} = opaques_solve_remote(List, R, C), + {NewList, RR} = opaques_solve_remote(List, ET, R, C), {?opaque(ordsets:from_list(NewList)), RR}; -t_solve_remote(?tuple(?any, _, _) = T, _R, _C) -> {T, []}; -t_solve_remote(?tuple(Types, Arity, Tag), R, C) -> - {RL, RR} = list_solve_remote(Types, R, C), +t_solve_remote(?tuple(?any, _, _) = T, _ET, _R, _C) -> {T, []}; +t_solve_remote(?tuple(Types, Arity, Tag), ET, R, C) -> + {RL, RR} = list_solve_remote(Types, ET, R, C), {?tuple(RL, Arity, Tag), RR}; -t_solve_remote(?tuple_set(Set), R, C) -> - {NewSet, RR} = tuples_solve_remote(Set, R, C), +t_solve_remote(?tuple_set(Set), ET, R, C) -> + {NewSet, RR} = tuples_solve_remote(Set, ET, R, C), {?tuple_set(NewSet), RR}; -t_solve_remote(?remote(Set), R, C) -> +t_solve_remote(?remote(Set), ET, R, C) -> RemoteList = ordsets:to_list(Set), - {RL, RR} = list_solve_remote_type(RemoteList, R, C), + {RL, RR} = list_solve_remote_type(RemoteList, ET, R, C), {t_sup(RL), RR}; -t_solve_remote(?union(List), R, C) -> - {RL, RR} = list_solve_remote(List, R, C), +t_solve_remote(?union(List), ET, R, C) -> + {RL, RR} = list_solve_remote(List, ET, R, C), {t_sup(RL), RR}; -t_solve_remote(T, _R, _C) -> {T, []}. +t_solve_remote(T, _ET, _R, _C) -> {T, []}. t_solve_remote_type(#remote{mod = RemMod, name = Name, args = Args} = RemType, - R, C) -> + ET, R, C) -> + ArgsLen = length(Args), case dict:find(RemMod, R) of error -> - Msg = io_lib:format("Cannot locate module ~w to " - "resolve the remote type: ~w:~w()~n", - [RemMod, RemMod, Name]), - throw({error, Msg}); + self() ! {self(), ext_types, {RemMod, Name, ArgsLen}}, + {t_any(), []}; {ok, RemDict} -> - case lookup_type(Name, RemDict) of - {type, {_Mod, Type, ArgNames}} when length(Args) =:= length(ArgNames) -> - {NewType, NewCycle, NewRR} = - case unfold(RemType, C) of - true -> - List = lists:zip(ArgNames, Args), - TmpVarDict = dict:from_list(List), - {t_from_form(Type, RemDict, TmpVarDict), [RemType|C], []}; - false -> {t_any(), C, [RemType]} - end, - {RT, RR} = t_solve_remote(NewType, R, NewCycle), - RetRR = NewRR ++ RR, - RT1 = - case lists:member(RemType, RetRR) of - true -> t_limit(RT, ?REC_TYPE_LIMIT); - false -> RT - end, - {RT1, RetRR}; - {opaque, {Mod, Type, ArgNames}} when length(Args) =:= length(ArgNames) -> - List = lists:zip(ArgNames, Args), - TmpVarDict = dict:from_list(List), - {Rep, NewCycle, NewRR} = - case unfold(RemType, C) of - true -> {t_from_form(Type, RemDict, TmpVarDict), [RemType|C], []}; - false -> {t_any(), C, [RemType]} - end, - {NewRep, RR} = t_solve_remote(Rep, R, NewCycle), - RetRR = NewRR ++ RR, - RT1 = - case lists:member(RemType, RetRR) of - true -> t_limit(NewRep, ?REC_TYPE_LIMIT); - false -> NewRep - end, - {t_from_form({opaque, -1, Name, {Mod, Args, RT1}}, - RemDict, TmpVarDict), - RetRR}; - {type, _} -> - Msg = io_lib:format("Unknown remote type ~w\n", [Name]), - throw({error, Msg}); - {opaque, _} -> - Msg = io_lib:format("Unknown remote opaque type ~w\n", [Name]), - throw({error, Msg}); - error -> - Msg = io_lib:format("Unable to find remote type ~w:~w()\n", - [RemMod, Name]), + MFA = {RemMod, Name, ArgsLen}, + case sets:is_element(MFA, ET) of + true -> + case lookup_type(Name, RemDict) of + {type, {_Mod, Type, ArgNames}} when ArgsLen =:= length(ArgNames) -> + {NewType, NewCycle, NewRR} = + case unfold(RemType, C) of + true -> + List = lists:zip(ArgNames, Args), + TmpVarDict = dict:from_list(List), + {t_from_form(Type, RemDict, TmpVarDict), [RemType|C], []}; + false -> {t_any(), C, [RemType]} + end, + {RT, RR} = t_solve_remote(NewType, ET, R, NewCycle), + RetRR = NewRR ++ RR, + RT1 = + case lists:member(RemType, RetRR) of + true -> t_limit(RT, ?REC_TYPE_LIMIT); + false -> RT + end, + {RT1, RetRR}; + {opaque, {Mod, Type, ArgNames}} when ArgsLen =:= length(ArgNames) -> + List = lists:zip(ArgNames, Args), + TmpVarDict = dict:from_list(List), + {Rep, NewCycle, NewRR} = + case unfold(RemType, C) of + true -> {t_from_form(Type, RemDict, TmpVarDict), [RemType|C], []}; + false -> {t_any(), C, [RemType]} + end, + {NewRep, RR} = t_solve_remote(Rep, ET, R, NewCycle), + RetRR = NewRR ++ RR, + RT1 = + case lists:member(RemType, RetRR) of + true -> t_limit(NewRep, ?REC_TYPE_LIMIT); + false -> NewRep + end, + {t_from_form({opaque, -1, Name, {Mod, Args, RT1}}, + RemDict, TmpVarDict), + RetRR}; + {type, _} -> + Msg = io_lib:format("Unknown remote type ~w\n", [Name]), + throw({error, Msg}); + {opaque, _} -> + Msg = io_lib:format("Unknown remote opaque type ~w\n", [Name]), + throw({error, Msg}); + error -> + Msg = io_lib:format("Unable to find remote type ~w:~w()\n", + [RemMod, Name]), + throw({error, Msg}) + end; + false -> + Msg = io_lib:format("Unable to find exported type ~w:~w/~w\n", + [RemMod, Name, ArgsLen]), throw({error, Msg}) end end. -list_solve_remote([], _R, _C) -> +list_solve_remote([], _ET, _R, _C) -> {[], []}; -list_solve_remote([Type|Types], R, C) -> - {RT, RR1} = t_solve_remote(Type, R, C), - {RL, RR2} = list_solve_remote(Types, R, C), +list_solve_remote([Type|Types], ET, R, C) -> + {RT, RR1} = t_solve_remote(Type, ET, R, C), + {RL, RR2} = list_solve_remote(Types, ET, R, C), {[RT|RL], RR1 ++ RR2}. -list_solve_remote_type([], _R, _C) -> +list_solve_remote_type([], _ET, _R, _C) -> {[], []}; -list_solve_remote_type([Type|Types], R, C) -> - {RT, RR1} = t_solve_remote_type(Type, R, C), - {RL, RR2} = list_solve_remote_type(Types, R, C), +list_solve_remote_type([Type|Types], ET, R, C) -> + {RT, RR1} = t_solve_remote_type(Type, ET, R, C), + {RL, RR2} = list_solve_remote_type(Types, ET, R, C), {[RT|RL], RR1 ++ RR2}. -opaques_solve_remote([], _R, _C) -> +opaques_solve_remote([], _ET, _R, _C) -> {[], []}; -opaques_solve_remote([#opaque{struct = Struct} = Remote|Tail], R, C) -> - {RT, RR1} = t_solve_remote(Struct, R, C), - {LOp, RR2} = opaques_solve_remote(Tail, R, C), +opaques_solve_remote([#opaque{struct = Struct} = Remote|Tail], ET, R, C) -> + {RT, RR1} = t_solve_remote(Struct, ET, R, C), + {LOp, RR2} = opaques_solve_remote(Tail, ET, R, C), {[Remote#opaque{struct = RT}|LOp], RR1 ++ RR2}. -tuples_solve_remote([], _R, _C) -> +tuples_solve_remote([], _ET, _R, _C) -> {[], []}; -tuples_solve_remote([{Sz, Tuples}|Tail], R, C) -> - {RL, RR1} = list_solve_remote(Tuples, R, C), - {LSzTpls, RR2} = tuples_solve_remote(Tail, R, C), +tuples_solve_remote([{Sz, Tuples}|Tail], ET, R, C) -> + {RL, RR1} = list_solve_remote(Tuples, ET, R, C), + {LSzTpls, RR2} = tuples_solve_remote(Tail, ET, R, C), {[{Sz, RL}|LSzTpls], RR1 ++ RR2}. %%----------------------------------------------------------------------------- @@ -801,7 +812,7 @@ t_is_none_or_unit(?unit) -> true; t_is_none_or_unit(_) -> false. %%----------------------------------------------------------------------------- -%% Atoms and the derived type bool +%% Atoms and the derived type boolean %% -spec t_atom() -> erl_type(). @@ -2523,12 +2534,14 @@ t_subst(T, _Dict, _Fun) -> %% Unification %% --spec t_unify(erl_type(), erl_type()) -> {erl_type(), [{_, erl_type()}]}. +-type t_unify_ret() :: {erl_type(), [{_, erl_type()}]}. + +-spec t_unify(erl_type(), erl_type()) -> t_unify_ret(). t_unify(T1, T2) -> t_unify(T1, T2, []). --spec t_unify(erl_type(), erl_type(), [erl_type()]) -> {erl_type(), [{_, erl_type()}]}. +-spec t_unify(erl_type(), erl_type(), [erl_type()]) -> t_unify_ret(). t_unify(T1, T2, Opaques) -> {T, Dict} = t_unify(T1, T2, dict:new(), Opaques), @@ -2541,7 +2554,7 @@ t_unify(?var(Id1) = T, ?var(Id2), Dict, Opaques) -> error -> case dict:find(Id2, Dict) of error -> {T, dict:store(Id2, T, Dict)}; - {ok, Type} -> {Type, t_unify(T, Type, Dict, Opaques)} + {ok, Type} -> t_unify(T, Type, Dict, Opaques) end; {ok, Type1} -> case dict:find(Id2, Dict) of @@ -3338,8 +3351,8 @@ sequence([], [], _Delimiter) -> []; sequence([T], Acc, _Delimiter) -> lists:flatten(lists:reverse([T|Acc])); -sequence([T|Left], Acc, Delimiter) -> - sequence(Left, [T ++ Delimiter|Acc], Delimiter). +sequence([T|Ts], Acc, Delimiter) -> + sequence(Ts, [T ++ Delimiter|Acc], Delimiter). %%============================================================================= %% diff --git a/lib/hipe/flow/hipe_dominators.erl b/lib/hipe/flow/hipe_dominators.erl index 3bfa6d43c4..17357461a5 100644 --- a/lib/hipe/flow/hipe_dominators.erl +++ b/lib/hipe/flow/hipe_dominators.erl @@ -1,20 +1,20 @@ %% -*- erlang-indent-level: 2 -*- %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2004-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2004-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %%------------------------------------------------------------------------ @@ -37,6 +37,8 @@ domFrontier_create/2, domFrontier_get/2]). +-export_type([domTree/0]). + -include("cfg.hrl"). %%======================================================================== diff --git a/lib/hipe/util/hipe_digraph.erl b/lib/hipe/util/hipe_digraph.erl index a62e913fe5..fcfaa64684 100644 --- a/lib/hipe/util/hipe_digraph.erl +++ b/lib/hipe/util/hipe_digraph.erl @@ -1,20 +1,20 @@ %% -*- erlang-indent-level: 2 -*- %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2005-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2005-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %%----------------------------------------------------------------------- @@ -30,6 +30,8 @@ from_list/1, to_list/1, get_parents/2, get_children/2]). -export([reverse_preorder_sccs/1]). +-export_type([hdg/0]). + %%------------------------------------------------------------------------ -type ordset(T) :: [T]. % XXX: temporarily diff --git a/lib/inets/doc/src/httpc.xml b/lib/inets/doc/src/httpc.xml index 7430a62b1b..9c8df28fec 100644 --- a/lib/inets/doc/src/httpc.xml +++ b/lib/inets/doc/src/httpc.xml @@ -167,6 +167,8 @@ ssl_options() = {verify, code()} | <v>http_option() = {timeout, timeout()} | {connect_timeout, timeout()} | {ssl, ssl_options()} | + {ossl, ssl_options()} | + {essl, ssl_options()} | {autoredirect, boolean()} | {proxy_auth, {userstring(), passwordstring()}} | {version, http_version()} | @@ -222,7 +224,22 @@ ssl_options() = {verify, code()} | <tag><c><![CDATA[ssl]]></c></tag> <item> - <p>If using SSL, these SSL-specific options are used. </p> + <p>This is the default ssl config option, currently defaults to + <c>ossl</c>, see below. </p> + <p>Defaults to <c>[]</c>. </p> + </item> + + <tag><c><![CDATA[ossl]]></c></tag> + <item> + <p>If using the OpenSSL based (old) implementation of SSL, + these SSL-specific options are used. </p> + <p>Defaults to <c>[]</c>. </p> + </item> + + <tag><c><![CDATA[essl]]></c></tag> + <item> + <p>If using the Erlang based (new) implementation of SSL, + these SSL-specific options are used. </p> <p>Defaults to <c>[]</c>. </p> </item> diff --git a/lib/inets/doc/src/httpd.xml b/lib/inets/doc/src/httpd.xml index 7dabeb33e9..847605fe93 100644 --- a/lib/inets/doc/src/httpd.xml +++ b/lib/inets/doc/src/httpd.xml @@ -148,8 +148,13 @@ in the apache like configuration file. </item> - <tag>{socket_type, ip_comm | ssl}</tag> + <tag>{socket_type, ip_comm | ssl | ossl | essl}</tag> <item> + <p>When using ssl, there are several alternatives. + <c>ossl</c> specifically uses the OpenSSL based (old) SSL. + <c>essl</c> specifically uses the Erlang based (new) SSL. + When using <c>ssl</c> it <em>currently</em> defaults to + <c>ossl</c>. </p> <p>Defaults to <c>ip_comm</c>. </p> </item> @@ -267,18 +272,22 @@ text/plain asc txt The <c>common</c> format is one line that looks like this: <c>remotehost rfc931 authuser [date] "request" status bytes</c></p> - <pre>remotehost + <pre> +remotehost Remote rfc931 The client's remote username (RFC 931). authuser - The username with which the user authenticated himself. + The username with which the user authenticated + himself. [date] Date and time of the request (RFC 1123). "request" - The request line exactly as it came from the client(RFC 1945). + The request line exactly as it came from the client + (RFC 1945). status - The HTTP status code returned to the client (RFC 1945). + The HTTP status code returned to the client + (RFC 1945). bytes The content-length of the document transferred. </pre> @@ -286,10 +295,11 @@ bytes <p>The <c>combined</c> format is on line that look like this: <c>remotehost rfc931 authuser [date] "request" status bytes "referer" "user_agent" </c></p> - <pre>"referer" + <pre> +"referer" The url the client was on before - requesting your url. (If it could not be determined a minus - sign will be placed in this field) + requesting your url. (If it could not be determined + a minus sign will be placed in this field) "user_agent" The software the client claims to be using. (If it could not be determined a minus sign will be placed in @@ -389,6 +399,31 @@ bytes and an access to http://your.server.org/image/foo.gif would refer to the file /ftp/pub/image/foo.gif.</item> + <tag>{re_write, {Re, Replacement}}</tag> + + <item> Where Re = string() and Replacement = string(). + The ReWrite property allows documents to be stored in the local file + system instead of the document_root location. URLs are rewritten + by re:replace/3 to produce a path in the local filesystem. + For example: + + <code>{re_write, {"^/[~]([^/]+)(.*)$", "/home/\\1/public\\2"}</code> + + and an access to http://your.server.org/~bob/foo.gif would refer to + the file /home/bob/public/foo.gif. + + In an Apache like configuration file the Re is separated + from Replacement with one single space, and as expected + backslashes do not need to be backslash escaped so the + same example would become: + + <code>ReWrite ^/[~]([^/]+)(.*)$ /home/\1/public\2</code> + + Beware of trailing space in Replacement that will be used. + If you must have a space in Re use e.g the character encoding + <code>\040</code> see <seealso marker="re">re(3)</seealso>. + </item> + <tag>{directory_index, [string()]}</tag> <item> @@ -408,7 +443,7 @@ bytes </taglist> <marker id="cgi_prop"></marker> - <p><em>CGI properties - requires mod_cgi</em></p> + <p><em>CGI properties - requires mod_cgi</em></p> <taglist> <tag>{script_alias, {Alias, RealName}}</tag> <item> Where Alias = string() and RealName = string(). @@ -423,6 +458,19 @@ bytes the server to run the script /web/cgi-bin/foo. </item> + <tag>{script_re_write, {Re, Replacement}}</tag> + <item> Where Re = string() and Replacement = string(). + Has the same behavior as the ReWrite property, except that + it also marks the target directory as containing CGI + scripts. URLs with a path beginning with url-path are mapped to + scripts beginning with directory-filename, for example: + + <code> {script_re_write, {"^/cgi-bin/(\\d+)/", "/web/\\1/cgi-bin/"}</code> + + and an access to http://your.server.org/cgi-bin/17/foo would cause + the server to run the script /web/17/cgi-bin/foo. + </item> + <tag>{script_nocache, boolean()}</tag> <item> diff --git a/lib/inets/doc/src/mod_esi.xml b/lib/inets/doc/src/mod_esi.xml index 6bad77dc0a..3c473d3f94 100644 --- a/lib/inets/doc/src/mod_esi.xml +++ b/lib/inets/doc/src/mod_esi.xml @@ -73,7 +73,8 @@ <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>Input = string()</v> + <v>Key = query_string | content_length | server_software | gateway_interface | server_protocol | server_port | request_method | remote_addr | script_name</v> + <v>Input = string()</v> </type> <desc> <p>The <c>Module</c> must be found in the code path and export diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml index fed42291ab..23ad5c0df0 100644 --- a/lib/inets/doc/src/notes.xml +++ b/lib/inets/doc/src/notes.xml @@ -32,6 +32,67 @@ <file>notes.xml</file> </header> + <section><title>Inets 5.4</title> + + <section><title>Improvements and New Features</title> +<!-- + <p>-</p> +--> + + <list> + <item> + <p>[httpc|httpd] - Now allow the use of the "new" ssl, by using + the <c>essl</c> tag instead. </p> + <p>See the <c>http_option</c> option in the + <seealso marker="httpc#request2">request/4,5</seealso> or + the <seealso marker="httpd#comm_prop">socket-type</seealso> + section of the Communication properties chapter for more info, </p> + <p>Own Id: OTP-7907</p> + </item> + + <item> + <p>Deprecated functions designated to be removed in R14 has been + removed. Also, some new functions has been marked as deprecated + (the old http client api module). </p> + <p>Own Id: OTP-8564</p> + <p>*** POTENTIAL INCOMPATIBILITY ***</p> + </item> + + <item> + <p>[httpd] - Improved mod_alias. + Now able to do better URL rewrites. </p> + <p>See + <seealso marker="httpd#alias_prop">URL aliasing properties</seealso> + and the + <seealso marker="httpd#cgi_prop">CGI properties</seealso> + section(s) for more info, </p> + <p>Own Id: OTP-8573</p> + </item> + + </list> + </section> + + <section><title>Fixed Bugs and Malfunctions</title> + + <p>-</p> + +<!-- + <list> + <item> + <p>[httpd] The server did not fully support the documented module + callback api. Specifically, the load function should be able to + return the atom <c>ok</c>, but this was not accepted. </p> + <p>Own Id: OTP-8359</p> + </item> + + </list> +--> + + </section> + + </section> <!-- 5.4 --> + + <section><title>Inets 5.3.3</title> <section><title>Improvements and New Features</title> @@ -304,6 +365,7 @@ <p>Own Id: OTP-8016</p> <p>*** POTENTIAL INCOMPATIBILITY ***</p> </item> + </list> </section> diff --git a/lib/inets/examples/Makefile b/lib/inets/examples/Makefile index a42b0e38b6..775c449062 100644 --- a/lib/inets/examples/Makefile +++ b/lib/inets/examples/Makefile @@ -1,19 +1,19 @@ # # %CopyrightBegin% -# -# Copyright Ericsson AB 1997-2009. All Rights Reserved. -# +# +# Copyright Ericsson AB 2010. All Rights Reserved. +# # The contents of this file are subject to the Erlang Public License, # Version 1.1, (the "License"); you may not use this file except in # compliance with the License. You should have received a copy of the # Erlang Public License along with this software. If not, it can be # retrieved online at http://www.erlang.org/. -# +# # Software distributed under the License is distributed on an "AS IS" # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See # the License for the specific language governing rights and limitations # under the License. -# +# # %CopyrightEnd% # # @@ -21,189 +21,15 @@ include $(ERL_TOP)/make/target.mk include $(ERL_TOP)/make/$(TARGET)/otp.mk # ---------------------------------------------------- -# Application version -# ---------------------------------------------------- -include ../vsn.mk -VSN=$(INETS_VSN) - -# ---------------------------------------------------- -# Release directory specification -# ---------------------------------------------------- -RELSYSDIR = $(RELEASE_PATH)/lib/inets-$(VSN) - -# ---------------------------------------------------- -# Target Specs +# Common Macros # ---------------------------------------------------- -MODULE= -AUTH_FILES = server_root/auth/group \ - server_root/auth/passwd -CGI_FILES = server_root/cgi-bin/printenv.sh -CONF_FILES = server_root/conf/8080.conf \ - server_root/conf/8888.conf \ - server_root/conf/httpd.conf \ - server_root/conf/ssl.conf \ - server_root/conf/mime.types -OPEN_FILES = server_root/htdocs/open/dummy.html -MNESIA_OPEN_FILES = server_root/htdocs/mnesia_open/dummy.html -MISC_FILES = server_root/htdocs/misc/friedrich.html \ - server_root/htdocs/misc/oech.html -SECRET_FILES = server_root/htdocs/secret/dummy.html -MNESIA_SECRET_FILES = server_root/htdocs/mnesia_secret/dummy.html -HTDOCS_FILES = server_root/htdocs/index.html \ - server_root/htdocs/config.shtml \ - server_root/htdocs/echo.shtml \ - server_root/htdocs/exec.shtml \ - server_root/htdocs/flastmod.shtml \ - server_root/htdocs/fsize.shtml \ - server_root/htdocs/include.shtml -ICON_FILES = server_root/icons/README \ - server_root/icons/a.gif \ - server_root/icons/alert.black.gif \ - server_root/icons/alert.red.gif \ - server_root/icons/apache_pb.gif \ - server_root/icons/back.gif \ - server_root/icons/ball.gray.gif \ - server_root/icons/ball.red.gif \ - server_root/icons/binary.gif \ - server_root/icons/binhex.gif \ - server_root/icons/blank.gif \ - server_root/icons/bomb.gif \ - server_root/icons/box1.gif \ - server_root/icons/box2.gif \ - server_root/icons/broken.gif \ - server_root/icons/burst.gif \ - server_root/icons/button1.gif \ - server_root/icons/button10.gif \ - server_root/icons/button2.gif \ - server_root/icons/button3.gif \ - server_root/icons/button4.gif \ - server_root/icons/button5.gif \ - server_root/icons/button6.gif \ - server_root/icons/button7.gif \ - server_root/icons/button8.gif \ - server_root/icons/button9.gif \ - server_root/icons/buttonl.gif \ - server_root/icons/buttonr.gif \ - server_root/icons/c.gif \ - server_root/icons/comp.blue.gif \ - server_root/icons/comp.gray.gif \ - server_root/icons/compressed.gif \ - server_root/icons/continued.gif \ - server_root/icons/dir.gif \ - server_root/icons/down.gif \ - server_root/icons/dvi.gif \ - server_root/icons/f.gif \ - server_root/icons/folder.gif \ - server_root/icons/folder.open.gif \ - server_root/icons/folder.sec.gif \ - server_root/icons/forward.gif \ - server_root/icons/generic.gif \ - server_root/icons/generic.red.gif \ - server_root/icons/generic.sec.gif \ - server_root/icons/hand.right.gif \ - server_root/icons/hand.up.gif \ - server_root/icons/htdig.gif \ - server_root/icons/icon.sheet.gif \ - server_root/icons/image1.gif \ - server_root/icons/image2.gif \ - server_root/icons/image3.gif \ - server_root/icons/index.gif \ - server_root/icons/layout.gif \ - server_root/icons/left.gif \ - server_root/icons/link.gif \ - server_root/icons/movie.gif \ - server_root/icons/p.gif \ - server_root/icons/patch.gif \ - server_root/icons/pdf.gif \ - server_root/icons/pie0.gif \ - server_root/icons/pie1.gif \ - server_root/icons/pie2.gif \ - server_root/icons/pie3.gif \ - server_root/icons/pie4.gif \ - server_root/icons/pie5.gif \ - server_root/icons/pie6.gif \ - server_root/icons/pie7.gif \ - server_root/icons/pie8.gif \ - server_root/icons/portal.gif \ - server_root/icons/poweredby.gif \ - server_root/icons/ps.gif \ - server_root/icons/quill.gif \ - server_root/icons/right.gif \ - server_root/icons/screw1.gif \ - server_root/icons/screw2.gif \ - server_root/icons/script.gif \ - server_root/icons/sound1.gif \ - server_root/icons/sound2.gif \ - server_root/icons/sphere1.gif \ - server_root/icons/sphere2.gif \ - server_root/icons/star.gif \ - server_root/icons/star_blank.gif \ - server_root/icons/tar.gif \ - server_root/icons/tex.gif \ - server_root/icons/text.gif \ - server_root/icons/transfer.gif \ - server_root/icons/unknown.gif \ - server_root/icons/up.gif \ - server_root/icons/uu.gif \ - server_root/icons/uuencoded.gif \ - server_root/icons/world1.gif \ - server_root/icons/world2.gif +include subdirs.mk -SSL_FILES = server_root/ssl/ssl_client.pem \ - server_root/ssl/ssl_server.pem +SPECIAL_TARGETS = # ---------------------------------------------------- -# FLAGS +# Default Subdir Targets # ---------------------------------------------------- -ERL_COMPILE_FLAGS += - -# ---------------------------------------------------- -# Targets -# ---------------------------------------------------- - -debug opt: - -clean: - -docs: - -# ---------------------------------------------------- -# Release Target -# ---------------------------------------------------- -include $(ERL_TOP)/make/otp_release_targets.mk - -release_spec: opt - $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/auth - $(INSTALL_DATA) $(AUTH_FILES) $(RELSYSDIR)/examples/server_root/auth - $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/cgi-bin - $(INSTALL_SCRIPT) $(CGI_FILES) $(RELSYSDIR)/examples/server_root/cgi-bin - $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/conf - $(INSTALL_DATA) $(CONF_FILES) $(RELSYSDIR)/examples/server_root/conf - $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs/open - $(INSTALL_DATA) $(OPEN_FILES) \ - $(RELSYSDIR)/examples/server_root/htdocs/open - $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs/mnesia_open - $(INSTALL_DATA) $(MNESIA_OPEN_FILES) \ - $(RELSYSDIR)/examples/server_root/htdocs/mnesia_open - $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs/misc - $(INSTALL_DATA) $(MISC_FILES) \ - $(RELSYSDIR)/examples/server_root/htdocs/misc - $(INSTALL_DIR) \ - $(RELSYSDIR)/examples/server_root/htdocs/secret/top_secret - $(INSTALL_DIR) \ - $(RELSYSDIR)/examples/server_root/htdocs/mnesia_secret/top_secret - $(INSTALL_DATA) $(SECRET_FILES) \ - $(RELSYSDIR)/examples/server_root/htdocs/secret - $(INSTALL_DATA) $(MNESIA_SECRET_FILES) \ - $(RELSYSDIR)/examples/server_root/htdocs/mnesia_secret - $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs - $(INSTALL_DATA) $(HTDOCS_FILES) $(RELSYSDIR)/examples/server_root/htdocs - $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/icons - $(INSTALL_DATA) $(ICON_FILES) $(RELSYSDIR)/examples/server_root/icons - $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/ssl - $(INSTALL_DATA) $(SSL_FILES) $(RELSYSDIR)/examples/server_root/ssl - $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/logs - -release_docs_spec: +include $(ERL_TOP)/make/otp_subdir.mk diff --git a/lib/inets/examples/httpd_load_test/Makefile b/lib/inets/examples/httpd_load_test/Makefile new file mode 100644 index 0000000000..1cc61ad8ae --- /dev/null +++ b/lib/inets/examples/httpd_load_test/Makefile @@ -0,0 +1,123 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 2010. All Rights Reserved. +# +# The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# %CopyrightEnd% + +include $(ERL_TOP)/make/target.mk + +EBIN = . + +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../../vsn.mk + +VSN=$(INETS_VSN) + + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/inets-$(VSN) +EXAMPLE_RELSYSDIR = $(RELSYSDIR)/examples +HDLT_RELSYSDIR = $(EXAMPLE_RELSYSDIR)/httpd_load_test + + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- + +include modules.mk + +ERL_FILES = $(MODULES:%=%.erl) + +SOURCE = $(ERL_FILES) $(INTERNAL_HRL_FILES) + +TARGET_FILES = \ + $(ERL_FILES:%.erl=$(EBIN)/%.$(EMULATOR)) + +ifeq ($(TYPE),debug) +ERL_COMPILE_FLAGS += -Ddebug -W +endif + + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- + +include ../../src/inets_app/inets.mk + +ERL_COMPILE_FLAGS += \ + $(INETS_FLAGS) \ + $(INETS_ERL_COMPILE_FLAGS) \ + -I../../include + + +# ---------------------------------------------------- +# Special Build Targets +# ---------------------------------------------------- + + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- +debug: + @${MAKE} TYPE=debug opt + +opt: $(TARGET_FILES) + +clean: + rm -f $(TARGET_FILES) + rm -f errs core *~ + +docs: + + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + + +release_spec: opt + $(INSTALL_DIR) $(EXAMPLE_RELSYSDIR) + $(INSTALL_DIR) $(HDLT_RELSYSDIR) + $(INSTALL_DATA) $(SCRIPT_SKELETONS) $(HDLT_RELSYSDIR) + $(INSTALL_DATA) $(CONF_SKELETONS) $(HDLT_RELSYSDIR) + $(INSTALL_DATA) $(CERT_FILES) $(HDLT_RELSYSDIR) + $(INSTALL_DATA) $(TARGET_FILES) $(HDLT_RELSYSDIR) + $(INSTALL_DATA) $(ERL_FILES) $(HDLT_RELSYSDIR) + + +release_docs_spec: + + +# ---------------------------------------------------- +# Include dependencies +# ---------------------------------------------------- + +megaco_codec_transform.$(EMULATOR): megaco_codec_transform.erl + +megaco_codec_meas.$(EMULATOR): megaco_codec_meas.erl + +megaco_codec_mstone1.$(EMULATOR): megaco_codec_mstone1.erl + +megaco_codec_mstone2.$(EMULATOR): megaco_codec_mstone2.erl + +megaco_codec_mstone_lib.$(EMULATOR): megaco_codec_mstone_lib.erl + diff --git a/lib/inets/examples/httpd_load_test/hdlt.config.skel b/lib/inets/examples/httpd_load_test/hdlt.config.skel new file mode 100644 index 0000000000..640867ebac --- /dev/null +++ b/lib/inets/examples/httpd_load_test/hdlt.config.skel @@ -0,0 +1,20 @@ +%% Debug: silence | info | log | debug +{debug, [{ctrl, info}, {proxy, silence}, {slave, silence}, {client, silence}]}. +{server, {"/usr/local/bin", "fooserver"}}. +%% {port, 8888}. % integer() > 0 +{server_dir, "/tmp/hdlt"}. % Absolute path +{work_dir, "/tmp/hdlt"}. % Absolute path +{clients, + [ + {"/opt/local/bin", "foo"}, + {"/usr/local/bin", "bar"} + ] +}. +%% {send_rate, 80}. % Max number of outstanding requests, integer() > 0 +%% {test_time, 120}. % Number of seconds, +%% {max_nof_schedulers, 8}. % integer() >= 0 +%% {work_simulator, 10000}. % integer() > 0 +%% {data_size, {100, 500, 2}}. % {integer() > 0, integer() > 0, integer() > 0} +%% {socket_type, ip_comm}. % ip_comm | ssl | essl | ossl +%% {server_cert_file, "hdlt_ssl_server_cert.pem"}. +%% {client_cert_file, "hdlt_ssl_client_cert.pem"}.
\ No newline at end of file diff --git a/lib/inets/examples/httpd_load_test/hdlt.erl b/lib/inets/examples/httpd_load_test/hdlt.erl new file mode 100644 index 0000000000..18d8c34ccf --- /dev/null +++ b/lib/inets/examples/httpd_load_test/hdlt.erl @@ -0,0 +1,74 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%% +%%---------------------------------------------------------------------- +%% Purpose: Main API module for the httpd load test utility +%%---------------------------------------------------------------------- + +-module(hdlt). + + +%%----------------------------------------------------------------- +%% Public interface +%%----------------------------------------------------------------- + +-export([start/0, start/1, stop/0, help/0]). + + +%%----------------------------------------------------------------- +%% Start the HDLT utility +%%----------------------------------------------------------------- + +start() -> + ConfigFile = "hdlt.config", + case file:consult(ConfigFile) of + {ok, Config} when is_list(Config) -> + start(Config); + Error -> + Error + end. + +start(Config) -> + Flag = process_flag(trap_exit, true), + Result = + case hdlt_ctrl:start(Config) of + {ok, Pid} -> + receive + {'EXIT', Pid, normal} -> + ok; + {'EXIT', Pid, Reason} -> + io:format("HDLT failed: " + "~n ~p" + "~n", [Reason]), + {error, Reason} + end; + Error -> + Error + end, + process_flag(trap_exit, Flag), + Result. + + + +stop() -> + hdlt_ctrl:stop(). + + +help() -> + hdlt_ctrl:help(). diff --git a/lib/inets/examples/httpd_load_test/hdlt.sh.skel b/lib/inets/examples/httpd_load_test/hdlt.sh.skel new file mode 100644 index 0000000000..a250bad9c5 --- /dev/null +++ b/lib/inets/examples/httpd_load_test/hdlt.sh.skel @@ -0,0 +1,44 @@ +#!/bin/sh + +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 2010. All Rights Reserved. +# +# The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# %CopyrightEnd% + +# Skeleton for a script intended to run the hdlt(N) +# performance test. +# +# This test can be used for several things depending on the +# configuration: SMP or SocketType performance tests +# + +ERL_HOME=<path to otp top dir> +INETS_HOME=$ERL_HOME/lib/erlang/lib/<inets dir> +HDLT_HOME=$INETS_HOME/examples/httpd_load_test +PATH=$ERL_HOME/bin:$PATH + +HDLT="-s hdlt start" +STOP="-s init stop" + +ERL="erl \ + -noshell \ + -pa $HDLT_HOME \ + $HDLT \ + $STOP" + +echo $ERL +$ERL | tee hdlt.log + diff --git a/lib/inets/examples/httpd_load_test/hdlt_client.erl b/lib/inets/examples/httpd_load_test/hdlt_client.erl new file mode 100644 index 0000000000..d65ac5a885 --- /dev/null +++ b/lib/inets/examples/httpd_load_test/hdlt_client.erl @@ -0,0 +1,370 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%% +%%---------------------------------------------------------------------- +%% Purpose: The HDLT client module. +%% This is the traffic generator +%%---------------------------------------------------------------------- + +-module(hdlt_client). + +-export([ + start/1, + stop/0, + start_inets/0, + start_service/1, + release/0, + node_info/0 + ]). + +-export([ + proxy/1 + ]). + +-include("hdlt_logger.hrl"). + +-define(CTRL, hdlt_ctrl). +-define(PROXY, hdlt_proxy). + +-record(state, + { + mode = initial, + send_rate, + time, + stop_time, + url, + nof_reqs = 0, + nof_reps = 0, + last_req, + sizes, + socket_type, + cert_file + }). + + + +start(Debug) -> + proc_lib:start_link(?MODULE, proxy, [Debug]). + +stop() -> + (catch erlang:send(?PROXY, stop)), + ok. + +start_inets() -> + ?PROXY ! start_inets. + +start_service(Args) -> + ?PROXY ! {start_client, Args, self()}, + receive + client_started -> + %% ?LOG("client service started"), + ok + end. + +release() -> + ?PROXY ! release. + +node_info() -> + ?PROXY ! {node_info, self()}, + receive + {node_info, NodeInfo} -> + NodeInfo + end. + + +%% --------------------------------------------------------------------- +%% +%% The proxy process +%% + +proxy(Debug) -> + process_flag(trap_exit, true), + erlang:register(?PROXY, self()), + SName = lists:flatten( + io_lib:format("HDLT PROXY[~p,~p]", [self(), node()])), + ?SET_NAME(SName), + ?SET_LEVEL(Debug), + ?LOG("starting", []), + Ref = await_for_controller(10), + CtrlNode = node(Ref), + erlang:monitor_node(CtrlNode, true), + proc_lib:init_ack({ok, self()}), + ?DEBUG("started", []), + proxy_loop(Ref, CtrlNode, undefined). + +await_for_controller(N) when N > 0 -> + case global:whereis_name(hdlt_ctrl) of + Pid when is_pid(Pid) -> + erlang:monitor(process, Pid); + _ -> + timer:sleep(1000), + await_for_controller(N-1) + end; +await_for_controller(_) -> + proc_lib:init_ack({error, controller_not_found, nodes()}), + timer:sleep(500), + init:stop(). + + +proxy_loop(Ref, CtrlNode, Client) -> + ?DEBUG("await command", []), + receive + stop -> + ?LOG("stop", []), + timer:sleep(1000), + halt(); + + start_inets -> + ?LOG("start the inets service framework", []), + %% inets:enable_trace(max, "/tmp/inets-httpc-trace.log", all), + case (catch inets:start()) of + ok -> + ?LOG("framework started", []), + proxy_loop(Ref, CtrlNode, Client); + Error -> + ?LOG("failed starting inets service framework: " + "~n Error: ~p", [Error]), + timer:sleep(1000), + halt() + end; + + {start_client, Args, From} -> + ?LOG("start client with" + "~n Args: ~p", [Args]), + Client2 = spawn_link(fun() -> client(Args) end), + From ! client_started, + proxy_loop(Ref, CtrlNode, Client2); + + release -> + ?LOG("release", []), + Client ! go, + proxy_loop(Ref, CtrlNode, Client); + + {node_info, Pid} -> + ?LOG("received requets for node info", []), + NodeInfo = get_node_info(), + Pid ! {node_info, NodeInfo}, + proxy_loop(Ref, CtrlNode, Client); + + {'EXIT', Client, normal} -> + ?LOG("received normal exit message from client (~p)", + [Client]), + exit(normal); + + {'EXIT', Client, Reason} -> + ?INFO("received exit message from client (~p)" + "~n Reason: ~p", [Client, Reason]), + %% Unexpected client termination, inform the controller and die + global:send(hdlt_ctrl, {client_exit, Client, node(), Reason}), + exit({client_exit, Reason}); + + {nodedown, CtrlNode} -> + ?LOG("received nodedown for controller node - terminate", []), + halt(); + + {'DOWN', Ref, process, _, _} -> + ?INFO("received DOWN message for controller - terminate", []), + %% The controller has terminated, dont care why, time to die + halt() + + end. + + + +%% --------------------------------------------------------------------- +%% +%% The client process +%% + +client([SocketType, CertFile, URLBase, Sizes, Time, SendRate, Debug]) -> + SName = lists:flatten( + io_lib:format("HDLT CLIENT[~p,~p]", [self(), node()])), + ?SET_NAME(SName), + ?SET_LEVEL(Debug), + ?LOG("starting with" + "~n SocketType: ~p" + "~n Time: ~p" + "~n SendRate: ~p", [SocketType, Time, SendRate]), + httpc:set_options([{max_pipeline_length, 0}]), + if + (SocketType =:= ssl) orelse + (SocketType =:= ossl) orelse + (SocketType =:= essl) -> + %% Ensure crypto and ssl started: + crypto:start(), + ssl:start(); + true -> + ok + end, + State = #state{mode = idle, + url = URLBase, + time = Time, + send_rate = SendRate, + sizes = Sizes, + socket_type = SocketType, + cert_file = CertFile}, + ?DEBUG("started", []), + client_loop(State). + +%% The point is to first start all client nodes and then this +%% process. Then, when they are all started, the go-ahead, go, +%% message is sent to let them lose at the same time. +client_loop(#state{mode = idle, + time = Time, + send_rate = SendRate} = State) -> + ?DEBUG("[idle] awaiting the go command", []), + receive + go -> + ?LOG("[idle] received go", []), + erlang:send_after(Time, self(), stop), + NewState = send_requests(State, SendRate), + client_loop(NewState#state{mode = generating, + nof_reqs = SendRate}) + end; + +%% In this mode the client is generating traffic. +%% It will continue to do so until the stop message +%% is received. +client_loop(#state{mode = generating} = State) -> + receive + stop -> + ?LOG("[generating] received stop", []), + StopTime = timestamp(), + req_reply(State), + client_loop(State#state{mode = stopping, stop_time = StopTime}); + + {http, {_, {{_, 200, _}, _, _}}} -> + %% ?DEBUG("[generating] received reply - send another request", []), + NewState = send_requests(State, 1), + client_loop(NewState#state{nof_reps = NewState#state.nof_reps + 1, + nof_reqs = NewState#state.nof_reqs + 1}); + + {http, {ReqId, {error, Reason}}} -> + ?INFO("[generating] request ~p failed: " + "~n Reason: ~p" + "~n NofReqs: ~p" + "~n NofReps: ~p", + [ReqId, Reason, State#state.nof_reqs, State#state.nof_reps]), + exit({Reason, generating, State#state.nof_reqs, State#state.nof_reps}); + + Else -> + ?LOG("[generating] received unexpected message: " + "~n~p", [Else]), + unexpected_data(Else), + client_loop(State) + end; + +%% The client no longer issues any new requests, instead it +%% waits for replies for all the oustanding requests to +%% arrive. +client_loop(#state{mode = stopping, + time = Time, + last_req = LastReqId} = State) -> + receive + {http, {LastReqId, {{_, 200, _}, _, _}}} -> + ?DEBUG("[stopping] received reply for last request (~p)", [LastReqId]), + time_to_complete(State), + ok; + + {http, {ReqId, {{_, 200, _}, _, _}}} -> + ?DEBUG("[stopping] received reply ~p", [ReqId]), + client_loop(State); + + {http, {ReqId, {error, Reason}}} -> + ?INFO("[stopping] request ~p failed: " + "~n Reason: ~p" + "~n NofReqs: ~p" + "~n NofReps: ~p", + [ReqId, Reason, State#state.nof_reqs, State#state.nof_reps]), + exit({Reason, stopping, State#state.nof_reqs, State#state.nof_reps}); + + Else -> + ?LOG("[stopping] received unexpected message: " + "~n~p", [Else]), + unexpected_data(Else), + client_loop(State) + + after Time -> + ?INFO("timeout when" + "~n Number of requests: ~p" + "~n Number of replies: ~p", + [State#state.nof_reqs, State#state.nof_reps]), + exit({timeout, State#state.nof_reqs, State#state.nof_reps}) + end. + +req_reply(#state{nof_reqs = NofReqs, nof_reps = NofReps}) -> + load_data({req_reply, node(), NofReqs, NofReps}). + +time_to_complete(#state{stop_time = StopTime}) -> + StoppedTime = os:timestamp(), + load_data({time_to_complete, node(), StopTime, StoppedTime}). + +load_data(Data) -> + global:send(?CTRL, {load_data, Data}). + +unexpected_data(Else) -> + global:send(?CTRL, {unexpected_data, Else}). + + +send_requests(#state{sizes = Sizes} = State, N) -> + send_requests(State, N, Sizes). + +send_requests(State, 0, Sizes) -> + State#state{sizes = Sizes}; +send_requests(#state{socket_type = SocketType, + cert_file = CertFile} = State, N, [Sz | Sizes]) -> + URL = lists:flatten(io_lib:format("~s~w", [State#state.url, Sz])), + Method = get, + Request = {URL, []}, + HTTPOptions = + case SocketType of + ip_comm -> + []; + _ -> + SslOpts = [{verify, 0}, + {certfile, CertFile}, + {keyfile, CertFile}], + case SocketType of + ssl -> + [{ssl, SslOpts}]; + ossl -> + [{ssl, {ossl, SslOpts}}]; + essl -> + [{ssl, {essl, SslOpts}}] + end + end, + Options = [{sync, false}], + {ok, Ref} = httpc:request(Method, Request, HTTPOptions, Options), + send_requests(State#state{last_req = Ref}, N-1, lists:append(Sizes, [Sz])). + + +timestamp() -> + os:timestamp(). + + +get_node_info() -> + [{cpu_topology, erlang:system_info(cpu_topology)}, + {heap_type, erlang:system_info(heap_type)}, + {nof_schedulers, erlang:system_info(schedulers)}, + {otp_release, erlang:system_info(otp_release)}, + {version, erlang:system_info(version)}, + {system_version, erlang:system_info(system_version)}, + {system_architecture, erlang:system_info(system_architecture)}]. + + diff --git a/lib/inets/examples/httpd_load_test/hdlt_ctrl.erl b/lib/inets/examples/httpd_load_test/hdlt_ctrl.erl new file mode 100644 index 0000000000..950d2632f7 --- /dev/null +++ b/lib/inets/examples/httpd_load_test/hdlt_ctrl.erl @@ -0,0 +1,1530 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%% +%%---------------------------------------------------------------------- +%% Purpose: The httpd load test (hdlt) controller/collector module, +%% This module contains all the code of the httpd load test +%% controller/collector. It sets up the test, starts all +%% server and client nodes and applications and finally +%% collects test data. +%%---------------------------------------------------------------------- + +-module(hdlt_ctrl). + +-export([start/1, stop/0, help/0]). + +-export([init/1, proxy/7]). + +-include_lib("kernel/include/file.hrl"). +-include("hdlt_logger.hrl"). + +-define(DEFAULT_SENDRATE, 89). +-define(DEFAULT_TEST_TIME, 120). % 2 minutes +-define(DEFAULT_PORT, 8889). +-define(TIMEOUT, 60000). +-define(DEFAULT_MAX_NOF_SCHEDULERS, 8). +-define(DEFAULT_SERVER_DIR, "/tmp/hdlt"). +-define(DEFAULT_WORK_DIR, "/tmp/hdlt"). +-define(SSH_PORT, 22). +-define(DEFAULT_SOCKET_TYPE, ip_comm). +-define(DEFAULT_SERVER_CERT, "hdlt_ssl_server_cert.pem"). +-define(DEFAULT_CLIENT_CERT, "hdlt_ssl_client_cert.pem"). +-define(SSH_CONNECT_TIMEOUT, 5000). +-define(NODE_START_TIMEOUT, 5000). +-define(LOCAL_PROXY_START_TIMEOUT, ?NODE_START_TIMEOUT * 4). +-define(DEFAULT_DEBUGS, + [{ctrl, info}, {slave, silence}, {proxy, silence}, {client, silence}]). +-define(DEFAULT_WORK_SIM, 10000). +-define(DEFAULT_DATA_SIZE_START, 500). +-define(DEFAULT_DATA_SIZE_END, 1500). +-define(DEFAULT_DATA_SIZE_INCR, 1). +-define(DEFAULT_DATA_SIZE, {?DEFAULT_DATA_SIZE_START, + ?DEFAULT_DATA_SIZE_END, + ?DEFAULT_DATA_SIZE_INCR}). + + +%% hdlt = httpd load test + +-define(COLLECTOR, hdlt_ctrl). +-define(RESULTS_TAB, hdlt_results). + +-define(CLIENT_MOD, hdlt_client). +-define(CLIENT_NODE_NAME, ?CLIENT_MOD). + +-define(SERVER_MOD, hdlt_server). +-define(SERVER_NODE_NAME, ?SERVER_MOD). + +-define(LOGGER, hdlt_logger). + + +-record(state, + { + url, + test_time, + send_rate, + http_server, + http_port, + results = ?RESULTS_TAB, + nodes, + server_root, + doc_root, + server_dir, + work_dir, + server_conn, + client_conns = [], + client_mod = ?CLIENT_MOD, + clients, + nof_schedulers = 0, + max_nof_schedulers, + socket_type, + server_cert_file, + client_cert_file, + debugs, + client_sz_from, + client_sz_to, + client_sz_incr + } + ). + +-record(proxy, + { + mode, + mod, + connection, + channel, + host, + cmd, + node_name, + node, + ref, + erl_path, + paths, + args + }). + +-record(connection, + { + proxy, + node, + node_name, + host + }). + + +-record(client, {host, path, version}). +-record(server, {host, path, version}). + + +start(Config) when is_list(Config) -> + proc_lib:start_link(?MODULE, init, [Config]). + +stop() -> + global:send(?COLLECTOR, stop). + +init(Config) -> + %% io:format("Config: ~n~p~n", [Config]), + case (catch do_init(Config)) of + {ok, State} -> + proc_lib:init_ack({ok, self()}), + loop(State); + {error, _Reason} = Error -> + proc_lib:init_ack(Error), + ok; + {'EXIT', Reason} -> + proc_lib:init_ack({error, Reason}), + ok + end. + +do_init(Config) -> + %% Do not trap exit, but register ourself + global:register_name(?COLLECTOR, self()), + + State = #state{}, + ets:new(State#state.results, [bag, named_table]), + + hdlt_logger:start(), + global:sync(), + + %% Maybe enable debug + Debugs = get_debugs(Config), + ?SET_NAME("HDLT CTRL"), + set_debug_level(Debugs), + + ?DEBUG("network info: " + "~n Global names: ~p" + "~n Nodes: ~p", [global:registered_names(), nodes()]), + + %% Read config + ?LOG("read config", []), + SendRate = get_send_rate(Config), + Clients = get_clients(Config), + TestTime = get_test_time(Config), + Server = get_server(Config), + Port = get_port(Config), + ServerDir = get_server_dir(Config), + WorkingDir = get_work_dir(Config), + MaxNofSchedulers = get_max_nof_schedulers(Config), + SocketType = get_socket_type(Config), + ServerCertFile = get_server_cert_file(Config), + ClientCertFile = get_client_cert_file(Config), + WorkSim = get_work_sim(Config), + {From, To, Incr} = get_data_size(Config), + + URL = url(Server, Port, SocketType, WorkSim), + ServerRoot = filename:join(ServerDir, "server_root"), + DocRoot = ServerRoot, %% Not really used in this test + + ?DEBUG("randomize setup", []), + randomized_sizes_init(), + + %% Start used applications + ?DEBUG("ensure crypto started", []), + crypto:start(), + ?DEBUG("ensure ssh started", []), + ssh:start(), + + State2 = State#state{server_root = ServerRoot, + doc_root = DocRoot, + server_dir = ServerDir, + work_dir = WorkingDir, + max_nof_schedulers = MaxNofSchedulers, + socket_type = SocketType, + server_cert_file = ServerCertFile, + client_cert_file = ClientCertFile, + http_server = Server, + http_port = Port, + url = URL, + test_time = TestTime, + send_rate = SendRate, + clients = Clients, + debugs = Debugs, + client_sz_from = From, + client_sz_to = To, + client_sz_incr = Incr}, + + ?LOG("prepare server host", []), + prepare_server_host(State2), + + ?LOG("prepare client hosts", []), + State3 = prepare_client_hosts(State2), + + ?LOG("basic init done", []), + {ok, State3}. + + +loop(#state{nof_schedulers = N, max_nof_schedulers = M} = State) when N > M -> + + ?INFO("Starting to analyse data", []), + + AnalysedTab = analyse_data(State), + + Files = save_results_to_file(AnalysedTab, State), + io:format("~n******************************************************" + "~n~nResult(s) saved to: ~n~p~n", [Files]), + clean_up(State); + +loop(#state{url = URL, + test_time = TestTime, + send_rate = SendRate, + nof_schedulers = NofSchedulers} = State) -> + + {StartH, StartM, StartS} = erlang:time(), + + ?INFO("Performing test with ~p smp-scheduler(s): ~n" + " It will take a minimum of: ~p seconds. ~n" + " Start time: ~.2.0w:~.2.0w:~.2.0w", + [NofSchedulers, round(TestTime/1000), StartH, StartM, StartS]), + + %% Start the server node + %% (The local proxy, the node, the remote proxy, and the inets framework) + State1 = start_server_node(State), + ?DEBUG("nodes after server start: ~p", [nodes() -- [node()]]), + + %% Start the client node(s) + %% (The local proxy, the node, the remote proxy, and the inets framework) + ?LOG("start client node(s)", []), + State2 = start_client_nodes(State1), + ?DEBUG("nodes after client(s) start: ~p", [nodes() -- [node()]]), + + ?LOG("start server", []), + start_server(State2), + + ?LOG("start clients", []), + start_clients(State2, URL, TestTime, SendRate), + + ?LOG("release clients", []), + release_clients(State2), + + ?LOG("collect data", []), + collect_data(State2), + + ?LOG("stop all nodes", []), + State3 = stop_nodes(State2), + + ?INFO("Test with ~p smp-scheduler(s) complete" + "~n~n" + "****************************************************************" + "~n", + [NofSchedulers]), + loop(State3#state{nof_schedulers = NofSchedulers + 1}). + + +prepare_server_host(#state{server_root = ServerRoot, + http_server = #server{host = Host}, + socket_type = SocketType, + server_cert_file = CertFile}) -> + ?INFO("prepare server host ~s", [Host]), + Opts = [{user_interaction, false}, + {silently_accept_hosts, true}, + {timeout, 2*?SSH_CONNECT_TIMEOUT}, + {connect_timeout, ?SSH_CONNECT_TIMEOUT}], + case ssh_sftp:start_channel(Host, Opts) of + {ok, Sftp, ConnectionRef} -> + ?DEBUG("sftp connection established - now transer server content", + []), + create_server_content(Sftp, ServerRoot, SocketType, CertFile), + ?DEBUG("server content transfered - now close ssh connection ", + []), + ssh:close(ConnectionRef), + ?DEBUG("server preparation complete ", []), + ok; + Error -> + ?INFO("FAILED creating sftp channel to server host ~s: " + "~n ~p", [Host, Error]), + exit({failed_establishing_sftp_connection, Error}) + end. + +create_server_content(Sftp, ServerRoot, SocketType, CertFile) -> + %% Create server root + ?DEBUG("ensure existence of ~p", [ServerRoot]), + ensure_remote_dir_exist(Sftp, ServerRoot), + + %% Create the server ebin dir (for the starter module) + EBIN = filename:join(ServerRoot, "ebin"), + ?DEBUG("make ebin dir: ~p", [EBIN]), + maybe_create_remote_dir(Sftp, EBIN), + + %% Create the server ebin dir (for the starter module) + LOG = filename:join(ServerRoot, "log"), + ?DEBUG("make log dir: ~p", [LOG]), + maybe_create_remote_dir(Sftp, LOG), + + LocalServerMod = local_server_module(), + ?DEBUG("copy server stub/proxy module ~s", [LocalServerMod]), + RemoteServerMod = remote_server_module(EBIN), + {ok, ServerModBin} = file:read_file(LocalServerMod), + ok = ssh_sftp:write_file(Sftp, RemoteServerMod, ServerModBin), + + LocalSlaveMod = local_slave_module(), + ?DEBUG("copy slave module ~s", [LocalSlaveMod]), + RemoteSlaveMod = remote_slave_module(EBIN), + {ok, SlaveModBin} = file:read_file(LocalSlaveMod), + ok = ssh_sftp:write_file(Sftp, RemoteSlaveMod, SlaveModBin), + + LocalLoggerMod = local_logger_module(), + ?DEBUG("copy logger module ~s", [LocalLoggerMod]), + RemoteLoggerMod = remote_logger_module(EBIN), + {ok, LoggerModBin} = file:read_file(LocalLoggerMod), + ok = ssh_sftp:write_file(Sftp, RemoteLoggerMod, LoggerModBin), + + %% Create the inets server data dir + CGI = filename:join(ServerRoot, "cgi-bin"), + ?DEBUG("make cgi dir: ~p", [CGI]), + maybe_create_remote_dir(Sftp, CGI), + + LocalRandomMod = local_random_html_module(), + ?DEBUG("copy random-html module ~s", [LocalRandomMod]), + RemoteRandomMod = remote_random_html_module(EBIN), + {ok, RandomModBin} = file:read_file(LocalRandomMod), + ok = ssh_sftp:write_file(Sftp, RemoteRandomMod, RandomModBin), + + case SocketType of + ip_comm -> + ok; + _ -> + SSLDir = filename:join(ServerRoot, "ssl"), + ?DEBUG("make conf dir: ~p", [SSLDir]), + maybe_create_remote_dir(Sftp, SSLDir), + ?DEBUG("copy ssl cert file ~s", [CertFile]), + {ok, CertBin} = file:read_file(CertFile), + RemoteCertFile = filename:join(SSLDir, + filename:basename(CertFile)), + ok = ssh_sftp:write_file(Sftp, RemoteCertFile, CertBin), + ok + end, + + ?DEBUG("done", []), + ok. + +remote_server_module(Path) -> + Mod = server_module(), + filename:join(Path, Mod). + +local_server_module() -> + Mod = server_module(), + case code:where_is_file(Mod) of + Path when is_list(Path) -> + Path; + _ -> + exit({server_module_not_found, Mod}) + end. + +server_module() -> + module(?SERVER_MOD). + + +prepare_client_hosts(#state{work_dir = WorkDir, + clients = Clients, + socket_type = SocketType, + client_cert_file = CertFile} = State) -> + Clients2 = + prepare_client_hosts(WorkDir, SocketType, CertFile, Clients, []), + State#state{clients = Clients2}. + +prepare_client_hosts(_WorkDir, _SocketType, _CertFile, [], Acc) -> + lists:reverse(Acc); +prepare_client_hosts(WorkDir, SocketType, CertFile, [Client|Clients], Acc) -> + case prepare_client_host(WorkDir, SocketType, CertFile, Client) of + ok -> + prepare_client_hosts(WorkDir, SocketType, CertFile, Clients, + [Client|Acc]); + _ -> + prepare_client_hosts(WorkDir, SocketType, CertFile, Clients, Acc) + end. + +prepare_client_host(WorkDir, SocketType, CertFile, #client{host = Host}) -> + ?INFO("prepare client host ~s", [Host]), + Opts = [{user_interaction, false}, + {silently_accept_hosts, true}, + {timeout, 2*?SSH_CONNECT_TIMEOUT}, + {connect_timeout, ?SSH_CONNECT_TIMEOUT}], + case ssh_sftp:start_channel(Host, Opts) of + {ok, Sftp, ConnectionRef} -> + ?DEBUG("sftp connection established - now transer client content", + []), + create_client_content(Sftp, WorkDir, SocketType, CertFile), + ?DEBUG("client content transered - now close ssh connection ", []), + ssh:close(ConnectionRef), + ?DEBUG("client preparation complete ", []), + ok; + Error -> + ?INFO("FAILED creating sftp channel to client host ~s: skipping" + "~n ~p", [Host, Error]), + Error + end. + +create_client_content(Sftp, WorkDir, SocketType, CertFile) -> + %% Create work dir + ?DEBUG("ensure existence of ~p", [WorkDir]), + ensure_remote_dir_exist(Sftp, WorkDir), + + %% Create the client ebin dir + EBIN = filename:join(WorkDir, "ebin"), + RemoteClientMod = remote_client_module(EBIN), + ?DEBUG("make ebin dir: ~p", [EBIN]), + maybe_create_remote_dir(Sftp, EBIN), + + LocalClientMod = local_client_module(), + ?DEBUG("copy client stub/proxy module ~s", [LocalClientMod]), + {ok, ClientModBin} = file:read_file(LocalClientMod), + ok = ssh_sftp:write_file(Sftp, RemoteClientMod, ClientModBin), + + LocalSlaveMod = local_slave_module(), + ?DEBUG("copy slave module ~s", [LocalSlaveMod]), + RemoteSlaveMod = remote_slave_module(EBIN), + {ok, SlaveModBin} = file:read_file(LocalSlaveMod), + ok = ssh_sftp:write_file(Sftp, RemoteSlaveMod, SlaveModBin), + + LocalLoggerMod = local_logger_module(), + ?DEBUG("copy logger module ~s", [LocalLoggerMod]), + RemoteLoggerMod = remote_logger_module(EBIN), + {ok, LoggerModBin} = file:read_file(LocalLoggerMod), + ok = ssh_sftp:write_file(Sftp, RemoteLoggerMod, LoggerModBin), + + case SocketType of + ip_comm -> + ok; + _ -> + %% We should really store the remote path somewhere as + %% we use it when starting the client service... + SSLDir = filename:join(WorkDir, "ssl"), + ?DEBUG("make ssl dir: ~p", [SSLDir]), + maybe_create_remote_dir(Sftp, SSLDir), + ?DEBUG("copy ssl cert file ~s", [CertFile]), + {ok, CertBin} = file:read_file(CertFile), + RemoteCertFile = filename:join(SSLDir, + filename:basename(CertFile)), + ok = ssh_sftp:write_file(Sftp, RemoteCertFile, CertBin), + ok + end, + + ?DEBUG("done", []), + ok. + +remote_client_module(Path) -> + Mod = client_module(), + filename:join(Path, Mod). + +local_client_module() -> + Mod = client_module(), + case code:where_is_file(Mod) of + Path when is_list(Path) -> + Path; + _ -> + exit({client_module_not_found, Mod}) + end. + +client_module() -> + module(?CLIENT_MOD). + + +remote_slave_module(Path) -> + Mod = slave_module(), + filename:join(Path, Mod). + +local_slave_module() -> + Mod = slave_module(), + case code:where_is_file(Mod) of + Path when is_list(Path) -> + Path; + _ -> + exit({slave_module_not_found, Mod}) + end. + +slave_module() -> + module(hdlt_slave). + + +remote_logger_module(Path) -> + Mod = logger_module(), + filename:join(Path, Mod). + +local_logger_module() -> + Mod = logger_module(), + case code:where_is_file(Mod) of + Path when is_list(Path) -> + Path; + _ -> + exit({logger_module_not_found, Mod}) + end. + +logger_module() -> + module(hdlt_logger). + + +remote_random_html_module(Path) -> + Mod = random_html_module(), + filename:join(Path, Mod). + +local_random_html_module() -> + Mod = random_html_module(), + case code:where_is_file(Mod) of + Path when is_list(Path) -> + Path; + _ -> + exit({random_module_not_found, Mod}) + end. + +random_html_module() -> + module(hdlt_random_html). + + +module(Mod) -> + Ext = string:to_lower(erlang:system_info(machine)), + lists:flatten(io_lib:format("~w.~s", [Mod, Ext])). + + +%% ----------------------------------------------------------------------- +%% - For every node created (server and client both) there is both +%% a local and remote proxy. +%% - The local proxy is running on the local (controller/collector) node. +%% - The remote proxy is running on the client or server node(s). +%% - The local (ctrl) proxy monitor the remote (server/client) proxy. +%% - The remote (server/client) proxy monitor the local (ctrl) proxy. +%% + +start_client_nodes(#state{clients = Clients, + work_dir = WorkDir, + debugs = Debugs} = State) -> + Connections = + [start_client_node(Client, WorkDir, Debugs) || Client <- Clients], + State#state{client_conns = Connections}. + +start_client_node(#client{path = ErlPath, host = Host}, WorkDir, Debugs) -> + ?INFO("start client on host ~p", [Host]), + EbinDir = filename:join(WorkDir, "ebin"), + start_client_node(Host, ErlPath, [EbinDir], Debugs). + +start_client_node(Host, ErlPath, Paths, Debugs) -> + start_node(Host, ?CLIENT_NODE_NAME, + ErlPath, Paths, [], ?CLIENT_MOD, Debugs). + + +start_server_node(#state{http_server = #server{path = ErlPath, host = Host}, + server_root = ServerRoot, + nof_schedulers = NofScheds, + debugs = Debugs} = State) -> + ?INFO("start server on host ~p", [Host]), + CgiBinDir = filename:join(ServerRoot, "cgi-bin"), + EbinDir = filename:join(ServerRoot, "ebin"), + Connection = + start_server_node(Host, ErlPath, [CgiBinDir, EbinDir], + Debugs, NofScheds), + State#state{server_conn = Connection}. + +start_server_node(Host, ErlPath, Paths, Debugs, NofScheds) -> + Args = + if + NofScheds =:= 0 -> + "-smp disable"; + true -> + lists:flatten(io_lib:format("-smp +S ~w", [NofScheds])) + end, + start_node(Host, ?SERVER_NODE_NAME, + ErlPath, Paths, Args, ?SERVER_MOD, Debugs). + + +%% ----------------------------------------------------------------------- +%% - For every node created (server and client both) there is both +%% a local and remote proxy. +%% - The local proxy is running on the local (controller/collector) node. +%% - The remote proxy is running on the client or server node(s). +%% - The local (ctrl) proxy monitor the remote (server/client) proxy. +%% - The remote (server/client) proxy monitor the local (ctrl) proxy. +%% + +start_node(Host, NodeName, ErlPath, Paths, Args, Module, Debugs) -> + %% Start the (local) proxy + ?DEBUG("start_node -> start local proxy and remote node", []), + ProxyDebug = proplists:get_value(proxy, Debugs, silence), + Proxy = proxy_start(Host, NodeName, ErlPath, Paths, Args, Module, + ProxyDebug), + + ?DEBUG("start_node -> local proxy started - now start node", []), + SlaveDebug = proplists:get_value(slave, Debugs, silence), + Node = proxy_start_node(Proxy, SlaveDebug), + + ?DEBUG("start_node -> sync global", []), + global:sync(), + + ?DEBUG("start_node -> start remote proxy", []), + proxy_start_remote(Proxy), + + ?DEBUG("start_node -> start (remote) inets framework", []), + proxy_start_inets(Proxy), + + ?DEBUG("start_node -> done", []), + #connection{proxy = Proxy, node = Node, node_name = NodeName, host = Host}. + + +proxy_start(Host, NodeName, ErlPath, Paths, Args, Module, Debug) -> + ?LOG("try starting local proxy for ~p@~s", [NodeName, Host]), + ProxyArgs = [Host, NodeName, ErlPath, Paths, Args, Module, Debug], + case proc_lib:start_link(?MODULE, proxy, + ProxyArgs, ?LOCAL_PROXY_START_TIMEOUT) of + {ok, Proxy} -> + Proxy; + Error -> + exit({failed_starting_proxy, Error}) + end. + +proxy_start_node(Proxy, Debug) -> + {ok, Node} = proxy_request(Proxy, {start_node, Debug}), + Node. + +proxy_start_remote(Proxy) -> + proxy_request(Proxy, start_remote_proxy). + +proxy_start_inets(Proxy) -> + proxy_request(Proxy, start_inets). + +proxy_start_service(Proxy, Args) -> + proxy_request(Proxy, {start_service, Args}). + +proxy_release(Proxy) -> + proxy_request(Proxy, release). + +proxy_stop(Proxy) -> + StopResult = proxy_request(Proxy, stop), + ?DEBUG("proxy stop result: ~p", [StopResult]), + StopResult. + +proxy_request(Proxy, Req) -> + Ref = make_ref(), + Proxy ! {proxy_request, Ref, self(), Req}, + receive + {proxy_reply, Ref, Proxy, Rep} -> + Rep + end. + +proxy_reply(From, Ref, Rep) -> + From ! {proxy_reply, Ref, self(), Rep}. + +proxy(Host, NodeName, ErlPath, Paths, Args, Module, Debug) -> + process_flag(trap_exit, true), + SName = lists:flatten( + io_lib:format("HDLT CTRL PROXY[~p,~s,~w]", + [self(), Host, NodeName])), + ?SET_NAME(SName), + ?SET_LEVEL(Debug), + ?LOG("starting with" + "~n Host: ~p" + "~n NodeName: ~p" + "~n ErlPath: ~p" + "~n Paths: ~p" + "~n Args: ~p" + "~n Module: ~p", [Host, NodeName, ErlPath, Paths, Args, Module]), + State = #proxy{mode = started, + mod = Module, + host = Host, + node_name = NodeName, + erl_path = ErlPath, + paths = Paths, + args = Args}, + proc_lib:init_ack({ok, self()}), + ?DEBUG("started", []), + proxy_loop(State). + + +proxy_loop(#proxy{mode = stopping}) -> + receive + {proxy_request, Ref, From, stop} -> + ?LOG("[stopping] received stop order", []), + proxy_reply(From, Ref, ok), + exit(normal); + + {'EXIT', Pid, Reason} -> + ?INFO("[stopping] received exit message from ~p: " + "~n Reason: ~p", [Pid, Reason]), + exit(Reason) + + end; + +proxy_loop(#proxy{mode = started, + host = Host, + node_name = NodeName, + erl_path = ErlPath, + paths = Paths, + args = Args} = State) -> + receive + {proxy_request, Ref, From, {start_node, Debug}} -> + ?LOG("[starting] received start_node order", []), + case hdlt_slave:start_link(Host, NodeName, + ErlPath, Paths, Args, + Debug) of + {ok, Node} -> + ?DEBUG("[starting] node ~p started - now monitor", [Node]), + erlang:monitor_node(Node, true), + State2 = State#proxy{mode = operational, + node = Node}, + proxy_reply(From, Ref, {ok, Node}), + proxy_loop(State2); + {error, Reason} -> + ?INFO("[starting] failed starting node: " + "~n Reason: ~p", [Reason]), + exit({failed_starting_node, {Host, NodeName, Reason}}) + end; + + {'EXIT', Pid, Reason} -> + ?INFO("[stopping] received exit message from ~p: " + "~n Reason: ~p", [Pid, Reason]), + exit(Reason) + + end; + +proxy_loop(#proxy{mode = operational, + mod = Mod, + node = Node} = State) -> + ?DEBUG("[operational] await command", []), + receive + {proxy_request, Ref, From, start_remote_proxy} -> + ?LOG("[operational] start remote proxy", []), + case rpc:call(Node, Mod, start, [?GET_LEVEL()]) of + {ok, Pid} -> + ?DEBUG("[operational] remote proxy started (~p) - " + "create monitor", [Pid]), + ProxyRef = erlang:monitor(process, Pid), + ?DEBUG("[operational] monitor: ~p", [Ref]), + proxy_reply(From, Ref, ok), + proxy_loop(State#proxy{ref = ProxyRef}); + Error -> + ?INFO("[operational] failed starting remote proxy" + "~n Error: ~p", [Error]), + ReplyReason = {failed_starting_remote_proxy, + {Node, Error}}, + Reply = {error, ReplyReason}, + proxy_reply(From, Ref, Reply), + exit({failed_starting_remote_proxy, {Node, Error}}) + end; + + {proxy_request, Ref, From, start_inets} -> + ?INFO("[operational] start inets framework", []), + rpc:cast(Node, Mod, start_inets, []), + proxy_reply(From, Ref, ok), + proxy_loop(State); + + {proxy_request, Ref, From, {start_service, Args}} -> + ?INFO("[operational] start service with" + "~n ~p", [Args]), + case rpc:call(Node, Mod, start_service, Args) of + ok -> + ?DEBUG("[operational] service started", []), + proxy_reply(From, Ref, ok), + proxy_loop(State); + Error -> + ?INFO("[operational] failed starting service: " + "~n Args. ~p" + "~n Error: ~p", [Args, Error]), + erlang:demonitor(State#proxy.ref, [flush]), + Reply = {error, {failed_starting_service, Node, Error}}, + proxy_reply(From, Ref, Reply), + exit({failed_starting_service, Node, Error}) + end; + + {proxy_request, Ref, From, release} -> + ?INFO("[operational] release", []), + rpc:call(Node, Mod, release, []), + proxy_reply(From, Ref, ok), + proxy_loop(State); + + {proxy_request, Ref, From, stop} -> + ?INFO("[operational] received stop order", []), + erlang:demonitor(State#proxy.ref, [flush]), + ?DEBUG("[operational] rpc cast stop order", []), + rpc:cast(Node, Mod, stop, []), + %% And wait for the node death to be reported + Reason = + receive + {nodedown, Node} when State#proxy.node =:= Node -> + ok + after 10000 -> + ?INFO("Node did not die within expected time frame", + []), + {node_death_timeout, Node} + end, + ?DEBUG("[operational] ack stop", []), + proxy_reply(From, Ref, Reason), + exit(normal); + + {nodedown, Node} when State#proxy.node =:= Node -> + ?INFO("[operational] received unexpected nodedoen message", []), + exit({node_died, Node}); + + {'DOWN', Ref, process, _, normal} when State#proxy.ref =:= Ref -> + ?INFO("[operational] remote proxy terminated normally", []), + proxy_loop(State#proxy{ref = undefined, + connection = undefined, + mode = stopping}); + + {'DOWN', Ref, process, _, noconnection} when State#proxy.ref =:= Ref -> + ?INFO("[operational] remote proxy terminated - no node", []), + proxy_loop(State#proxy{ref = undefined, + connection = undefined, + mode = stopping}); + + {'DOWN', Ref, process, _, Reason} when State#proxy.ref =:= Ref -> + ?INFO("[operational] remote proxy terminated: " + "~n Reason: ~p", [Reason]), + exit({remote_proxy_crash, Reason}); + + {'EXIT', Pid, Reason} -> + ?INFO("[operational] received unexpected exit message from ~p: " + "~n Reason: ~p", [Pid, Reason]), + proxy_loop(State) + + end. + + +stop_nodes(#state{server_conn = ServerConn, + client_conns = ClientConns} = State) -> + lists:foreach( + fun(#connection{proxy = Proxy, node_name = NodeName, host = Host}) -> + ?DEBUG("stop_erlang_nodes -> send stop order to local proxy ~p" + "~n for node ~p on ~s", [Proxy, NodeName, Host]), + proxy_stop(Proxy) + end, + ClientConns ++ [ServerConn]), + ?DEBUG("stop_erlang_nodes -> sleep some to give the nodes time to die", + []), + timer:sleep(1000), + ?DEBUG("stop_erlang_nodes -> and a final cleanup round", []), + lists:foreach(fun(Node) -> + ?INFO("try brutal stop node ~p", [Node]), + rpc:cast(Node, erlang, halt, []) + end, + nodes() -- [node()]), + ?DEBUG("stop_erlang_nodes -> done", []), + State#state{server_conn = undefined, client_conns = []}. + + +%% The nodes on which the HDLT clients run have been started previously +start_clients(#state{client_conns = Connections, + debugs = Debugs, + work_dir = WorkDir, + socket_type = SocketType, + client_cert_file = CertFile, + client_sz_from = From, + client_sz_to = To, + client_sz_incr = Incr}, + URL, TestTime, SendRate) -> + Debug = proplists:get_value(client, Debugs, silence), + StartClient = + fun(#connection{host = Host} = Connection) -> + ?DEBUG("start client on ~p", [Host]), + start_client(Connection, + WorkDir, SocketType, CertFile, + URL, From, To, Incr, + TestTime, SendRate, Debug); + (_) -> + ok + end, + lists:foreach(StartClient, Connections). + +start_client(#connection{proxy = Proxy}, + WorkDir, SocketType, LocalCertFile, + URL, From, To, Incr, + TestTime, SendRate, Debug) -> + SSLDir = filename:join(WorkDir, "ssl"), + CertFile = filename:join(SSLDir, filename:basename(LocalCertFile)), + Sizes = randomized_sizes(From, To, Incr), + Args = [SocketType, CertFile, URL, Sizes, TestTime, SendRate, Debug], + proxy_start_service(Proxy, [Args]). + +release_clients(#state{client_conns = Connections}) -> + ReleaseClient = + fun(#connection{proxy = Proxy, + host = Host}) -> + ?DEBUG("release client on ~p", [Host]), + proxy_release(Proxy); + (_) -> + ok + end, + lists:foreach(ReleaseClient, Connections). + + +start_server(#state{server_conn = #connection{proxy = Proxy}, + http_port = Port, + server_root = ServerRoot, + doc_root = DocRoot, + socket_type = SocketType, + server_cert_file = CertFile}) -> + + HttpdConfig = + httpd_config(Port, "hdlt", ServerRoot, DocRoot, SocketType, CertFile), + ?LOG("start the httpd inets service with config: " + "~n ~p", [HttpdConfig]), + proxy_start_service(Proxy, [HttpdConfig]), + ?DEBUG("start_server -> done", []), + ok. + + +httpd_config(Port, ServerName, ServerRoot, DocRoot, + SocketType, LocalCertFile) -> + LogDir = filename:join(ServerRoot, "log"), + ErrorLog = filename:join(LogDir, "error_log"), + TransferLog = filename:join(LogDir, "access_log"), + + SSL = + case SocketType of + ip_comm -> + []; + _ -> % ssl + SSLDir = filename:join(ServerRoot, "ssl"), + CertFile = + filename:join(SSLDir, filename:basename(LocalCertFile)), + [ + {ssl_certificate_file, CertFile}, + {ssl_certificate_key_file, CertFile}, + {ssl_verify_client, 0} + ] + end, + [{port, Port}, + {server_name, ServerName}, + {server_root, ServerRoot}, + {document_root, DocRoot}, + {error_log, ErrorLog}, + {error_log_format, pretty}, + {transfer_log, TransferLog}, + {socket_type, SocketType}, + {max_clients, 10000}, + {modules, [mod_alias, mod_auth, mod_esi, mod_actions, mod_cgi, + mod_dir, mod_get, mod_head, mod_log, mod_disk_log]}, + {script_alias, {"/cgi-bin", filename:join(ServerRoot, "cgi-bin")}}, + {erl_script_alias, {"/cgi-bin", [hdlt_random_html]}}, + {erl_script_timeout, 120000} | SSL]. + + +clean_up(#state{server_root = ServerRoot, + work_dir = WorkDir, + http_server = #server{host = Host}, + clients = Clients}) -> + ?DEBUG("begin server cleanup", []), + server_clean_up(ServerRoot, WorkDir, Host), + ?DEBUG("begin lient cleanup", []), + clients_clean_up(WorkDir, Clients), + ?DEBUG("cleanup done", []), + ok. + +server_clean_up(ServerRoot, WorkDir, Host) -> + ?DEBUG("server cleanup - create sftp channel", []), + {ok, Sftp, ConnectionRef} = + ssh_sftp:start_channel(Host, [{user_interaction, false}, + {silently_accept_hosts, true}]), + ?DEBUG("server cleanup - delete ~p dirs", [ServerRoot]), + del_dirs(Sftp, ServerRoot), + ?DEBUG("server cleanup - delete ~p dirs", [WorkDir]), + del_dirs(Sftp, WorkDir), + ?DEBUG("server cleanup - close sftp channel", []), + ssh:close(ConnectionRef). + +clients_clean_up(_WorkDir, []) -> + ok; +clients_clean_up(WorkDir, [Client|Clients]) -> + client_clean_up(WorkDir, Client), + clients_clean_up(WorkDir, Clients). + +client_clean_up(WorkDir, #client{host = Host}) -> + ?DEBUG("client cleanup - create sftp channel to ~p", [Host]), + {ok, Sftp, ConnectionRef} = + ssh_sftp:start_channel(Host, [{user_interaction, false}, + {silently_accept_hosts, true}]), + ?DEBUG("client cleanup - delete ~p dirs", [WorkDir]), + del_dirs(Sftp, WorkDir), + ?DEBUG("client cleanup - close sftp channel", []), + ssh:close(ConnectionRef). + + +del_dirs(Sftp, Dir) -> + case ssh_sftp:list_dir(Sftp, Dir) of + {ok, []} -> + ssh_sftp:del_dir(Sftp, Dir); + {ok, Files} -> + Files2 = [F || F <- Files, (F =/= "..") andalso (F =/= ".")], + lists:foreach(fun(File) when ((File =/= "..") andalso + (File =/= ".")) -> + FullPath = filename:join(Dir, File), + case ssh_sftp:read_file_info(Sftp, + FullPath) of + {ok, #file_info{type = directory}} -> + del_dirs(Sftp, FullPath), + ssh_sftp:del_dir(Sftp, FullPath); + {ok, _} -> + ssh_sftp:delete(Sftp, FullPath) + end + end, Files2); + _ -> + ok + end. + +collect_data(#state{clients = Clients} = State) -> + N = length(Clients), + collect_req_reply(N, State), + collect_time(N, State). + +collect_req_reply(0, _State) -> + ?DEBUG("all reply data collected", []), + ok; +collect_req_reply(N, #state{nof_schedulers = NofScheduler, + results = Db, + client_conns = Conns} = State) -> + ?DEBUG("await reply data from ~p client(s)", [N]), + receive + {load_data, + {req_reply, Client, NoRequests, NoReplys}} -> + ?DEBUG("received req_reply load-data from client ~p: " + "~n Number of requests: ~p" + "~n Number of replies: ~p", + [Client, NoRequests, NoReplys]), + ets:insert(Db, {{NofScheduler, Client}, + {req_reply, NoRequests, NoReplys}}); + stop -> + ?INFO("received stop", []), + exit(self(), stop); + + {client_exit, Client, Node, Reason} -> + ?INFO("Received unexpected client exit from ~p on node ~p " + "while collecting replies: " + "~n ~p", [Client, Node, Reason]), + case lists:keysearch(Node, #connection.node, Conns) of + {value, Conn} -> + ?LOG("Found problem connection: " + "~n ~p", [Conn]), + exit({unexpected_client_exit, Reason}); + false -> + collect_req_reply(N, State) + end + end, + collect_req_reply(N-1, State). + +collect_time(0, _State) -> + ?DEBUG("all time data collected", []), + ok; +collect_time(N, #state{nof_schedulers = NofScheduler, + results = Db, + client_conns = Conns} = State) -> + ?DEBUG("await time data from ~p clients", [N]), + receive + {load_data, + {time_to_complete, Client, StopTime, LastResponseTime}} -> + ?LOG("received time load-data from client ~p: " + "~n Time of stop: ~p" + "~n Time of last response: ~p", + [Client, StopTime, LastResponseTime]), + ets:insert(Db, {{NofScheduler, Client}, + {time, StopTime, LastResponseTime}}); + stop -> + ?INFO("received stop while collecting data, when N = ~p", [N]), + exit(self(), stop); + + {client_exit, Client, Node, Reason} -> + ?INFO("Received unexpected exit from client ~p on node ~p " + "while collecting time data: " + "~n ~p", [Client, Node, Reason]), + case lists:keysearch(Node, #connection.node, Conns) of + {value, Conn} -> + ?LOG("Found problem connection: " + "~n ~p", [Conn]), + exit({unexpected_client_exit, Reason}); + false -> + collect_req_reply(N, State) + end; + + Else -> %%% Something is wrong! + ?INFO("RECEIVED UNEXPECTED MESSAGE WHILE COLLECTING TIME DATA: " + "~n ~p", [Else]), + collect_time(N, State) + end, + collect_time(N-1, State). + +analyse_data(#state{results = Db, + max_nof_schedulers = MaxNofSchedulers, + test_time = MicroSec}) -> + Tab = ets:new(analysed_results, [set]), + lists:foreach(fun(NofSchedulers) -> + Result = analyse(NofSchedulers, Db, MicroSec), + ets:insert(Tab, Result) + end, [N || N <- lists:seq(0, MaxNofSchedulers)]), + Tab. + + +no_requests_replys(NoSchedulers, Tab) -> + NoRequests = + ets:select(Tab, [{{{NoSchedulers,'_'},{req_reply, '$1', '_'}}, + [],['$$']}]), + NoReplys = + ets:select(Tab, [{{{NoSchedulers, '_'}, {req_reply, '_', '$1'}}, + [], ['$$']}]), + + {lists:sum(lists:append(NoRequests)), + lists:sum(lists:append(NoReplys))}. + +max_time_to_final_response(NofSchedulers, Tab) -> + Candidates = + ets:select(Tab, [{{{NofSchedulers, '_'}, {time, '$1', '$2'}}, + [], ['$$']}]), + + NewCandidates = lists:map( + fun([StopTime, LastTime]) -> + round( + timer:now_diff(LastTime, StopTime) / 100000)/10 + end, Candidates), + + lists:max(NewCandidates). + + +analyse(NofSchedulers, Db, TestTime) -> + Sec = TestTime / 1000, + {NoRequests, NoReplys} = no_requests_replys(NofSchedulers, Db), + {NofSchedulers, round(NoReplys / Sec), NoRequests, + max_time_to_final_response(NofSchedulers, Db)}. + + +save_results_to_file(AnalysedTab, + #state{socket_type = SocketType, + http_server = #server{host = Server}, + max_nof_schedulers = MaxNofSchedulers}) -> + FileName = fun(Post) -> + File = + lists:flatten( + io_lib:format("~s_~w_~s", + [Server, SocketType, Post])), + filename:join("./", File) + end, + Reps = FileName("replys_per_sec.txt"), + Reqs = FileName("total_requests.txt"), + Decay = FileName("decay_time.txt"), + + [FdReps, FdReqs, FdDecay] = + lists:map(fun(File) -> + {ok, Fd} = file:open(File, [write]), + Fd + end, [Reps, Reqs, Decay]), + lists:foreach(fun(NofSchedulers) -> + save_result_to_file(NofSchedulers, + FdReps, FdReqs, + FdDecay, AnalysedTab) + end, [N || N <- lists:seq(0, MaxNofSchedulers)]), + [Reps, Reqs, Decay]. + +save_result_to_file(NofSchedulers, + FdReps, FdReqs, FdDecay, AnalysedTab) -> + + [{NofSchedulers, NofRepsPerSec, NofReqs, MaxFinalResponseTime}] = + ets:lookup(AnalysedTab, NofSchedulers), + + file:write(FdReps, io_lib:format("~p,~p~n", + [NofRepsPerSec, NofSchedulers])), + file:write(FdReqs, io_lib:format("~p,~p~n", + [NofReqs, NofSchedulers])), + file:write(FdDecay, io_lib:format("~p,~p~n", [MaxFinalResponseTime, + NofSchedulers])). + + +help() -> + io:format("hdlt:start(Options). Where options:~n " + " ~n~p~n~n hdlt:start([]). -> hdlt:start(~p)~n~n", + [[{send_rate, "integer()", + "Numer of outstanding requests that a client " + "should have during the test to create a load situation."}, + {clients, "[{path(), host()}]", "Paths to erlang and names of hosts to run clients on."}, + {test_time, "{hours(), mins(), sec()}", + "How long the test should be run."}, + {server, "{path(), host()}", "Path to erl and name of host to run the HTTP-server on."}, + {port, "port()", "The port that the HTTP-server should use."}, + {server_dir, "dir()", "The directory where the HTTP server " + " stores its contents and configuration."}, + {work_dir, "dir()", "Path on the computer, where the test " + "is run, to a directory where the results can be saved."}, + {max_no_schedulers, "integer()", + "Max number of schedulers to run."}, + {socket_type, "Httpd configuration option socket_type"}], + defaults()]). + + +defaults() -> + [{send_rate, ?DEFAULT_SENDRATE}, + %% {clients, []}, + {test_time, ?DEFAULT_TEST_TIME}, + %% {server, ?DEFAULT_SERVER}, + {port, ?DEFAULT_PORT}, + {server_dir, ?DEFAULT_SERVER_DIR}, + {work_dir, ?DEFAULT_WORK_DIR}, + {max_nof_schedulers, ?DEFAULT_MAX_NOF_SCHEDULERS}, + {socket_type, ?DEFAULT_SOCKET_TYPE}]. + + +get_debugs(Config) -> + ?DEBUG("get debugs", []), + Debugs = proplists:get_value(debug, Config, ?DEFAULT_DEBUGS), + verify_debugs(Debugs), + Debugs. + +verify_debugs([]) -> + ok; +verify_debugs([{Tag, Debug}|Debugs]) -> + verify_debug(Tag, Debug), + verify_debugs(Debugs). + +verify_debug(Tag, Debug) -> + case lists:member(Tag, [ctrl, proxy, slave, client]) of + true -> + ok; + false -> + exit({bad_debug_tag, Tag}) + end, + case lists:member(Debug, [silence, info, log, debug]) of + true -> + ok; + false -> + exit({bad_debug_level, Debug}) + end. + +get_send_rate(Config) -> + ?DEBUG("get send_rate", []), + case proplists:get_value(send_rate, Config, ?DEFAULT_SENDRATE) of + SendRate when is_integer(SendRate) andalso (SendRate > 0) -> + SendRate; + BadSendRate -> + exit({bad_sendrate, BadSendRate}) + end. + + +get_clients(Config) -> + ?DEBUG("get clients", []), + case proplists:get_value(clients, Config, undefined) of + undefined -> + missing_mandatory_config(clients); + Clients when is_list(Clients) andalso (length(Clients) > 0) -> + case [#client{path = Path, host = Host} || + {Path, Host} <- Clients] of + Clients2 when (length(Clients2) > 0) -> + Clients2; + _ -> + exit({bad_clients, Clients}) + end; + + BadClients -> + exit({bad_clients, BadClients}) + + end. + +get_server(Config) -> + ?DEBUG("get server", []), + case proplists:get_value(server, Config) of + {Path, Host} when is_list(Path) andalso is_list(Host) -> + #server{path = Path, host = Host}; + undefined -> + missing_mandatory_config(server) + end. + +get_server_dir(Config) -> + ?DEBUG("get server_dir", []), + get_dir(server_dir, Config, ?DEFAULT_SERVER_DIR). + +get_work_dir(Config) -> + ?DEBUG("get work_dir", []), + get_dir(work_dir, Config, ?DEFAULT_WORK_DIR). + +get_dir(Key, Config, Default) -> + Dir = proplists:get_value(Key, Config, Default), + ensure_absolute(Dir), + Dir. + +ensure_absolute(Path) -> + case filename:pathtype(Path) of + absolute -> + ok; + PathType -> + exit({bad_pathtype, Path, PathType}) + end. + +get_port(Config) -> + ?DEBUG("get port", []), + case proplists:get_value(port, Config, ?DEFAULT_PORT) of + Port when is_integer(Port) andalso (Port > 0) -> + Port; + BadPort -> + exit({bad_port, BadPort}) + end. + +get_socket_type(Config) -> + ?DEBUG("get socket_type", []), + case proplists:get_value(socket_type, Config, ?DEFAULT_SOCKET_TYPE) of + SocketType when ((SocketType =:= ip_comm) orelse + (SocketType =:= ssl) orelse + (SocketType =:= essl) orelse + (SocketType =:= ossl)) -> + SocketType; + BadSocketType -> + exit({bad_socket_type, BadSocketType}) + end. + +get_test_time(Config) -> + ?DEBUG("get test_time", []), + case proplists:get_value(test_time, Config, ?DEFAULT_TEST_TIME) of + Seconds when is_integer(Seconds) andalso (Seconds > 0) -> + timer:seconds(Seconds); + BadTestTime -> + exit({bad_test_time, BadTestTime}) + end. + +get_max_nof_schedulers(Config) -> + ?DEBUG("get max_nof_schedulers", []), + case proplists:get_value(max_nof_schedulers, + Config, + ?DEFAULT_MAX_NOF_SCHEDULERS) of + MaxNofScheds when (is_integer(MaxNofScheds) andalso + (MaxNofScheds >= 0)) -> + MaxNofScheds; + BadMaxNofScheds -> + exit({bad_max_nof_schedulers, BadMaxNofScheds}) + end. + + +get_server_cert_file(Config) -> + ?DEBUG("get server cert file", []), + get_cert_file(server_cert_file, ?DEFAULT_SERVER_CERT, Config). + +get_client_cert_file(Config) -> + ?DEBUG("get client cert file", []), + get_cert_file(client_cert_file, ?DEFAULT_CLIENT_CERT, Config). + +get_cert_file(Tag, DefaultCertFileName, Config) -> + LibDir = code:lib_dir(inets), + HdltDir = filename:join(LibDir, "examples/httpd_load_test"), + DefaultCertFile = filename:join(HdltDir, DefaultCertFileName), + case proplists:get_value(Tag, Config, DefaultCertFile) of + F when is_list(F) -> + case file:read_file_info(F) of + {ok, #file_info{type = regular}} -> + F; + {ok, #file_info{type = Type}} -> + exit({wrong_file_type, Tag, F, Type}); + {error, Reason} -> + exit({failed_readin_file_info, Tag, F, Reason}) + end; + BadFile -> + exit({bad_cert_file, Tag, BadFile}) + end. + + +get_work_sim(Config) -> + ?DEBUG("get work_sim", []), + case proplists:get_value(work_simulator, Config, ?DEFAULT_WORK_SIM) of + WS when is_integer(WS) andalso (WS > 0) -> + WS; + BadWS -> + exit({bad_work_simulator, BadWS}) + end. + + +get_data_size(Config) -> + ?DEBUG("get data_size", []), + case proplists:get_value(data_size, Config, ?DEFAULT_DATA_SIZE) of + {From, To, Incr} = DS when (is_integer(From) andalso + is_integer(To) andalso + is_integer(Incr) andalso + (To > From) andalso + (From > 0) andalso + (Incr > 0)) -> + DS; + {From, To} when (is_integer(From) andalso + is_integer(To) andalso + (To > From) andalso + (From > 0)) -> + {From, To, ?DEFAULT_DATA_SIZE_INCR}; + BadDS -> + exit({bad_data_size, BadDS}) + end. + + +url(#server{host = Host}, Port, SocketType, WorkSim) -> + Scheme = + case SocketType of + ip_comm -> + "http"; + _ -> %% SSL + "https" + end, + lists:flatten( + io_lib:format("~s://~s:~w/cgi-bin/hdlt_random_html:page?~w:", + [Scheme, Host, Port, WorkSim])). + + +missing_mandatory_config(Missing) -> + exit({missing_mandatory_config, Missing}). + + +ensure_remote_dir_exist(Sftp, Path0) -> + case filename:split(Path0) of + [Root, Dir | Rest] -> + %% We never accept creating the root directory, + %% or the next level, so these *must* exist: + Path = filename:join(Root, Dir), + case ssh_sftp:read_file_info(Sftp, Path) of + {ok, #file_info{type = directory}} -> + ensure_remote_dir_exist(Sftp, Path, Rest); + {ok, #file_info{type = Type}} -> + ?INFO("Not a dir: ~p (~p)", [Path, Type]), + exit({not_a_dir, Path, Type}); + {error, Reason} -> + ?INFO("Failed reading file info for ~p: ~p", + [Path, Reason]), + exit({failed_reading_file_info, Path, Reason}) + end; + BadSplit -> + ?INFO("Bad remote dir path: ~p -> ~p", [Path0, BadSplit]), + exit({bad_dir, Path0}) + end. + +ensure_remote_dir_exist(_Sftp, _Dir, []) -> + ok; +ensure_remote_dir_exist(Sftp, Path, [Dir|Rest]) -> + NewPath = filename:join(Path, Dir), + case ssh_sftp:read_file_info(Sftp, NewPath) of + {ok, #file_info{type = directory}} -> + ensure_remote_dir_exist(Sftp, NewPath, Rest); + {ok, #file_info{type = Type}} -> + %% Exist, but is not a dir + ?INFO("Not a dir: ~p (~p)", [NewPath, Type]), + exit({not_a_dir, NewPath, Type}); + {error, Reason} -> + %% This *could* be because the dir does not exist, + %% but it could also be some other error. + %% As usual, the error reason of the sftp is + %% a pease of crap, so we cannot use the + %% error reason. + %% The simplest way to test this is to simply + %% try to create the directory, since we should + %% ensure its existence anyway.. + case ssh_sftp:make_dir(Sftp, NewPath) of + ok -> + ensure_remote_dir_exist(Sftp, NewPath, Rest); + _ -> + ?INFO("Failed reading file info for ~p: ~p", + [Dir, Reason]), + exit({failed_reading_file_info, NewPath, Reason}) + end + end. + +maybe_create_remote_dir(Sftp, Dir) -> + case ssh_sftp:read_file_info(Sftp, Dir) of + {ok, #file_info{type = directory}} -> + ok; + {ok, #file_info{type = Type}} -> + %% Exist, but is not a dir + ?INFO("Not a dir: ~p (~p)", [Dir, Type]), + exit({not_a_dir, Dir, Type}); + {error, Reason} -> + %% Assume dir noes not exist... + case ssh_sftp:make_dir(Sftp, Dir) of + ok -> + ok; + _ -> + ?INFO("Failed reading file info for ~p: ~p", + [Dir, Reason]), + exit({failed_reading_file_info, Dir, Reason}) + end + end. + + +set_debug_level(Debugs) -> + Debug = proplists:get_value(ctrl, Debugs, silence), + ?SET_LEVEL(Debug). + + +%% Generates a list of numbers between A and B, such that +%% there is exact one number between A and B and then +%% randomizes that list. + +randomized_sizes_init() -> + {A, B, C} = os:timestamp(), + random:seed(A, B, C). + +randomized_sizes(From, To, Incr) -> + L = lists:seq(From, To, Incr), + Len = length(L), + randomized_sizes2(L, 0, Len-1). + +randomized_sizes2(L, N, Len) when N >= Len -> + L; +randomized_sizes2(L, N, Len) -> + SplitWhere = random:uniform(Len), + {A, B} = lists:split(SplitWhere, L), + randomized_sizes2(B ++ A, N+1, Len). diff --git a/lib/inets/examples/httpd_load_test/hdlt_logger.erl b/lib/inets/examples/httpd_load_test/hdlt_logger.erl new file mode 100644 index 0000000000..b0c7eab2d1 --- /dev/null +++ b/lib/inets/examples/httpd_load_test/hdlt_logger.erl @@ -0,0 +1,138 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%---------------------------------------------------------------------- +%% Purpose: This is a simple logger utility for the HDLT toolkit. +%% It assumesd that the debug level and the "name" of the +%% logging entity has been put in process environment +%% (using the set_level and set_name functions respectively). +%%---------------------------------------------------------------------- + +%% + +-module(hdlt_logger). + +-export([ + start/0, + set_level/1, get_level/0, set_name/1, + info/2, log/2, debug/2 + ]). + +-export([logger/1]). + +-define(LOGGER, ?MODULE). +-define(MSG, hdlt_logger_msg). +-define(LEVEL, hdlt_logger_level). +-define(NAME, hdlt_logger_name). +-define(INFO_STR, "INFO"). +-define(LOG_STR, "LOG "). +-define(DEBUG_STR, "DBG "). + + +start() -> + Self = self(), + proc_lib:start(?MODULE, logger, [Self]). + +set_name(Name) when is_list(Name) -> + put(?NAME, Name), + ok. + +get_level() -> + get(?LEVEL). + +set_level(Level) -> + case lists:member(Level, [silence, info, log, debug]) of + true -> + put(?LEVEL, Level), + ok; + false -> + erlang:error({bad_debug_level, Level}) + end. + + +info(F, A) -> +%% io:format("info -> " ++ F ++ "~n", A), + do_log(info, get(?LEVEL), F, A). + +log(F, A) -> +%% io:format("log -> " ++ F ++ "~n", A), + do_log(log, get(?LEVEL), F, A). + +debug(F, A) -> +%% io:format("debug -> " ++ F ++ "~n", A), + do_log(debug, get(?LEVEL), F, A). + + +logger(Parent) -> + global:register_name(?LOGGER, self()), + Ref = erlang:monitor(process, Parent), + proc_lib:init_ack(self()), + logger_loop(Ref). + +logger_loop(Ref) -> + receive + {?MSG, F, A} -> + io:format(F, A), + logger_loop(Ref); + {'DOWN', Ref, process, _Object, _Info} -> + %% start the stop timer + erlang:send_after(timer:seconds(5), self(), stop), + logger_loop(undefined); + stop -> + global:unregister_name(?LOGGER), + ok + end. + + +formated_timestamp() -> + {Date, Time} = erlang:localtime(), + {YYYY,MM,DD} = Date, + {Hour,Min,Sec} = Time, + FormatDate = + io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w", + [YYYY,MM,DD,Hour,Min,Sec]), + lists:flatten(FormatDate). + +do_log(_, silence, _, _) -> + ok; +do_log(info, info, F, A) -> + do_log(?INFO_STR, F, A); +do_log(info, log, F, A) -> + do_log(?INFO_STR, F, A); +do_log(log, log, F, A) -> + do_log(?LOG_STR, F, A); +do_log(info, debug, F, A) -> + do_log(?INFO_STR, F, A); +do_log(log, debug, F, A) -> + do_log(?LOG_STR, F, A); +do_log(debug, debug, F, A) -> + do_log(?DEBUG_STR, F, A); +do_log(_, _, _F, _A) -> + ok. + +do_log(SEV, F, A) -> + Name = + case get(?NAME) of + L when is_list(L) -> + L; + _ -> + "UNDEFINED" + end, + Msg = {?MSG, "~s ~s [~s] " ++ F ++ "~n", + [SEV, Name, formated_timestamp() | A]}, + (catch global:send(?LOGGER, Msg)). diff --git a/lib/inets/examples/httpd_load_test/hdlt_logger.hrl b/lib/inets/examples/httpd_load_test/hdlt_logger.hrl new file mode 100644 index 0000000000..aa94babc48 --- /dev/null +++ b/lib/inets/examples/httpd_load_test/hdlt_logger.hrl @@ -0,0 +1,33 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%% + +-ifndef(hdlt_logger_hrl). +-define(hdlt_logger_hrl, true). + +%% Various log macros +-define(SET_LEVEL(N), hdlt_logger:set_level(N)). +-define(GET_LEVEL(), hdlt_logger:get_level()). +-define(SET_NAME(N), hdlt_logger:set_name(N)). + +-define(INFO(F, A), hdlt_logger:info(F, A)). +-define(LOG(F, A), hdlt_logger:log(F, A)). +-define(DEBUG(F, A), hdlt_logger:debug(F, A)). + +-endif. % -ifdef(hdlt_logger_hrl). diff --git a/lib/inets/examples/httpd_load_test/hdlt_random_html.erl b/lib/inets/examples/httpd_load_test/hdlt_random_html.erl new file mode 100644 index 0000000000..e3a572c61f --- /dev/null +++ b/lib/inets/examples/httpd_load_test/hdlt_random_html.erl @@ -0,0 +1,59 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%% + +-module(hdlt_random_html). +-export([page/3]). + +page(SessionID, _Env, Input) -> +%% log("page(~p) -> deliver content-type when" +%% "~n SessionID: ~p" +%% "~n Env: ~p" +%% "~n Input: ~p", [self(), SessionID, Env, Input]), + [WorkSimStr, SzSimStr] = string:tokens(Input, [$:]), + WorkSim = list_to_integer(WorkSimStr), + SzSim = list_to_integer(SzSimStr), + mod_esi:deliver(SessionID, "Content-Type:text/html\r\n\r\n"), + mod_esi:deliver(SessionID, start("Random test page")), + mod_esi:deliver(SessionID, content(WorkSim, SzSim)), + mod_esi:deliver(SessionID, stop()), + ok. + +start(Title) -> + "<HTML> +<HEAD> +<TITLE>" ++ Title ++ "</TITLE> + </HEAD> +<BODY>\n". + +stop() -> + "</BODY> +</HTML> +". + +content(WorkSim, SzSim) -> + {A, B, C} = now(), + random:seed(A, B, C), + lists:sort([random:uniform(X) || X <- lists:seq(1, WorkSim)]), + lists:flatten(lists:duplicate(SzSim, "Dummy data ")). + +%% log(F, A) -> +%% hdlt_logger:set_name("HDLT RANDOM-HTML"), +%% hdlt_logger:set_level(debug), +%% hdlt_logger:log(F, A). diff --git a/lib/inets/examples/httpd_load_test/hdlt_server.erl b/lib/inets/examples/httpd_load_test/hdlt_server.erl new file mode 100644 index 0000000000..3e5a849d5b --- /dev/null +++ b/lib/inets/examples/httpd_load_test/hdlt_server.erl @@ -0,0 +1,163 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%% +%%---------------------------------------------------------------------- +%% Purpose: The HDLT server module. +%% This is just a stub, making future expansion easy. +%% All code in this module is executed in the local node! +%%---------------------------------------------------------------------- + +-module(hdlt_server). + +-export([start/1, stop/0, start_inets/0, start_service/1]). + +-export([proxy/1]). + +-include_lib("kernel/include/file.hrl"). +-include("hdlt_logger.hrl"). + + +-define(PROXY, hdlt_proxy). + + +%% This function is used to start the proxy process +%% This function is called *after* the nodes has been +%% "connected" with the controller/collector node. + +start(Debug) -> + proc_lib:start(?MODULE, proxy, [Debug]). + +stop() -> + ?PROXY ! stop. + +start_inets() -> + ?PROXY ! start_inets. + +start_service(Config) -> + ?PROXY ! {server_start, Config, self()}, + receive + {server_start_result, Result} -> + Result + after 15000 -> + {error, timeout} + end. + + +proxy(Debug) -> + process_flag(trap_exit, true), + erlang:register(?PROXY, self()), + ?SET_NAME("HDLT PROXY"), + ?SET_LEVEL(Debug), + ?LOG("starting", []), + Ref = await_for_controller(10), + CtrlNode = node(Ref), + erlang:monitor_node(CtrlNode, true), + proc_lib:init_ack({ok, self()}), + ?DEBUG("started", []), + proxy_loop(Ref, CtrlNode). + +await_for_controller(N) when N > 0 -> + case global:whereis_name(hdlt_ctrl) of + Pid when is_pid(Pid) -> + erlang:monitor(process, Pid); + _ -> + timer:sleep(1000), + await_for_controller(N-1) + end; +await_for_controller(_) -> + proc_lib:init_ack({error, controller_not_found, nodes()}), + timer:sleep(500), + halt(). + + +proxy_loop(Ref, CtrlNode) -> + ?DEBUG("await command", []), + receive + stop -> + ?LOG("received stop", []), + halt(); + + start_inets -> + ?LOG("start the inets service framework", []), + case (catch inets:start()) of + ok -> + ?LOG("framework started", []), + proxy_loop(Ref, CtrlNode); + Error -> + ?LOG("failed starting inets service framework: " + "~n Error: ~p", [Error]), + halt() + end; + + {server_start, Config, From} -> + ?LOG("start-server", []), + maybe_start_crypto_and_ssl(Config), + %% inets:enable_trace(max, "/tmp/inets-httpd-trace.log", httpd), + %% inets:enable_trace(max, "/tmp/inets-httpd-trace.log", all), + case (catch inets:start(httpd, Config)) of + {ok, _} -> + ?LOG("server started when" + "~n which(inets): ~p" + "~n RootDir: ~p" + "~n System info: ~p", [code:which(inets), + code:root_dir(), + get_node_info()]), + From ! {server_start_result, ok}, + proxy_loop(Ref, CtrlNode); + Error -> + ?INFO("server start failed" + "~n Error: ~p", [Error]), + From ! {server_start_result, Error}, + halt() + end; + + {nodedown, CtrlNode} -> + ?LOG("received nodedown for controller node - terminate", []), + halt(); + + {'DOWN', Ref, process, _, _} -> + ?LOG("received DOWN message for controller - terminate", []), + %% The controller has terminated, time to die + halt() + + end. + + +maybe_start_crypto_and_ssl(Config) -> + case lists:keysearch(socket_type, 1, Config) of + {value, {socket_type, SocketType}} when ((SocketType =:= ssl) orelse + (SocketType =:= ossl) orelse + (SocketType =:= essl)) -> + ?LOG("maybe start crypto and ssl", []), + (catch crypto:start()), + ssl:start(); + _ -> + ok + end. + + +get_node_info() -> + [{cpu_topology, erlang:system_info(cpu_topology)}, + {heap_type, erlang:system_info(heap_type)}, + {nof_schedulers, erlang:system_info(schedulers)}, + {otp_release, erlang:system_info(otp_release)}, + {version, erlang:system_info(version)}, + {system_version, erlang:system_info(system_version)}, + {system_architecture, erlang:system_info(system_architecture)}]. + diff --git a/lib/inets/examples/httpd_load_test/hdlt_slave.erl b/lib/inets/examples/httpd_load_test/hdlt_slave.erl new file mode 100644 index 0000000000..52af9b5b90 --- /dev/null +++ b/lib/inets/examples/httpd_load_test/hdlt_slave.erl @@ -0,0 +1,291 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(hdlt_slave). + + +-export([start_link/4, start_link/5, start_link/6, stop/1]). + +%% Internal exports +-export([wait_for_slave/9, slave_start/1, wait_for_master_to_die/3]). + +-include("hdlt_logger.hrl"). + +-define(SSH_PORT, 22). +-define(TIMEOUT, 60000). +-define(LOGGER, hdlt_logger). + + +%% *********************************************************************** +%% start_link/4,5 -- +%% +%% The start/4,5 functions are used to start a slave Erlang node. +%% The node on which the start/N functions are used is called the +%% master in the description below. +%% +%% If hostname is the same for the master and the slave, +%% the Erlang node will simply be spawned. The only requirment for +%% this to work is that the 'erl' program can be found in PATH. +%% +%% If the master and slave are on different hosts, start/N uses +%% the 'rsh' program to spawn an Erlang node on the other host. +%% Alternative, if the master was started as +%% 'erl -sname xxx -rsh my_rsh...', then 'my_rsh' will be used instead +%% of 'rsh' (this is useful for systems where the rsh program is named +%% 'remsh'). +%% +%% For this to work, the following conditions must be fulfilled: +%% +%% 1. There must be an Rsh program on computer; if not an error +%% is returned. +%% +%% 2. The hosts must be configured to allowed 'rsh' access without +%% prompts for password. +%% +%% The slave node will have its filer and user server redirected +%% to the master. When the master node dies, the slave node will +%% terminate. For the start_link functions, the slave node will +%% terminate also if the process which called start_link terminates. +%% +%% Returns: {ok, Name@Host} | +%% {error, timeout} | +%% {error, no_rsh} | +%% {error, {already_running, Name@Host}} + +start_link(Host, Name, ErlPath, Paths) -> + start_link(Host, Name, ErlPath, Paths, [], silence). + +start_link(Host, Name, ErlPath, Paths, DebugLevel) when is_atom(DebugLevel) -> + start_link(Host, Name, ErlPath, Paths, [], DebugLevel); +start_link(Host, Name, ErlPath, Paths, Args) when is_list(Args) -> + start_link(Host, Name, ErlPath, Paths, Args, silence). + +start_link(Host, Name, ErlPath, Paths, Args, DebugLevel) -> + Node = list_to_atom(lists:concat([Name, "@", Host])), + case net_adm:ping(Node) of + pang -> + start_it(Host, Name, Node, ErlPath, Paths, Args, DebugLevel); + pong -> + {error, {already_running, Node}} + end. + +%% Stops a running node. + +stop(Node) -> + rpc:call(Node, erlang, halt, []), + ok. + + +%% Starts a new slave node. + +start_it(Host, Name, Node, ErlPath, Paths, Args, DebugLevel) -> + Prog = filename:join([ErlPath, "erl"]), + spawn(?MODULE, wait_for_slave, [self(), Host, Name, Node, Paths, Args, self(), Prog, DebugLevel]), + receive + {result, Result} -> Result + end. + +%% Waits for the slave to start. + +wait_for_slave(Parent, Host, Name, Node, Paths, Args, + LinkTo, Prog, DebugLevel) -> + ?SET_NAME("HDLT SLAVE STARTER"), + ?SET_LEVEL(DebugLevel), + ?DEBUG("begin", []), + Waiter = register_unique_name(0), + case mk_cmd(Host, Name, Paths, Args, Waiter, Prog) of + {ok, Cmd} -> + ?DEBUG("command generated: ~n~s", [Cmd]), + case (catch ssh_slave_start(Host, Cmd)) of + {ok, Conn, _Chan} -> + ?DEBUG("ssh channel created", []), + receive + {SlavePid, slave_started} -> + ?DEBUG("slave started: ~p", [SlavePid]), + unregister(Waiter), + slave_started(Parent, LinkTo, SlavePid, Conn, + DebugLevel) + after 32000 -> + ?INFO("slave node failed to report in on time", + []), + %% If it seems that the node was partially started, + %% try to kill it. + case net_adm:ping(Node) of + pong -> + spawn(Node, erlang, halt, []), + ok; + _ -> + ok + end, + Parent ! {result, {error, timeout}} + end; + {error, Reason} = Error -> + ?INFO("FAILED starting node: " + "~n ~p" + "~n ~p", [Reason, Cmd]), + Parent ! {result, Error} + end; + Other -> + ?INFO("FAILED creating node command string: " + "~n ~p", [Other]), + Parent ! {result, Other} + end. + + +ssh_slave_start(Host, ErlCmd) -> + ?DEBUG("ssh_slave_start -> try connect to ~p", [Host]), + Connection = + case (catch ssh:connect(Host, ?SSH_PORT, + [{silently_accept_hosts, true}])) of + {ok, Conn} -> + ?DEBUG("ssh_exec_erl -> connected: ~p", [Conn]), + Conn; + Error1 -> + ?LOG("failed connecting to ~p: ~p", [Host, Error1]), + throw({error, {ssh_connect_failed, Error1}}) + end, + + ?DEBUG("ssh_exec_erl -> connected - now create channel", []), + Channel = + case (catch ssh_connection:session_channel(Connection, ?TIMEOUT)) of + {ok, Chan} -> + ?DEBUG("ssh_exec_erl -> channel ~p created", [Chan]), + Chan; + Error2 -> + ?LOG("failed creating channel: ~p", [Error2]), + throw({error, {ssh_channel_create_failed, Error2}}) + end, + + ?DEBUG("ssh_exec_erl -> channel created - now exec command: " + "~n ~p", [ErlCmd]), + case (catch ssh_connection:exec(Connection, Channel, ErlCmd, infinity)) of + success -> + ?DEBUG("ssh_exec_erl -> command exec'ed - clean ssh msg", []), + clean_ssh_msg(), + ?DEBUG("ssh_exec_erl -> done", []), + {ok, Connection, Channel}; + Error3 -> + ?LOG("failed exec comand: ~p", [Error3]), + throw({error, {ssh_exec_failed, Error3}}) + end. + +clean_ssh_msg() -> + receive + {ssh_cm, _X, _Y} -> + clean_ssh_msg() + after 1000 -> + ok + end. + + +slave_started(ReplyTo, Master, Slave, Conn, Level) + when is_pid(Master) andalso is_pid(Slave) -> + process_flag(trap_exit, true), + SName = lists:flatten( + io_lib:format("HDLT SLAVE CTRL[~p,~p]", + [self(), node(Slave)])), + ?SET_NAME(SName), + ?SET_LEVEL(Level), + ?LOG("initiating", []), + MasterRef = erlang:monitor(process, Master), + SlaveRef = erlang:monitor(process, Slave), + ReplyTo ! {result, {ok, node(Slave)}}, + slave_running(Master, MasterRef, Slave, SlaveRef, Conn). + + +%% The slave node will be killed if the master process terminates, +%% The master process will not be killed if the slave node terminates. + +slave_running(Master, MasterRef, Slave, SlaveRef, Conn) -> + ?DEBUG("await message", []), + receive + {'DOWN', MasterRef, process, _Object, _Info} -> + ?LOG("received DOWN from master", []), + erlang:demonitor(SlaveRef, [flush]), + Slave ! {nodedown, node()}, + ssh:close(Conn); + + {'DOWN', SlaveRef, process, Object, _Info} -> + ?LOG("received DOWN from slave (~p)", [Object]), + erlang:demonitor(MasterRef, [flush]), + ssh:close(Conn); + + Other -> + ?DEBUG("received unknown: ~n~p", [Other]), + slave_running(Master, MasterRef, Slave, SlaveRef, Conn) + + end. + +register_unique_name(Number) -> + Name = list_to_atom(lists:concat([?MODULE, "_waiter_", Number])), + case catch register(Name, self()) of + true -> + Name; + {'EXIT', {badarg, _}} -> + register_unique_name(Number+1) + end. + + +%% Makes up the command to start the nodes. +%% If the node should run on the local host, there is +%% no need to use rsh. + +mk_cmd(Host, Name, Paths, Args, Waiter, Prog) -> + PaPaths = [[" -pa ", Path] || Path <- Paths], + {ok, lists:flatten( + lists:concat([Prog, + " -detached -nopinput ", + Args, " ", + " -sname ", Name, "@", Host, + " -s ", ?MODULE, " slave_start ", node(), + " ", Waiter, + " ", PaPaths]))}. + + +%% This function will be invoked on the slave, using the -s option of erl. +%% It will wait for the master node to terminate. + +slave_start([Master, Waiter]) -> + spawn(?MODULE, wait_for_master_to_die, [Master, Waiter, silence]); +slave_start([Master, Waiter, Level]) -> + spawn(?MODULE, wait_for_master_to_die, [Master, Waiter, Level]). + + +wait_for_master_to_die(Master, Waiter, Level) -> + process_flag(trap_exit, true), + SName = lists:flatten( + io_lib:format("HDLT-SLAVE MASTER MONITOR[~p,~p,~p]", + [self(), node(), Master])), + ?SET_NAME(SName), + ?SET_LEVEL(Level), + erlang:monitor_node(Master, true), + {Waiter, Master} ! {self(), slave_started}, + wloop(Master). + +wloop(Master) -> + ?DEBUG("await message", []), + receive + {nodedown, Master} -> + ?INFO("received master nodedown", []), + halt(); + _Other -> + wloop(Master) + end. + + + diff --git a/lib/inets/examples/httpd_load_test/hdlt_ssl_client_cert.pem b/lib/inets/examples/httpd_load_test/hdlt_ssl_client_cert.pem new file mode 120000 index 0000000000..41644a1098 --- /dev/null +++ b/lib/inets/examples/httpd_load_test/hdlt_ssl_client_cert.pem @@ -0,0 +1 @@ +../../test/httpc_SUITE_data/ssl_client_cert.pem
\ No newline at end of file diff --git a/lib/inets/examples/httpd_load_test/hdlt_ssl_server_cert.pem b/lib/inets/examples/httpd_load_test/hdlt_ssl_server_cert.pem new file mode 120000 index 0000000000..41644a1098 --- /dev/null +++ b/lib/inets/examples/httpd_load_test/hdlt_ssl_server_cert.pem @@ -0,0 +1 @@ +../../test/httpc_SUITE_data/ssl_client_cert.pem
\ No newline at end of file diff --git a/lib/inets/examples/httpd_load_test/modules.mk b/lib/inets/examples/httpd_load_test/modules.mk new file mode 100644 index 0000000000..9d0d7103d5 --- /dev/null +++ b/lib/inets/examples/httpd_load_test/modules.mk @@ -0,0 +1,44 @@ +#-*-makefile-*- ; force emacs to enter makefile-mode + +# %CopyrightBegin% +# +# Copyright Ericsson AB 2010. All Rights Reserved. +# +# The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# %CopyrightEnd% + +SCRIPT_SKELETONS = \ + hdlt.sh.skel + +CONF_SKELETONS = \ + hdlt.config.skel + +CERT_FILES = \ + hdlt_ssl_client_cert.pem \ + hdlt_ssl_server_cert.pem + +README = HDLT_README + +MODULES = \ + hdlt \ + hdlt_ctrl \ + hdlt_client \ + hdlt_logger \ + hdlt_random_html \ + hdlt_server \ + hdlt_slave + +INTERNAL_HRL_FILES = \ + hdlt_logger.hrl + + diff --git a/lib/inets/examples/server_root/Makefile b/lib/inets/examples/server_root/Makefile new file mode 100644 index 0000000000..d7a3231068 --- /dev/null +++ b/lib/inets/examples/server_root/Makefile @@ -0,0 +1,209 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1997-2010. All Rights Reserved. +# +# The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# %CopyrightEnd% +# +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../../vsn.mk +VSN=$(INETS_VSN) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/inets-$(VSN) + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- +MODULE= + +AUTH_FILES = auth/group \ + auth/passwd +CGI_FILES = cgi-bin/printenv.sh +CONF_FILES = conf/8080.conf \ + conf/8888.conf \ + conf/httpd.conf \ + conf/ssl.conf \ + conf/mime.types +OPEN_FILES = htdocs/open/dummy.html +MNESIA_OPEN_FILES = htdocs/mnesia_open/dummy.html +MISC_FILES = htdocs/misc/friedrich.html \ + htdocs/misc/oech.html +SECRET_FILES = htdocs/secret/dummy.html +MNESIA_SECRET_FILES = htdocs/mnesia_secret/dummy.html +HTDOCS_FILES = htdocs/index.html \ + htdocs/config.shtml \ + htdocs/echo.shtml \ + htdocs/exec.shtml \ + htdocs/flastmod.shtml \ + htdocs/fsize.shtml \ + htdocs/include.shtml +ICON_FILES = icons/README \ + icons/a.gif \ + icons/alert.black.gif \ + icons/alert.red.gif \ + icons/apache_pb.gif \ + icons/back.gif \ + icons/ball.gray.gif \ + icons/ball.red.gif \ + icons/binary.gif \ + icons/binhex.gif \ + icons/blank.gif \ + icons/bomb.gif \ + icons/box1.gif \ + icons/box2.gif \ + icons/broken.gif \ + icons/burst.gif \ + icons/button1.gif \ + icons/button10.gif \ + icons/button2.gif \ + icons/button3.gif \ + icons/button4.gif \ + icons/button5.gif \ + icons/button6.gif \ + icons/button7.gif \ + icons/button8.gif \ + icons/button9.gif \ + icons/buttonl.gif \ + icons/buttonr.gif \ + icons/c.gif \ + icons/comp.blue.gif \ + icons/comp.gray.gif \ + icons/compressed.gif \ + icons/continued.gif \ + icons/dir.gif \ + icons/down.gif \ + icons/dvi.gif \ + icons/f.gif \ + icons/folder.gif \ + icons/folder.open.gif \ + icons/folder.sec.gif \ + icons/forward.gif \ + icons/generic.gif \ + icons/generic.red.gif \ + icons/generic.sec.gif \ + icons/hand.right.gif \ + icons/hand.up.gif \ + icons/htdig.gif \ + icons/icon.sheet.gif \ + icons/image1.gif \ + icons/image2.gif \ + icons/image3.gif \ + icons/index.gif \ + icons/layout.gif \ + icons/left.gif \ + icons/link.gif \ + icons/movie.gif \ + icons/p.gif \ + icons/patch.gif \ + icons/pdf.gif \ + icons/pie0.gif \ + icons/pie1.gif \ + icons/pie2.gif \ + icons/pie3.gif \ + icons/pie4.gif \ + icons/pie5.gif \ + icons/pie6.gif \ + icons/pie7.gif \ + icons/pie8.gif \ + icons/portal.gif \ + icons/poweredby.gif \ + icons/ps.gif \ + icons/quill.gif \ + icons/right.gif \ + icons/screw1.gif \ + icons/screw2.gif \ + icons/script.gif \ + icons/sound1.gif \ + icons/sound2.gif \ + icons/sphere1.gif \ + icons/sphere2.gif \ + icons/star.gif \ + icons/star_blank.gif \ + icons/tar.gif \ + icons/tex.gif \ + icons/text.gif \ + icons/transfer.gif \ + icons/unknown.gif \ + icons/up.gif \ + icons/uu.gif \ + icons/uuencoded.gif \ + icons/world1.gif \ + icons/world2.gif + +SSL_FILES = ssl/ssl_client.pem \ + ssl/ssl_server.pem + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +ERL_COMPILE_FLAGS += + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +debug opt: + +clean: + +docs: + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/auth + $(INSTALL_DATA) $(AUTH_FILES) $(RELSYSDIR)/examples/server_root/auth + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/cgi-bin + $(INSTALL_SCRIPT) $(CGI_FILES) $(RELSYSDIR)/examples/server_root/cgi-bin + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/conf + $(INSTALL_DATA) $(CONF_FILES) $(RELSYSDIR)/examples/server_root/conf + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs/open + $(INSTALL_DATA) $(OPEN_FILES) \ + $(RELSYSDIR)/examples/server_root/htdocs/open + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs/mnesia_open + $(INSTALL_DATA) $(MNESIA_OPEN_FILES) \ + $(RELSYSDIR)/examples/server_root/htdocs/mnesia_open + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs/misc + $(INSTALL_DATA) $(MISC_FILES) \ + $(RELSYSDIR)/examples/server_root/htdocs/misc + $(INSTALL_DIR) \ + $(RELSYSDIR)/examples/server_root/htdocs/secret/top_secret + $(INSTALL_DIR) \ + $(RELSYSDIR)/examples/server_root/htdocs/mnesia_secret/top_secret + $(INSTALL_DATA) $(SECRET_FILES) \ + $(RELSYSDIR)/examples/server_root/htdocs/secret + $(INSTALL_DATA) $(MNESIA_SECRET_FILES) \ + $(RELSYSDIR)/examples/server_root/htdocs/mnesia_secret + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs + $(INSTALL_DATA) $(HTDOCS_FILES) $(RELSYSDIR)/examples/server_root/htdocs + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/icons + $(INSTALL_DATA) $(ICON_FILES) $(RELSYSDIR)/examples/server_root/icons + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/ssl + $(INSTALL_DATA) $(SSL_FILES) $(RELSYSDIR)/examples/server_root/ssl + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/logs + +release_docs_spec: + diff --git a/lib/inets/examples/subdirs.mk b/lib/inets/examples/subdirs.mk new file mode 100644 index 0000000000..10a331fc26 --- /dev/null +++ b/lib/inets/examples/subdirs.mk @@ -0,0 +1,3 @@ +#-*-makefile-*- ; force emacs to enter makefile-mode + +SUB_DIRECTORIES = server_root httpd_load_test
\ No newline at end of file diff --git a/lib/inets/src/ftp/Makefile b/lib/inets/src/ftp/Makefile index 0c15277a18..19b93870df 100644 --- a/lib/inets/src/ftp/Makefile +++ b/lib/inets/src/ftp/Makefile @@ -22,6 +22,7 @@ include $(ERL_TOP)/make/target.mk EBIN = ../../ebin include $(ERL_TOP)/make/$(TARGET)/otp.mk + # ---------------------------------------------------- # Application version # ---------------------------------------------------- @@ -29,6 +30,7 @@ include ../../vsn.mk VSN = $(INETS_VSN) + # ---------------------------------------------------- # Release directory specification # ---------------------------------------------------- @@ -52,24 +54,21 @@ TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) # ---------------------------------------------------- -# INETS FLAGS +# FLAGS # ---------------------------------------------------- -INETS_FLAGS = -D'SERVER_SOFTWARE="$(APPLICATION)/$(VSN)"' + +include ../inets_app/inets.mk ifeq ($(FTP_DEBUG),true) INETS_FLAGS += -Dftp_debug endif +ERL_COMPILE_FLAGS += \ + $(INETS_FLAGS) \ + $(INETS_ERL_COMPILE_FLAGS) \ + -I../../include \ + -I../inets_app -# ---------------------------------------------------- -# FLAGS -# ---------------------------------------------------- -INETS_ERL_FLAGS += -I ../inets_app -pa ../../ebin - -ERL_COMPILE_FLAGS += $(INETS_ERL_FLAGS) \ - $(INETS_FLAGS) \ - +'{parse_transform,sys_pre_attributes}' \ - +'{attribute,insert,app_vsn,$(APP_VSN)}' # ---------------------------------------------------- # Targets @@ -89,9 +88,10 @@ docs: include $(ERL_TOP)/make/otp_release_targets.mk release_spec: opt - $(INSTALL_DIR) $(RELSYSDIR)/src - $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src - $(INSTALL_DIR) $(RELSYSDIR)/ebin + $(INSTALL_DIR) $(RELSYSDIR)/src + $(INSTALL_DIR) $(RELSYSDIR)/src/ftp + $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src/ftp + $(INSTALL_DIR) $(RELSYSDIR)/ebin $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin release_docs_spec: diff --git a/lib/inets/src/ftp/ftp.erl b/lib/inets/src/ftp/ftp.erl index 534fcae675..5ad74851c8 100644 --- a/lib/inets/src/ftp/ftp.erl +++ b/lib/inets/src/ftp/ftp.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1997-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %% @@ -25,14 +25,12 @@ -behaviour(gen_server). -behaviour(inets_service). --deprecated({open, 3, next_major_release}). --deprecated({force_active, 1, next_major_release}). %% API - Client interface -export([cd/2, close/1, delete/2, formaterror/1, lcd/2, lpwd/1, ls/1, ls/2, mkdir/2, nlist/1, nlist/2, - open/1, open/2, open/3, force_active/1, + open/1, open/2, pwd/1, quote/2, recv/2, recv/3, recv_bin/2, recv_chunk_start/2, recv_chunk/1, @@ -133,11 +131,6 @@ open(Host, Port) when is_integer(Port) -> open(Host, [{port, Port}]); %% </BACKWARD-COMPATIBILLITY> -%% <BACKWARD-COMPATIBILLITY> -open(Host, [H|_] = Flags) when is_atom(H) -> - open(Host, ?FTP_PORT, Flags); -%% </BACKWARD-COMPATIBILLITY> - open(Host, Opts) when is_list(Opts) -> ?fcrt("open", [{host, Host}, {opts, Opts}]), try @@ -160,32 +153,6 @@ open(Host, Opts) when is_list(Opts) -> end. -%% <BACKWARD-COMPATIBILLITY> -open(Host, Port, Flags) when is_integer(Port) andalso is_list(Flags) -> - ?fcrt("open", [{host, Host}, {port, Port}, {flags, Flags}]), - try - {ok, StartOptions} = start_options([{flags, Flags}]), - ?fcrt("open", [{start_options, StartOptions}]), - {ok, OpenOptions} = open_options([{host, Host}, {port, Port}|Flags]), - ?fcrt("open", [{open_options, OpenOptions}]), - case ftp_sup:start_child([[{client, self()} | StartOptions], []]) of - {ok, Pid} -> - ?fcrt("open - ok", [{pid, Pid}]), - call(Pid, {open, ip_comm, OpenOptions}, plain); - Error1 -> - ?fcrt("open - error", [{error1, Error1}]), - Error1 - end - catch - throw:Error2 -> - Error2 - end. -%% </BACKWARD-COMPATIBILLITY> - - - - - %%-------------------------------------------------------------------------- %% user(Pid, User, Pass, <Acc>) -> ok | {error, euser} | {error, econn} %% | {error, eacct} @@ -528,16 +495,6 @@ close(Pid) -> cast(Pid, close), ok. -%%-------------------------------------------------------------------------- -%% force_active(Pid) -> ok -%% Pid = pid() -%% -%% Description: Force connection to use active mode. -%%-------------------------------------------------------------------------- -force_active(Pid) -> - error_logger:info_report("This function is deprecated use the mode flag " - "instead"), - call(Pid, force_active, atom). %%-------------------------------------------------------------------------- %% formaterror(Tag) -> string() @@ -886,9 +843,6 @@ handle_call({_, {open, ip_comm, Host, Opts}}, From, State) -> {stop, normal, State2#state{client = undefined}} end; -handle_call({_, force_active}, _, State) -> - {reply, ok, State#state{mode = active}}; - handle_call({_, {user, User, Password}}, From, #state{csock = CSock} = State) when (CSock =/= undefined) -> handle_user(User, Password, "", State#state{client = From}); diff --git a/lib/inets/src/ftp/ftp_internal.hrl b/lib/inets/src/ftp/ftp_internal.hrl index c3fa1e611d..148f8217ba 100644 --- a/lib/inets/src/ftp/ftp_internal.hrl +++ b/lib/inets/src/ftp/ftp_internal.hrl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2005-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2005-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %% @@ -21,7 +21,8 @@ -ifndef(ftp_internal_hrl). -define(ftp_internal_hrl, true). --include("inets_internal.hrl"). +-include_lib("inets/src/inets_app/inets_internal.hrl"). + -define(SERVICE, ftpc). -define(fcri(Label, Content), ?report_important(Label, ?SERVICE, Content)). -define(fcrv(Label, Content), ?report_verbose(Label, ?SERVICE, Content)). diff --git a/lib/inets/src/http_client/Makefile b/lib/inets/src/http_client/Makefile index 628c91421f..575c6efaec 100644 --- a/lib/inets/src/http_client/Makefile +++ b/lib/inets/src/http_client/Makefile @@ -61,20 +61,17 @@ TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) # ---------------------------------------------------- -# INETS FLAGS -# ---------------------------------------------------- -INETS_FLAGS = -D'SERVER_SOFTWARE="$(APPLICATION)/$(VSN)"' - - -# ---------------------------------------------------- # FLAGS # ---------------------------------------------------- -INETS_ERL_FLAGS += -I ../http_lib -I ../inets_app -pa ../../ebin -ERL_COMPILE_FLAGS += $(INETS_ERL_FLAGS) \ - $(INETS_FLAGS) \ - +'{parse_transform,sys_pre_attributes}' \ - +'{attribute,insert,app_vsn,$(APP_VSN)}' +include ../inets_app/inets.mk + +ERL_COMPILE_FLAGS += \ + $(INETS_FLAGS) \ + $(INETS_ERL_COMPILE_FLAGS) \ + -I../../include \ + -I../inets_app \ + -I../http_lib # ---------------------------------------------------- @@ -94,9 +91,10 @@ docs: include $(ERL_TOP)/make/otp_release_targets.mk release_spec: opt - $(INSTALL_DIR) $(RELSYSDIR)/src - $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src - $(INSTALL_DIR) $(RELSYSDIR)/ebin + $(INSTALL_DIR) $(RELSYSDIR)/src + $(INSTALL_DIR) $(RELSYSDIR)/src/http_client + $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src/http_client + $(INSTALL_DIR) $(RELSYSDIR)/ebin $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin release_docs_spec: diff --git a/lib/inets/src/http_client/http.erl b/lib/inets/src/http_client/http.erl index 7e1e90b50e..bbe2fec267 100644 --- a/lib/inets/src/http_client/http.erl +++ b/lib/inets/src/http_client/http.erl @@ -18,21 +18,38 @@ %% %% -%% Description: -%%% This version of the HTTP/1.1 client supports: -%%% - RFC 2616 HTTP 1.1 client part -%%% - RFC 2818 HTTP Over TLS +%%% Description: OLD API MODULE - USE httpc INSTEAD -module(http). -%% API --export([request/1, request/2, request/4, request/5, +-deprecated({request, 1, next_major_release}). +-deprecated({request, 2, next_major_release}). +-deprecated({request, 4, next_major_release}). +-deprecated({request, 5, next_major_release}). +-deprecated({cancel_request, 1, next_major_release}). +-deprecated({cancel_request, 2, next_major_release}). +-deprecated({set_option, 2, next_major_release}). +-deprecated({set_option, 3, next_major_release}). +-deprecated({set_options, 1, next_major_release}). +-deprecated({set_options, 2, next_major_release}). +-deprecated({verify_cookies, 2, next_major_release}). +-deprecated({verify_cookies, 3, next_major_release}). +-deprecated({cookie_header, 1, next_major_release}). +-deprecated({cookie_header, 2, next_major_release}). +-deprecated({stream_next, 1, next_major_release}). +-deprecated({default_profile, 0, next_major_release}). + +%% Deprecated +-export([ + request/1, request/2, request/4, request/5, cancel_request/1, cancel_request/2, set_option/2, set_option/3, set_options/1, set_options/2, - verify_cookies/2, verify_cookies/3, cookie_header/1, - cookie_header/2, stream_next/1, - default_profile/0]). + verify_cookies/2, verify_cookies/3, + cookie_header/1, cookie_header/2, + stream_next/1, + default_profile/0 + ]). %%%========================================================================= diff --git a/lib/inets/src/http_client/httpc.erl b/lib/inets/src/http_client/httpc.erl index 6deeab6948..851364001c 100644 --- a/lib/inets/src/http_client/httpc.erl +++ b/lib/inets/src/http_client/httpc.erl @@ -48,7 +48,7 @@ stop_service/1, services/0, service_info/1]). --include("http_internal.hrl"). +-include_lib("inets/src/http_lib/http_internal.hrl"). -include("httpc_internal.hrl"). -define(DEFAULT_PROFILE, default). @@ -104,8 +104,14 @@ request(Url, Profile) -> %% HTTPOptions - [HttpOption] %% HTTPOption - {timeout, Time} | {connect_timeout, Time} | %% {ssl, SSLOptions} | {proxy_auth, {User, Password}} -%% Ssloptions = [SSLOption] -%% SSLOption = {verify, code()} | {depth, depth()} | {certfile, path()} | +%% Ssloptions = ssl_options() | +%% {ssl, ssl_options()} | +%% {ossl, ssl_options()} | +%% {essl, ssl_options()} +%% ssl_options() = [ssl_option()] +%% ssl_option() = {verify, code()} | +%% {depth, depth()} | +%% {certfile, path()} | %% {keyfile, path()} | {password, string()} | {cacertfile, path()} | %% {ciphers, string()} %% Options - [Option] @@ -579,7 +585,13 @@ http_options_default() -> error end, SslPost = fun(Value) when is_list(Value) -> - {ok, Value}; + {ok, {?HTTP_DEFAULT_SSL_KIND, Value}}; + ({ssl, SslOptions}) when is_list(SslOptions) -> + {ok, {?HTTP_DEFAULT_SSL_KIND, SslOptions}}; + ({ossl, SslOptions}) when is_list(SslOptions) -> + {ok, {ossl, SslOptions}}; + ({essl, SslOptions}) when is_list(SslOptions) -> + {ok, {essl, SslOptions}}; (_) -> error end, @@ -604,14 +616,14 @@ http_options_default() -> error end, [ - {version, {value, "HTTP/1.1"}, #http_options.version, VersionPost}, - {timeout, {value, ?HTTP_REQUEST_TIMEOUT}, #http_options.timeout, TimeoutPost}, - {autoredirect, {value, true}, #http_options.autoredirect, AutoRedirectPost}, - {ssl, {value, []}, #http_options.ssl, SslPost}, - {proxy_auth, {value, undefined}, #http_options.proxy_auth, ProxyAuthPost}, - {relaxed, {value, false}, #http_options.relaxed, RelaxedPost}, - %% this field has to be *after* the timeout field (as that field is used for the default value) - {connect_timeout, {field, #http_options.timeout}, #http_options.connect_timeout, ConnTimeoutPost} + {version, {value, "HTTP/1.1"}, #http_options.version, VersionPost}, + {timeout, {value, ?HTTP_REQUEST_TIMEOUT}, #http_options.timeout, TimeoutPost}, + {autoredirect, {value, true}, #http_options.autoredirect, AutoRedirectPost}, + {ssl, {value, {?HTTP_DEFAULT_SSL_KIND, []}}, #http_options.ssl, SslPost}, + {proxy_auth, {value, undefined}, #http_options.proxy_auth, ProxyAuthPost}, + {relaxed, {value, false}, #http_options.relaxed, RelaxedPost}, + %% this field has to be *after* the timeout option (as that field is used for the default value) + {connect_timeout, {field, #http_options.timeout}, #http_options.connect_timeout, ConnTimeoutPost} ]. diff --git a/lib/inets/src/http_client/httpc_handler.erl b/lib/inets/src/http_client/httpc_handler.erl index 5e79d874fb..c34b641b7b 100644 --- a/lib/inets/src/http_client/httpc_handler.erl +++ b/lib/inets/src/http_client/httpc_handler.erl @@ -22,8 +22,8 @@ -behaviour(gen_server). +-include_lib("inets/src/http_lib/http_internal.hrl"). -include("httpc_internal.hrl"). --include("http_internal.hrl"). %%-------------------------------------------------------------------- @@ -177,8 +177,8 @@ stream(BodyPart, Request = #request{stream = Self}, Code) stream(BodyPart, Request = #request{stream = Self}, 404) when (Self =:= self) orelse (Self =:= {self, once}) -> ?hcrt("stream - self with 404", [{stream, Self}]), - httpc_response:send(Request#request.from, - {Request#request.id, stream, BodyPart}), + httpc_response:send(Request#request.from, + {Request#request.id, stream, BodyPart}), {<<>>, Request}; %% Stream to file @@ -286,8 +286,7 @@ handle_call({connect_and_send, #request{address = Address0, handle_call(#request{address = Addr} = Request, _, #state{status = Status, - session = #tcp_session{socket = Socket, - type = pipeline} = Session, + session = #session{type = pipeline} = Session, timers = Timers, options = #options{proxy = Proxy} = _Options, profile_name = ProfileName} = State) @@ -301,7 +300,7 @@ handle_call(#request{address = Addr} = Request, _, Address = handle_proxy(Addr, Proxy), - case httpc_request:send(Address, Request, Socket) of + case httpc_request:send(Address, Session, Request) of ok -> ?hcrd("request sent", []), @@ -320,10 +319,10 @@ handle_call(#request{address = Addr} = Request, _, NewTimers = NewState#state.timers, NewPipeline = queue:in(Request, State#state.pipeline), NewSession = - Session#tcp_session{queue_length = - %% Queue + current - queue:len(NewPipeline) + 1, - client_close = ClientClose}, + Session#session{queue_length = + %% Queue + current + queue:len(NewPipeline) + 1, + client_close = ClientClose}, httpc_manager:insert_session(NewSession, ProfileName), ?hcrd("session updated", []), {reply, ok, State#state{pipeline = NewPipeline, @@ -336,8 +335,8 @@ handle_call(#request{address = Addr} = Request, _, cancel_timer(Timers#timers.queue_timer, timeout_queue), NewSession = - Session#tcp_session{queue_length = 1, - client_close = ClientClose}, + Session#session{queue_length = 1, + client_close = ClientClose}, httpc_manager:insert_session(NewSession, ProfileName), Relaxed = (Request#request.settings)#http_options.relaxed, @@ -357,8 +356,7 @@ handle_call(#request{address = Addr} = Request, _, handle_call(#request{address = Addr} = Request, _, #state{status = Status, - session = #tcp_session{socket = Socket, - type = keep_alive} = Session, + session = #session{type = keep_alive} = Session, timers = Timers, options = #options{proxy = Proxy} = _Options, profile_name = ProfileName} = State) @@ -370,7 +368,7 @@ handle_call(#request{address = Addr} = Request, _, {status, Status}]), Address = handle_proxy(Addr, Proxy), - case httpc_request:send(Address, Request, Socket) of + case httpc_request:send(Address, Session, Request) of ok -> ?hcrd("request sent", []), @@ -389,10 +387,10 @@ handle_call(#request{address = Addr} = Request, _, NewTimers = NewState#state.timers, NewKeepAlive = queue:in(Request, State#state.keep_alive), NewSession = - Session#tcp_session{queue_length = - %% Queue + current - queue:len(NewKeepAlive) + 1, - client_close = ClientClose}, + Session#session{queue_length = + %% Queue + current + queue:len(NewKeepAlive) + 1, + client_close = ClientClose}, httpc_manager:insert_session(NewSession, ProfileName), ?hcrd("session updated", []), {reply, ok, State#state{keep_alive = NewKeepAlive, @@ -405,8 +403,8 @@ handle_call(#request{address = Addr} = Request, _, cancel_timer(Timers#timers.queue_timer, timeout_queue), NewSession = - Session#tcp_session{queue_length = 1, - client_close = ClientClose}, + Session#session{queue_length = 1, + client_close = ClientClose}, httpc_manager:insert_session(NewSession, ProfileName), Relaxed = (Request#request.settings)#http_options.relaxed, @@ -589,13 +587,13 @@ handle_info({ssl_closed, _}, State = #state{request = undefined}) -> %%% Error cases handle_info({tcp_closed, _}, #state{session = Session0} = State) -> - Socket = Session0#tcp_session.socket, - Session = Session0#tcp_session{socket = {remote_close, Socket}}, + Socket = Session0#session.socket, + Session = Session0#session{socket = {remote_close, Socket}}, %% {stop, session_remotly_closed, State}; {stop, normal, State#state{session = Session}}; handle_info({ssl_closed, _}, #state{session = Session0} = State) -> - Socket = Session0#tcp_session.socket, - Session = Session0#tcp_session{socket = {remote_close, Socket}}, + Socket = Session0#session.socket, + Session = Session0#session{socket = {remote_close, Socket}}, %% {stop, session_remotly_closed, State}; {stop, normal, State#state{session = Session}}; handle_info({tcp_error, _, _} = Reason, State) -> @@ -699,19 +697,18 @@ terminate(normal, #state{session = undefined}) -> %% Init error sending, no session information has been setup but %% there is a socket that needs closing. terminate(normal, - #state{request = Request, - session = #tcp_session{id = undefined, - socket = Socket}}) -> - http_transport:close(socket_type(Request), Socket); + #state{session = #session{id = undefined} = Session}) -> + close_socket(Session); %% Socket closed remotely terminate(normal, - #state{session = #tcp_session{socket = {remote_close, Socket}, - id = Id}, + #state{session = #session{socket = {remote_close, Socket}, + socket_type = SocketType, + id = Id}, profile_name = ProfileName, - request = Request, - timers = Timers, - pipeline = Pipeline}) -> + request = Request, + timers = Timers, + pipeline = Pipeline}) -> ?hcrt("terminate(normal) - remote close", [{id, Id}, {profile, ProfileName}]), @@ -728,11 +725,11 @@ terminate(normal, deliver_answers([Request | queue:to_list(Pipeline)]), %% And, just in case, close our side (**really** overkill) - http_transport:close(socket_type(Request), Socket); + http_transport:close(SocketType, Socket); -terminate(_, #state{session = #tcp_session{id = Id, - socket = Socket, - scheme = Scheme}, +terminate(_, #state{session = #session{id = Id, + socket = Socket, + socket_type = SocketType}, request = undefined, profile_name = ProfileName, timers = Timers, @@ -744,7 +741,7 @@ terminate(_, #state{session = #tcp_session{id = Id, maybe_retry_queue(KeepAlive, State), cancel_timer(Timers#timers.queue_timer, timeout_queue), - http_transport:close(socket_type(Scheme), Socket); + http_transport:close(SocketType, Socket); terminate(Reason, #state{request = undefined}) -> ?hcrt("terminate", [{reason, Reason}]), @@ -878,22 +875,23 @@ connect_and_send_first_request(Address, ConnTimeout = Settings#http_options.connect_timeout, case connect(SocketType, Address, Options, ConnTimeout) of {ok, Socket} -> + Session = #session{id = {OrigAddress, self()}, + scheme = Scheme, + socket = Socket, + socket_type = SocketType}, ?hcrd("connected - now send first request", [{socket, Socket}]), - case httpc_request:send(Address, Request, Socket) of + case httpc_request:send(Address, Session, Request) of ok -> ?hcrd("first request sent", []), ClientClose = httpc_request:is_client_closing(Headers), SessionType = httpc_manager:session_type(Options), - Session = - #tcp_session{id = {OrigAddress, self()}, - scheme = Scheme, - socket = Socket, - client_close = ClientClose, - type = SessionType}, + Session2 = + Session#session{client_close = ClientClose, + type = SessionType}, TmpState = State#state{request = Request, - session = Session, + session = Session2, mfa = init_mfa(Request, State), status_line = init_status_line(Request), headers = undefined, @@ -947,21 +945,20 @@ handler_info(#state{request = Request, ?hcrt("handler info", [{request_info, RequestInfo}]), %% Info about the current session/socket - SessionType = Session#tcp_session.type, - QueueLen = case Session#tcp_session.type of + SessionType = Session#session.type, + QueueLen = case SessionType of pipeline -> queue:len(Pipeline); keep_alive -> queue:len(KeepAlive) end, - Socket = Session#tcp_session.socket, - Scheme = Session#tcp_session.scheme, - SocketType = socket_type(Scheme), + Scheme = Session#session.scheme, + Socket = Session#session.socket, + SocketType = Session#session.socket_type, ?hcrt("handler info", [{session_type, SessionType}, {queue_length, QueueLen}, {scheme, Scheme}, - {socket_type, SocketType}, {socket, Socket}]), SocketOpts = http_transport:getopts(SocketType, Socket), @@ -1118,9 +1115,7 @@ handle_response(#state{request = Request, ?hcrd("handle response - continue", []), %% Send request body {_, RequestBody} = Request#request.content, - http_transport:send(socket_type(Session#tcp_session.scheme), - Session#tcp_session.socket, - RequestBody), + send_raw(Session, RequestBody), %% Wait for next response activate_once(Session), Relaxed = (Request#request.settings)#http_options.relaxed, @@ -1217,7 +1212,7 @@ handle_pipeline(#state{status = pipeline, %% If a pipeline that has been idle for some time is not %% closed by the server, the client may want to close it. NewState = activate_queue_timeout(TimeOut, State), - NewSession = Session#tcp_session{queue_length = 0}, + NewSession = Session#session{queue_length = 0}, httpc_manager:insert_session(NewSession, ProfileName), %% Note mfa will be initilized when a new request %% arrives. @@ -1239,9 +1234,9 @@ handle_pipeline(#state{status = pipeline, false -> ?hcrv("next request", [{request, NextRequest}]), NewSession = - Session#tcp_session{queue_length = - %% Queue + current - queue:len(Pipeline) + 1}, + Session#session{queue_length = + %% Queue + current + queue:len(Pipeline) + 1}, httpc_manager:insert_session(NewSession, ProfileName), Relaxed = (NextRequest#request.settings)#http_options.relaxed, @@ -1290,16 +1285,16 @@ handle_keep_alive_queue( %% If a keep_alive session has been idle for some time is not %% closed by the server, the client may want to close it. NewState = activate_queue_timeout(TimeOut, State), - NewSession = Session#tcp_session{queue_length = 0}, + NewSession = Session#session{queue_length = 0}, httpc_manager:insert_session(NewSession, ProfileName), %% Note mfa will be initilized when a new request %% arrives. {noreply, - NewState#state{request = undefined, - mfa = undefined, + NewState#state{request = undefined, + mfa = undefined, status_line = undefined, - headers = undefined, - body = undefined + headers = undefined, + body = undefined } }; {{value, NextRequest}, KeepAlive} -> @@ -1342,10 +1337,12 @@ case_insensitive_header(Str) when is_list(Str) -> case_insensitive_header(Str) -> Str. -activate_once(#tcp_session{scheme = Scheme, socket = Socket}) -> - SocketType = socket_type(Scheme), +activate_once(#session{socket = Socket, socket_type = SocketType}) -> http_transport:setopts(SocketType, Socket, [{active, once}]). +close_socket(#session{socket = Socket, socket_type = SocketType}) -> + http_transport:close(SocketType, Socket). + activate_request_timeout( #state{request = #request{timer = undefined} = Request} = State) -> Timeout = (Request#request.settings)#http_options.timeout, @@ -1378,7 +1375,7 @@ activate_queue_timeout(Time, State) -> State#state{timers = #timers{queue_timer = Ref}}. -is_pipeline_enabled_client(#tcp_session{type = pipeline}) -> +is_pipeline_enabled_client(#session{type = pipeline}) -> true; is_pipeline_enabled_client(_) -> false. @@ -1391,7 +1388,7 @@ is_keep_alive_enabled_server("HTTP/1.0", is_keep_alive_enabled_server(_,_) -> false. -is_keep_alive_connection(Headers, #tcp_session{client_close = ClientClose}) -> +is_keep_alive_connection(Headers, #session{client_close = ClientClose}) -> (not ((ClientClose) orelse httpc_response:is_server_closing(Headers))). try_to_enable_pipeline_or_keep_alive( @@ -1416,7 +1413,7 @@ try_to_enable_pipeline_or_keep_alive( httpc_manager:insert_session(Session, ProfileName), %% Make sure type is keep_alive in session %% as it in this case might be pipeline - NewSession = Session#tcp_session{type = keep_alive}, + NewSession = Session#session{type = keep_alive}, State#state{status = keep_alive, session = NewSession} end; @@ -1551,11 +1548,11 @@ init_status_line(#request{settings = Settings}) -> socket_type(#request{scheme = http}) -> ip_comm; socket_type(#request{scheme = https, settings = Settings}) -> - {ssl, Settings#http_options.ssl}; -socket_type(http) -> - ip_comm; -socket_type(https) -> - {ssl, []}. %% Dummy value ok for ex setopts that does not use this value + Settings#http_options.ssl. +%% socket_type(http) -> +%% ip_comm; +%% socket_type(https) -> +%% {ssl1, []}. %% Dummy value ok for ex setopts that does not use this value start_stream({_Version, _Code, _ReasonPhrase}, _Headers, #request{stream = none} = Request) -> @@ -1624,18 +1621,15 @@ end_stream(SL, R) -> next_body_chunk(#state{request = #request{stream = {self, once}}, - once = once, session = Session} = State) -> - http_transport:setopts(socket_type(Session#tcp_session.scheme), - Session#tcp_session.socket, - [{active, once}]), + once = once, + session = Session} = State) -> + activate_once(Session), State#state{once = inactive}; next_body_chunk(#state{request = #request{stream = {self, once}}, once = inactive} = State) -> State; %% Wait for user to call stream_next next_body_chunk(#state{session = Session} = State) -> - http_transport:setopts(socket_type(Session#tcp_session.scheme), - Session#tcp_session.socket, - [{active, once}]), + activate_once(Session), State. handle_verbose(verbose) -> @@ -1712,6 +1706,11 @@ handle_verbose(_) -> %% ok. +send_raw(#session{socket = Socket, socket_type = SocketType}, Body) -> + http_transport:send(SocketType, Socket, Body). + + + call(Msg, Pid) -> Timeout = infinity, call(Msg, Pid, Timeout). diff --git a/lib/inets/src/http_client/httpc_internal.hrl b/lib/inets/src/http_client/httpc_internal.hrl index 4d76c4beb3..3cdd95a02b 100644 --- a/lib/inets/src/http_client/httpc_internal.hrl +++ b/lib/inets/src/http_client/httpc_internal.hrl @@ -18,7 +18,11 @@ %% %% --include("inets_internal.hrl"). +-ifndef(httpc_internal_hrl). +-define(httpc_internal_hrl, true). + +-include_lib("inets/src/inets_app/inets_internal.hrl"). + -define(SERVICE, httpc). -define(hcri(Label, Data), ?report_important(Label, ?SERVICE, Data)). -define(hcrv(Label, Data), ?report_verbose(Label, ?SERVICE, Data)). @@ -104,13 +108,14 @@ } ). --record(tcp_session, +-record(session, { id, % {{Host, Port}, HandlerPid} client_close, % true | false scheme, % http (HTTP/TCP) | https (HTTP/SSL/TCP) socket, % Open socket, used by connection - queue_length = 1, % Current length of pipeline or keep alive queue + socket_type, % socket-type, used by connection + queue_length = 1, % Current length of pipeline or keep-alive queue type % pipeline | keep_alive (wait for response before sending new request) }). @@ -138,3 +143,6 @@ %% path, % string() %% q % query: string() %% }). + + +-endif. % -ifdef(httpc_internal_hrl). diff --git a/lib/inets/src/http_client/httpc_manager.erl b/lib/inets/src/http_client/httpc_manager.erl index b278077a66..d5d6376369 100644 --- a/lib/inets/src/http_client/httpc_manager.erl +++ b/lib/inets/src/http_client/httpc_manager.erl @@ -21,8 +21,8 @@ -behaviour(gen_server). +-include_lib("inets/src/http_lib/http_internal.hrl"). -include("httpc_internal.hrl"). --include("http_internal.hrl"). %% Internal Application API -export([ @@ -333,7 +333,7 @@ do_init(ProfileName, CookiesDir) -> ?hcrt("create session db", []), SessionDbName = session_db_name(ProfileName), ets:new(SessionDbName, - [public, set, named_table, {keypos, #tcp_session.id}]), + [public, set, named_table, {keypos, #session.id}]), %% Create handler db ?hcrt("create handler/request db", []), @@ -876,12 +876,12 @@ select_session(Method, HostPort, Scheme, SessionType, %% client_close, scheme and type specified. %% The fields id (part of: HandlerPid) and queue_length %% specified. - Pattern = #tcp_session{id = {HostPort, '$1'}, - client_close = false, - scheme = Scheme, - socket = '_', - queue_length = '$2', - type = SessionType}, + Pattern = #session{id = {HostPort, '$1'}, + client_close = false, + scheme = Scheme, + queue_length = '$2', + type = SessionType, + _ = '_'}, %% {'_', {HostPort, '$1'}, false, Scheme, '_', '$2', SessionTyp}, Candidates = ets:match(SessionDb, Pattern), ?hcrd("select session", [{host_port, HostPort}, diff --git a/lib/inets/src/http_client/httpc_request.erl b/lib/inets/src/http_client/httpc_request.erl index 55e0af4b42..d4df97ad40 100644 --- a/lib/inets/src/http_client/httpc_request.erl +++ b/lib/inets/src/http_client/httpc_request.erl @@ -19,12 +19,13 @@ -module(httpc_request). --include("http_internal.hrl"). +-include_lib("inets/src/http_lib/http_internal.hrl"). -include("httpc_internal.hrl"). %%% Internal API -export([send/3, is_idempotent/1, is_client_closing/1]). + %%%========================================================================= %%% Internal application API %%%========================================================================= @@ -39,10 +40,9 @@ %% %% Description: Composes and sends a HTTP-request. %%------------------------------------------------------------------------- -send(SendAddr, #request{scheme = Scheme, socket_opts = SocketOpts} = Request, - Socket) +send(SendAddr, #session{socket = Socket, socket_type = SocketType}, + #request{socket_opts = SocketOpts} = Request) when is_list(SocketOpts) -> - SocketType = socket_type(Scheme), case http_transport:setopts(SocketType, Socket, SocketOpts) of ok -> send(SendAddr, Socket, SocketType, @@ -50,8 +50,7 @@ send(SendAddr, #request{scheme = Scheme, socket_opts = SocketOpts} = Request, {error, Reason} -> {error, {setopts_failed, Reason}} end; -send(SendAddr, #request{scheme = Scheme} = Request, Socket) -> - SocketType = socket_type(Scheme), +send(SendAddr, #session{socket = Socket, socket_type = SocketType}, Request) -> send(SendAddr, Socket, SocketType, Request). send(SendAddr, Socket, SocketType, @@ -209,10 +208,6 @@ headers(_, "HTTP/0.9") -> headers(Headers, _) -> Headers. -socket_type(http) -> - ip_comm; -socket_type(https) -> - {ssl, []}. http_headers([], Headers) -> lists:flatten(Headers); diff --git a/lib/inets/src/http_client/httpc_response.erl b/lib/inets/src/http_client/httpc_response.erl index df7d40a33e..bb9c516259 100644 --- a/lib/inets/src/http_client/httpc_response.erl +++ b/lib/inets/src/http_client/httpc_response.erl @@ -19,7 +19,7 @@ -module(httpc_response). --include("http_internal.hrl"). +-include_lib("inets/src/http_lib/http_internal.hrl"). -include("httpc_internal.hrl"). %% API diff --git a/lib/inets/src/http_lib/Makefile b/lib/inets/src/http_lib/Makefile index 7f4c92861c..5dac3b0c00 100644 --- a/lib/inets/src/http_lib/Makefile +++ b/lib/inets/src/http_lib/Makefile @@ -55,24 +55,16 @@ TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) # ---------------------------------------------------- -# INETS FLAGS -# ---------------------------------------------------- -INETS_FLAGS = -D'SERVER_SOFTWARE="$(APPLICATION)/$(VSN)"' - - -# ---------------------------------------------------- # FLAGS # ---------------------------------------------------- -INETS_ERL_FLAGS += -I ../inets_app -ifeq ($(WARN_UNUSED_WARS),true) -ERL_COMPILE_FLAGS += +warn_unused_vars -endif +include ../inets_app/inets.mk -ERL_COMPILE_FLAGS += $(INETS_ERL_FLAGS) \ - $(INETS_FLAGS) \ - +'{parse_transform,sys_pre_attributes}' \ - +'{attribute,insert,app_vsn,$(APP_VSN)}' +ERL_COMPILE_FLAGS += \ + $(INETS_FLAGS) \ + $(INETS_ERL_COMPILE_FLAGS) \ + -I../../include \ + -I../inets_app # ---------------------------------------------------- @@ -94,9 +86,10 @@ docs: include $(ERL_TOP)/make/otp_release_targets.mk release_spec: opt - $(INSTALL_DIR) $(RELSYSDIR)/src - $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src - $(INSTALL_DIR) $(RELSYSDIR)/ebin + $(INSTALL_DIR) $(RELSYSDIR)/src + $(INSTALL_DIR) $(RELSYSDIR)/src/http_lib + $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src/http_lib + $(INSTALL_DIR) $(RELSYSDIR)/ebin $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin release_docs_spec: diff --git a/lib/inets/src/http_lib/http_internal.hrl b/lib/inets/src/http_lib/http_internal.hrl index bb2e831727..5440f214b5 100644 --- a/lib/inets/src/http_lib/http_internal.hrl +++ b/lib/inets/src/http_lib/http_internal.hrl @@ -1,28 +1,37 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2002-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2002-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %% --include("inets_internal.hrl"). +-ifndef(http_internal_hrl). +-define(http_internal_hrl, true). --define(HTTP_MAX_BODY_SIZE, nolimit). +-include_lib("inets/src/inets_app/inets_internal.hrl"). + +-define(HTTP_MAX_BODY_SIZE, nolimit). -define(HTTP_MAX_HEADER_SIZE, 10240). --define(HTTP_MAX_URI_SIZE, nolimit). +-define(HTTP_MAX_URI_SIZE, nolimit). + +-ifndef(HTTP_DEFAULT_SSL_KIND). +-define(HTTP_DEFAULT_SSL_KIND, ossl). +%% -define(HTTP_DEFAULT_SSL_KIND, essl). +-endif. % -ifdef(HTTP_DEFAULT_SSL_KIND). + %%% Response headers -record(http_response_h,{ @@ -106,3 +115,5 @@ 'last-modified', other=[] % list() - Key/Value list with other headers }). + +-endif. % -ifdef(http_internal_hrl). diff --git a/lib/inets/src/http_lib/http_transport.erl b/lib/inets/src/http_lib/http_transport.erl index 7c2ac626e6..b8121852b8 100644 --- a/lib/inets/src/http_lib/http_transport.erl +++ b/lib/inets/src/http_lib/http_transport.erl @@ -36,7 +36,9 @@ -export([negotiate/3]). --include("inets_internal.hrl"). +-include_lib("inets/src/inets_app/inets_internal.hrl"). +-include("http_internal.hrl"). + -define(SERVICE, httpl). -define(hlri(Label, Content), ?report_important(Label, ?SERVICE, Content)). -define(hlrv(Label, Content), ?report_verbose(Label, ?SERVICE, Content)). @@ -55,6 +57,18 @@ %% Description: Makes sure inet_db or ssl is started. %%------------------------------------------------------------------------- start(ip_comm) -> + do_start_ip_comm(); + +%% This is just for backward compatibillity +start({ssl, _}) -> + do_start_ssl(); +start({ossl, _}) -> + do_start_ssl(); +start({essl, _}) -> + do_start_ssl(). + + +do_start_ip_comm() -> case inet_db:start() of {ok, _} -> ok; @@ -62,8 +76,9 @@ start(ip_comm) -> ok; Error -> Error - end; -start({ssl, _}) -> + end. + +do_start_ssl() -> case ssl:start() of ok -> ok; @@ -97,18 +112,26 @@ connect(ip_comm = _SocketType, {Host, Port}, Opts0, Timeout) [{host, Host}, {port, Port}, {opts, Opts}, {timeout, Timeout}]), gen_tcp:connect(Host, Port, Opts, Timeout); -connect({ssl, SslConfig}, {Host, Port}, _, Timeout) -> - Opts = [binary, {active, false}] ++ SslConfig, - ?hlrt("connect using ssl", - [{host, Host}, {port, Port}, {ssl_config, SslConfig}, - {timeout, Timeout}]), +%% Wrapper for backaward compatibillity +connect({ssl, SslConfig}, Address, Opts, Timeout) -> + connect({?HTTP_DEFAULT_SSL_KIND, SslConfig}, Address, Opts, Timeout); + +connect({ossl, SslConfig}, {Host, Port}, _, Timeout) -> + Opts = [binary, {active, false}, {ssl_imp, old}] ++ SslConfig, + ?hlrt("connect using ossl", + [{host, Host}, + {port, Port}, + {ssl_config, SslConfig}, + {timeout, Timeout}]), ssl:connect(Host, Port, Opts, Timeout); -connect({erl_ssl, SslConfig}, {Host, Port}, _, Timeout) -> +connect({essl, SslConfig}, {Host, Port}, _, Timeout) -> Opts = [binary, {active, false}, {ssl_imp, new}] ++ SslConfig, - ?hlrt("connect using erl_ssl", - [{host, Host}, {port, Port}, {ssl_config, SslConfig}, - {timeout, Timeout}]), + ?hlrt("connect using essl", + [{host, Host}, + {port, Port}, + {ssl_config, SslConfig}, + {timeout, Timeout}]), ssl:connect(Host, Port, Opts, Timeout). @@ -136,13 +159,32 @@ listen(ip_comm, Addr, Port) -> Else end; -listen({ssl, SSLConfig} = Ssl, Addr, Port) -> +%% Wrapper for backaward compatibillity +listen({ssl, SSLConfig}, Addr, Port) -> + ?hlrt("listen (wrapper)", + [{addr, Addr}, + {port, Port}, + {ssl_config, SSLConfig}]), + listen({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Addr, Port); + +listen({ossl, SSLConfig} = Ssl, Addr, Port) -> + ?hlrt("listen (ossl)", + [{addr, Addr}, + {port, Port}, + {ssl_config, SSLConfig}]), Opt = sock_opt(Ssl, Addr, SSLConfig), - ssl:listen(Port, Opt); - -listen({erl_ssl, SSLConfig} = Ssl, Addr, Port) -> + ?hlrt("listen options", [{opt, Opt}]), + ssl:listen(Port, [{ssl_imp, old} | Opt]); + +listen({essl, SSLConfig} = Ssl, Addr, Port) -> + ?hlrt("listen (essl)", + [{addr, Addr}, + {port, Port}, + {ssl_config, SSLConfig}]), Opt = sock_opt(Ssl, Addr, SSLConfig), - ssl:listen(Port, [{ssl_imp, new} | Opt]). + ?hlrt("listen options", [{opt, Opt}]), + Opt2 = [{ssl_imp, new}, {reuseaddr, true} | Opt], + ssl:listen(Port, Opt2). listen_ip_comm(Addr, Port) -> @@ -228,9 +270,17 @@ ip_family_of(IpFamilyStr) -> %%------------------------------------------------------------------------- accept(SocketType, ListenSocket) -> accept(SocketType, ListenSocket, infinity). + accept(ip_comm, ListenSocket, Timeout) -> gen_tcp:accept(ListenSocket, Timeout); -accept({ssl,_SSLConfig}, ListenSocket, Timeout) -> + +%% Wrapper for backaward compatibillity +accept({ssl, SSLConfig}, ListenSocket, Timeout) -> + accept({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, ListenSocket, Timeout); + +accept({ossl, _SSLConfig}, ListenSocket, Timeout) -> + ssl:transport_accept(ListenSocket, Timeout); +accept({essl, _SSLConfig}, ListenSocket, Timeout) -> ssl:transport_accept(ListenSocket, Timeout). @@ -244,7 +294,15 @@ accept({ssl,_SSLConfig}, ListenSocket, Timeout) -> %%------------------------------------------------------------------------- controlling_process(ip_comm, Socket, NewOwner) -> gen_tcp:controlling_process(Socket, NewOwner); -controlling_process({ssl, _}, Socket, NewOwner) -> + +%% Wrapper for backaward compatibillity +controlling_process({ssl, SSLConfig}, Socket, NewOwner) -> + controlling_process({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket, NewOwner); + +controlling_process({ossl, _}, Socket, NewOwner) -> + ssl:controlling_process(Socket, NewOwner); + +controlling_process({essl, _}, Socket, NewOwner) -> ssl:controlling_process(Socket, NewOwner). @@ -259,9 +317,23 @@ controlling_process({ssl, _}, Socket, NewOwner) -> setopts(ip_comm, Socket, Options) -> ?hlrt("ip_comm setopts", [{socket, Socket}, {options, Options}]), inet:setopts(Socket, Options); -setopts({ssl, _}, Socket, Options) -> - ?hlrt("ssl setopts", [{socket, Socket}, {options, Options}]), - ssl:setopts(Socket, Options). + +%% Wrapper for backaward compatibillity +setopts({ssl, SSLConfig}, Socket, Options) -> + setopts({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket, Options); + +setopts({ossl, _}, Socket, Options) -> + ?hlrt("[o]ssl setopts", [{socket, Socket}, {options, Options}]), + Reason = (catch ssl:setopts(Socket, Options)), + ?hlrt("[o]ssl setopts result", [{reason, Reason}]), + Reason; + + +setopts({essl, _}, Socket, Options) -> + ?hlrt("[e]ssl setopts", [{socket, Socket}, {options, Options}]), + Reason = (catch ssl:setopts(Socket, Options)), + ?hlrt("[e]ssl setopts result", [{reason, Reason}]), + Reason. %%------------------------------------------------------------------------- @@ -283,15 +355,27 @@ getopts(ip_comm, Socket, Options) -> {error, _} -> [] end; -getopts({ssl, _}, Socket, Options) -> + +%% Wrapper for backaward compatibillity +getopts({ssl, SSLConfig}, Socket, Options) -> + getopts({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket, Options); + +getopts({ossl, _}, Socket, Options) -> ?hlrt("ssl getopts", [{socket, Socket}, {options, Options}]), + getopts_ssl(Socket, Options); + +getopts({essl, _}, Socket, Options) -> + ?hlrt("essl getopts", [{socket, Socket}, {options, Options}]), + getopts_ssl(Socket, Options). + +getopts_ssl(Socket, Options) -> case ssl:getopts(Socket, Options) of {ok, SocketOpts} -> SocketOpts; {error, _} -> [] end. - + %%------------------------------------------------------------------------- %% getstat(SocketType, Socket) -> socket_stats() @@ -308,8 +392,15 @@ getstat(ip_comm = _SocketType, Socket) -> {error, _} -> [] end; -getstat({ssl, _} = _SocketType, _Socket) -> - %% ?hlrt("ssl getstat", [{socket, Socket}]), + +%% Wrapper for backaward compatibillity +getstat({ssl, SSLConfig}, Socket) -> + getstat({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket); + +getstat({ossl, _} = _SocketType, _Socket) -> + []; + +getstat({essl, _} = _SocketType, _Socket) -> []. @@ -322,7 +413,15 @@ getstat({ssl, _} = _SocketType, _Socket) -> %%------------------------------------------------------------------------- send(ip_comm, Socket, Message) -> gen_tcp:send(Socket, Message); -send({ssl, _}, Socket, Message) -> + +%% Wrapper for backaward compatibillity +send({ssl, SSLConfig}, Socket, Message) -> + send({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket, Message); + +send({ossl, _}, Socket, Message) -> + ssl:send(Socket, Message); + +send({essl, _}, Socket, Message) -> ssl:send(Socket, Message). @@ -335,9 +434,18 @@ send({ssl, _}, Socket, Message) -> %%------------------------------------------------------------------------- close(ip_comm, Socket) -> gen_tcp:close(Socket); -close({ssl, _}, Socket) -> + +%% Wrapper for backaward compatibillity +close({ssl, SSLConfig}, Socket) -> + close({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket); + +close({ossl, _}, Socket) -> + ssl:close(Socket); + +close({essl, _}, Socket) -> ssl:close(Socket). + %%------------------------------------------------------------------------- %% peername(SocketType, Socket) -> {Port, SockName} %% SocketType = ip_comm | {ssl, _} @@ -368,7 +476,17 @@ peername(ip_comm, Socket) -> {-1, "unknown"} end; -peername({ssl, _}, Socket) -> +%% Wrapper for backaward compatibillity +peername({ssl, SSLConfig}, Socket) -> + peername({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket); + +peername({ossl, _}, Socket) -> + peername_ssl(Socket); + +peername({essl, _}, Socket) -> + peername_ssl(Socket). + +peername_ssl(Socket) -> case ssl:peername(Socket) of {ok,{{A, B, C, D}, Port}} -> PeerName = integer_to_list(A)++"."++integer_to_list(B)++"."++ @@ -409,7 +527,17 @@ sockname(ip_comm, Socket) -> {-1, "unknown"} end; -sockname({ssl, _}, Socket) -> +%% Wrapper for backaward compatibillity +sockname({ssl, SSLConfig}, Socket) -> + sockname({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket); + +sockname({ossl, _}, Socket) -> + sockname_ssl(Socket); + +sockname({essl, _}, Socket) -> + sockname_ssl(Socket). + +sockname_ssl(Socket) -> case ssl:sockname(Socket) of {ok,{{A, B, C, D}, Port}} -> SockName = integer_to_list(A)++"."++integer_to_list(B)++"."++ @@ -455,22 +583,31 @@ sock_opt2(Opts) -> [{packet, 0}, {active, false} | Opts]. negotiate(ip_comm,_,_) -> + ?hlrt("negotiate(ip_comm)", []), ok; -negotiate({ssl,_},Socket,Timeout) -> - negotiate(Socket, Timeout); -negotiate({erl_ssl, _}, Socket, Timeout) -> - negotiate(Socket, Timeout). - -negotiate(Socket, Timeout) -> +negotiate({ssl, SSLConfig}, Socket, Timeout) -> + ?hlrt("negotiate(ssl)", []), + negotiate({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket, Timeout); +negotiate({ossl, _}, Socket, Timeout) -> + ?hlrt("negotiate(ossl)", []), + negotiate_ssl(Socket, Timeout); +negotiate({essl, _}, Socket, Timeout) -> + ?hlrt("negotiate(essl)", []), + negotiate_ssl(Socket, Timeout). + +negotiate_ssl(Socket, Timeout) -> + ?hlrt("negotiate_ssl", [{socket, Socket}, {timeout, Timeout}]), case ssl:ssl_accept(Socket, Timeout) of ok -> ok; - {error, Error} -> - case lists:member(Error, - [timeout,econnreset,esslaccept,esslerrssl]) of + {error, Reason} -> + ?hlrd("negotiate_ssl - accept failed", [{reason, Reason}]), + %% Look for "valid" error reasons + ValidReasons = [timeout, econnreset, esslaccept, esslerrssl], + case lists:member(Reason, ValidReasons) of true -> - {error,normal}; + {error, normal}; false -> - {error, Error} + {error, Reason} end end. diff --git a/lib/inets/src/http_server/Makefile b/lib/inets/src/http_server/Makefile index ce1405011e..879e605217 100644 --- a/lib/inets/src/http_server/Makefile +++ b/lib/inets/src/http_server/Makefile @@ -90,20 +90,17 @@ TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) # ---------------------------------------------------- -# INETS FLAGS -# ---------------------------------------------------- -INETS_FLAGS = -D'SERVER_SOFTWARE="$(APPLICATION)/$(VSN)"' - - -# ---------------------------------------------------- # FLAGS # ---------------------------------------------------- -INETS_ERL_FLAGS += -I ../http_lib -I ../inets_app -pa ../../ebin -ERL_COMPILE_FLAGS += $(INETS_ERL_FLAGS) \ - $(INETS_FLAGS) \ - +'{parse_transform,sys_pre_attributes}' \ - +'{attribute,insert,app_vsn,$(APP_VSN)}' +include ../inets_app/inets.mk + +ERL_COMPILE_FLAGS += \ + $(INETS_FLAGS) \ + $(INETS_ERL_COMPILE_FLAGS) \ + -I../../include \ + -I../inets_app \ + -I../http_lib # ---------------------------------------------------- @@ -125,9 +122,10 @@ docs: include $(ERL_TOP)/make/otp_release_targets.mk release_spec: opt - $(INSTALL_DIR) $(RELSYSDIR)/src - $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src - $(INSTALL_DIR) $(RELSYSDIR)/ebin + $(INSTALL_DIR) $(RELSYSDIR)/src + $(INSTALL_DIR) $(RELSYSDIR)/src/http_server + $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src/http_server + $(INSTALL_DIR) $(RELSYSDIR)/ebin $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin release_docs_spec: diff --git a/lib/inets/src/http_server/httpd.erl b/lib/inets/src/http_server/httpd.erl index 8fe54ccef6..fb5fa1c758 100644 --- a/lib/inets/src/http_server/httpd.erl +++ b/lib/inets/src/http_server/httpd.erl @@ -24,54 +24,25 @@ -include("httpd.hrl"). --deprecated({start, 0, next_major_release}). --deprecated({start, 1, next_major_release}). --deprecated({start_link, 1, next_major_release}). --deprecated({start_child, 0, next_major_release}). --deprecated({start_child, 1, next_major_release}). --deprecated({stop, 0, next_major_release}). --deprecated({stop, 1, next_major_release}). --deprecated({stop, 2, next_major_release}). --deprecated({stop_child, 0, next_major_release}). --deprecated({stop_child, 1, next_major_release}). --deprecated({stop_child, 2, next_major_release}). --deprecated({restart, 0, next_major_release}). --deprecated({restart, 1, next_major_release}). --deprecated({restart, 2, next_major_release}). --deprecated({block, 0, next_major_release}). --deprecated({block, 1, next_major_release}). --deprecated({block, 2, next_major_release}). --deprecated({block, 3, next_major_release}). --deprecated({block, 4, next_major_release}). --deprecated({unblock, 0, next_major_release}). --deprecated({unblock, 1, next_major_release}). --deprecated({unblock, 2, next_major_release}). %% Behavior callbacks --export([start_standalone/1, start_service/1, stop_service/1, services/0, - service_info/1]). +-export([ + start_standalone/1, + start_service/1, + stop_service/1, + services/0, + service_info/1 + ]). %% API -export([parse_query/1, reload_config/2, info/1, info/2, info/3]). -%% Deprecated --export([start/0, start/1, - start_link/0, start_link/1, - start_child/0,start_child/1, - stop/0,stop/1,stop/2, - stop_child/0,stop_child/1,stop_child/2, - restart/0,restart/1,restart/2]). - -%% Management stuff should be internal functions -%% Will be from r13 --export([block/0,block/1,block/2,block/3,block/4, - unblock/0,unblock/1,unblock/2]). - -%% Internal Debugging and status info stuff... -%% Keep for now should probably be moved to test catalog --export([get_status/1,get_status/2,get_status/3, - get_admin_state/0,get_admin_state/1,get_admin_state/2, - get_usage_state/0,get_usage_state/1,get_usage_state/2]). +%% Internal debugging and status info stuff... +-export([ + get_status/1, get_status/2, get_status/3, + get_admin_state/0, get_admin_state/1, get_admin_state/2, + get_usage_state/0, get_usage_state/1, get_usage_state/2 + ]). %%%======================================================================== %%% API @@ -111,6 +82,7 @@ info(Address, Port, Properties) when is_integer(Port) andalso is_list(Properties) -> httpd_conf:get_config(Address, Port, Properties). + %%%======================================================================== %%% Behavior callbacks %%%======================================================================== @@ -149,6 +121,8 @@ service_info(Pid) -> exit:{noproc, _} -> {error, service_not_available} end. + + %%%-------------------------------------------------------------- %%% Internal functions %%%-------------------------------------------------------------------- @@ -176,6 +150,7 @@ child_name2info({httpd_instance_sup, Address, Port}) -> {ok, [{bind_address, Address}, {port, Port} | Info]} end. + reload(Config, Address, Port) -> Name = make_name(Address,Port), case whereis(Name) of @@ -185,26 +160,12 @@ reload(Config, Address, Port) -> {error,not_started} end. -reload(Addr, Port) when is_integer(Port) -> - Name = make_name(Addr,Port), - case whereis(Name) of - Pid when is_pid(Pid) -> - httpd_manager:reload(Pid, undefined); - _ -> - {error,not_started} - end. %%% ========================================================= -%%% Function: block/0, block/1, block/2, block/3, block/4 -%%% block() -%%% block(Port) -%%% block(ConfigFile) -%%% block(Addr,Port) -%%% block(Port,Mode) -%%% block(ConfigFile,Mode) -%%% block(Addr,Port,Mode) -%%% block(ConfigFile,Mode,Timeout) -%%% block(Addr,Port,Mode,Timeout) +%%% Function: block/3, block/4 +%%% block(Addr, Port, Mode) +%%% block(ConfigFile, Mode, Timeout) +%%% block(Addr, Port, Mode, Timeout) %%% %%% Returns: ok | {error,Reason} %%% @@ -237,58 +198,32 @@ reload(Addr, Port) when is_integer(Port) -> %%% Mode -> disturbing | non_disturbing %%% Timeout -> integer() %%% -block() -> block(undefined,8888,disturbing). - -block(Port) when is_integer(Port) -> - block(undefined,Port,disturbing); - -block(ConfigFile) when is_list(ConfigFile) -> - case get_addr_and_port(ConfigFile) of - {ok,Addr,Port} -> - block(Addr,Port,disturbing); - Error -> - Error - end. - -block(Addr,Port) when is_integer(Port) -> - block(Addr,Port,disturbing); - -block(Port,Mode) when is_integer(Port) andalso is_atom(Mode) -> - block(undefined,Port,Mode); - -block(ConfigFile,Mode) when is_list(ConfigFile) andalso is_atom(Mode) -> - case get_addr_and_port(ConfigFile) of - {ok,Addr,Port} -> - block(Addr,Port,Mode); - Error -> - Error - end. - -block(Addr,Port,disturbing) when is_integer(Port) -> - do_block(Addr,Port,disturbing); -block(Addr,Port,non_disturbing) when is_integer(Port) -> - do_block(Addr,Port,non_disturbing); +block(Addr, Port, disturbing) when is_integer(Port) -> + do_block(Addr, Port, disturbing); +block(Addr, Port, non_disturbing) when is_integer(Port) -> + do_block(Addr, Port, non_disturbing); -block(ConfigFile,Mode,Timeout) when is_list(ConfigFile) andalso - is_atom(Mode) andalso - is_integer(Timeout) -> +block(ConfigFile, Mode, Timeout) + when is_list(ConfigFile) andalso + is_atom(Mode) andalso + is_integer(Timeout) -> case get_addr_and_port(ConfigFile) of - {ok,Addr,Port} -> - block(Addr,Port,Mode,Timeout); + {ok, Addr, Port} -> + block(Addr, Port, Mode, Timeout); Error -> Error end. -block(Addr,Port,non_disturbing,Timeout) +block(Addr, Port, non_disturbing, Timeout) + when is_integer(Port) andalso is_integer(Timeout) -> + do_block(Addr, Port, non_disturbing, Timeout); +block(Addr,Port,disturbing,Timeout) when is_integer(Port) andalso is_integer(Timeout) -> - do_block(Addr,Port,non_disturbing,Timeout); -block(Addr,Port,disturbing,Timeout) when is_integer(Port) andalso - is_integer(Timeout) -> - do_block(Addr,Port,disturbing,Timeout). + do_block(Addr, Port, disturbing, Timeout). -do_block(Addr,Port,Mode) when is_integer(Port) andalso is_atom(Mode) -> +do_block(Addr, Port, Mode) when is_integer(Port) andalso is_atom(Mode) -> Name = make_name(Addr,Port), case whereis(Name) of Pid when is_pid(Pid) -> @@ -298,7 +233,7 @@ do_block(Addr,Port,Mode) when is_integer(Port) andalso is_atom(Mode) -> end. -do_block(Addr,Port,Mode,Timeout) +do_block(Addr, Port, Mode, Timeout) when is_integer(Port) andalso is_atom(Mode) -> Name = make_name(Addr,Port), case whereis(Name) of @@ -310,11 +245,8 @@ do_block(Addr,Port,Mode,Timeout) %%% ========================================================= -%%% Function: unblock/0, unblock/1, unblock/2 -%%% unblock() -%%% unblock(Port) -%%% unblock(ConfigFile) -%%% unblock(Addr,Port) +%%% Function: unblock/2 +%%% unblock(Addr, Port) %%% %%% Description: This function is used to reverse a previous block %%% operation on the HTTP server. @@ -323,16 +255,6 @@ do_block(Addr,Port,Mode,Timeout) %%% Addr -> {A,B,C,D} | string() | undefined %%% ConfigFile -> string() %%% -unblock() -> unblock(undefined,8888). -unblock(Port) when is_integer(Port) -> unblock(undefined,Port); - -unblock(ConfigFile) when is_list(ConfigFile) -> - case get_addr_and_port(ConfigFile) of - {ok,Addr,Port} -> - unblock(Addr,Port); - Error -> - Error - end. unblock(Addr, Port) when is_integer(Port) -> Name = make_name(Addr,Port), @@ -521,80 +443,81 @@ do_reload_config(ConfigList, Mode) -> %%%-------------------------------------------------------------- %%% Deprecated %%%-------------------------------------------------------------- -start() -> - start("/var/tmp/server_root/conf/8888.conf"). -start(ConfigFile) -> - {ok, Pid} = inets:start(httpd, ConfigFile, stand_alone), - unlink(Pid), - {ok, Pid}. +%% start() -> +%% start("/var/tmp/server_root/conf/8888.conf"). -start_link() -> - start("/var/tmp/server_root/conf/8888.conf"). +%% start(ConfigFile) -> +%% {ok, Pid} = inets:start(httpd, ConfigFile, stand_alone), +%% unlink(Pid), +%% {ok, Pid}. -start_link(ConfigFile) when is_list(ConfigFile) -> - inets:start(httpd, ConfigFile, stand_alone). +%% start_link() -> +%% start("/var/tmp/server_root/conf/8888.conf"). -stop() -> - stop(8888). +%% start_link(ConfigFile) when is_list(ConfigFile) -> +%% inets:start(httpd, ConfigFile, stand_alone). -stop(Port) when is_integer(Port) -> - stop(undefined, Port); -stop(Pid) when is_pid(Pid) -> - old_stop(Pid); -stop(ConfigFile) when is_list(ConfigFile) -> - old_stop(ConfigFile). +%% stop() -> +%% stop(8888). -stop(Addr, Port) when is_integer(Port) -> - old_stop(Addr, Port). +%% stop(Port) when is_integer(Port) -> +%% stop(undefined, Port); +%% stop(Pid) when is_pid(Pid) -> +%% old_stop(Pid); +%% stop(ConfigFile) when is_list(ConfigFile) -> +%% old_stop(ConfigFile). -start_child() -> - start_child("/var/tmp/server_root/conf/8888.conf"). +%% stop(Addr, Port) when is_integer(Port) -> +%% old_stop(Addr, Port). -start_child(ConfigFile) -> - httpd_sup:start_child(ConfigFile). +%% start_child() -> +%% start_child("/var/tmp/server_root/conf/8888.conf"). -stop_child() -> - stop_child(8888). +%% start_child(ConfigFile) -> +%% httpd_sup:start_child(ConfigFile). -stop_child(Port) -> - stop_child(undefined, Port). +%% stop_child() -> +%% stop_child(8888). -stop_child(Addr, Port) when is_integer(Port) -> - httpd_sup:stop_child(Addr, Port). +%% stop_child(Port) -> +%% stop_child(undefined, Port). -restart() -> reload(undefined, 8888). +%% stop_child(Addr, Port) when is_integer(Port) -> +%% httpd_sup:stop_child(Addr, Port). -restart(Port) when is_integer(Port) -> - reload(undefined, Port). -restart(Addr, Port) -> - reload(Addr, Port). +%% restart() -> reload(undefined, 8888). -old_stop(Pid) when is_pid(Pid) -> - do_stop(Pid); -old_stop(ConfigFile) when is_list(ConfigFile) -> - case get_addr_and_port(ConfigFile) of - {ok, Addr, Port} -> - old_stop(Addr, Port); - - Error -> - Error - end; -old_stop(_StartArgs) -> - ok. +%% restart(Port) when is_integer(Port) -> +%% reload(undefined, Port). +%% restart(Addr, Port) -> +%% reload(Addr, Port). -old_stop(Addr, Port) when is_integer(Port) -> - Name = old_make_name(Addr, Port), - case whereis(Name) of - Pid when is_pid(Pid) -> - do_stop(Pid), - ok; - _ -> - not_started - end. +%% old_stop(Pid) when is_pid(Pid) -> +%% do_stop(Pid); +%% old_stop(ConfigFile) when is_list(ConfigFile) -> +%% case get_addr_and_port(ConfigFile) of +%% {ok, Addr, Port} -> +%% old_stop(Addr, Port); + +%% Error -> +%% Error +%% end; +%% old_stop(_StartArgs) -> +%% ok. + +%% old_stop(Addr, Port) when is_integer(Port) -> +%% Name = old_make_name(Addr, Port), +%% case whereis(Name) of +%% Pid when is_pid(Pid) -> +%% do_stop(Pid), +%% ok; +%% _ -> +%% not_started +%% end. -do_stop(Pid) -> - exit(Pid, shutdown). +%% do_stop(Pid) -> +%% exit(Pid, shutdown). -old_make_name(Addr,Port) -> - httpd_util:make_name("httpd_instance_sup",Addr,Port). +%% old_make_name(Addr,Port) -> +%% httpd_util:make_name("httpd_instance_sup",Addr,Port). diff --git a/lib/inets/src/http_server/httpd_acceptor.erl b/lib/inets/src/http_server/httpd_acceptor.erl index 568fd3c610..c261eff6b2 100644 --- a/lib/inets/src/http_server/httpd_acceptor.erl +++ b/lib/inets/src/http_server/httpd_acceptor.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2001-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2001-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %% @@ -138,9 +138,9 @@ acceptor_loop(Manager, SocketType, ListenSocket, ConfigDb, AcceptTimeout) -> handle_error(Reason, ConfigDb), ?MODULE:acceptor_loop(Manager, SocketType, ListenSocket, ConfigDb, AcceptTimeout); - {'EXIT', Reason} -> - ?hdri("accept exited", [{reason, Reason}]), - handle_error({'EXIT', Reason}, ConfigDb), + {'EXIT', _Reason} = EXIT -> + ?hdri("accept exited", [{reason, _Reason}]), + handle_error(EXIT, ConfigDb), ?MODULE:acceptor_loop(Manager, SocketType, ListenSocket, ConfigDb, AcceptTimeout) end. diff --git a/lib/inets/src/http_server/httpd_cgi.erl b/lib/inets/src/http_server/httpd_cgi.erl index 0532d7d100..c06a06aad3 100644 --- a/lib/inets/src/http_server/httpd_cgi.erl +++ b/lib/inets/src/http_server/httpd_cgi.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2005-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2005-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %% @@ -21,7 +21,8 @@ -export([parse_headers/1, handle_headers/1]). --include("inets_internal.hrl"). +-include_lib("inets/src/inets_app/inets_internal.hrl"). + %%%========================================================================= %%% Internal application API diff --git a/lib/inets/src/http_server/httpd_conf.erl b/lib/inets/src/http_server/httpd_conf.erl index 5ca2e47eb5..8438c4037e 100644 --- a/lib/inets/src/http_server/httpd_conf.erl +++ b/lib/inets/src/http_server/httpd_conf.erl @@ -25,13 +25,15 @@ %% Application internal API -export([load/1, load/2, load_mime_types/1, store/1, store/2, - remove/1, remove_all/1, config/1, get_config/2, get_config/3, - lookup/2, lookup/3, lookup/4, - validate_properties/1]). + remove/1, remove_all/1, get_config/2, get_config/3, + lookup_socket_type/1, + lookup/2, lookup/3, lookup/4, + validate_properties/1]). -define(VMODULE,"CONF"). -include("httpd.hrl"). -include("httpd_internal.hrl"). +-include_lib("inets/src/http_lib/http_internal.hrl"). %%%========================================================================= @@ -216,9 +218,12 @@ load("ServerName " ++ ServerName, []) -> {ok,[],{server_name,clean(ServerName)}}; load("SocketType " ++ SocketType, []) -> - case check_enum(clean(SocketType),["ssl","ip_comm"]) of + %% ssl is the same as HTTP_DEFAULT_SSL_KIND + %% ossl is ssl based on OpenSSL (the "old" ssl) + %% essl is the pure Erlang-based ssl (the "new" ssl) + case check_enum(clean(SocketType), ["ssl", "ossl", "essl", "ip_comm"]) of {ok, ValidSocketType} -> - {ok, [], {socket_type,ValidSocketType}}; + {ok, [], {socket_type, ValidSocketType}}; {error,_} -> {error, ?NICE(clean(SocketType) ++ " is an invalid SocketType")} end; @@ -226,7 +231,7 @@ load("SocketType " ++ SocketType, []) -> load("Port " ++ Port, []) -> case make_integer(Port) of {ok, Integer} -> - {ok, [], {port,Integer}}; + {ok, [], {port, Integer}}; {error, _} -> {error, ?NICE(clean(Port)++" is an invalid Port")} end; @@ -534,7 +539,10 @@ validate_config_params([{server_name, Value} | _]) -> throw({server_name, Value}); validate_config_params([{socket_type, Value} | Rest]) - when (Value =:= ip_comm) orelse (Value =:= ssl) -> + when (Value =:= ip_comm) orelse + (Value =:= ssl) orelse + (Value =:= ossl) orelse + (Value =:= essl) -> validate_config_params(Rest); validate_config_params([{socket_type, Value} | _]) -> throw({socket_type, Value}); @@ -695,6 +703,8 @@ store(ConfigList0) -> ConfigList) catch throw:Error -> + ?hdri("store - config parameter validation failed", + [{error, Error}]), {error, {invalid_option, Error}} end. @@ -741,27 +751,27 @@ remove(ConfigDB) -> ets:delete(ConfigDB), ok. -config(ConfigDB) -> - case httpd_util:lookup(ConfigDB, socket_type,ip_comm) of - ssl -> - case ssl_certificate_file(ConfigDB) of - undefined -> - {error, - "Directive SSLCertificateFile " - "not found in the config file"}; - SSLCertificateFile -> - {ssl, - SSLCertificateFile++ - ssl_certificate_key_file(ConfigDB)++ - ssl_verify_client(ConfigDB)++ - ssl_ciphers(ConfigDB)++ - ssl_password(ConfigDB)++ - ssl_verify_depth(ConfigDB)++ - ssl_ca_certificate_file(ConfigDB)} - end; - ip_comm -> - ip_comm - end. +%% config(ConfigDB) -> +%% case httpd_util:lookup(ConfigDB, socket_type, ip_comm) of +%% ssl -> +%% case ssl_certificate_file(ConfigDB) of +%% undefined -> +%% {error, +%% "Directive SSLCertificateFile " +%% "not found in the config file"}; +%% SSLCertificateFile -> +%% {ssl, +%% SSLCertificateFile++ +%% ssl_certificate_key_file(ConfigDB)++ +%% ssl_verify_client(ConfigDB)++ +%% ssl_ciphers(ConfigDB)++ +%% ssl_password(ConfigDB)++ +%% ssl_verify_depth(ConfigDB)++ +%% ssl_ca_certificate_file(ConfigDB)} +%% end; +%% ip_comm -> +%% ip_comm +%% end. get_config(Address, Port) -> @@ -797,6 +807,38 @@ table(Address, Port) -> httpd_util:make_name("httpd_conf", Address, Port). +lookup_socket_type(ConfigDB) -> + case httpd_util:lookup(ConfigDB, socket_type, ip_comm) of + ip_comm -> + ip_comm; + SSL when (SSL =:= ssl) orelse (SSL =:= ossl) orelse (SSL =:= essl) -> + SSLTag = + if + (SSL =:= ssl) -> + ?HTTP_DEFAULT_SSL_KIND; + true -> + SSL + end, + case ssl_certificate_file(ConfigDB) of + undefined -> + Reason = "Directive SSLCertificateFile " + "not found in the config file", + throw({error, Reason}); + SSLCertificateFile -> + {SSLTag, SSLCertificateFile ++ ssl_config(ConfigDB)} + end + end. + +ssl_config(ConfigDB) -> + ssl_certificate_key_file(ConfigDB) ++ + ssl_verify_client(ConfigDB) ++ + ssl_ciphers(ConfigDB) ++ + ssl_password(ConfigDB) ++ + ssl_verify_depth(ConfigDB) ++ + ssl_ca_certificate_file(ConfigDB). + + + %%%======================================================================== %%% Internal functions %%%======================================================================== diff --git a/lib/inets/src/http_server/httpd_esi.erl b/lib/inets/src/http_server/httpd_esi.erl index b1a75fda52..026ec9a5fe 100644 --- a/lib/inets/src/http_server/httpd_esi.erl +++ b/lib/inets/src/http_server/httpd_esi.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2005-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2005-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %% @@ -21,7 +21,8 @@ -export([parse_headers/1, handle_headers/1]). --include("inets_internal.hrl"). +-include_lib("inets/src/inets_app/inets_internal.hrl"). + %%%========================================================================= %%% Internal application API diff --git a/lib/inets/src/http_server/httpd_internal.hrl b/lib/inets/src/http_server/httpd_internal.hrl index 7795ab6c18..38b0ddefd3 100644 --- a/lib/inets/src/http_server/httpd_internal.hrl +++ b/lib/inets/src/http_server/httpd_internal.hrl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2009-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %% @@ -21,7 +21,8 @@ -ifndef(httpd_internal_hrl). -define(httpd_internal_hrl, true). --include("inets_internal.hrl"). +-include_lib("inets/src/inets_app/inets_internal.hrl"). + -define(SERVICE, httpd). -define(hdri(Label, Content), ?report_important(Label, ?SERVICE, Content)). -define(hdrv(Label, Content), ?report_verbose(Label, ?SERVICE, Content)). diff --git a/lib/inets/src/http_server/httpd_manager.erl b/lib/inets/src/http_server/httpd_manager.erl index f2e8763907..b44bc77c41 100644 --- a/lib/inets/src/http_server/httpd_manager.erl +++ b/lib/inets/src/http_server/httpd_manager.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2000-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2000-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %% @@ -238,24 +238,25 @@ init([ConfigFile, ConfigList, AcceptTimeout, Addr, Port]) -> case (catch do_init(ConfigFile, ConfigList, AcceptTimeout, Addr, Port)) of {error, Reason} -> String = lists:flatten( - io_lib:format("Failed initiating " - "web server: ~n~p~n~p~n", - [ConfigFile,Reason])), + io_lib:format("Failed initiating web server: " + "~n~p" + "~n~p" + "~n", [ConfigFile, Reason])), error_logger:error_report(String), {stop, {error, Reason}}; {ok, State} -> {ok, State} end; -init([ConfigFile, ConfigList, AcceptTimeout, Addr, Port, - ListenInfo]) -> +init([ConfigFile, ConfigList, AcceptTimeout, Addr, Port, ListenInfo]) -> process_flag(trap_exit, true), case (catch do_init(ConfigFile, ConfigList, AcceptTimeout, Addr, Port, ListenInfo)) of {error, Reason} -> String = lists:flatten( - io_lib:format("Failed initiating " - "web server: ~n~p~n~p~n", - [ConfigFile,Reason])), + io_lib:format("Failed initiating web server: " + "~n~p" + "~n~p" + "~n", [ConfigFile, Reason])), error_logger:error_report(String), {stop, {error, Reason}}; {ok, State} -> @@ -264,13 +265,14 @@ init([ConfigFile, ConfigList, AcceptTimeout, Addr, Port, do_init(ConfigFile, ConfigList, AcceptTimeout, Addr, Port) -> NewConfigFile = proplists:get_value(file, ConfigList, ConfigFile), - ConfigDB = do_initial_store(ConfigList), - SocketType = httpd_conf:config(ConfigDB), + ConfigDB = do_initial_store(ConfigList), + SocketType = httpd_conf:lookup_socket_type(ConfigDB), case httpd_acceptor_sup:start_acceptor(SocketType, Addr, Port, ConfigDB, AcceptTimeout) of {ok, _Pid} -> - Status = [{max_conn,0}, {last_heavy_load,never}, - {last_connection,never}], + Status = [{max_conn, 0}, + {last_heavy_load, never}, + {last_connection, never}], State = #state{socket_type = SocketType, config_file = NewConfigFile, config_db = ConfigDB, @@ -284,7 +286,7 @@ do_init(ConfigFile, ConfigList, AcceptTimeout, Addr, Port) -> do_init(ConfigFile, ConfigList, AcceptTimeout, Addr, Port, ListenInfo) -> NewConfigFile = proplists:get_value(file, ConfigList, ConfigFile), ConfigDB = do_initial_store(ConfigList), - SocketType = httpd_conf:config(ConfigDB), + SocketType = httpd_conf:lookup_socket_type(ConfigDB), case httpd_acceptor_sup:start_acceptor(SocketType, Addr, Port, ConfigDB, AcceptTimeout, ListenInfo) of diff --git a/lib/inets/src/http_server/httpd_request.erl b/lib/inets/src/http_server/httpd_request.erl index 8eee08e766..883acbf585 100644 --- a/lib/inets/src/http_server/httpd_request.erl +++ b/lib/inets/src/http_server/httpd_request.erl @@ -19,22 +19,35 @@ -module(httpd_request). --include("http_internal.hrl"). +-include_lib("inets/src/http_lib/http_internal.hrl"). -include("httpd.hrl"). +-include("httpd_internal.hrl"). --export([parse/1, whole_body/2, validate/3, update_mod_data/5, - body_data/2]). +-export([ + parse/1, + whole_body/2, + validate/3, + update_mod_data/5, + body_data/2 + ]). %% Callback API - used for example if the header/body is received a %% little at a time on a socket. --export([parse_method/1, parse_uri/1, parse_version/1, parse_headers/1, - whole_body/1]). +-export([ + parse_method/1, parse_uri/1, parse_version/1, parse_headers/1, + whole_body/1 + ]). + %%%========================================================================= %%% Internal application API %%%========================================================================= parse([Bin, MaxSizes]) -> - parse_method(Bin, [], MaxSizes, []). + ?hdrt("parse", [{bin, Bin}, {max_sizes, MaxSizes}]), + parse_method(Bin, [], MaxSizes, []); +parse(Unknown) -> + ?hdrt("parse", [{unknown, Unknown}]), + exit({bad_args, Unknown}). %% Functions that may be returned during the decoding process %% if the input data is incompleate. @@ -119,30 +132,65 @@ update_mod_data(ModData, Method, RequestURI, HTTPVersion, Headers)-> %%% Internal functions %%%======================================================================== parse_method(<<>>, Method, MaxSizes, Result) -> + ?hdrt("parse_method - empty bin", + [{method, Method}, {max_sizes, MaxSizes}, {result, Result}]), {?MODULE, parse_method, [Method, MaxSizes, Result]}; parse_method(<<?SP, Rest/binary>>, Method, MaxSizes, Result) -> + ?hdrt("parse_method - SP begin", + [{rest, Rest}, + {method, Method}, + {max_sizes, MaxSizes}, + {result, Result}]), parse_uri(Rest, [], 0, MaxSizes, [string:strip(lists:reverse(Method)) | Result]); parse_method(<<Octet, Rest/binary>>, Method, MaxSizes, Result) -> + ?hdrt("parse_method", + [{octet, Octet}, + {rest, Rest}, + {method, Method}, + {max_sizes, MaxSizes}, + {result, Result}]), parse_method(Rest, [Octet | Method], MaxSizes, Result). -parse_uri(_, _, CurrSize, {MaxURI, _}, _) when CurrSize > MaxURI, - MaxURI =/= nolimit -> +parse_uri(_, _, CurrSize, {MaxURI, _}, _) + when (CurrSize > MaxURI) andalso (MaxURI =/= nolimit) -> + ?hdrt("parse_uri", + [{current_size, CurrSize}, + {max_uri, MaxURI}]), %% We do not know the version of the client as it comes after the %% uri send the lowest version in the response so that the client %% will be able to handle it. HttpVersion = "HTTP/0.9", {error, {uri_too_long, MaxURI}, HttpVersion}; parse_uri(<<>>, URI, CurrSize, MaxSizes, Result) -> + ?hdrt("parse_uri - empty bin", + [{uri, URI}, + {current_size, CurrSize}, + {max_sz, MaxSizes}, + {result, Result}]), {?MODULE, parse_uri, [URI, CurrSize, MaxSizes, Result]}; parse_uri(<<?SP, Rest/binary>>, URI, _, MaxSizes, Result) -> + ?hdrt("parse_uri - SP begin", + [{uri, URI}, + {max_sz, MaxSizes}, + {result, Result}]), parse_version(Rest, [], MaxSizes, [string:strip(lists:reverse(URI)) | Result]); %% Can happen if it is a simple HTTP/0.9 request e.i "GET /\r\n\r\n" -parse_uri(<<?CR, _Rest/binary>> = Data, URI, _,MaxSizes, Result) -> +parse_uri(<<?CR, _Rest/binary>> = Data, URI, _, MaxSizes, Result) -> + ?hdrt("parse_uri - CR begin", + [{uri, URI}, + {max_sz, MaxSizes}, + {result, Result}]), parse_version(Data, [], MaxSizes, [string:strip(lists:reverse(URI)) | Result]); parse_uri(<<Octet, Rest/binary>>, URI, CurrSize, MaxSizes, Result) -> + ?hdrt("parse_uri", + [{octet, Octet}, + {uri, URI}, + {curr_sz, CurrSize}, + {max_sz, MaxSizes}, + {result, Result}]), parse_uri(Rest, [Octet | URI], CurrSize + 1, MaxSizes, Result). parse_version(<<>>, Version, MaxSizes, Result) -> diff --git a/lib/inets/src/http_server/httpd_request_handler.erl b/lib/inets/src/http_server/httpd_request_handler.erl index fa832cba3f..a9db6e2058 100644 --- a/lib/inets/src/http_server/httpd_request_handler.erl +++ b/lib/inets/src/http_server/httpd_request_handler.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1997-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %% @@ -101,11 +101,13 @@ init([Manager, ConfigDB, AcceptTimeout]) -> Then = erlang:now(), + ?hdrd("negotiate", []), case http_transport:negotiate(SocketType, Socket, TimeOut) of {error, Error} -> + ?hdrd("negotiation failed", [{error, Error}]), exit(Error); %% Can be 'normal'. ok -> - ?hdrt("negotiated", []), + ?hdrt("negotiation successfull", []), NewTimeout = TimeOut - timer:now_diff(now(),Then) div 1000, continue_init(Manager, ConfigDB, SocketType, Socket, NewTimeout) end. @@ -121,12 +123,9 @@ continue_init(Manager, ConfigDB, SocketType, Socket, TimeOut) -> socket = Socket, init_data = InitData}, - MaxHeaderSize = httpd_util:lookup(ConfigDB, max_header_size, - ?HTTP_MAX_HEADER_SIZE), - MaxURISize = httpd_util:lookup(ConfigDB, max_uri_size, - ?HTTP_MAX_URI_SIZE), - NrOfRequest = httpd_util:lookup(ConfigDB, - max_keep_alive_request, infinity), + MaxHeaderSize = max_header_size(ConfigDB), + MaxURISize = max_uri_size(ConfigDB), + NrOfRequest = max_keep_alive_request(ConfigDB), {_, Status} = httpd_manager:new_connection(Manager), @@ -142,9 +141,10 @@ continue_init(Manager, ConfigDB, SocketType, Socket, TimeOut) -> ?hdrt("activate request timeout", []), NewState = activate_request_timeout(State), - ?hdrt("update socket options", []), - http_transport:setopts(SocketType, Socket, [binary,{packet, 0}, - {active, once}]), + ?hdrt("set socket options (binary, packet & active)", []), + http_transport:setopts(SocketType, Socket, + [binary, {packet, 0}, {active, once}]), + ?hdrt("init done", []), gen_server:enter_loop(?MODULE, [], NewState). @@ -180,21 +180,29 @@ handle_cast(Msg, State) -> %% {stop, Reason, State} %% Description: Handling all non call/cast messages %%-------------------------------------------------------------------- -handle_info({Proto, Socket, Data}, State = +handle_info({Proto, Socket, Data}, #state{mfa = {Module, Function, Args} = MFA, mod = #mod{socket_type = SockType, socket = Socket} = ModData} = State) when (((Proto =:= tcp) orelse (Proto =:= ssl) orelse (Proto =:= dummy)) andalso is_binary(Data)) -> + ?hdrd("received data", [{data, Data}, {proto, Proto}, {socket, Socket}, {socket_type, SockType}, {mfa, MFA}]), - case Module:Function([Data | Args]) of + +%% case (catch Module:Function([Data | Args])) of + PROCESSED = (catch Module:Function([Data | Args])), + + ?hdrt("data processed", [{processing_result, PROCESSED}]), + + case PROCESSED of {ok, Result} -> ?hdrd("data processed", [{result, Result}]), NewState = cancel_request_timeout(State), handle_http_msg(Result, NewState); + {error, {uri_too_long, MaxSize}, Version} -> ?hdrv("uri too long", [{max_size, MaxSize}, {version, Version}]), NewModData = ModData#mod{http_version = Version}, @@ -205,7 +213,8 @@ handle_info({Proto, Socket, Data}, State = {stop, normal, State#state{response_sent = true, mod = NewModData}}; {error, {header_too_long, MaxSize}, Version} -> - ?hdrv("header too long", [{max_size, MaxSize}, {version, Version}]), + ?hdrv("header too long", + [{max_size, MaxSize}, {version, Version}]), NewModData = ModData#mod{http_version = Version}, httpd_response:send_status(NewModData, 413, "Header too long"), Reason = io_lib:format("Header too long, max size is ~p~n", @@ -263,14 +272,16 @@ terminate(Reason, #state{response_sent = false, mod = ModData} = State) -> httpd_response:send_status(ModData, 500, none), error_log(httpd_util:reason_phrase(500), ModData), terminate(Reason, State#state{response_sent = true, mod = ModData}); -terminate(_, State) -> +terminate(_Reason, State) -> do_terminate(State). do_terminate(#state{mod = ModData, manager = Manager} = State) -> catch httpd_manager:done_connection(Manager), cancel_request_timeout(State), + %% receive after 5000 -> ok end, httpd_socket:close(ModData#mod.socket_type, ModData#mod.socket). + %%-------------------------------------------------------------------- %% code_change(OldVsn, State, Extra) -> {ok, NewState} %% @@ -279,6 +290,7 @@ do_terminate(#state{mod = ModData, manager = Manager} = State) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- @@ -383,9 +395,8 @@ is_host_specified_if_required(_, _, _) -> handle_body(#state{mod = #mod{config_db = ConfigDB}} = State) -> ?hdrt("handle body", []), - MaxHeaderSize = - httpd_util:lookup(ConfigDB, max_header_size, ?HTTP_MAX_HEADER_SIZE), - MaxBodySize = httpd_util:lookup(ConfigDB, max_body_size, nolimit), + MaxHeaderSize = max_header_size(ConfigDB), + MaxBodySize = max_body_size(ConfigDB), case handle_expect(State, MaxBodySize) of ok -> @@ -538,24 +549,23 @@ handle_response(#state{body = Body, {stop, normal, State#state{response_sent = true}}. handle_next_request(#state{mod = #mod{connection = true} = ModData, - max_keep_alive_request = Max} = State, Data) -> + max_keep_alive_request = Max} = State, Data) -> ?hdrt("handle next request", [{max, Max}]), + NewModData = #mod{socket_type = ModData#mod.socket_type, - socket = ModData#mod.socket, - config_db = ModData#mod.config_db, - init_data = ModData#mod.init_data}, - MaxHeaderSize = - httpd_util:lookup(ModData#mod.config_db, - max_header_size, ?HTTP_MAX_HEADER_SIZE), - MaxURISize = httpd_util:lookup(ModData#mod.config_db, max_uri_size, - ?HTTP_MAX_URI_SIZE), - TmpState = State#state{mod = NewModData, - mfa = {httpd_request, parse, [{MaxURISize, - MaxHeaderSize}]}, + socket = ModData#mod.socket, + config_db = ModData#mod.config_db, + init_data = ModData#mod.init_data}, + MaxHeaderSize = max_header_size(ModData#mod.config_db), + MaxURISize = max_uri_size(ModData#mod.config_db), + + MFA = {httpd_request, parse, [{MaxURISize, MaxHeaderSize}]}, + TmpState = State#state{mod = NewModData, + mfa = MFA, max_keep_alive_request = decrease(Max), - headers = undefined, - body = undefined, - response_sent = false}, + headers = undefined, + body = undefined, + response_sent = false}, NewState = activate_request_timeout(TmpState), @@ -596,7 +606,7 @@ decrease(N) -> error_log(ReasonString, Info) -> Error = lists:flatten( - io_lib:format("Error reading request:~s",[ReasonString])), + io_lib:format("Error reading request: ~s", [ReasonString])), error_log(mod_log, Info, Error), error_log(mod_disk_log, Info, Error). @@ -609,3 +619,21 @@ error_log(Mod, #mod{config_db = ConfigDB} = Info, String) -> _ -> ok end. + + +%%-------------------------------------------------------------------- +%% Config access wrapper functions +%%-------------------------------------------------------------------- + +max_header_size(ConfigDB) -> + httpd_util:lookup(ConfigDB, max_header_size, ?HTTP_MAX_HEADER_SIZE). + +max_uri_size(ConfigDB) -> + httpd_util:lookup(ConfigDB, max_uri_size, ?HTTP_MAX_URI_SIZE). + +max_body_size(ConfigDB) -> + httpd_util:lookup(ConfigDB, max_body_size, nolimit). + +max_keep_alive_request(ConfigDB) -> + httpd_util:lookup(ConfigDB, max_keep_alive_request, infinity). + diff --git a/lib/inets/src/http_server/mod_alias.erl b/lib/inets/src/http_server/mod_alias.erl index ec0a12242f..9c5a8cc1c6 100644 --- a/lib/inets/src/http_server/mod_alias.erl +++ b/lib/inets/src/http_server/mod_alias.erl @@ -103,6 +103,19 @@ real_name(ConfigDB, RequestURI, []) -> httpd_util:split_path(default_index(ConfigDB, RealName)), {ShortPath, Path, AfterPath}; +real_name(ConfigDB, RequestURI, [{MP,Replacement}|Rest]) + when element(1, MP) =:= re_pattern -> + case re:run(RequestURI, MP, [{capture,[]}]) of + match -> + NewURI = re:replace(RequestURI, MP, Replacement, [{return,list}]), + {ShortPath,_} = httpd_util:split_path(NewURI), + {Path,AfterPath} = + httpd_util:split_path(default_index(ConfigDB, NewURI)), + {ShortPath, Path, AfterPath}; + nomatch -> + real_name(ConfigDB, RequestURI, Rest) + end; + real_name(ConfigDB, RequestURI, [{FakeName,RealName}|Rest]) -> case inets_regexp:match(RequestURI, "^" ++ FakeName) of {match, _, _} -> @@ -120,6 +133,18 @@ real_name(ConfigDB, RequestURI, [{FakeName,RealName}|Rest]) -> real_script_name(_ConfigDB, _RequestURI, []) -> not_a_script; + +real_script_name(ConfigDB, RequestURI, [{MP,Replacement} | Rest]) + when element(1, MP) =:= re_pattern -> + case re:run(RequestURI, MP, [{capture,[]}]) of + match -> + ActualName = + re:replace(RequestURI, MP, Replacement, [{return,list}]), + httpd_util:split_script_path(default_index(ConfigDB, ActualName)); + nomatch -> + real_script_name(ConfigDB, RequestURI, Rest) + end; + real_script_name(ConfigDB, RequestURI, [{FakeName,RealName} | Rest]) -> case inets_regexp:match(RequestURI, "^" ++ FakeName) of {match,_,_} -> @@ -180,6 +205,8 @@ load("Alias " ++ Alias, []) -> {ok, _} -> {error,?NICE(httpd_conf:clean(Alias)++" is an invalid Alias")} end; +load("ReWrite " ++ Rule, Acc) -> + load_re_write(Rule, Acc, "ReWrite", re_write); load("ScriptAlias " ++ ScriptAlias, []) -> case inets_regexp:split(ScriptAlias, " ") of {ok, [FakeName, RealName]} -> @@ -189,6 +216,24 @@ load("ScriptAlias " ++ ScriptAlias, []) -> {ok, _} -> {error, ?NICE(httpd_conf:clean(ScriptAlias)++ " is an invalid ScriptAlias")} + end; +load("ScriptReWrite " ++ Rule, Acc) -> + load_re_write(Rule, Acc, "ScriptReWrite", script_re_write). + +load_re_write(Rule0, Acc, Type, Tag) -> + case lists:dropwhile( + fun ($\s) -> true; ($\t) -> true; (_) -> false end, + Rule0) of + "" -> + {error, ?NICE(httpd_conf:clean(Rule0)++" is an invalid "++Type)}; + Rule -> + case string:chr(Rule, $\s) of + 0 -> + {ok, Acc, {Tag, {Rule, ""}}}; + N -> + {Re, [_|Replacement]} = lists:split(N-1, Rule), + {ok, Acc, {Tag, {Re, Replacement}}} + end end. store({directory_index, Value} = Conf, _) when is_list(Value) -> @@ -200,16 +245,36 @@ store({directory_index, Value} = Conf, _) when is_list(Value) -> end; store({directory_index, Value}, _) -> {error, {wrong_type, {directory_index, Value}}}; -store({alias, {Fake, Real}} = Conf, _) - when is_list(Fake) andalso is_list(Real) -> +store({alias, {Fake, Real}} = Conf, _) + when is_list(Fake), is_list(Real) -> {ok, Conf}; store({alias, Value}, _) -> {error, {wrong_type, {alias, Value}}}; +store({re_write, {Re, Replacement}} = Conf, _) + when is_list(Re), is_list(Replacement) -> + case re:compile(Re) of + {ok, MP} -> + {ok, {alias, {MP, Replacement}}}; + {error,_} -> + {error, {re_compile, Conf}} + end; +store({re_write, _} = Conf, _) -> + {error, {wrong_type, Conf}}; store({script_alias, {Fake, Real}} = Conf, _) - when is_list(Fake) andalso is_list(Real) -> + when is_list(Fake), is_list(Real) -> {ok, Conf}; store({script_alias, Value}, _) -> - {error, {wrong_type, {script_alias, Value}}}. + {error, {wrong_type, {script_alias, Value}}}; +store({script_re_write, {Re, Replacement}} = Conf, _) + when is_list(Re), is_list(Replacement) -> + case re:compile(Re) of + {ok, MP} -> + {ok, {script_alias, {MP, Replacement}}}; + {error,_} -> + {error, {re_compile, Conf}} + end; +store({script_re_write, _} = Conf, _) -> + {error, {wrong_type, Conf}}. is_directory_index_list([]) -> true; diff --git a/lib/inets/src/http_server/mod_esi.erl b/lib/inets/src/http_server/mod_esi.erl index cb33544540..f7877aa9e2 100644 --- a/lib/inets/src/http_server/mod_esi.erl +++ b/lib/inets/src/http_server/mod_esi.erl @@ -29,6 +29,7 @@ -export([do/1, load/2, store/2]). -include("httpd.hrl"). +-include("httpd_internal.hrl"). -define(VMODULE,"ESI"). -define(DEFAULT_ERL_TIMEOUT,15000). @@ -37,6 +38,7 @@ %%%========================================================================= %%% API %%%========================================================================= + %%-------------------------------------------------------------------------- %% deliver(SessionID, Data) -> ok | {error, bad_sessionID} %% SessionID = pid() @@ -48,7 +50,7 @@ %% request handling process so it can forward it to the client. %%------------------------------------------------------------------------- deliver(SessionID, Data) when is_pid(SessionID) -> - SessionID ! {ok, Data}, + SessionID ! {esi_data, Data}, ok; deliver(_SessionID, _Data) -> {error, bad_sessionID}. @@ -65,6 +67,7 @@ deliver(_SessionID, _Data) -> %% Description: See httpd(3) ESWAPI CALLBACK FUNCTIONS %%------------------------------------------------------------------------- do(ModData) -> + ?hdrt("do", []), case proplists:get_value(status, ModData#mod.data) of {_StatusCode, _PhraseArgs, _Reason} -> {proceed, ModData#mod.data}; @@ -184,6 +187,7 @@ store({erl_script_nocache, Value}, _) -> %%% Internal functions %%%======================================================================== generate_response(ModData) -> + ?hdrt("generate response", []), case scheme(ModData#mod.request_uri, ModData#mod.config_db) of {eval, ESIBody, Modules} -> eval(ModData, ESIBody, Modules); @@ -235,6 +239,7 @@ alias_match_str(Alias, eval_script_alias) -> erl(#mod{method = Method} = ModData, ESIBody, Modules) when (Method =:= "GET") orelse (Method =:= "HEAD") -> + ?hdrt("erl", [{method, Method}]), case httpd_util:split(ESIBody,":|%3A|/",2) of {ok, [ModuleName, FuncAndInput]} -> case httpd_util:split(FuncAndInput,"[\?/]",2) of @@ -260,6 +265,7 @@ erl(#mod{request_uri = ReqUri, method = "PUT", http_version = Version, data = Data}, _ESIBody, _Modules) -> + ?hdrt("erl", [{method, put}]), {proceed, [{status,{501,{"PUT", ReqUri, Version}, ?NICE("Erl mechanism doesn't support method PUT")}}| Data]}; @@ -268,12 +274,14 @@ erl(#mod{request_uri = ReqUri, method = "DELETE", http_version = Version, data = Data}, _ESIBody, _Modules) -> + ?hdrt("erl", [{method, delete}]), {proceed,[{status,{501,{"DELETE", ReqUri, Version}, ?NICE("Erl mechanism doesn't support method DELETE")}}| Data]}; erl(#mod{method = "POST", entity_body = Body} = ModData, ESIBody, Modules) -> + ?hdrt("erl", [{method, post}]), case httpd_util:split(ESIBody,":|%3A|/",2) of {ok,[ModuleName, Function]} -> generate_webpage(ModData, ESIBody, Modules, @@ -289,6 +297,7 @@ generate_webpage(ModData, ESIBody, [all], Module, FunctionName, FunctionName, Input, ScriptElements); generate_webpage(ModData, ESIBody, Modules, Module, FunctionName, Input, ScriptElements) -> + ?hdrt("generate webpage", []), Function = list_to_atom(FunctionName), case lists:member(Module, Modules) of true -> @@ -309,8 +318,9 @@ generate_webpage(ModData, ESIBody, Modules, Module, FunctionName, %% Old API that waits for the dymnamic webpage to be totally generated %% before anythig is sent back to the client. -erl_scheme_webpage_whole(Module, Function, Env, Input, ModData) -> - case (catch Module:Function(Env, Input)) of +erl_scheme_webpage_whole(Mod, Func, Env, Input, ModData) -> + ?hdrt("erl_scheme_webpage_whole", [{module, Mod}, {function, Func}]), + case (catch Mod:Func(Env, Input)) of {'EXIT',{undef, _}} -> {proceed, [{status, {404, ModData#mod.request_uri, "Not found"}} | ModData#mod.data]}; @@ -347,6 +357,7 @@ erl_scheme_webpage_whole(Module, Function, Env, Input, ModData) -> %% in small chunks at the time during generation. erl_scheme_webpage_chunk(Mod, Func, Env, Input, ModData) -> process_flag(trap_exit, true), + ?hdrt("erl_scheme_webpage_chunk", [{module, Mod}, {function, Func}]), Self = self(), %% Spawn worker that generates the webpage. %% It would be nicer to use erlang:function_exported/3 but if the @@ -372,9 +383,12 @@ deliver_webpage_chunk(#mod{config_db = Db} = ModData, Pid) -> deliver_webpage_chunk(ModData, Pid, Timeout). deliver_webpage_chunk(#mod{config_db = Db} = ModData, Pid, Timeout) -> + ?hdrt("deliver_webpage_chunk", [{timeout, Timeout}]), case receive_headers(Timeout) of {error, Reason} -> %% Happens when webpage generator callback/3 is undefined + ?hdrv("deliver_webpage_chunk - failed receiving headers", + [{reason, Reason}]), {error, Reason}; {Headers, Body} -> case httpd_esi:handle_headers(Headers) of @@ -399,6 +413,7 @@ deliver_webpage_chunk(#mod{config_db = Db} = ModData, Pid, Timeout) -> IsDisableChunkedSend) end; timeout -> + ?hdrv("deliver_webpage_chunk - timeout", []), send_headers(ModData, {504, "Timeout"},[{"connection", "close"}]), httpd_socket:close(ModData#mod.socket_type, ModData#mod.socket), process_flag(trap_exit,false), @@ -407,11 +422,17 @@ deliver_webpage_chunk(#mod{config_db = Db} = ModData, Pid, Timeout) -> receive_headers(Timeout) -> receive + {esi_data, Chunk} -> + ?hdrt("receive_headers - received esi data (esi)", []), + httpd_esi:parse_headers(lists:flatten(Chunk)); {ok, Chunk} -> + ?hdrt("receive_headers - received esi data (ok)", []), httpd_esi:parse_headers(lists:flatten(Chunk)); {'EXIT', Pid, erl_scheme_webpage_chunk_undefined} when is_pid(Pid) -> + ?hdrd("receive_headers - exit:chunk-undef", []), {error, erl_scheme_webpage_chunk_undefined}; {'EXIT', Pid, Reason} when is_pid(Pid) -> + ?hdrv("receive_headers - exit", [{reason, Reason}]), exit({mod_esi_linked_process_died, Pid, Reason}) after Timeout -> timeout @@ -427,19 +448,29 @@ handle_body(_, #mod{method = "HEAD"} = ModData, _, _, Size, _) -> {proceed, [{response, {already_sent, 200, Size}} | ModData#mod.data]}; handle_body(Pid, ModData, Body, Timeout, Size, IsDisableChunkedSend) -> + ?hdrt("handle_body - send chunk", [{timeout, Timeout}, {size, Size}]), httpd_response:send_chunk(ModData, Body, IsDisableChunkedSend), receive + {esi_data, Data} -> + ?hdrt("handle_body - received data (esi)", []), + handle_body(Pid, ModData, Data, Timeout, Size + length(Data), + IsDisableChunkedSend); {ok, Data} -> + ?hdrt("handle_body - received data (ok)", []), handle_body(Pid, ModData, Data, Timeout, Size + length(Data), IsDisableChunkedSend); {'EXIT', Pid, normal} when is_pid(Pid) -> + ?hdrt("handle_body - exit:normal", []), httpd_response:send_final_chunk(ModData, IsDisableChunkedSend), {proceed, [{response, {already_sent, 200, Size}} | ModData#mod.data]}; {'EXIT', Pid, Reason} when is_pid(Pid) -> + ?hdrv("handle_body - exit", [{reason, Reason}]), httpd_response:send_final_chunk(ModData, IsDisableChunkedSend), exit({mod_esi_linked_process_died, Pid, Reason}) + after Timeout -> + ?hdrv("handle_body - timeout", []), process_flag(trap_exit,false), httpd_response:send_final_chunk(ModData, IsDisableChunkedSend), exit({mod_esi_linked_process_timeout, Pid}) @@ -473,6 +504,7 @@ eval(#mod{request_uri = ReqUri, method = "PUT", http_version = Version, data = Data}, _ESIBody, _Modules) -> + ?hdrt("eval", [{method, put}]), {proceed,[{status,{501,{"PUT", ReqUri, Version}, ?NICE("Eval mechanism doesn't support method PUT")}}| Data]}; @@ -481,6 +513,7 @@ eval(#mod{request_uri = ReqUri, method = "DELETE", http_version = Version, data = Data}, _ESIBody, _Modules) -> + ?hdrt("eval", [{method, delete}]), {proceed,[{status,{501,{"DELETE", ReqUri, Version}, ?NICE("Eval mechanism doesn't support method DELETE")}}| Data]}; @@ -489,12 +522,14 @@ eval(#mod{request_uri = ReqUri, method = "POST", http_version = Version, data = Data}, _ESIBody, _Modules) -> + ?hdrt("eval", [{method, post}]), {proceed,[{status,{501,{"POST", ReqUri, Version}, ?NICE("Eval mechanism doesn't support method POST")}}| Data]}; eval(#mod{method = Method} = ModData, ESIBody, Modules) - when Method == "GET"; Method == "HEAD" -> + when (Method =:= "GET") orelse (Method =:= "HEAD") -> + ?hdrt("eval", [{method, Method}]), case is_authorized(ESIBody, Modules) of true -> case generate_webpage(ESIBody) of diff --git a/lib/inets/src/inets_app/Makefile b/lib/inets/src/inets_app/Makefile index 33c9e34a3a..4632ff3b68 100644 --- a/lib/inets/src/inets_app/Makefile +++ b/lib/inets/src/inets_app/Makefile @@ -67,18 +67,15 @@ APPUP_TARGET = $(EBIN)/$(APPUP_FILE) # ---------------------------------------------------- -# INETS FLAGS -# ---------------------------------------------------- -INETS_FLAGS = -D'SERVER_SOFTWARE="$(APPLICATION)/$(VSN)"' - - -# ---------------------------------------------------- # FLAGS # ---------------------------------------------------- -ERL_COMPILE_FLAGS += $(INETS_FLAGS) \ - +'{parse_transform,sys_pre_attributes}' \ - +'{attribute,insert,app_vsn,$(APP_VSN)}' +include inets.mk + +ERL_COMPILE_FLAGS += \ + $(INETS_FLAGS) \ + $(INETS_ERL_COMPILE_FLAGS) \ + -I../../include # ---------------------------------------------------- @@ -112,7 +109,8 @@ include $(ERL_TOP)/make/otp_release_targets.mk release_spec: opt $(INSTALL_DIR) $(RELSYSDIR)/src - $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src + $(INSTALL_DIR) $(RELSYSDIR)/src/inets_app + $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src/inets_app $(INSTALL_DIR) $(RELSYSDIR)/ebin $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin diff --git a/lib/inets/src/inets_app/inets.app.src b/lib/inets/src/inets_app/inets.app.src index 04f6365b98..cb036157a5 100644 --- a/lib/inets/src/inets_app/inets.app.src +++ b/lib/inets/src/inets_app/inets.app.src @@ -107,5 +107,6 @@ tftp_sup ]}, {registered,[inets_sup, httpc_manager]}, + %% If the "new" ssl is used then 'crypto' must be started before inets. {applications,[kernel,stdlib]}, {mod,{inets_app,[]}}]}. diff --git a/lib/inets/src/inets_app/inets.appup.src b/lib/inets/src/inets_app/inets.appup.src index 718f37b09e..64fe664006 100644 --- a/lib/inets/src/inets_app/inets.appup.src +++ b/lib/inets/src/inets_app/inets.appup.src @@ -18,29 +18,24 @@ {"%VSN%", [ + {"5.3.3", + [ + {restart_application, inets} + ] + }, {"5.3.2", [ - {load_module, http_util, soft_purge, soft_purge, []}, - {load_module, httpc_cookie, soft_purge, soft_purge, []} + {restart_application, inets} ] }, {"5.3.1", [ - {load_module, http_util, soft_purge, soft_purge, []}, - {load_module, httpc, soft_purge, soft_purge, []}, - {load_module, httpc_cookie, soft_purge, soft_purge, []}, - {update, httpc_handler, soft, soft_purge, soft_purge, [httpc_manager]}, - {update, httpc_manager, soft, soft_purge, soft_purge, []} + {restart_application, inets} ] }, {"5.3", [ - {load_module, http_util, soft_purge, soft_purge, []}, - {load_module, httpc, soft_purge, soft_purge, []}, - {load_module, httpc_cookie, soft_purge, soft_purge, []}, - {update, httpc_handler, soft, soft_purge, soft_purge, [httpc_manager]}, - {update, httpc_manager, soft, soft_purge, soft_purge, []}, - {load_module, mod_esi, soft_purge, soft_purge, []} + {restart_application, inets} ] }, {"5.2", @@ -60,29 +55,24 @@ } ], [ + {"5.3.3", + [ + {restart_application, inets} + ] + }, {"5.3.2", [ - {load_module, http_util, soft_purge, soft_purge, []}, - {load_module, httpc_cookie, soft_purge, soft_purge, []} + {restart_application, inets} ] }, {"5.3.1", [ - {load_module, http_util, soft_purge, soft_purge, []}, - {load_module, httpc, soft_purge, soft_purge, []}, - {load_module, httpc_cookie, soft_purge, soft_purge, []}, - {update, httpc_handler, soft, soft_purge, soft_purge, [httpc_manager]}, - {update, httpc_manager, soft, soft_purge, soft_purge, []} + {restart_application, inets} ] }, {"5.3", [ - {load_module, http_util, soft_purge, soft_purge, []}, - {load_module, httpc, soft_purge, soft_purge, []}, - {load_module, httpc_cookie, soft_purge, soft_purge, []}, - {update, httpc_handler, soft, soft_purge, soft_purge, [httpc_manager]}, - {update, httpc_manager, soft, soft_purge, soft_purge, []}, - {load_module, mod_esi, soft_purge, soft_purge, []} + {restart_application, inets} ] }, {"5.2", diff --git a/lib/inets/src/inets_app/inets.mk b/lib/inets/src/inets_app/inets.mk new file mode 100644 index 0000000000..b6e9fe1d96 --- /dev/null +++ b/lib/inets/src/inets_app/inets.mk @@ -0,0 +1,45 @@ +#-*-makefile-*- ; force emacs to enter makefile-mode + +# %CopyrightBegin% +# +# Copyright Ericsson AB 2010. All Rights Reserved. +# +# The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# %CopyrightEnd% + +ifeq ($(INETS_TRACE), io) +ERL_COMPILE_FLAGS += -Dinets_trace_io +endif + +ifeq ($(INETS_DEBUG), true) +ERL_COMPILE_FLAGS += -Dinets_debug +endif + +ifeq ($(USE_INETS_HIPE), true) +ERL_COMPILE_FLAGS += +native +endif + +ifeq ($(WARN_UNUSED_WARS), true) +ERL_COMPILE_FLAGS += +warn_unused_vars +endif + +INETS_APP_VSN_COMPILE_FLAGS = \ + +'{parse_transform,sys_pre_attributes}' \ + +'{attribute,insert,app_vsn,$(APP_VSN)}' + +INETS_FLAGS = -D'SERVER_SOFTWARE="$(APPLICATION)/$(VSN)"' + +INETS_ERL_COMPILE_FLAGS += \ + -pa $(ERL_TOP)/lib/inets/ebin \ + $(INETS_APP_VSN_COMPILE_FLAGS) + diff --git a/lib/inets/src/inets_app/inets_service.erl b/lib/inets/src/inets_app/inets_service.erl index 3499314d54..e9eb9892f2 100644 --- a/lib/inets/src/inets_app/inets_service.erl +++ b/lib/inets/src/inets_app/inets_service.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2007-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2007-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %% @@ -61,5 +61,5 @@ behaviour_info(_) -> %% service_info() -> [{Property, Value}] | {error, Reason} -%% ex: http:service_info() -> [{profile, ProfileName}] +%% ex: httpc:service_info() -> [{profile, ProfileName}] %% httpd:service_info() -> [{host, Host}, {port, Port}] diff --git a/lib/inets/src/tftp/Makefile b/lib/inets/src/tftp/Makefile index b4339da1e2..759b70c8e4 100644 --- a/lib/inets/src/tftp/Makefile +++ b/lib/inets/src/tftp/Makefile @@ -56,17 +56,16 @@ TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) # ---------------------------------------------------- -# INETS FLAGS +# FLAGS # ---------------------------------------------------- -INETS_FLAGS = -D'SERVER_SOFTWARE="$(APPLICATION)/$(VSN)"' +include ../inets_app/inets.mk -# ---------------------------------------------------- -# FLAGS -# ---------------------------------------------------- -ERL_COMPILE_FLAGS += $(INETS_FLAGS) \ - +'{parse_transform,sys_pre_attributes}' \ - +'{attribute,insert,app_vsn,$(APP_VSN)}' +ERL_COMPILE_FLAGS += \ + $(INETS_FLAGS) \ + $(INETS_ERL_COMPILE_FLAGS) \ + -I../../include \ + -I../inets_app # ---------------------------------------------------- @@ -87,9 +86,10 @@ docs: include $(ERL_TOP)/make/otp_release_targets.mk release_spec: opt - $(INSTALL_DIR) $(RELSYSDIR)/src - $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src - $(INSTALL_DIR) $(RELSYSDIR)/ebin + $(INSTALL_DIR) $(RELSYSDIR)/src + $(INSTALL_DIR) $(RELSYSDIR)/src/tftp + $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src/tftp + $(INSTALL_DIR) $(RELSYSDIR)/ebin $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin release_docs_spec: diff --git a/lib/inets/test/Makefile b/lib/inets/test/Makefile index 668752da9e..bb7f2186af 100644 --- a/lib/inets/test/Makefile +++ b/lib/inets/test/Makefile @@ -143,6 +143,8 @@ else INETS_FLAGS += -Dhttpd_security_verbosity=log endif +INETS_FLAGS += -pa ../../inets/ebin + INETS_ROOT = ../../inets MODULES = \ @@ -241,8 +243,11 @@ RELTESTSYSBINDIR = $(RELTESTSYSALLDATADIR)/bin # The path to the test_server ebin dir is needed when # running the target "targets". # ---------------------------------------------------- -ERL_COMPILE_FLAGS += -pa ../../../internal_tools/test_server/ebin \ - $(INCLUDES) $(FTP_FLAGS) $(INETS_FLAGS) +ERL_COMPILE_FLAGS += \ + -pa ../../../internal_tools/test_server/ebin \ + $(INCLUDES) \ + $(FTP_FLAGS) \ + $(INETS_FLAGS) # ---------------------------------------------------- # Targets diff --git a/lib/inets/test/ftp_suite_lib.erl b/lib/inets/test/ftp_suite_lib.erl index 75e1a5a7f9..5e27bc3a86 100644 --- a/lib/inets/test/ftp_suite_lib.erl +++ b/lib/inets/test/ftp_suite_lib.erl @@ -48,14 +48,17 @@ -ifdef(ftp_debug_client). -define(ftp_open(Host, Flags), - do_ftp_open(Host, [debug, {timeout, timer:seconds(15)}] ++ Flags)). + do_ftp_open(Host, [{debug, debug}, + {timeout, timer:seconds(15)} | Flags])). -else. -ifdef(ftp_trace_client). -define(ftp_open(Host, Flags), - do_ftp_open(Host, [trace, {timeout, timer:seconds(15)}] ++ Flags)). + do_ftp_open(Host, [{debug, trace}, + {timeout, timer:seconds(15)} | Flags])). -else. -define(ftp_open(Host, Flags), - do_ftp_open(Host, [verbose, {timeout, timer:seconds(15)}] ++ Flags)). + do_ftp_open(Host, [{verbose, true}, + {timeout, timer:seconds(15)} | Flags])). -endif. -endif. @@ -113,9 +116,7 @@ get_ftpd_host([Host|Hosts]) -> p("get_ftpd_host -> entry with" "~n Host: ~p" "~n", [Host]), - case (catch ftp:open({option_list, - [{host, Host}, {port, ?FTP_PORT}, - {timeout, 20000}]})) of + case (catch ftp:open(Host, [{port, ?FTP_PORT}, {timeout, 20000}])) of {ok, Pid} -> (catch ftp:close(Pid)), {ok, Host}; @@ -212,7 +213,7 @@ do_init_per_testcase(Case, Config) inets:start(), NewConfig = close_connection(watch_dog(Config)), Host = ftp_host(Config), - case (catch ?ftp_open(Host, [])) of + case (catch ?ftp_open(Host, [{mode, passive}])) of {ok, Pid} -> [{ftp, Pid} | data_dir(NewConfig)]; {skip, _} = SKIP -> @@ -225,9 +226,8 @@ do_init_per_testcase(Case, Config) inets:start(), NewConfig = close_connection(watch_dog(Config)), Host = ftp_host(Config), - case (catch ?ftp_open(Host, [])) of + case (catch ?ftp_open(Host, [{mode, active}])) of {ok, Pid} -> - ok = ftp:force_active(Pid), [{ftp, Pid} | data_dir(NewConfig)]; {skip, _} = SKIP -> SKIP @@ -240,11 +240,10 @@ do_init_per_testcase(Case, Config) io:format(user, "~n~n*** INIT ~w:~w ***~n~n", [?MODULE, Case]), NewConfig = close_connection(watch_dog(Config)), Host = ftp_host(Config), - Opts = [{host, Host}, - {port, ?FTP_PORT}, - {flags, [verbose]}, + Opts = [{port, ?FTP_PORT}, + {verbose, true}, {progress, {?MODULE, progress, #progress{}}}], - case ftp:open({option_list, Opts}) of + case ftp:open(Host, Opts) of {ok, Pid} -> ok = ftp:user(Pid, ?FTP_USER, ?FTP_PASS), [{ftp, Pid} | data_dir(NewConfig)]; @@ -257,22 +256,23 @@ do_init_per_testcase(Case, Config) -> inets:start(), NewConfig = close_connection(watch_dog(Config)), Host = ftp_host(Config), - Flags = + Opts1 = if ((Case =:= passive_ip_v6_disabled) orelse (Case =:= active_ip_v6_disabled)) -> - [ip_v6_disabled]; + [{ipfamily, inet}]; true -> [] end, - case (catch ?ftp_open(Host, Flags)) of + Opts2 = + case string:tokens(atom_to_list(Case), [$_]) of + [_, "active" | _] -> + [{mode, active} | Opts1]; + _ -> + [{mode, passive} | Opts1] + end, + case (catch ?ftp_open(Host, Opts2)) of {ok, Pid} -> - case string:tokens(atom_to_list(Case), [$_]) of - [_, "active"|_] -> - ok = ftp:force_active(Pid); - _ -> - ok - end, ok = ftp:user(Pid, ?FTP_USER, ?FTP_PASS), [{ftp, Pid} | data_dir(NewConfig)]; {skip, _} = SKIP -> @@ -365,6 +365,7 @@ open(Config) when is_list(Config) -> Host = ftp_host(Config), (catch tc_open(Host)). + tc_open(Host) -> {ok, Pid} = ?ftp_open(Host, []), ok = ftp:close(Pid), @@ -374,8 +375,9 @@ tc_open(Host) -> {flags, [verbose]}, {timeout, 30000}]}), ok = ftp:close(Pid1), - {error, ehost} = ftp:open({option_list, [{port, ?FTP_PORT}, - {flags, [verbose]}]}), + + {error, ehost} = + ftp:open({option_list, [{port, ?FTP_PORT}, {flags, [verbose]}]}), {ok, Pid2} = ftp:open(Host), ok = ftp:close(Pid2), @@ -408,6 +410,15 @@ tc_open(Host) -> {mode, cool}]}), test_server:sleep(100), ok = ftp:close(Pid6), + + {ok, Pid7} = + ftp:open(Host, [{port, ?FTP_PORT}, {verbose, true}, {timeout, 30000}]), + ok = ftp:close(Pid7), + + {ok, Pid8} = + ftp:open(Host, ?FTP_PORT), + ok = ftp:close(Pid8), + ok. @@ -420,7 +431,7 @@ open_port(suite) -> []; open_port(Config) when is_list(Config) -> Host = ftp_host(Config), - {ok, Pid} = ftp:open(Host, ?FTP_PORT), + {ok, Pid} = ftp:open(Host, [{port, ?FTP_PORT}]), ok = ftp:close(Pid), {error, ehost} = ftp:open(?BAD_HOST, []), ok. @@ -954,26 +965,39 @@ api_missuse(doc)-> ["Test that behaviour of the ftp process if the api is abused"]; api_missuse(suite) -> []; api_missuse(Config) when is_list(Config) -> + io:format("api_missuse -> entry~n", []), + Flag = process_flag(trap_exit, true), Pid = ?config(ftp, Config), Host = ftp_host(Config), - + %% Serious programming fault, connetion will be shut down - {error, {connection_terminated, 'API_violation'}} = - gen_server:call(Pid, {self(), foobar, 10}, infinity), + io:format("api_missuse -> verify bad call termination (~p)~n", [Pid]), + case (catch gen_server:call(Pid, {self(), foobar, 10}, infinity)) of + {error, {connection_terminated, 'API_violation'}} -> + ok; + Unexpected1 -> + exit({unexpected_result, Unexpected1}) + end, test_server:sleep(500), undefined = process_info(Pid, status), + io:format("api_missuse -> start new client~n", []), {ok, Pid2} = ?ftp_open(Host, []), %% Serious programming fault, connetion will be shut down + io:format("api_missuse -> verify bad cast termination~n", []), gen_server:cast(Pid2, {self(), foobar, 10}), test_server:sleep(500), undefined = process_info(Pid2, status), + io:format("api_missuse -> start new client~n", []), {ok, Pid3} = ?ftp_open(Host, []), %% Could be an innocent misstake the connection lives. + io:format("api_missuse -> verify bad bang~n", []), Pid3 ! foobar, test_server:sleep(500), {status, _} = process_info(Pid3, status), + process_flag(trap_exit, Flag), + io:format("api_missuse -> done~n", []), ok. @@ -1525,11 +1549,11 @@ split([C| Cs], I, Is) -> split([], I, Is) -> lists:reverse([lists:reverse(I)| Is]). -do_ftp_open(Host, Flags) -> +do_ftp_open(Host, Opts) -> io:format("do_ftp_open -> entry with" - "~n Host: ~p" - "~n Flags: ~p", [Host, Flags]), - case ftp:open(Host, Flags) of + "~n Host: ~p" + "~n Opts: ~p", [Host, Opts]), + case ftp:open(Host, Opts) of {ok, _} = OK -> OK; {error, Reason} -> diff --git a/lib/inets/test/httpc_SUITE.erl b/lib/inets/test/httpc_SUITE.erl index aa65fb1197..b5fd896001 100644 --- a/lib/inets/test/httpc_SUITE.erl +++ b/lib/inets/test/httpc_SUITE.erl @@ -87,8 +87,14 @@ all(suite) -> http_headers_dummy, http_bad_response, ssl_head, + ossl_head, + essl_head, ssl_get, + ossl_get, + essl_get, ssl_trace, + ossl_trace, + essl_trace, http_redirect, http_redirect_loop, http_internal_server_error, @@ -179,49 +185,66 @@ init_per_testcase(otp_8154_1 = Case, Config) -> init_per_testcase(Case, Config) -> init_per_testcase(Case, 2, Config). +init_per_testcase_ssl(Tag, PrivDir, SslConfFile, Config) -> + tsp("init_per_testcase_ssl -> stop ssl"), + application:stop(ssl), + Config2 = lists:keydelete(local_ssl_server, 1, Config), + %% Will start inets + tsp("init_per_testcase_ssl -> try start http server (including inets)"), + Server = inets_test_lib:start_http_server( + filename:join(PrivDir, SslConfFile), Tag), + tsp("init_per_testcase -> Server: ~p", [Server]), + [{local_ssl_server, Server} | Config2]. + init_per_testcase(Case, Timeout, Config) -> - io:format(user, "~n~n*** INIT ~w:~w[~w] ***~n~n", + io:format(user, "~n~n*** INIT ~w:[~w][~w] ***~n~n", [?MODULE, Timeout, Case]), - PrivDir = ?config(priv_dir, Config), + PrivDir = ?config(priv_dir, Config), + tsp("init_per_testcase -> stop inets"), application:stop(inets), - Dog = test_server:timetrap(inets_test_lib:minutes(Timeout)), - TmpConfig = lists:keydelete(watchdog, 1, Config), - IpConfFile = integer_to_list(?IP_PORT) ++ ".conf", + Dog = test_server:timetrap(inets_test_lib:minutes(Timeout)), + TmpConfig = lists:keydelete(watchdog, 1, Config), + IpConfFile = integer_to_list(?IP_PORT) ++ ".conf", SslConfFile = integer_to_list(?SSL_PORT) ++ ".conf", + %% inets:enable_trace(max, io, httpd), + %% inets:enable_trace(max, io, httpc), + inets:enable_trace(max, io, all), + NewConfig = case atom_to_list(Case) of - "ssl" ++ _ -> - application:stop(ssl), - TmpConfig2 = - lists:keydelete(local_ssl_server, 1, TmpConfig), - %% Will start inets - Server = - inets_test_lib:start_http_server( - filename:join(PrivDir, SslConfFile)), - [{watchdog, Dog}, {local_ssl_server, Server} | TmpConfig2]; + [$s, $s, $l | _] -> + init_per_testcase_ssl(ssl, PrivDir, SslConfFile, [{watchdog, Dog} | TmpConfig]); + + [$o, $s, $s, $l | _] -> + init_per_testcase_ssl(ossl, PrivDir, SslConfFile, [{watchdog, Dog} | TmpConfig]); + + [$e, $s, $s, $l | _] -> + init_per_testcase_ssl(essl, PrivDir, SslConfFile, [{watchdog, Dog} | TmpConfig]); + "proxy" ++ Rest -> - case Rest of - "_https_not_supported" -> - inets:start(), - case (catch application:start(ssl)) of - ok -> - [{watchdog, Dog} | TmpConfig]; - _ -> - [{skip, - "SSL does not seem to be supported"} - | TmpConfig] - end; - _ -> - case is_proxy_available(?PROXY, ?PROXY_PORT) of - true -> - inets:start(), - [{watchdog, Dog} | TmpConfig]; - false -> - [{skip, "Failed to contact proxy"} | - TmpConfig] - end - end; + case Rest of + "_https_not_supported" -> + tsp("init_per_testcase -> [proxy case] start inets"), + inets:start(), + tsp("init_per_testcase -> [proxy case] start ssl"), + case (catch application:start(ssl)) of + ok -> + [{watchdog, Dog} | TmpConfig]; + _ -> + [{skip, "SSL does not seem to be supported"} + | TmpConfig] + end; + _ -> + case is_proxy_available(?PROXY, ?PROXY_PORT) of + true -> + inets:start(), + [{watchdog, Dog} | TmpConfig]; + false -> + [{skip, "Failed to contact proxy"} | + TmpConfig] + end + end; _ -> TmpConfig2 = lists:keydelete(local_server, 1, TmpConfig), Server = @@ -231,13 +254,12 @@ init_per_testcase(Case, Timeout, Config) -> [{watchdog, Dog}, {local_server, Server} | TmpConfig2] end, - http:set_options([{proxy, {{?PROXY, ?PROXY_PORT}, - ["localhost", ?IPV6_LOCAL_HOST]}}]), - inets:enable_trace(max, io, httpc), - %% inets:enable_trace(max, io, all), + httpc:set_options([{proxy, {{?PROXY, ?PROXY_PORT}, + ["localhost", ?IPV6_LOCAL_HOST]}}]), %% snmp:set_trace([gen_tcp, inet_tcp, prim_inet]), NewConfig. + %%-------------------------------------------------------------------- %% Function: end_per_testcase(Case, Config) -> _ %% Case - atom() @@ -306,7 +328,7 @@ http_head(Config) when is_list(Config) -> ok -> Port = ?config(local_port, Config), URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", - case http:request(head, {URL, []}, [], []) of + case httpc:request(head, {URL, []}, [], []) of {ok, {{_,200,_}, [_ | _], []}} -> ok; {ok, WrongReply} -> @@ -337,7 +359,7 @@ http_get(Config) when is_list(Config) -> HttpOptions1 = [{timeout, Timeout}, {connect_timeout, ConnTimeout}], Options1 = [], Body = - case http:request(Method, Request, HttpOptions1, Options1) of + case httpc:request(Method, Request, HttpOptions1, Options1) of {ok, {{_,200,_}, [_ | _], ReplyBody = [_ | _]}} -> ReplyBody; {ok, UnexpectedReply1} -> @@ -346,12 +368,12 @@ http_get(Config) when is_list(Config) -> tsf({bad_reply, Error1}) end, - %% eqvivivalent to http:request(get, {URL, []}, [], []), + %% eqvivivalent to httpc:request(get, {URL, []}, [], []), inets_test_lib:check_body(Body), HttpOptions2 = [], Options2 = [{body_format, binary}], - case http:request(Method, Request, HttpOptions2, Options2) of + case httpc:request(Method, Request, HttpOptions2, Options2) of {ok, {{_,200,_}, [_ | _], Bin}} when is_binary(Bin) -> ok; {ok, {{_,200,_}, [_ | _], BadBin}} -> @@ -390,11 +412,11 @@ http_post(Config) when is_list(Config) -> Body = lists:duplicate(100, "1"), {ok, {{_,200,_}, [_ | _], [_ | _]}} = - http:request(post, {URL, [{"expect","100-continue"}], + httpc:request(post, {URL, [{"expect","100-continue"}], "text/plain", Body}, [], []), {ok, {{_,504,_}, [_ | _], []}} = - http:request(post, {URL, [{"expect","100-continue"}], + httpc:request(post, {URL, [{"expect","100-continue"}], "text/plain", "foobar"}, [], []); _ -> {skip, "Failed to start local http-server"} @@ -411,13 +433,13 @@ http_emulate_lower_versions(Config) when is_list(Config) -> Port = ?config(local_port, Config), URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", {ok, Body0} = - http:request(get, {URL, []}, [{version, "HTTP/0.9"}], []), + httpc:request(get, {URL, []}, [{version, "HTTP/0.9"}], []), inets_test_lib:check_body(Body0), {ok, {{"HTTP/1.0", 200, _}, [_ | _], Body1 = [_ | _]}} = - http:request(get, {URL, []}, [{version, "HTTP/1.0"}], []), + httpc:request(get, {URL, []}, [{version, "HTTP/1.0"}], []), inets_test_lib:check_body(Body1), {ok, {{"HTTP/1.1", 200, _}, [_ | _], Body2 = [_ | _]}} = - http:request(get, {URL, []}, [{version, "HTTP/1.1"}], []), + httpc:request(get, {URL, []}, [{version, "HTTP/1.1"}], []), inets_test_lib:check_body(Body2); _-> {skip, "Failed to start local http-server"} @@ -431,24 +453,24 @@ http_relaxed(doc) -> http_relaxed(suite) -> []; http_relaxed(Config) when is_list(Config) -> - ok = http:set_options([{ipv6, disabled}]), % also test the old option - %% ok = http:set_options([{ipfamily, inet}]), + ok = httpc:set_options([{ipv6, disabled}]), % also test the old option + %% ok = httpc:set_options([{ipfamily, inet}]), {DummyServerPid, Port} = dummy_server(self(), ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/missing_reason_phrase.html", {error, Reason} = - http:request(get, {URL, []}, [{relaxed, false}], []), + httpc:request(get, {URL, []}, [{relaxed, false}], []), test_server:format("Not relaxed: ~p~n", [Reason]), {ok, {{_, 200, _}, [_ | _], [_ | _]}} = - http:request(get, {URL, []}, [{relaxed, true}], []), + httpc:request(get, {URL, []}, [{relaxed, true}], []), DummyServerPid ! stop, - ok = http:set_options([{ipv6, enabled}]), - %% ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* + ok = httpc:set_options([{ipv6, enabled}]), + %% ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* ok. @@ -458,7 +480,7 @@ http_dummy_pipe(doc) -> http_dummy_pipe(suite) -> []; http_dummy_pipe(Config) when is_list(Config) -> - ok = http:set_options([{ipfamily, inet}]), + ok = httpc:set_options([{ipfamily, inet}]), {DummyServerPid, Port} = dummy_server(self(), ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/foobar.html", @@ -466,7 +488,7 @@ http_dummy_pipe(Config) when is_list(Config) -> test_pipeline(URL), DummyServerPid ! stop, - ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* + ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* ok. http_inets_pipe(doc) -> @@ -488,11 +510,11 @@ test_pipeline(URL) -> p("test_pipeline -> entry with" "~n URL: ~p", [URL]), - http:set_options([{pipeline_timeout, 50000}]), + httpc:set_options([{pipeline_timeout, 50000}]), p("test_pipeline -> issue (async) request 1"), {ok, RequestId1} = - http:request(get, {URL, []}, [], [{sync, false}]), + httpc:request(get, {URL, []}, [], [{sync, false}]), test_server:format("RequestId1: ~p~n", [RequestId1]), p("test_pipeline -> RequestId1: ~p", [RequestId1]), @@ -502,13 +524,13 @@ test_pipeline(URL) -> p("test_pipeline -> issue (async) request 2"), {ok, RequestId2} = - http:request(get, {URL, []}, [], [{sync, false}]), + httpc:request(get, {URL, []}, [], [{sync, false}]), tsp("RequestId2: ~p", [RequestId2]), p("test_pipeline -> RequestId2: ~p", [RequestId2]), p("test_pipeline -> issue (sync) request 3"), {ok, {{_,200,_}, [_ | _], [_ | _]}} = - http:request(get, {URL, []}, [], []), + httpc:request(get, {URL, []}, [], []), p("test_pipeline -> expect reply for (async) request 1 or 2"), receive @@ -544,18 +566,18 @@ test_pipeline(URL) -> p("test_pipeline -> issue (async) request 4"), {ok, RequestId3} = - http:request(get, {URL, []}, [], [{sync, false}]), + httpc:request(get, {URL, []}, [], [{sync, false}]), tsp("RequestId3: ~p", [RequestId3]), p("test_pipeline -> RequestId3: ~p", [RequestId3]), p("test_pipeline -> issue (async) request 5"), {ok, RequestId4} = - http:request(get, {URL, []}, [], [{sync, false}]), + httpc:request(get, {URL, []}, [], [{sync, false}]), tsp("RequestId4: ~p~n", [RequestId4]), p("test_pipeline -> RequestId4: ~p", [RequestId4]), p("test_pipeline -> cancel (async) request 4"), - ok = http:cancel_request(RequestId3), + ok = httpc:cancel_request(RequestId3), p("test_pipeline -> expect *no* reply for cancelled (async) request 4 (for 3 secs)"), receive @@ -607,7 +629,7 @@ http_trace(Config) when is_list(Config) -> ok -> Port = ?config(local_port, Config), URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", - case http:request(trace, {URL, []}, [], []) of + case httpc:request(trace, {URL, []}, [], []) of {ok, {{_,200,_}, [_ | _], "TRACE /dummy.html" ++ _}} -> ok; {ok, {{_,200,_}, [_ | _], WrongBody}} -> @@ -631,7 +653,7 @@ http_async(Config) when is_list(Config) -> Port = ?config(local_port, Config), URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", {ok, RequestId} = - http:request(get, {URL, []}, [], [{sync, false}]), + httpc:request(get, {URL, []}, [], [{sync, false}]), Body = receive @@ -644,8 +666,8 @@ http_async(Config) when is_list(Config) -> inets_test_lib:check_body(binary_to_list(Body)), {ok, NewRequestId} = - http:request(get, {URL, []}, [], [{sync, false}]), - ok = http:cancel_request(NewRequestId), + httpc:request(get, {URL, []}, [], [{sync, false}]), + ok = httpc:cancel_request(NewRequestId), receive {http, {NewRequestId, _NewResult}} -> test_server:fail(http_cancel_request_failed) @@ -669,9 +691,9 @@ http_save_to_file(Config) when is_list(Config) -> Port = ?config(local_port, Config), URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", {ok, saved_to_file} - = http:request(get, {URL, []}, [], [{stream, FilePath}]), + = httpc:request(get, {URL, []}, [], [{stream, FilePath}]), {ok, Bin} = file:read_file(FilePath), - {ok, {{_,200,_}, [_ | _], Body}} = http:request(URL), + {ok, {{_,200,_}, [_ | _], Body}} = httpc:request(URL), Bin == Body; _ -> {skip, "Failed to start local http-server"} @@ -690,7 +712,7 @@ http_save_to_file_async(Config) when is_list(Config) -> FilePath = filename:join(PrivDir, "dummy.html"), Port = ?config(local_port, Config), URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", - {ok, RequestId} = http:request(get, {URL, []}, [], + {ok, RequestId} = httpc:request(get, {URL, []}, [], [{stream, FilePath}, {sync, false}]), receive @@ -701,7 +723,7 @@ http_save_to_file_async(Config) when is_list(Config) -> end, {ok, Bin} = file:read_file(FilePath), - {ok, {{_,200,_}, [_ | _], Body}} = http:request(URL), + {ok, {{_,200,_}, [_ | _], Body}} = httpc:request(URL), Bin == Body; _ -> {skip, "Failed to start local http-server"} @@ -731,7 +753,7 @@ http_headers(Config) when is_list(Config) -> Date = httpd_util:rfc1123_date({date(), time()}), {ok, {{_,200,_}, [_ | _], [_ | _]}} = - http:request(get, {URL, [{"If-Modified-Since", + httpc:request(get, {URL, [{"If-Modified-Since", Mod}, {"From","[email protected]"}, {"Date", Date} @@ -742,7 +764,7 @@ http_headers(Config) when is_list(Config) -> CreatedSec+1)), {ok, {{_,200,_}, [_ | _], [_ | _]}} = - http:request(get, {URL, [{"If-UnModified-Since", + httpc:request(get, {URL, [{"If-UnModified-Since", Mod1} ]}, [], []), @@ -750,12 +772,12 @@ http_headers(Config) when is_list(Config) -> {ok, {{_,200,_}, [_ | _], [_ | _]}} = - http:request(get, {URL, [{"If-Match", + httpc:request(get, {URL, [{"If-Match", Tag} ]}, [], []), {ok, {{_,200,_}, [_ | _], _}} = - http:request(get, {URL, [{"If-None-Match", + httpc:request(get, {URL, [{"If-None-Match", "NotEtag,NeihterEtag"}, {"Connection", "Close"} ]}, [], []), @@ -773,7 +795,7 @@ http_headers_dummy(doc) -> http_headers_dummy(suite) -> []; http_headers_dummy(Config) when is_list(Config) -> - ok = http:set_options([{ipfamily, inet}]), + ok = httpc:set_options([{ipfamily, inet}]), {DummyServerPid, Port} = dummy_server(self(), ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy_headers.html", @@ -789,7 +811,7 @@ http_headers_dummy(Config) when is_list(Config) -> %% that the client header-handling code. This would not %% be a vaild http-request! {ok, {{_,200,_}, [_ | _], [_|_]}} = - http:request(post, + httpc:request(post, {URL, [{"Via", "1.0 fred, 1.1 nowhere.com (Apache/1.1)"}, @@ -828,7 +850,7 @@ http_headers_dummy(Config) when is_list(Config) -> ], "text/plain", FooBar}, [], []), DummyServerPid ! stop, - ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* + ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* ok. @@ -838,21 +860,21 @@ http_bad_response(doc) -> http_bad_response(suite) -> []; http_bad_response(Config) when is_list(Config) -> - ok = http:set_options([{ipfamily, inet}]), + ok = httpc:set_options([{ipfamily, inet}]), {DummyServerPid, Port} = dummy_server(self(), ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/missing_crlf.html", URL1 = ?URL_START ++ integer_to_list(Port) ++ "/wrong_statusline.html", - {error, timeout} = http:request(get, {URL, []}, [{timeout, 400}], []), + {error, timeout} = httpc:request(get, {URL, []}, [{timeout, 400}], []), - {error, Reason} = http:request(URL1), + {error, Reason} = httpc:request(URL1), test_server:format("Wrong Statusline: ~p~n", [Reason]), DummyServerPid ! stop, - ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* + ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* ok. @@ -862,69 +884,168 @@ ssl_head(doc) -> ssl_head(suite) -> []; ssl_head(Config) when is_list(Config) -> + ssl_head(ssl, Config). + +ossl_head(doc) -> + ["Same as http_head/1 but over ssl sockets."]; +ossl_head(suite) -> + []; +ossl_head(Config) when is_list(Config) -> + ssl_head(ossl, Config). + +essl_head(doc) -> + ["Same as http_head/1 but over ssl sockets."]; +essl_head(suite) -> + []; +essl_head(Config) when is_list(Config) -> + ssl_head(essl, Config). + +ssl_head(SslTag, Config) -> + tsp("ssl_head -> entry with" + "~n SslTag: ~p" + "~n Config: ~p", [SslTag, Config]), case ?config(local_ssl_server, Config) of ok -> - DataDir = ?config(data_dir, Config), - Port = ?config(local_ssl_port, Config), - URL = ?SSL_URL_START ++ integer_to_list(Port) ++ "/dummy.html", - CertFile = filename:join(DataDir, "ssl_client_cert.pem"), + DataDir = ?config(data_dir, Config), + Port = ?config(local_ssl_port, Config), + URL = ?SSL_URL_START ++ integer_to_list(Port) ++ "/dummy.html", + CertFile = filename:join(DataDir, "ssl_client_cert.pem"), SSLOptions = [{certfile, CertFile}, {keyfile, CertFile}], + SSLConfig = + case SslTag of + ssl -> + SSLOptions; + ossl -> + {ossl, SSLOptions}; + essl -> + {essl, SSLOptions} + end, + tsp("ssl_head -> make request using: " + "~n URL: ~p" + "~n SslTag: ~p" + "~n SSLOptions: ~p", [URL, SslTag, SSLOptions]), {ok, {{_,200, _}, [_ | _], []}} = - http:request(head, {URL, []}, [{ssl, SSLOptions}], []); + httpc:request(head, {URL, []}, [{ssl, SSLConfig}], []); {ok, _} -> - {skip, "Failed to start local http-server"}; + {skip, "local http-server not started"}; _ -> - {skip, "Failed to start SSL"} + {skip, "SSL not started"} end. + + %%------------------------------------------------------------------------- ssl_get(doc) -> ["Same as http_get/1 but over ssl sockets."]; ssl_get(suite) -> []; ssl_get(Config) when is_list(Config) -> + ssl_get(ssl, Config). + +ossl_get(doc) -> + ["Same as http_get/1 but over ssl sockets."]; +ossl_get(suite) -> + []; +ossl_get(Config) when is_list(Config) -> + ssl_get(ossl, Config). + +essl_get(doc) -> + ["Same as http_get/1 but over ssl sockets."]; +essl_get(suite) -> + []; +essl_get(Config) when is_list(Config) -> + ssl_get(essl, Config). + +ssl_get(SslTag, Config) when is_list(Config) -> case ?config(local_ssl_server, Config) of ok -> - DataDir = ?config(data_dir, Config), - Port = ?config(local_ssl_port, Config), - URL = ?SSL_URL_START ++ integer_to_list(Port) ++ "/dummy.html", - CertFile = filename:join(DataDir, "ssl_client_cert.pem"), + DataDir = ?config(data_dir, Config), + Port = ?config(local_ssl_port, Config), + URL = ?SSL_URL_START ++ integer_to_list(Port) ++ "/dummy.html", + CertFile = filename:join(DataDir, "ssl_client_cert.pem"), SSLOptions = [{certfile, CertFile}, {keyfile, CertFile}], - {ok, {{_,200, _}, [_ | _], Body = [_ | _]}} = - http:request(get, {URL, []}, [{ssl, SSLOptions}], []), - inets_test_lib:check_body(Body); + SSLConfig = + case SslTag of + ssl -> + SSLOptions; + ossl -> + {ossl, SSLOptions}; + essl -> + {essl, SSLOptions} + end, + tsp("ssl_get -> make request using: " + "~n URL: ~p" + "~n SslTag: ~p" + "~n SSLOptions: ~p", [URL, SslTag, SSLOptions]), + {ok, {{_,200, _}, [_ | _], Body = [_ | _]}} = + httpc:request(get, {URL, []}, [{ssl, SSLConfig}], []), + inets_test_lib:check_body(Body); {ok, _} -> {skip, "Failed to start local http-server"}; _ -> {skip, "Failed to start SSL"} end. + + %%------------------------------------------------------------------------- ssl_trace(doc) -> ["Same as http_trace/1 but over ssl sockets."]; ssl_trace(suite) -> []; ssl_trace(Config) when is_list(Config) -> + ssl_trace(ssl, Config). + +ossl_trace(doc) -> + ["Same as http_trace/1 but over ssl sockets."]; +ossl_trace(suite) -> + []; +ossl_trace(Config) when is_list(Config) -> + ssl_trace(ossl, Config). + +essl_trace(doc) -> + ["Same as http_trace/1 but over ssl sockets."]; +essl_trace(suite) -> + []; +essl_trace(Config) when is_list(Config) -> + ssl_trace(essl, Config). + +ssl_trace(SslTag, Config) when is_list(Config) -> case ?config(local_ssl_server, Config) of ok -> - DataDir = ?config(data_dir, Config), - Port = ?config(local_ssl_port, Config), - URL = ?SSL_URL_START ++ integer_to_list(Port) ++ "/dummy.html", - CertFile = filename:join(DataDir, "ssl_client_cert.pem"), + DataDir = ?config(data_dir, Config), + Port = ?config(local_ssl_port, Config), + URL = ?SSL_URL_START ++ integer_to_list(Port) ++ "/dummy.html", + CertFile = filename:join(DataDir, "ssl_client_cert.pem"), SSLOptions = [{certfile, CertFile}, {keyfile, CertFile}], - case http:request(trace, {URL, []}, [{ssl, SSLOptions}], []) of + SSLConfig = + case SslTag of + ssl -> + SSLOptions; + ossl -> + {ossl, SSLOptions}; + essl -> + {essl, SSLOptions} + end, + tsp("ssl_trace -> make request using: " + "~n URL: ~p" + "~n SslTag: ~p" + "~n SSLOptions: ~p", [URL, SslTag, SSLOptions]), + case httpc:request(trace, {URL, []}, [{ssl, SSLConfig}], []) of {ok, {{_,200, _}, [_ | _], "TRACE /dummy.html" ++ _}} -> ok; {ok, {{_,200,_}, [_ | _], WrongBody}} -> - test_server:fail({wrong_body, WrongBody}); + tsf({wrong_body, WrongBody}); {ok, WrongReply} -> - test_server:fail({wrong_reply, WrongReply}); + tsf({wrong_reply, WrongReply}); Error -> - test_server:fail({failed, Error}) + tsf({failed, Error}) end; {ok, _} -> {skip, "Failed to start local http-server"}; _ -> {skip, "Failed to start SSL"} end. + + %%------------------------------------------------------------------------- http_redirect(doc) -> ["Test redirect with dummy server as httpd does not implement" @@ -937,7 +1058,7 @@ http_redirect(Config) when is_list(Config) -> case ?config(local_server, Config) of ok -> tsp("http_redirect -> set ipfamily option to inet"), - ok = http:set_options([{ipfamily, inet}]), + ok = httpc:set_options([{ipfamily, inet}]), tsp("http_redirect -> start dummy server inet"), {DummyServerPid, Port} = dummy_server(self(), ipv4), @@ -948,29 +1069,29 @@ http_redirect(Config) when is_list(Config) -> tsp("http_redirect -> issue request 1: " "~n ~p", [URL300]), {ok, {{_,200,_}, [_ | _], [_|_]}} - = http:request(get, {URL300, []}, [], []), + = httpc:request(get, {URL300, []}, [], []), tsp("http_redirect -> issue request 2: " "~n ~p", [URL300]), {ok, {{_,300,_}, [_ | _], _}} = - http:request(get, {URL300, []}, [{autoredirect, false}], []), + httpc:request(get, {URL300, []}, [{autoredirect, false}], []), URL301 = ?URL_START ++ integer_to_list(Port) ++ "/301.html", tsp("http_redirect -> issue request 3: " "~n ~p", [URL301]), {ok, {{_,200,_}, [_ | _], [_|_]}} - = http:request(get, {URL301, []}, [], []), + = httpc:request(get, {URL301, []}, [], []), tsp("http_redirect -> issue request 4: " "~n ~p", [URL301]), {ok, {{_,200,_}, [_ | _], []}} - = http:request(head, {URL301, []}, [], []), + = httpc:request(head, {URL301, []}, [], []), tsp("http_redirect -> issue request 5: " "~n ~p", [URL301]), {ok, {{_,301,_}, [_ | _], [_|_]}} - = http:request(post, {URL301, [],"text/plain", "foobar"}, + = httpc:request(post, {URL301, [],"text/plain", "foobar"}, [], []), URL302 = ?URL_START ++ integer_to_list(Port) ++ "/302.html", @@ -978,8 +1099,8 @@ http_redirect(Config) when is_list(Config) -> tsp("http_redirect -> issue request 6: " "~n ~p", [URL302]), {ok, {{_,200,_}, [_ | _], [_|_]}} - = http:request(get, {URL302, []}, [], []), - case http:request(get, {URL302, []}, [], []) of + = httpc:request(get, {URL302, []}, [], []), + case httpc:request(get, {URL302, []}, [], []) of {ok, Reply7} -> case Reply7 of {{_,200,_}, [_ | _], [_|_]} -> @@ -1006,12 +1127,12 @@ http_redirect(Config) when is_list(Config) -> tsp("http_redirect -> issue request 7: " "~n ~p", [URL302]), {ok, {{_,200,_}, [_ | _], []}} - = http:request(head, {URL302, []}, [], []), + = httpc:request(head, {URL302, []}, [], []), tsp("http_redirect -> issue request 8: " "~n ~p", [URL302]), {ok, {{_,302,_}, [_ | _], [_|_]}} - = http:request(post, {URL302, [],"text/plain", "foobar"}, + = httpc:request(post, {URL302, [],"text/plain", "foobar"}, [], []), URL307 = ?URL_START ++ integer_to_list(Port) ++ "/307.html", @@ -1019,23 +1140,23 @@ http_redirect(Config) when is_list(Config) -> tsp("http_redirect -> issue request 9: " "~n ~p", [URL307]), {ok, {{_,200,_}, [_ | _], [_|_]}} - = http:request(get, {URL307, []}, [], []), + = httpc:request(get, {URL307, []}, [], []), tsp("http_redirect -> issue request 10: " "~n ~p", [URL307]), {ok, {{_,200,_}, [_ | _], []}} - = http:request(head, {URL307, []}, [], []), + = httpc:request(head, {URL307, []}, [], []), tsp("http_redirect -> issue request 11: " "~n ~p", [URL307]), {ok, {{_,307,_}, [_ | _], [_|_]}} - = http:request(post, {URL307, [],"text/plain", "foobar"}, + = httpc:request(post, {URL307, [],"text/plain", "foobar"}, [], []), tsp("http_redirect -> stop dummy server"), DummyServerPid ! stop, tsp("http_redirect -> reset ipfamily option (to inet6fb4)"), - ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* + ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* tsp("http_redirect -> done"), ok; @@ -1051,15 +1172,15 @@ http_redirect_loop(doc) -> http_redirect_loop(suite) -> []; http_redirect_loop(Config) when is_list(Config) -> - ok = http:set_options([{ipfamily, inet}]), + ok = httpc:set_options([{ipfamily, inet}]), {DummyServerPid, Port} = dummy_server(self(), ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/redirectloop.html", {ok, {{_,300,_}, [_ | _], _}} - = http:request(get, {URL, []}, [], []), + = httpc:request(get, {URL, []}, [], []), DummyServerPid ! stop, - ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* + ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* ok. %%------------------------------------------------------------------------- @@ -1068,13 +1189,13 @@ http_internal_server_error(doc) -> http_internal_server_error(suite) -> []; http_internal_server_error(Config) when is_list(Config) -> - ok = http:set_options([{ipfamily, inet}]), + ok = httpc:set_options([{ipfamily, inet}]), {DummyServerPid, Port} = dummy_server(self(), ipv4), URL500 = ?URL_START ++ integer_to_list(Port) ++ "/500.html", {ok, {{_,500,_}, [_ | _], _}} - = http:request(get, {URL500, []}, [], []), + = httpc:request(get, {URL500, []}, [], []), URL503 = ?URL_START ++ integer_to_list(Port) ++ "/503.html", @@ -1084,16 +1205,16 @@ http_internal_server_error(Config) when is_list(Config) -> ets:insert(unavailable, {503, unavailable}), {ok, {{_,200, _}, [_ | _], [_|_]}} = - http:request(get, {URL503, []}, [], []), + httpc:request(get, {URL503, []}, [], []), ets:insert(unavailable, {503, long_unavailable}), {ok, {{_,503, _}, [_ | _], [_|_]}} = - http:request(get, {URL503, []}, [], []), + httpc:request(get, {URL503, []}, [], []), ets:delete(unavailable), DummyServerPid ! stop, - ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* + ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* ok. @@ -1103,7 +1224,7 @@ http_userinfo(doc) -> http_userinfo(suite) -> []; http_userinfo(Config) when is_list(Config) -> - ok = http:set_options([{ipfamily, inet}]), + ok = httpc:set_options([{ipfamily, inet}]), {DummyServerPid, Port} = dummy_server(self(), ipv4), @@ -1111,16 +1232,16 @@ http_userinfo(Config) when is_list(Config) -> ++ integer_to_list(Port) ++ "/userinfo.html", {ok, {{_,200,_}, [_ | _], _}} - = http:request(get, {URLAuth, []}, [], []), + = httpc:request(get, {URLAuth, []}, [], []), URLUnAuth = "http://alladin:foobar@localhost:" ++ integer_to_list(Port) ++ "/userinfo.html", {ok, {{_,401, _}, [_ | _], _}} = - http:request(get, {URLUnAuth, []}, [], []), + httpc:request(get, {URLUnAuth, []}, [], []), DummyServerPid ! stop, - ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* + ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* ok. @@ -1130,7 +1251,7 @@ http_cookie(doc) -> http_cookie(suite) -> []; http_cookie(Config) when is_list(Config) -> - ok = http:set_options([{cookies, enabled}, {ipfamily, inet}]), + ok = httpc:set_options([{cookies, enabled}, {ipfamily, inet}]), {DummyServerPid, Port} = dummy_server(self(), ipv4), URLStart = ?URL_START @@ -1139,19 +1260,19 @@ http_cookie(Config) when is_list(Config) -> URLCookie = URLStart ++ "/cookie.html", {ok, {{_,200,_}, [_ | _], [_|_]}} - = http:request(get, {URLCookie, []}, [], []), + = httpc:request(get, {URLCookie, []}, [], []), ets:new(cookie, [named_table, public, set]), ets:insert(cookie, {cookies, true}), {ok, {{_,200,_}, [_ | _], [_|_]}} - = http:request(get, {URLStart ++ "/", []}, [], []), + = httpc:request(get, {URLStart ++ "/", []}, [], []), ets:delete(cookie), - ok = http:set_options([{cookies, disabled}, {ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* + ok = httpc:set_options([{cookies, disabled}, {ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* DummyServerPid ! stop, - ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6************ + ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6************ ok. %%------------------------------------------------------------------------- @@ -1162,7 +1283,7 @@ proxy_options(suite) -> proxy_options(Config) when is_list(Config) -> case ?config(skip, Config) of undefined -> - case http:request(options, {?PROXY_URL, []}, [], []) of + case httpc:request(options, {?PROXY_URL, []}, [], []) of {ok, {{_,200,_}, Headers, _}} -> case lists:keysearch("allow", 1, Headers) of {value, {"allow", _}} -> @@ -1186,7 +1307,7 @@ proxy_head(suite) -> proxy_head(Config) when is_list(Config) -> case ?config(skip, Config) of undefined -> - case http:request(head, {?PROXY_URL, []}, [], []) of + case httpc:request(head, {?PROXY_URL, []}, [], []) of {ok, {{_,200, _}, [_ | _], []}} -> ok; Unexpected -> @@ -1205,7 +1326,7 @@ proxy_get(suite) -> proxy_get(Config) when is_list(Config) -> case ?config(skip, Config) of undefined -> - case http:request(get, {?PROXY_URL, []}, [], []) of + case httpc:request(get, {?PROXY_URL, []}, [], []) of {ok, {{_,200,_}, [_ | _], Body = [_ | _]}} -> inets_test_lib:check_body(Body); Unexpected -> @@ -1257,7 +1378,7 @@ proxy_emulate_lower_versions(Config) when is_list(Config) -> end. pelv_get(Version) -> - http:request(get, {?PROXY_URL, []}, [{version, Version}], []). + httpc:request(get, {?PROXY_URL, []}, [{version, Version}], []). %%------------------------------------------------------------------------- proxy_trace(doc) -> @@ -1266,7 +1387,7 @@ proxy_trace(suite) -> []; proxy_trace(Config) when is_list(Config) -> %%{ok, {{_,200,_}, [_ | _], "TRACE " ++ _}} = - %% http:request(trace, {?PROXY_URL, []}, [], []), + %% httpc:request(trace, {?PROXY_URL, []}, [], []), {skip, "HTTP TRACE is no longer allowed on the ?PROXY_URL server due " "to security reasons"}. @@ -1281,7 +1402,7 @@ proxy_post(suite) -> proxy_post(Config) when is_list(Config) -> case ?config(skip, Config) of undefined -> - case http:request(post, {?PROXY_URL, [], + case httpc:request(post, {?PROXY_URL, [], "text/plain", "foobar"}, [],[]) of {ok, {{_,405,_}, [_ | _], [_ | _]}} -> ok; @@ -1303,7 +1424,7 @@ proxy_put(suite) -> proxy_put(Config) when is_list(Config) -> case ?config(skip, Config) of undefined -> - case http:request(put, {"http://www.erlang.org/foobar.html", [], + case httpc:request(put, {"http://www.erlang.org/foobar.html", [], "html", "<html> <body><h1> foo </h1>" "<p>bar</p> </body></html>"}, [], []) of {ok, {{_,405,_}, [_ | _], [_ | _]}} -> @@ -1328,7 +1449,7 @@ proxy_delete(Config) when is_list(Config) -> case ?config(skip, Config) of undefined -> URL = ?PROXY_URL ++ "/foobar.html", - case http:request(delete, {URL, []}, [], []) of + case httpc:request(delete, {URL, []}, [], []) of {ok, {{_,404,_}, [_ | _], [_ | _]}} -> ok; Unexpected -> @@ -1348,7 +1469,7 @@ proxy_headers(Config) when is_list(Config) -> case ?config(skip, Config) of undefined -> {ok, {{_,200,_}, [_ | _], [_ | _]}} - = http:request(get, {?PROXY_URL, + = httpc:request(get, {?PROXY_URL, [ {"Accept", "text/*, text/html," @@ -1383,7 +1504,7 @@ proxy_auth(Config) when is_list(Config) -> %% atleast the code for sending the header does not crash! case ?config(skip, Config) of undefined -> - case http:request(get, {?PROXY_URL, []}, + case httpc:request(get, {?PROXY_URL, []}, [{proxy_auth, {"foo", "bar"}}], []) of {ok, {{_,200, _}, [_ | _], [_|_]}} -> ok; @@ -1403,7 +1524,7 @@ http_server_does_not_exist(suite) -> []; http_server_does_not_exist(Config) when is_list(Config) -> {error, _} = - http:request(get, {"http://localhost:" ++ + httpc:request(get, {"http://localhost:" ++ integer_to_list(?NOT_IN_USE_PORT) ++ "/", []},[], []), ok. @@ -1418,7 +1539,7 @@ page_does_not_exist(Config) when is_list(Config) -> Port = ?config(local_port, Config), URL = ?URL_START ++ integer_to_list(Port) ++ "/doesnotexist.html", {ok, {{_,404,_}, [_ | _], [_ | _]}} - = http:request(get, {URL, []}, [], []), + = httpc:request(get, {URL, []}, [], []), ok. @@ -1432,7 +1553,7 @@ proxy_page_does_not_exist(Config) when is_list(Config) -> undefined -> URL = ?PROXY_URL ++ "/doesnotexist.html", {ok, {{_,404,_}, [_ | _], [_ | _]}} = - http:request(get, {URL, []}, [], []), + httpc:request(get, {URL, []}, [], []), ok; Reason -> {skip, Reason} @@ -1446,7 +1567,7 @@ proxy_https_not_supported(doc) -> proxy_https_not_supported(suite) -> []; proxy_https_not_supported(Config) when is_list(Config) -> - Result = http:request(get, {"https://login.yahoo.com", []}, [], []), + Result = httpc:request(get, {"https://login.yahoo.com", []}, [], []), case Result of {error, Reason} -> %% ok so far @@ -1478,10 +1599,10 @@ http_stream(Config) when is_list(Config) -> Port = ?config(local_port, Config), URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", {ok, {{_,200,_}, [_ | _], Body}} = - http:request(get, {URL, []}, [], []), + httpc:request(get, {URL, []}, [], []), {ok, RequestId} = - http:request(get, {URL, []}, [], [{sync, false}, + httpc:request(get, {URL, []}, [], [{sync, false}, {stream, self}]), receive @@ -1506,7 +1627,7 @@ http_stream_once(Config) when is_list(Config) -> "~n Config: ~p", [Config]), p("http_stream_once -> set ipfamily to inet", []), - ok = http:set_options([{ipfamily, inet}]), + ok = httpc:set_options([{ipfamily, inet}]), p("http_stream_once -> start dummy server", []), {DummyServerPid, Port} = dummy_server(self(), ipv4), @@ -1521,18 +1642,18 @@ http_stream_once(Config) when is_list(Config) -> p("http_stream_once -> stop dummy server", []), DummyServerPid ! stop, p("http_stream_once -> set ipfamily to inet6fb4", []), - ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* + ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* p("http_stream_once -> done", []), ok. once(URL) -> p("once -> issue sync request for ~p", [URL]), {ok, {{_,200,_}, [_ | _], Body}} = - http:request(get, {URL, []}, [], []), + httpc:request(get, {URL, []}, [], []), p("once -> issue async (self stream) request for ~p", [URL]), {ok, RequestId} = - http:request(get, {URL, []}, [], [{sync, false}, + httpc:request(get, {URL, []}, [], [{sync, false}, {stream, {self, once}}]), p("once -> await stream_start reply for (async) request ~p", [RequestId]), @@ -1576,10 +1697,10 @@ proxy_stream(Config) when is_list(Config) -> case ?config(skip, Config) of undefined -> {ok, {{_,200,_}, [_ | _], Body}} = - http:request(get, {?PROXY_URL, []}, [], []), + httpc:request(get, {?PROXY_URL, []}, [], []), {ok, RequestId} = - http:request(get, {?PROXY_URL, []}, [], + httpc:request(get, {?PROXY_URL, []}, [], [{sync, false}, {stream, self}]), receive @@ -1659,7 +1780,7 @@ ipv6(Config) when is_list(Config) -> URL = "http://[" ++ ?IPV6_LOCAL_HOST ++ "]:" ++ integer_to_list(Port) ++ "/foobar.html", {ok, {{_,200,_}, [_ | _], [_|_]}} = - http:request(get, {URL, []}, [], []), + httpc:request(get, {URL, []}, [], []), DummyServerPid ! stop, ok; @@ -1677,11 +1798,11 @@ headers_as_is(Config) when is_list(Config) -> Port = ?config(local_port, Config), URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", {ok, {{_,200,_}, [_|_], [_|_]}} = - http:request(get, {URL, [{"Host", "localhost"},{"Te", ""}]}, + httpc:request(get, {URL, [{"Host", "localhost"},{"Te", ""}]}, [], [{headers_as_is, true}]), {ok, {{_,400,_}, [_|_], [_|_]}} = - http:request(get, {URL, [{"Te", ""}]},[], [{headers_as_is, true}]), + httpc:request(get, {URL, [{"Te", ""}]},[], [{headers_as_is, true}]), ok. @@ -1696,13 +1817,13 @@ options(Config) when is_list(Config) -> Port = ?config(local_port, Config), URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", {ok, {{_,200,_}, [_ | _], Bin}} - = http:request(get, {URL, []}, [{foo, bar}], + = httpc:request(get, {URL, []}, [{foo, bar}], %% Ignore unknown options [{body_format, binary}, {foo, bar}]), true = is_binary(Bin), {ok, {200, [_|_]}} - = http:request(get, {URL, []}, [{timeout, infinity}], + = httpc:request(get, {URL, []}, [{timeout, infinity}], [{full_result, false}]); _ -> {skip, "Failed to start local http-server"} @@ -1715,17 +1836,17 @@ http_invalid_http(doc) -> http_invalid_http(suite) -> []; http_invalid_http(Config) when is_list(Config) -> - ok = http:set_options([{ipfamily, inet}]), + ok = httpc:set_options([{ipfamily, inet}]), {DummyServerPid, Port} = dummy_server(self(), ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/invalid_http.html", {error, {could_not_parse_as_http, _} = Reason} = - http:request(get, {URL, []}, [], []), + httpc:request(get, {URL, []}, [], []), test_server:format("Parse error: ~p ~n", [Reason]), DummyServerPid ! stop, - ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* + ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* ok. @@ -1762,7 +1883,7 @@ empty_body_otp_6243(Config) when is_list(Config) -> Port = ?config(local_port, Config), URL = ?URL_START ++ integer_to_list(Port) ++ "/empty.html", {ok, {{_,200,_}, [_ | _], []}} = - http:request(get, {URL, []}, [{timeout, 500}], []). + httpc:request(get, {URL, []}, [{timeout, 500}], []). %%------------------------------------------------------------------------- @@ -1772,14 +1893,14 @@ transfer_encoding_otp_6807(doc) -> transfer_encoding_otp_6807(suite) -> []; transfer_encoding_otp_6807(Config) when is_list(Config) -> - ok = http:set_options([{ipfamily, inet}]), + ok = httpc:set_options([{ipfamily, inet}]), {DummyServerPid, Port} = dummy_server(self(), ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/capital_transfer_encoding.html", - {ok, {{_,200,_}, [_|_], [_ | _]}} = http:request(URL), + {ok, {{_,200,_}, [_|_], [_ | _]}} = httpc:request(URL), DummyServerPid ! stop, - ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* + ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* ok. @@ -1805,13 +1926,13 @@ empty_response_header_otp_6830(doc) -> empty_response_header_otp_6830(suite) -> []; empty_response_header_otp_6830(Config) when is_list(Config) -> - ok = http:set_options([{ipfamily, inet}]), + ok = httpc:set_options([{ipfamily, inet}]), {DummyServerPid, Port} = dummy_server(self(), ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/no_headers.html", - {ok, {{_,200,_}, [], [_ | _]}} = http:request(URL), + {ok, {{_,200,_}, [], [_ | _]}} = httpc:request(URL), DummyServerPid ! stop, - ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* + ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* ok. @@ -1822,13 +1943,13 @@ no_content_204_otp_6982(doc) -> no_content_204_otp_6982(suite) -> []; no_content_204_otp_6982(Config) when is_list(Config) -> - ok = http:set_options([{ipfamily, inet}]), + ok = httpc:set_options([{ipfamily, inet}]), {DummyServerPid, Port} = dummy_server(self(), ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/no_content.html", - {ok, {{_,204,_}, [], []}} = http:request(URL), + {ok, {{_,204,_}, [], []}} = httpc:request(URL), DummyServerPid ! stop, - ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* + ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* ok. @@ -1840,13 +1961,13 @@ missing_CR_otp_7304(doc) -> missing_CR_otp_7304(suite) -> []; missing_CR_otp_7304(Config) when is_list(Config) -> - ok = http:set_options([{ipfamily, inet}]), + ok = httpc:set_options([{ipfamily, inet}]), {DummyServerPid, Port} = dummy_server(self(), ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/missing_CR.html", - {ok, {{_,200,_}, _, [_ | _]}} = http:request(URL), + {ok, {{_,200,_}, _, [_ | _]}} = httpc:request(URL), DummyServerPid ! stop, - ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* + ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* ok. @@ -1860,15 +1981,15 @@ otp_7883_1(doc) -> otp_7883_1(suite) -> []; otp_7883_1(Config) when is_list(Config) -> - ok = http:set_options([{ipfamily, inet}]), + ok = httpc:set_options([{ipfamily, inet}]), {DummyServerPid, Port} = dummy_server(self(), ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/just_close.html", - {error, socket_closed_remotely} = http:request(URL), + {error, socket_closed_remotely} = httpc:request(URL), DummyServerPid ! stop, - ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* + ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* ok. otp_7883_2(doc) -> @@ -1876,7 +1997,7 @@ otp_7883_2(doc) -> otp_7883_2(suite) -> []; otp_7883_2(Config) when is_list(Config) -> - ok = http:set_options([{ipfamily, inet}]), + ok = httpc:set_options([{ipfamily, inet}]), {DummyServerPid, Port} = dummy_server(self(), ipv4), @@ -1885,9 +2006,9 @@ otp_7883_2(Config) when is_list(Config) -> Request = {URL, []}, HttpOptions = [], Options = [{sync, false}], - Profile = http:default_profile(), + Profile = httpc:default_profile(), {ok, RequestId} = - http:request(Method, Request, HttpOptions, Options, Profile), + httpc:request(Method, Request, HttpOptions, Options, Profile), ok = receive {http, {RequestId, {error, socket_closed_remotely}}} -> @@ -1895,7 +2016,7 @@ otp_7883_2(Config) when is_list(Config) -> end, DummyServerPid ! stop, - ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* + ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 ************* ok. @@ -1966,7 +2087,7 @@ run_clients(NumClients, ServerPort, SeqNumServer) -> fun() -> io:format("[~w] client started - " "issue request~n", [Id]), - case http:request(Url) of + case httpc:request(Url) of {ok, {{_,200,_}, _, Resp}} -> io:format("[~w] 200 response: " "~p~n", [Id, Resp]), @@ -2354,7 +2475,7 @@ otp_8352(Config) when is_list(Config) -> ConnOptions = [{max_sessions, MaxSessions}, {max_keep_alive_length, MaxKeepAlive}, {keep_alive_timeout, KeepAliveTimeout}], - http:set_options(ConnOptions), + httpc:set_options(ConnOptions), Method = get, Port = ?config(local_port, Config), @@ -2366,9 +2487,9 @@ otp_8352(Config) when is_list(Config) -> Options1 = [{socket_opts, [{tos, 87}, {recbuf, 16#FFFF}, {sndbuf, 16#FFFF}]}], - case http:request(Method, Request, HttpOptions1, Options1) of + case httpc:request(Method, Request, HttpOptions1, Options1) of {ok, {{_,200,_}, [_ | _], ReplyBody1 = [_ | _]}} -> - %% equivaliant to http:request(get, {URL, []}, [], []), + %% equivaliant to httpc:request(get, {URL, []}, [], []), inets_test_lib:check_body(ReplyBody1); {ok, UnexpectedReply1} -> tsf({unexpected_reply, UnexpectedReply1}); @@ -2382,9 +2503,9 @@ otp_8352(Config) when is_list(Config) -> Options2 = [{socket_opts, [{tos, 84}, {recbuf, 32#1FFFF}, {sndbuf, 32#1FFFF}]}], - case http:request(Method, Request, HttpOptions2, Options2) of + case httpc:request(Method, Request, HttpOptions2, Options2) of {ok, {{_,200,_}, [_ | _], ReplyBody2 = [_ | _]}} -> - %% equivaliant to http:request(get, {URL, []}, [], []), + %% equivaliant to httpc:request(get, {URL, []}, [], []), inets_test_lib:check_body(ReplyBody2); {ok, UnexpectedReply2} -> tsf({unexpected_reply, UnexpectedReply2}); @@ -2406,13 +2527,13 @@ otp_8371(doc) -> otp_8371(suite) -> []; otp_8371(Config) when is_list(Config) -> - ok = http:set_options([{ipv6, disabled}]), % also test the old option + ok = httpc:set_options([{ipv6, disabled}]), % also test the old option {DummyServerPid, Port} = dummy_server(self(), ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/ensure_host_header_with_port.html", - case http:request(get, {URL, []}, [], []) of + case httpc:request(get, {URL, []}, [], []) of {ok, Result} -> case Result of {{_, 200, _}, _Headers, Body} -> @@ -2436,7 +2557,7 @@ otp_8371(Config) when is_list(Config) -> end, DummyServerPid ! stop, - ok = http:set_options([{ipv6, enabled}]), + ok = httpc:set_options([{ipv6, enabled}]), ok. @@ -2537,7 +2658,7 @@ receive_streamed_body(RequestId, Body) -> end. receive_streamed_body(RequestId, Body, Pid) -> - http:stream_next(Pid), + httpc:stream_next(Pid), test_server:format("~p:receive_streamed_body -> requested next stream ~n", [?MODULE]), receive {http, {RequestId, stream, BinBodyPart}} -> @@ -2921,11 +3042,11 @@ provocate_not_modified_bug(Url) -> Timeout = 15000, %% 15s should be plenty {ok, {{_, 200, _}, ReplyHeaders, _Body}} = - http:request(get, {Url, []}, [{timeout, Timeout}], []), + httpc:request(get, {Url, []}, [{timeout, Timeout}], []), Etag = pick_header(ReplyHeaders, "ETag"), Last = pick_header(ReplyHeaders, "last-modified"), - case http:request(get, {Url, [{"If-None-Match", Etag}, + case httpc:request(get, {Url, [{"If-None-Match", Etag}, {"If-Modified-Since", Last}]}, [{timeout, 15000}], []) of diff --git a/lib/inets/test/httpd_SUITE.erl b/lib/inets/test/httpd_SUITE.erl index 7403d4a643..3c9b5e41a7 100644 --- a/lib/inets/test/httpd_SUITE.erl +++ b/lib/inets/test/httpd_SUITE.erl @@ -32,44 +32,176 @@ init_per_suite/1, end_per_suite/1]). %% Test cases must be exported. --export([ip/1, ssl/1, http_1_1_ip/1, http_1_0_ip/1, http_0_9_ip/1, - ipv6/1, tickets/1]). +-export([ + ip/1, + ssl/1, pssl/1, ossl/1, essl/1, + http_1_1_ip/1, + http_1_0_ip/1, + http_0_9_ip/1, + ipv6/1, + tickets/1 + ]). %% Core Server tests --export([ip_mod_alias/1, ip_mod_actions/1, ip_mod_security/1, ip_mod_auth/1, - ip_mod_auth_api/1, ip_mod_auth_mnesia_api/1, - ip_mod_htaccess/1, ip_mod_cgi/1, ip_mod_esi/1, - ip_mod_get/1, ip_mod_head/1, ip_mod_all/1, ip_load_light/1, - ip_load_medium/1, ip_load_heavy/1, ip_dos_hostname/1, - ip_time_test/1, ip_block_disturbing_idle/1, - ip_block_non_disturbing_idle/1, ip_block_503/1, - ip_block_disturbing_active/1, ip_block_non_disturbing_active/1, +-export([ + ip_mod_alias/1, + ip_mod_actions/1, + ip_mod_security/1, + ip_mod_auth/1, + ip_mod_auth_api/1, + ip_mod_auth_mnesia_api/1, + ip_mod_htaccess/1, + ip_mod_cgi/1, + ip_mod_esi/1, + ip_mod_get/1, + ip_mod_head/1, + ip_mod_all/1, + ip_load_light/1, + ip_load_medium/1, + ip_load_heavy/1, + ip_dos_hostname/1, + ip_time_test/1, + ip_block_disturbing_idle/1, + ip_block_non_disturbing_idle/1, + ip_block_503/1, + ip_block_disturbing_active/1, + ip_block_non_disturbing_active/1, ip_block_disturbing_active_timeout_not_released/1, ip_block_disturbing_active_timeout_released/1, ip_block_non_disturbing_active_timeout_not_released/1, ip_block_non_disturbing_active_timeout_released/1, ip_block_disturbing_blocker_dies/1, ip_block_non_disturbing_blocker_dies/1, - ip_restart_no_block/1, ip_restart_disturbing_block/1, + ip_restart_no_block/1, + ip_restart_disturbing_block/1, ip_restart_non_disturbing_block/1 ]). --export([ssl_mod_alias/1, ssl_mod_actions/1, ssl_mod_security/1, - ssl_mod_auth/1, ssl_mod_auth_api/1, - ssl_mod_auth_mnesia_api/1, ssl_mod_htaccess/1, - ssl_mod_cgi/1, ssl_mod_esi/1, ssl_mod_get/1, ssl_mod_head/1, - ssl_mod_all/1, ssl_load_light/1, ssl_load_medium/1, - ssl_load_heavy/1, ssl_dos_hostname/1, ssl_time_test/1, - ssl_restart_no_block/1, ssl_restart_disturbing_block/1, - ssl_restart_non_disturbing_block/1, ssl_block_disturbing_idle/1, - ssl_block_non_disturbing_idle/1, ssl_block_503/1, - ssl_block_disturbing_active/1, ssl_block_non_disturbing_active/1, - ssl_block_disturbing_active_timeout_not_released/1, - ssl_block_disturbing_active_timeout_released/1, - ssl_block_non_disturbing_active_timeout_not_released/1, - ssl_block_non_disturbing_active_timeout_released/1, - ssl_block_disturbing_blocker_dies/1, - ssl_block_non_disturbing_blocker_dies/1]). +-export([ + pssl_mod_alias/1, + ossl_mod_alias/1, + essl_mod_alias/1, + + pssl_mod_actions/1, + ossl_mod_actions/1, + essl_mod_actions/1, + + pssl_mod_security/1, + ossl_mod_security/1, + essl_mod_security/1, + + pssl_mod_auth/1, + ossl_mod_auth/1, + essl_mod_auth/1, + + pssl_mod_auth_api/1, + ossl_mod_auth_api/1, + essl_mod_auth_api/1, + + pssl_mod_auth_mnesia_api/1, + ossl_mod_auth_mnesia_api/1, + essl_mod_auth_mnesia_api/1, + + pssl_mod_htaccess/1, + ossl_mod_htaccess/1, + essl_mod_htaccess/1, + + pssl_mod_cgi/1, + ossl_mod_cgi/1, + essl_mod_cgi/1, + + pssl_mod_esi/1, + ossl_mod_esi/1, + essl_mod_esi/1, + + pssl_mod_get/1, + ossl_mod_get/1, + essl_mod_get/1, + + pssl_mod_head/1, + ossl_mod_head/1, + essl_mod_head/1, + + pssl_mod_all/1, + ossl_mod_all/1, + essl_mod_all/1, + + pssl_load_light/1, + ossl_load_light/1, + essl_load_light/1, + + pssl_load_medium/1, + ossl_load_medium/1, + essl_load_medium/1, + + pssl_load_heavy/1, + ossl_load_heavy/1, + essl_load_heavy/1, + + pssl_dos_hostname/1, + ossl_dos_hostname/1, + essl_dos_hostname/1, + + pssl_time_test/1, + ossl_time_test/1, + essl_time_test/1, + + pssl_restart_no_block/1, + ossl_restart_no_block/1, + essl_restart_no_block/1, + + pssl_restart_disturbing_block/1, + ossl_restart_disturbing_block/1, + essl_restart_disturbing_block/1, + + pssl_restart_non_disturbing_block/1, + ossl_restart_non_disturbing_block/1, + essl_restart_non_disturbing_block/1, + + pssl_block_disturbing_idle/1, + ossl_block_disturbing_idle/1, + essl_block_disturbing_idle/1, + + pssl_block_non_disturbing_idle/1, + ossl_block_non_disturbing_idle/1, + essl_block_non_disturbing_idle/1, + + pssl_block_503/1, + ossl_block_503/1, + essl_block_503/1, + + pssl_block_disturbing_active/1, + ossl_block_disturbing_active/1, + essl_block_disturbing_active/1, + + pssl_block_non_disturbing_active/1, + ossl_block_non_disturbing_active/1, + essl_block_non_disturbing_active/1, + + pssl_block_disturbing_active_timeout_not_released/1, + ossl_block_disturbing_active_timeout_not_released/1, + essl_block_disturbing_active_timeout_not_released/1, + + pssl_block_disturbing_active_timeout_released/1, + ossl_block_disturbing_active_timeout_released/1, + essl_block_disturbing_active_timeout_released/1, + + pssl_block_non_disturbing_active_timeout_not_released/1, + ossl_block_non_disturbing_active_timeout_not_released/1, + essl_block_non_disturbing_active_timeout_not_released/1, + + pssl_block_non_disturbing_active_timeout_released/1, + ossl_block_non_disturbing_active_timeout_released/1, + essl_block_non_disturbing_active_timeout_released/1, + + pssl_block_disturbing_blocker_dies/1, + ossl_block_disturbing_blocker_dies/1, + essl_block_disturbing_blocker_dies/1, + + pssl_block_non_disturbing_blocker_dies/1, + ossl_block_non_disturbing_blocker_dies/1, + essl_block_non_disturbing_blocker_dies/1 + ]). %%% HTTP 1.1 tests -export([ip_host/1, ip_chunked/1, ip_expect/1, ip_range/1, @@ -103,8 +235,8 @@ %% Seconds before successful auths timeout. -define(AUTH_TIMEOUT,5). --record(httpd_user, {user_name, password, user_data}). --record(httpd_group,{group_name, userlist}). +-record(httpd_user, {user_name, password, user_data}). +-record(httpd_group, {group_name, userlist}). %%-------------------------------------------------------------------- @@ -197,9 +329,9 @@ init_per_testcase2(Case, Config) -> "~n Config: ~p" "~n", [?MODULE, Case, Config]), - IpNormal = integer_to_list(?IP_PORT) ++ ".conf", - IpHtacess = integer_to_list(?IP_PORT) ++ "htacess.conf", - SslNormal = integer_to_list(?SSL_PORT) ++ ".conf", + IpNormal = integer_to_list(?IP_PORT) ++ ".conf", + IpHtacess = integer_to_list(?IP_PORT) ++ "htacess.conf", + SslNormal = integer_to_list(?SSL_PORT) ++ ".conf", SslHtacess = integer_to_list(?SSL_PORT) ++ "htacess.conf", DataDir = ?config(data_dir, Config), @@ -210,8 +342,8 @@ init_per_testcase2(Case, Config) -> "~n DataDir: ~p" "~n", [?MODULE, Case, SuiteTopDir, DataDir]), - TcTopDir = filename:join(SuiteTopDir, Case), - ?line ok = file:make_dir(TcTopDir), + TcTopDir = filename:join(SuiteTopDir, Case), + ?line ok = file:make_dir(TcTopDir), io:format(user, "~w:init_per_testcase2(~w) -> " "~n TcTopDir: ~p" @@ -267,9 +399,21 @@ init_per_testcase2(Case, Config) -> %% To be used by SSL test cases io:format(user, "~w:init_per_testcase2(~w) -> ssl testcase setups~n", [?MODULE, Case]), - create_config([{port, ?SSL_PORT}, {sock_type, ssl} | NewConfig], + SocketType = + case atom_to_list(Case) of + [X, $s, $s, $l | _] -> + case X of + $p -> ssl; + $o -> ossl; + $e -> essl + end; + _ -> + ssl + end, + + create_config([{port, ?SSL_PORT}, {sock_type, SocketType} | NewConfig], normal_acess, SslNormal), - create_config([{port, ?SSL_PORT}, {sock_type, ssl} | NewConfig], + create_config([{port, ?SSL_PORT}, {sock_type, SocketType} | NewConfig], mod_htaccess, SslHtacess), %% To be used by IPv6 test cases. Case-clause is so that @@ -300,8 +444,14 @@ init_per_testcase3(Case, Config) -> io:format(user, "~w:init_per_testcase3(~w) -> entry with" "~n Config: ~p", [?MODULE, Case, Config]), + +%% %% Create a new fresh node to be used by the server in this test-case + +%% NodeName = list_to_atom(atom_to_list(Case) ++ "_httpd"), +%% Node = inets_test_lib:start_node(NodeName), + %% Clean up (we do not want this clean up in end_per_testcase - %% if init_per_testcase crases for some testcase it will + %% if init_per_testcase crashes for some testcase it will %% have contaminated the environment and there will be no clean up.) %% This init can take a few different paths so that one crashes %% does not mean that all invocations will. @@ -310,15 +460,26 @@ init_per_testcase3(Case, Config) -> application:stop(inets), application:stop(ssl), cleanup_mnesia(), - - %% TraceLevel = max, - TraceLevel = 70, - TraceDest = io, - inets:enable_trace(TraceLevel, TraceDest), + %% Set trace + case lists:reverse(atom_to_list(Case)) of + "tset_emit" ++ _Rest -> % test-cases ending with time_test + io:format(user, "~w:init_per_testcase3(~w) -> disabling trace", + [?MODULE, Case]), + inets:disable_trace(); + _ -> + %% TraceLevel = max, + io:format(user, "~w:init_per_testcase3(~w) -> enabling trace", + [?MODULE, Case]), + TraceLevel = 70, + TraceDest = io, + inets:enable_trace(TraceLevel, TraceDest, httpd) + end, + %% Start initialization io:format(user, "~w:init_per_testcase3(~w) -> start init", [?MODULE, Case]), + Dog = test_server:timetrap(inets_test_lib:minutes(10)), NewConfig = lists:keydelete(watchdog, 1, Config), @@ -351,22 +512,35 @@ init_per_testcase3(Case, Config) -> filename:join(TcTopDir, integer_to_list(?IP_PORT) ++ ".conf")}]), Rest; - "ssl_mod_htaccess" -> + + [X, $s, $s, $l, $_, $m, $o, $d, $_, $h, $t, $a, $c, $c, $e, $s, $s] -> + SslTag = + case X of + $p -> ssl; % plain + $o -> ossl; % OpenSSL based ssl + $e -> essl % Erlang based ssl + end, case inets_test_lib:start_http_server_ssl( filename:join(TcTopDir, integer_to_list(?SSL_PORT) ++ - "htacess.conf")) of + "htacess.conf"), SslTag) of ok -> "mod_htaccess"; Other -> error_logger:info_report("Other: ~p~n", [Other]), {skip, "SSL does not seem to be supported"} end; - "ssl_" ++ Rest -> + [X, $s, $s, $l, $_ | Rest] -> + SslTag = + case X of + $p -> ssl; + $o -> ossl; + $e -> essl + end, case inets_test_lib:start_http_server_ssl( filename:join(TcTopDir, integer_to_list(?SSL_PORT) ++ - ".conf")) of + ".conf"), SslTag) of ok -> Rest; Other -> @@ -431,6 +605,7 @@ end_per_testcase2(Case, Config) -> application:unset_env(inets, services), application:stop(inets), application:stop(ssl), + application:stop(crypto), % used by the new ssl (essl test cases) cleanup_mnesia(), io:format(user, "~w:end_per_testcase2(~w) -> done~n", [?MODULE, Case]), @@ -461,6 +636,9 @@ ip(suite) -> ip_load_heavy, ip_dos_hostname, ip_time_test, + ip_restart_no_block, + ip_restart_disturbing_block, + ip_restart_non_disturbing_block, ip_block_disturbing_idle, ip_block_non_disturbing_idle, ip_block_503, @@ -471,10 +649,7 @@ ip(suite) -> ip_block_non_disturbing_active_timeout_not_released, ip_block_non_disturbing_active_timeout_released, ip_block_disturbing_blocker_dies, - ip_block_non_disturbing_blocker_dies, - ip_restart_no_block, - ip_restart_disturbing_block, - ip_restart_non_disturbing_block + ip_block_non_disturbing_blocker_dies ]. %%------------------------------------------------------------------------- @@ -482,39 +657,124 @@ ssl(doc) -> ["HTTP test using SSL"]; ssl(suite) -> [ - ssl_mod_alias, - ssl_mod_actions, - ssl_mod_security, - ssl_mod_auth, - ssl_mod_auth_api, - ssl_mod_auth_mnesia_api, - ssl_mod_htaccess, - ssl_mod_cgi, - ssl_mod_esi, - ssl_mod_get, - ssl_mod_head, - ssl_mod_all, - ssl_load_light, - ssl_load_medium, - ssl_load_heavy, - ssl_dos_hostname, - ssl_time_test, - ssl_restart_no_block, - ssl_restart_disturbing_block, - ssl_restart_non_disturbing_block, - ssl_block_disturbing_idle, - ssl_block_non_disturbing_idle, - ssl_block_503, - ssl_block_disturbing_active, - ssl_block_non_disturbing_active, - ssl_block_disturbing_active_timeout_not_released, - ssl_block_disturbing_active_timeout_released, - ssl_block_non_disturbing_active_timeout_not_released, - ssl_block_non_disturbing_active_timeout_released, - ssl_block_disturbing_blocker_dies, - ssl_block_non_disturbing_blocker_dies + pssl, + ossl, + essl + ]. + + +pssl(doc) -> + ["HTTP test using SSL - using old way of configuring SSL"]; +pssl(suite) -> + [ + pssl_mod_alias, + pssl_mod_actions, + pssl_mod_security, + pssl_mod_auth, + pssl_mod_auth_api, + pssl_mod_auth_mnesia_api, + pssl_mod_htaccess, + pssl_mod_cgi, + pssl_mod_esi, + pssl_mod_get, + pssl_mod_head, + pssl_mod_all, + pssl_load_light, + pssl_load_medium, + pssl_load_heavy, + pssl_dos_hostname, + pssl_time_test, + pssl_restart_no_block, + pssl_restart_disturbing_block, + pssl_restart_non_disturbing_block, + pssl_block_disturbing_idle, + pssl_block_non_disturbing_idle, + pssl_block_503, + pssl_block_disturbing_active, + pssl_block_non_disturbing_active, + pssl_block_disturbing_active_timeout_not_released, + pssl_block_disturbing_active_timeout_released, + pssl_block_non_disturbing_active_timeout_not_released, + pssl_block_non_disturbing_active_timeout_released, + pssl_block_disturbing_blocker_dies, + pssl_block_non_disturbing_blocker_dies + ]. + +ossl(doc) -> + ["HTTP test using SSL - using new way of configuring usage of old SSL"]; +ossl(suite) -> + [ + ossl_mod_alias, + ossl_mod_actions, + ossl_mod_security, + ossl_mod_auth, + ossl_mod_auth_api, + ossl_mod_auth_mnesia_api, + ossl_mod_htaccess, + ossl_mod_cgi, + ossl_mod_esi, + ossl_mod_get, + ossl_mod_head, + ossl_mod_all, + ossl_load_light, + ossl_load_medium, + ossl_load_heavy, + ossl_dos_hostname, + ossl_time_test, + ossl_restart_no_block, + ossl_restart_disturbing_block, + ossl_restart_non_disturbing_block, + ossl_block_disturbing_idle, + ossl_block_non_disturbing_idle, + ossl_block_503, + ossl_block_disturbing_active, + ossl_block_non_disturbing_active, + ossl_block_disturbing_active_timeout_not_released, + ossl_block_disturbing_active_timeout_released, + ossl_block_non_disturbing_active_timeout_not_released, + ossl_block_non_disturbing_active_timeout_released, + ossl_block_disturbing_blocker_dies, + ossl_block_non_disturbing_blocker_dies ]. +essl(doc) -> + ["HTTP test using SSL - using new way of configuring usage of new SSL"]; +essl(suite) -> + [ + essl_mod_alias, + essl_mod_actions, + essl_mod_security, + essl_mod_auth, + essl_mod_auth_api, + essl_mod_auth_mnesia_api, + essl_mod_htaccess, + essl_mod_cgi, + essl_mod_esi, + essl_mod_get, + essl_mod_head, + essl_mod_all, + essl_load_light, + essl_load_medium, + essl_load_heavy, + essl_dos_hostname, + essl_time_test, + essl_restart_no_block, + essl_restart_disturbing_block, + essl_restart_non_disturbing_block, + essl_block_disturbing_idle, + essl_block_non_disturbing_idle, + essl_block_503, + essl_block_disturbing_active, + essl_block_non_disturbing_active, + essl_block_disturbing_active_timeout_not_released, + essl_block_disturbing_active_timeout_released, + essl_block_non_disturbing_active_timeout_not_released, + essl_block_non_disturbing_active_timeout_released, + essl_block_disturbing_blocker_dies, + essl_block_non_disturbing_blocker_dies + ]. + + %%------------------------------------------------------------------------- http_1_1_ip(doc) -> ["HTTP/1.1"]; @@ -721,6 +981,8 @@ ip_load_heavy(Config) when is_list(Config) -> ?config(node, Config), get_nof_clients(ip_comm, heavy)), ok. + + %%------------------------------------------------------------------------- ip_dos_hostname(doc) -> ["Denial Of Service (DOS) attack test case"]; @@ -730,6 +992,8 @@ ip_dos_hostname(Config) when is_list(Config) -> dos_hostname(ip_comm, ?IP_PORT, ?config(host, Config), ?config(node, Config), ?MAX_HEADER_SIZE), ok. + + %%------------------------------------------------------------------------- ip_time_test(doc) -> [""]; @@ -966,352 +1230,1042 @@ ip_restart_non_disturbing_block(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -ssl_mod_alias(doc) -> - ["Module test: mod_alias"]; -ssl_mod_alias(suite) -> + +pssl_mod_alias(doc) -> + ["Module test: mod_alias - old SSL config"]; +pssl_mod_alias(suite) -> + []; +pssl_mod_alias(Config) when is_list(Config) -> + ssl_mod_alias(ssl, Config). + +ossl_mod_alias(doc) -> + ["Module test: mod_alias - using new of configure old SSL"]; +ossl_mod_alias(suite) -> []; -ssl_mod_alias(Config) when is_list(Config) -> - httpd_mod:alias(ssl, ?SSL_PORT, +ossl_mod_alias(Config) when is_list(Config) -> + ssl_mod_alias(ossl, Config). + +essl_mod_alias(doc) -> + ["Module test: mod_alias - using new of configure new SSL"]; +essl_mod_alias(suite) -> + []; +essl_mod_alias(Config) when is_list(Config) -> + ssl_mod_alias(essl, Config). + + +ssl_mod_alias(Tag, Config) -> + httpd_mod:alias(Tag, ?SSL_PORT, ?config(host, Config), ?config(node, Config)), ok. + + %%------------------------------------------------------------------------- -ssl_mod_actions(doc) -> - ["Module test: mod_actions"]; -ssl_mod_actions(suite) -> + +pssl_mod_actions(doc) -> + ["Module test: mod_actions - old SSL config"]; +pssl_mod_actions(suite) -> []; -ssl_mod_actions(Config) when is_list(Config) -> - httpd_mod:actions(ssl, ?SSL_PORT, - ?config(host, Config), ?config(node, Config)), +pssl_mod_actions(Config) when is_list(Config) -> + ssl_mod_actions(ssl, Config). + +ossl_mod_actions(doc) -> + ["Module test: mod_actions - using new of configure old SSL"]; +ossl_mod_actions(suite) -> + []; +ossl_mod_actions(Config) when is_list(Config) -> + ssl_mod_actions(ossl, Config). + +essl_mod_actions(doc) -> + ["Module test: mod_actions - using new of configure new SSL"]; +essl_mod_actions(suite) -> + []; +essl_mod_actions(Config) when is_list(Config) -> + ssl_mod_actions(essl, Config). + + +ssl_mod_actions(Tag, Config) -> + httpd_mod:actions(Tag, + ?SSL_PORT, + ?config(host, Config), + ?config(node, Config)), ok. + + %%------------------------------------------------------------------------- -ssl_mod_security(doc) -> - ["Module test: mod_security"]; -ssl_mod_security(suite) -> + +pssl_mod_security(doc) -> + ["Module test: mod_security - old SSL config"]; +pssl_mod_security(suite) -> + []; +pssl_mod_security(Config) when is_list(Config) -> + ssl_mod_security(ssl, Config). + +ossl_mod_security(doc) -> + ["Module test: mod_security - using new of configure old SSL"]; +ossl_mod_security(suite) -> + []; +ossl_mod_security(Config) when is_list(Config) -> + ssl_mod_security(ossl, Config). + +essl_mod_security(doc) -> + ["Module test: mod_security - using new of configure new SSL"]; +essl_mod_security(suite) -> []; -ssl_mod_security(Config) when is_list(Config) -> +essl_mod_security(Config) when is_list(Config) -> + ssl_mod_security(essl, Config). + +ssl_mod_security(Tag, Config) -> ServerRoot = ?config(server_root, Config), - httpd_mod:security(ServerRoot, ssl, ?SSL_PORT, - ?config(host, Config), ?config(node, Config)), + httpd_mod:security(ServerRoot, + Tag, + ?SSL_PORT, + ?config(host, Config), + ?config(node, Config)), ok. + + %%------------------------------------------------------------------------- -ssl_mod_auth(doc) -> - ["Module test: mod_auth"]; -ssl_mod_auth(suite) -> + +pssl_mod_auth(doc) -> + ["Module test: mod_auth - old SSL config"]; +pssl_mod_auth(suite) -> []; -ssl_mod_auth(Config) when is_list(Config) -> - httpd_mod:auth(ssl, ?SSL_PORT, - ?config(host, Config), ?config(node, Config)), +pssl_mod_auth(Config) when is_list(Config) -> + ssl_mod_auth(ssl, Config). + +ossl_mod_auth(doc) -> + ["Module test: mod_auth - using new of configure old SSL"]; +ossl_mod_auth(suite) -> + []; +ossl_mod_auth(Config) when is_list(Config) -> + ssl_mod_auth(ossl, Config). + +essl_mod_auth(doc) -> + ["Module test: mod_auth - using new of configure new SSL"]; +essl_mod_auth(suite) -> + []; +essl_mod_auth(Config) when is_list(Config) -> + ssl_mod_auth(essl, Config). + +ssl_mod_auth(Tag, Config) -> + httpd_mod:auth(Tag, + ?SSL_PORT, + ?config(host, Config), + ?config(node, Config)), ok. + + %%------------------------------------------------------------------------- -ssl_mod_auth_api(doc) -> - ["Module test: mod_auth"]; -ssl_mod_auth_api(suite) -> + +pssl_mod_auth_api(doc) -> + ["Module test: mod_auth - old SSL config"]; +pssl_mod_auth_api(suite) -> + []; +pssl_mod_auth_api(Config) when is_list(Config) -> + ssl_mod_auth_api(ssl, Config). + +ossl_mod_auth_api(doc) -> + ["Module test: mod_auth - using new of configure old SSL"]; +ossl_mod_auth_api(suite) -> + []; +ossl_mod_auth_api(Config) when is_list(Config) -> + ssl_mod_auth_api(ossl, Config). + +essl_mod_auth_api(doc) -> + ["Module test: mod_auth - using new of configure new SSL"]; +essl_mod_auth_api(suite) -> []; -ssl_mod_auth_api(Config) when is_list(Config) -> +essl_mod_auth_api(Config) when is_list(Config) -> + ssl_mod_auth_api(essl, Config). + +ssl_mod_auth_api(Tag, Config) -> ServerRoot = ?config(server_root, Config), - Host = ?config(host, Config), - Node = ?config(node, Config), - httpd_mod:auth_api(ServerRoot, "", ssl, ?SSL_PORT, Host, Node), - httpd_mod:auth_api(ServerRoot, "dets_", ssl, ?SSL_PORT, Host, Node), - httpd_mod:auth_api(ServerRoot, "mnesia_", ssl, ?SSL_PORT, Host, Node), + Host = ?config(host, Config), + Node = ?config(node, Config), + httpd_mod:auth_api(ServerRoot, "", Tag, ?SSL_PORT, Host, Node), + httpd_mod:auth_api(ServerRoot, "dets_", Tag, ?SSL_PORT, Host, Node), + httpd_mod:auth_api(ServerRoot, "mnesia_", Tag, ?SSL_PORT, Host, Node), ok. + %%------------------------------------------------------------------------- -ssl_mod_auth_mnesia_api(doc) -> - ["Module test: mod_auth_mnesia_api"]; -ssl_mod_auth_mnesia_api(suite) -> + +pssl_mod_auth_mnesia_api(doc) -> + ["Module test: mod_auth_mnesia_api - old SSL config"]; +pssl_mod_auth_mnesia_api(suite) -> []; -ssl_mod_auth_mnesia_api(Config) when is_list(Config) -> - httpd_mod:auth_mnesia_api(ssl, ?SSL_PORT, - ?config(host, Config), ?config(node, Config)), +pssl_mod_auth_mnesia_api(Config) when is_list(Config) -> + ssl_mod_auth_mnesia_api(ssl, Config). + +ossl_mod_auth_mnesia_api(doc) -> + ["Module test: mod_auth_mnesia_api - using new of configure old SSL"]; +ossl_mod_auth_mnesia_api(suite) -> + []; +ossl_mod_auth_mnesia_api(Config) when is_list(Config) -> + ssl_mod_auth_mnesia_api(ossl, Config). + +essl_mod_auth_mnesia_api(doc) -> + ["Module test: mod_auth_mnesia_api - using new of configure new SSL"]; +essl_mod_auth_mnesia_api(suite) -> + []; +essl_mod_auth_mnesia_api(Config) when is_list(Config) -> + ssl_mod_auth_mnesia_api(essl, Config). + +ssl_mod_auth_mnesia_api(Tag, Config) -> + httpd_mod:auth_mnesia_api(Tag, + ?SSL_PORT, + ?config(host, Config), + ?config(node, Config)), ok. + + %%------------------------------------------------------------------------- -ssl_mod_htaccess(doc) -> - ["Module test: mod_htaccess"]; -ssl_mod_htaccess(suite) -> + +pssl_mod_htaccess(doc) -> + ["Module test: mod_htaccess - old SSL config"]; +pssl_mod_htaccess(suite) -> []; -ssl_mod_htaccess(Config) when is_list(Config) -> - httpd_mod:htaccess(ssl, ?SSL_PORT, - ?config(host, Config), ?config(node, Config)), +pssl_mod_htaccess(Config) when is_list(Config) -> + ssl_mod_htaccess(ssl, Config). + +ossl_mod_htaccess(doc) -> + ["Module test: mod_htaccess - using new of configure old SSL"]; +ossl_mod_htaccess(suite) -> + []; +ossl_mod_htaccess(Config) when is_list(Config) -> + ssl_mod_htaccess(ossl, Config). + +essl_mod_htaccess(doc) -> + ["Module test: mod_htaccess - using new of configure new SSL"]; +essl_mod_htaccess(suite) -> + []; +essl_mod_htaccess(Config) when is_list(Config) -> + ssl_mod_htaccess(essl, Config). + +ssl_mod_htaccess(Tag, Config) -> + httpd_mod:htaccess(Tag, + ?SSL_PORT, + ?config(host, Config), + ?config(node, Config)), ok. + + %%------------------------------------------------------------------------- -ssl_mod_cgi(doc) -> - ["Module test: mod_cgi"]; -ssl_mod_cgi(suite) -> + +pssl_mod_cgi(doc) -> + ["Module test: mod_cgi - old SSL config"]; +pssl_mod_cgi(suite) -> + []; +pssl_mod_cgi(Config) when is_list(Config) -> + ssl_mod_cgi(ssl, Config). + +ossl_mod_cgi(doc) -> + ["Module test: mod_cgi - using new of configure old SSL"]; +ossl_mod_cgi(suite) -> + []; +ossl_mod_cgi(Config) when is_list(Config) -> + ssl_mod_cgi(ossl, Config). + +essl_mod_cgi(doc) -> + ["Module test: mod_cgi - using new of configure new SSL"]; +essl_mod_cgi(suite) -> []; -ssl_mod_cgi(Config) when is_list(Config) -> +essl_mod_cgi(Config) when is_list(Config) -> + ssl_mod_cgi(essl, Config). + +ssl_mod_cgi(Tag, Config) -> case test_server:os_type() of vxworks -> {skip, cgi_not_supported_on_vxwoks}; _ -> - httpd_mod:cgi(ssl, ?SSL_PORT, - ?config(host, Config), ?config(node, Config)), + httpd_mod:cgi(Tag, + ?SSL_PORT, + ?config(host, Config), + ?config(node, Config)), ok end. + + %%------------------------------------------------------------------------- -ssl_mod_esi(doc) -> - ["Module test: mod_esi"]; -ssl_mod_esi(suite) -> + +pssl_mod_esi(doc) -> + ["Module test: mod_esi - old SSL config"]; +pssl_mod_esi(suite) -> []; -ssl_mod_esi(Config) when is_list(Config) -> - httpd_mod:esi(ssl, ?SSL_PORT, - ?config(host, Config), ?config(node, Config)), +pssl_mod_esi(Config) when is_list(Config) -> + ssl_mod_esi(ssl, Config). + +ossl_mod_esi(doc) -> + ["Module test: mod_esi - using new of configure old SSL"]; +ossl_mod_esi(suite) -> + []; +ossl_mod_esi(Config) when is_list(Config) -> + ssl_mod_esi(ossl, Config). + +essl_mod_esi(doc) -> + ["Module test: mod_esi - using new of configure new SSL"]; +essl_mod_esi(suite) -> + []; +essl_mod_esi(Config) when is_list(Config) -> + ssl_mod_esi(essl, Config). + +ssl_mod_esi(Tag, Config) -> + httpd_mod:esi(Tag, + ?SSL_PORT, + ?config(host, Config), + ?config(node, Config)), ok. + %%------------------------------------------------------------------------- -ssl_mod_get(doc) -> - ["Module test: mod_get"]; -ssl_mod_get(suite) -> + +pssl_mod_get(doc) -> + ["Module test: mod_get - old SSL config"]; +pssl_mod_get(suite) -> []; -ssl_mod_get(Config) when is_list(Config) -> - httpd_mod:get(ssl, ?SSL_PORT, - ?config(host, Config), ?config(node, Config)), +pssl_mod_get(Config) when is_list(Config) -> + ssl_mod_get(ssl, Config). + +ossl_mod_get(doc) -> + ["Module test: mod_get - using new of configure old SSL"]; +ossl_mod_get(suite) -> + []; +ossl_mod_get(Config) when is_list(Config) -> + ssl_mod_get(ossl, Config). + +essl_mod_get(doc) -> + ["Module test: mod_get - using new of configure new SSL"]; +essl_mod_get(suite) -> + []; +essl_mod_get(Config) when is_list(Config) -> + ssl_mod_get(essl, Config). + +ssl_mod_get(Tag, Config) -> + httpd_mod:get(Tag, + ?SSL_PORT, + ?config(host, Config), + ?config(node, Config)), ok. + + %%------------------------------------------------------------------------- -ssl_mod_head(doc) -> - ["Module test: mod_head"]; -ssl_mod_head(suite) -> + +pssl_mod_head(doc) -> + ["Module test: mod_head - old SSL config"]; +pssl_mod_head(suite) -> []; -ssl_mod_head(Config) when is_list(Config) -> - httpd_mod:head(ssl, ?SSL_PORT, - ?config(host, Config), ?config(node, Config)), +pssl_mod_head(Config) when is_list(Config) -> + ssl_mod_head(ssl, Config). + +ossl_mod_head(doc) -> + ["Module test: mod_head - using new of configure old SSL"]; +ossl_mod_head(suite) -> + []; +ossl_mod_head(Config) when is_list(Config) -> + ssl_mod_head(ossl, Config). + +essl_mod_head(doc) -> + ["Module test: mod_head - using new of configure new SSL"]; +essl_mod_head(suite) -> + []; +essl_mod_head(Config) when is_list(Config) -> + ssl_mod_head(essl, Config). + +ssl_mod_head(Tag, Config) -> + httpd_mod:head(Tag, + ?SSL_PORT, + ?config(host, Config), + ?config(node, Config)), ok. + + %%------------------------------------------------------------------------- -ssl_mod_all(doc) -> - ["All modules test"]; -ssl_mod_all(suite) -> + +pssl_mod_all(doc) -> + ["All modules test - old SSL config"]; +pssl_mod_all(suite) -> []; -ssl_mod_all(Config) when is_list(Config) -> - httpd_mod:all(ssl, ?SSL_PORT, - ?config(host, Config), ?config(node, Config)), +pssl_mod_all(Config) when is_list(Config) -> + ssl_mod_all(ssl, Config). + +ossl_mod_all(doc) -> + ["All modules test - using new of configure old SSL"]; +ossl_mod_all(suite) -> + []; +ossl_mod_all(Config) when is_list(Config) -> + ssl_mod_all(ossl, Config). + +essl_mod_all(doc) -> + ["All modules test - using new of configure new SSL"]; +essl_mod_all(suite) -> + []; +essl_mod_all(Config) when is_list(Config) -> + ssl_mod_all(essl, Config). + +ssl_mod_all(Tag, Config) -> + httpd_mod:all(Tag, + ?SSL_PORT, + ?config(host, Config), + ?config(node, Config)), ok. + %%------------------------------------------------------------------------- -ssl_load_light(doc) -> - ["Test light load"]; -ssl_load_light(suite) -> + +pssl_load_light(doc) -> + ["Test light load - old SSL config"]; +pssl_load_light(suite) -> + []; +pssl_load_light(Config) when is_list(Config) -> + ssl_load_light(ssl, Config). + +ossl_load_light(doc) -> + ["Test light load - using new of configure old SSL"]; +ossl_load_light(suite) -> []; -ssl_load_light(Config) when is_list(Config) -> - httpd_load:load_test(ssl, ?SSL_PORT, ?config(host, Config), +ossl_load_light(Config) when is_list(Config) -> + ssl_load_light(ossl, Config). + +essl_load_light(doc) -> + ["Test light load - using new of configure new SSL"]; +essl_load_light(suite) -> + []; +essl_load_light(Config) when is_list(Config) -> + ssl_load_light(essl, Config). + +ssl_load_light(Tag, Config) -> + httpd_load:load_test(Tag, + ?SSL_PORT, + ?config(host, Config), ?config(node, Config), get_nof_clients(ssl, light)), ok. + %%------------------------------------------------------------------------- -ssl_load_medium(doc) -> - ["Test medium load"]; -ssl_load_medium(suite) -> + +pssl_load_medium(doc) -> + ["Test medium load - old SSL config"]; +pssl_load_medium(suite) -> + []; +pssl_load_medium(Config) when is_list(Config) -> + ssl_load_medium(ssl, Config). + +ossl_load_medium(doc) -> + ["Test medium load - using new of configure old SSL"]; +ossl_load_medium(suite) -> + []; +ossl_load_medium(Config) when is_list(Config) -> + ssl_load_medium(ossl, Config). + +essl_load_medium(doc) -> + ["Test medium load - using new of configure new SSL"]; +essl_load_medium(suite) -> []; -ssl_load_medium(Config) when is_list(Config) -> +essl_load_medium(Config) when is_list(Config) -> + ssl_load_medium(essl, Config). + +ssl_load_medium(Tag, Config) -> %% <CONDITIONAL-SKIP> Skippable = [win32], Condition = fun() -> ?OS_BASED_SKIP(Skippable) end, ?NON_PC_TC_MAYBE_SKIP(Config, Condition), %% </CONDITIONAL-SKIP> - httpd_load:load_test(ssl, ?SSL_PORT, ?config(host, Config), + httpd_load:load_test(Tag, + ?SSL_PORT, + ?config(host, Config), ?config(node, Config), get_nof_clients(ssl, medium)), ok. + %%------------------------------------------------------------------------- -ssl_load_heavy(doc) -> - ["Test heavy load"]; -ssl_load_heavy(suite) -> + +pssl_load_heavy(doc) -> + ["Test heavy load - old SSL config"]; +pssl_load_heavy(suite) -> + []; +pssl_load_heavy(Config) when is_list(Config) -> + ssl_load_heavy(ssl, Config). + +ossl_load_heavy(doc) -> + ["Test heavy load - using new of configure old SSL"]; +ossl_load_heavy(suite) -> []; -ssl_load_heavy(Config) when is_list(Config) -> +ossl_load_heavy(Config) when is_list(Config) -> + ssl_load_heavy(ossl, Config). + +essl_load_heavy(doc) -> + ["Test heavy load - using new of configure new SSL"]; +essl_load_heavy(suite) -> + []; +essl_load_heavy(Config) when is_list(Config) -> + ssl_load_heavy(essl, Config). + +ssl_load_heavy(Tag, Config) -> %% <CONDITIONAL-SKIP> Skippable = [win32], Condition = fun() -> ?OS_BASED_SKIP(Skippable) end, ?NON_PC_TC_MAYBE_SKIP(Config, Condition), %% </CONDITIONAL-SKIP> - httpd_load:load_test(ssl, ?SSL_PORT, ?config(host, Config), + httpd_load:load_test(Tag, + ?SSL_PORT, + ?config(host, Config), ?config(node, Config), get_nof_clients(ssl, heavy)), ok. + %%------------------------------------------------------------------------- -ssl_dos_hostname(doc) -> - ["Denial Of Service (DOS) attack test case"]; -ssl_dos_hostname(suite) -> + +pssl_dos_hostname(doc) -> + ["Denial Of Service (DOS) attack test case - old SSL config"]; +pssl_dos_hostname(suite) -> []; -ssl_dos_hostname(Config) when is_list(Config) -> - dos_hostname(ssl, ?SSL_PORT, ?config(host, Config), - ?config(node, Config), ?MAX_HEADER_SIZE), +pssl_dos_hostname(Config) when is_list(Config) -> + ssl_dos_hostname(ssl, Config). + +ossl_dos_hostname(doc) -> + ["Denial Of Service (DOS) attack test case - using new of configure old SSL"]; +ossl_dos_hostname(suite) -> + []; +ossl_dos_hostname(Config) when is_list(Config) -> + ssl_dos_hostname(ossl, Config). + +essl_dos_hostname(doc) -> + ["Denial Of Service (DOS) attack test case - using new of configure new SSL"]; +essl_dos_hostname(suite) -> + []; +essl_dos_hostname(Config) when is_list(Config) -> + ssl_dos_hostname(essl, Config). + +ssl_dos_hostname(Tag, Config) -> + dos_hostname(Tag, + ?SSL_PORT, + ?config(host, Config), + ?config(node, Config), + ?MAX_HEADER_SIZE), ok. + + %%------------------------------------------------------------------------- -ssl_time_test(doc) -> - [""]; -ssl_time_test(suite) -> + +pssl_time_test(doc) -> + ["old SSL config"]; +pssl_time_test(suite) -> + []; +pssl_time_test(Config) when is_list(Config) -> + ssl_time_test(ssl, Config). + +ossl_time_test(doc) -> + ["using new of configure old SSL"]; +ossl_time_test(suite) -> []; -ssl_time_test(Config) when is_list(Config) -> +ossl_time_test(Config) when is_list(Config) -> + ssl_time_test(ossl, Config). + +essl_time_test(doc) -> + ["using new of configure new SSL"]; +essl_time_test(suite) -> + []; +essl_time_test(Config) when is_list(Config) -> + ssl_time_test(essl, Config). + +ssl_time_test(Tag, Config) when is_list(Config) -> %% <CONDITIONAL-SKIP> - Condition = fun() -> true end, + Skippable = [win32], + Condition = fun() -> ?OS_BASED_SKIP(Skippable) end, ?NON_PC_TC_MAYBE_SKIP(Config, Condition), %% </CONDITIONAL-SKIP> - httpd_time_test:t(ssl, ?config(host, Config), ?SSL_PORT), + httpd_time_test:t(Tag, + ?config(host, Config), + ?SSL_PORT), ok. + %%------------------------------------------------------------------------- -ssl_block_503(doc) -> + +pssl_block_503(doc) -> ["Check that you will receive status code 503 when the server" - " is blocked and 200 when its not blocked."]; -ssl_block_503(suite) -> + " is blocked and 200 when its not blocked - old SSL config."]; +pssl_block_503(suite) -> + []; +pssl_block_503(Config) when is_list(Config) -> + ssl_block_503(ssl, Config). + +ossl_block_503(doc) -> + ["Check that you will receive status code 503 when the server" + " is blocked and 200 when its not blocked - using new of configure old SSL."]; +ossl_block_503(suite) -> + []; +ossl_block_503(Config) when is_list(Config) -> + ssl_block_503(ossl, Config). + +essl_block_503(doc) -> + ["Check that you will receive status code 503 when the server" + " is blocked and 200 when its not blocked - using new of configure new SSL."]; +essl_block_503(suite) -> []; -ssl_block_503(Config) when is_list(Config) -> - httpd_block:block_503(ssl, ?SSL_PORT, ?config(host, Config), +essl_block_503(Config) when is_list(Config) -> + ssl_block_503(essl, Config). + +ssl_block_503(Tag, Config) -> + httpd_block:block_503(Tag, + ?SSL_PORT, + ?config(host, Config), ?config(node, Config)), ok. + + %%------------------------------------------------------------------------- -ssl_block_disturbing_idle(doc) -> + +pssl_block_disturbing_idle(doc) -> ["Check that you can block/unblock an idle server. The strategy " - "distribing does not really make a difference in this case."]; -ssl_block_disturbing_idle(suite) -> + "distribing does not really make a difference in this case." + "Old SSL config"]; +pssl_block_disturbing_idle(suite) -> + []; +pssl_block_disturbing_idle(Config) when is_list(Config) -> + ssl_block_disturbing_idle(ssl, Config). + +ossl_block_disturbing_idle(doc) -> + ["Check that you can block/unblock an idle server. The strategy " + "distribing does not really make a difference in this case." + "Using new of configure old SSL"]; +ossl_block_disturbing_idle(suite) -> + []; +ossl_block_disturbing_idle(Config) when is_list(Config) -> + ssl_block_disturbing_idle(ossl, Config). + +essl_block_disturbing_idle(doc) -> + ["Check that you can block/unblock an idle server. The strategy " + "distribing does not really make a difference in this case." + "Using new of configure new SSL"]; +essl_block_disturbing_idle(suite) -> []; -ssl_block_disturbing_idle(Config) when is_list(Config) -> - httpd_block:block_disturbing_idle(ssl, ?SSL_PORT, +essl_block_disturbing_idle(Config) when is_list(Config) -> + ssl_block_disturbing_idle(essl, Config). + +ssl_block_disturbing_idle(Tag, Config) -> + httpd_block:block_disturbing_idle(Tag, + ?SSL_PORT, ?config(host, Config), ?config(node, Config)), ok. + + %%------------------------------------------------------------------------- -ssl_block_non_disturbing_idle(doc) -> + +pssl_block_non_disturbing_idle(doc) -> ["Check that you can block/unblock an idle server. The strategy " - "non distribing does not really make a difference in this case."]; -ssl_block_non_disturbing_idle(suite) -> + "non distribing does not really make a difference in this case." + "Old SSL config"]; +pssl_block_non_disturbing_idle(suite) -> []; -ssl_block_non_disturbing_idle(Config) when is_list(Config) -> - httpd_block:block_non_disturbing_idle(ssl, ?SSL_PORT, +pssl_block_non_disturbing_idle(Config) when is_list(Config) -> + ssl_block_non_disturbing_idle(ssl, Config). + +ossl_block_non_disturbing_idle(doc) -> + ["Check that you can block/unblock an idle server. The strategy " + "non distribing does not really make a difference in this case." + "Using new of configure old SSL"]; +ossl_block_non_disturbing_idle(suite) -> + []; +ossl_block_non_disturbing_idle(Config) when is_list(Config) -> + ssl_block_non_disturbing_idle(ossl, Config). + +essl_block_non_disturbing_idle(doc) -> + ["Check that you can block/unblock an idle server. The strategy " + "non distribing does not really make a difference in this case." + "Using new of configure new SSL"]; +essl_block_non_disturbing_idle(suite) -> + []; +essl_block_non_disturbing_idle(Config) when is_list(Config) -> + ssl_block_non_disturbing_idle(essl, Config). + +ssl_block_non_disturbing_idle(Tag, Config) -> + httpd_block:block_non_disturbing_idle(Tag, + ?SSL_PORT, ?config(host, Config), ?config(node, Config)), ok. + + %%------------------------------------------------------------------------- -ssl_block_disturbing_active(doc) -> + +pssl_block_disturbing_active(doc) -> ["Check that you can block/unblock an active server. The strategy " - "distribing means ongoing requests should be terminated."]; -ssl_block_disturbing_active(suite) -> + "distribing means ongoing requests should be terminated." + "Old SSL config"]; +pssl_block_disturbing_active(suite) -> + []; +pssl_block_disturbing_active(Config) when is_list(Config) -> + ssl_block_disturbing_active(ssl, Config). + +ossl_block_disturbing_active(doc) -> + ["Check that you can block/unblock an active server. The strategy " + "distribing means ongoing requests should be terminated." + "Using new of configure old SSL"]; +ossl_block_disturbing_active(suite) -> + []; +ossl_block_disturbing_active(Config) when is_list(Config) -> + ssl_block_disturbing_active(ossl, Config). + +essl_block_disturbing_active(doc) -> + ["Check that you can block/unblock an active server. The strategy " + "distribing means ongoing requests should be terminated." + "Using new of configure new SSL"]; +essl_block_disturbing_active(suite) -> []; -ssl_block_disturbing_active(Config) when is_list(Config) -> - httpd_block:block_disturbing_active(ssl, ?SSL_PORT, +essl_block_disturbing_active(Config) when is_list(Config) -> + ssl_block_disturbing_active(essl, Config). + +ssl_block_disturbing_active(Tag, Config) -> + httpd_block:block_disturbing_active(Tag, + ?SSL_PORT, ?config(host, Config), ?config(node, Config)), ok. + + %%------------------------------------------------------------------------- -ssl_block_non_disturbing_active(doc) -> + +pssl_block_non_disturbing_active(doc) -> ["Check that you can block/unblock an idle server. The strategy " - "non distribing means the ongoing requests should be compleated."]; -ssl_block_non_disturbing_active(suite) -> + "non distribing means the ongoing requests should be compleated." + "Old SSL config"]; +pssl_block_non_disturbing_active(suite) -> + []; +pssl_block_non_disturbing_active(Config) when is_list(Config) -> + ssl_block_non_disturbing_active(ssl, Config). + +ossl_block_non_disturbing_active(doc) -> + ["Check that you can block/unblock an idle server. The strategy " + "non distribing means the ongoing requests should be compleated." + "Using new of configure old SSL"]; +ossl_block_non_disturbing_active(suite) -> + []; +ossl_block_non_disturbing_active(Config) when is_list(Config) -> + ssl_block_non_disturbing_active(ossl, Config). + +essl_block_non_disturbing_active(doc) -> + ["Check that you can block/unblock an idle server. The strategy " + "non distribing means the ongoing requests should be compleated." + "Using new of configure new SSL"]; +essl_block_non_disturbing_active(suite) -> []; -ssl_block_non_disturbing_active(Config) when is_list(Config) -> - httpd_block:block_non_disturbing_idle(ssl, ?SSL_PORT, +essl_block_non_disturbing_active(Config) when is_list(Config) -> + ssl_block_non_disturbing_active(essl, Config). + +ssl_block_non_disturbing_active(Tag, Config) -> + httpd_block:block_non_disturbing_idle(Tag, + ?SSL_PORT, ?config(host, Config), ?config(node, Config)), ok. + %%------------------------------------------------------------------------- -ssl_block_disturbing_active_timeout_not_released(doc) -> + +pssl_block_disturbing_active_timeout_not_released(doc) -> ["Check that you can block an active server. The strategy " "distribing means ongoing requests should be compleated" - "if the timeout does not occur."]; -ssl_block_disturbing_active_timeout_not_released(suite) -> + "if the timeout does not occur." + "Old SSL config"]; +pssl_block_disturbing_active_timeout_not_released(suite) -> []; -ssl_block_disturbing_active_timeout_not_released(Config) +pssl_block_disturbing_active_timeout_not_released(Config) when is_list(Config) -> - httpd_block: - block_disturbing_active_timeout_not_released(ssl, - ?SSL_PORT, - ?config(host, - Config), - ?config(node, - Config)), + ssl_block_disturbing_active_timeout_not_released(ssl, Config). + +ossl_block_disturbing_active_timeout_not_released(doc) -> + ["Check that you can block an active server. The strategy " + "distribing means ongoing requests should be compleated" + "if the timeout does not occur." + "Using new of configure old SSL"]; +ossl_block_disturbing_active_timeout_not_released(suite) -> + []; +ossl_block_disturbing_active_timeout_not_released(Config) + when is_list(Config) -> + ssl_block_disturbing_active_timeout_not_released(ossl, Config). + +essl_block_disturbing_active_timeout_not_released(doc) -> + ["Check that you can block an active server. The strategy " + "distribing means ongoing requests should be compleated" + "if the timeout does not occur." + "Using new of configure new SSL"]; +essl_block_disturbing_active_timeout_not_released(suite) -> + []; +essl_block_disturbing_active_timeout_not_released(Config) + when is_list(Config) -> + ssl_block_disturbing_active_timeout_not_released(essl, Config). + +ssl_block_disturbing_active_timeout_not_released(Tag, Config) -> + Port = ?SSL_PORT, + Host = ?config(host, Config), + Node = ?config(node, Config), + httpd_block:block_disturbing_active_timeout_not_released(Tag, + Port, Host, Node), ok. + + %%------------------------------------------------------------------------- -ssl_block_disturbing_active_timeout_released(doc) -> + +pssl_block_disturbing_active_timeout_released(doc) -> ["Check that you can block an active server. The strategy " "distribing means ongoing requests should be terminated when" - "the timeout occurs."]; -ssl_block_disturbing_active_timeout_released(suite) -> + "the timeout occurs." + "Old SSL config"]; +pssl_block_disturbing_active_timeout_released(suite) -> []; -ssl_block_disturbing_active_timeout_released(Config) +pssl_block_disturbing_active_timeout_released(Config) when is_list(Config) -> - httpd_block:block_disturbing_active_timeout_released(ssl, - ?SSL_PORT, - ?config(host, - Config), - ?config(node, - Config)), + ssl_block_disturbing_active_timeout_released(ssl, Config). + +ossl_block_disturbing_active_timeout_released(doc) -> + ["Check that you can block an active server. The strategy " + "distribing means ongoing requests should be terminated when" + "the timeout occurs." + "Using new of configure old SSL"]; +ossl_block_disturbing_active_timeout_released(suite) -> + []; +ossl_block_disturbing_active_timeout_released(Config) + when is_list(Config) -> + ssl_block_disturbing_active_timeout_released(ossl, Config). + +essl_block_disturbing_active_timeout_released(doc) -> + ["Check that you can block an active server. The strategy " + "distribing means ongoing requests should be terminated when" + "the timeout occurs." + "Using new of configure new SSL"]; +essl_block_disturbing_active_timeout_released(suite) -> + []; +essl_block_disturbing_active_timeout_released(Config) + when is_list(Config) -> + ssl_block_disturbing_active_timeout_released(essl, Config). + +ssl_block_disturbing_active_timeout_released(Tag, Config) -> + Port = ?SSL_PORT, + Host = ?config(host, Config), + Node = ?config(node, Config), + httpd_block:block_disturbing_active_timeout_released(Tag, + Port, + Host, + Node), ok. + %%------------------------------------------------------------------------- -ssl_block_non_disturbing_active_timeout_not_released(doc) -> + +pssl_block_non_disturbing_active_timeout_not_released(doc) -> ["Check that you can block an active server. The strategy " - "non non distribing means ongoing requests should be completed."]; -ssl_block_non_disturbing_active_timeout_not_released(suite) -> + "non non distribing means ongoing requests should be completed." + "Old SSL config"]; +pssl_block_non_disturbing_active_timeout_not_released(suite) -> []; -ssl_block_non_disturbing_active_timeout_not_released(Config) +pssl_block_non_disturbing_active_timeout_not_released(Config) when is_list(Config) -> - httpd_block: - block_non_disturbing_active_timeout_not_released(ssl, - ?SSL_PORT, - ?config(host, - Config), - ?config(node, - Config)), + ssl_block_non_disturbing_active_timeout_not_released(ssl, Config). + +ossl_block_non_disturbing_active_timeout_not_released(doc) -> + ["Check that you can block an active server. The strategy " + "non non distribing means ongoing requests should be completed." + "Using new of configure old SSL"]; +ossl_block_non_disturbing_active_timeout_not_released(suite) -> + []; +ossl_block_non_disturbing_active_timeout_not_released(Config) + when is_list(Config) -> + ssl_block_non_disturbing_active_timeout_not_released(ossl, Config). + +essl_block_non_disturbing_active_timeout_not_released(doc) -> + ["Check that you can block an active server. The strategy " + "non non distribing means ongoing requests should be completed." + "Using new of configure new SSL"]; +essl_block_non_disturbing_active_timeout_not_released(suite) -> + []; +essl_block_non_disturbing_active_timeout_not_released(Config) + when is_list(Config) -> + ssl_block_non_disturbing_active_timeout_not_released(essl, Config). + +ssl_block_non_disturbing_active_timeout_not_released(Tag, Config) -> + Port = ?SSL_PORT, + Host = ?config(host, Config), + Node = ?config(node, Config), + httpd_block:block_non_disturbing_active_timeout_not_released(Tag, + Port, + Host, + Node), ok. + + %%------------------------------------------------------------------------- -ssl_block_non_disturbing_active_timeout_released(doc) -> + +pssl_block_non_disturbing_active_timeout_released(doc) -> ["Check that you can block an active server. The strategy " - "non non distribing means ongoing requests should be completed. " - "When the timeout occurs the block operation sohould be canceled." ]; -ssl_block_non_disturbing_active_timeout_released(suite) -> + "non distribing means ongoing requests should be completed. " + "When the timeout occurs the block operation sohould be canceled." + "Old SSL config"]; +pssl_block_non_disturbing_active_timeout_released(suite) -> []; -ssl_block_non_disturbing_active_timeout_released(Config) +pssl_block_non_disturbing_active_timeout_released(Config) when is_list(Config) -> - httpd_block: - block_non_disturbing_active_timeout_released(ssl, - ?SSL_PORT, - ?config(host, - Config), - ?config(node, - Config)), + ssl_block_non_disturbing_active_timeout_released(ssl, Config). + +ossl_block_non_disturbing_active_timeout_released(doc) -> + ["Check that you can block an active server. The strategy " + "non distribing means ongoing requests should be completed. " + "When the timeout occurs the block operation sohould be canceled." + "Using new of configure old SSL"]; +ossl_block_non_disturbing_active_timeout_released(suite) -> + []; +ossl_block_non_disturbing_active_timeout_released(Config) + when is_list(Config) -> + ssl_block_non_disturbing_active_timeout_released(ossl, Config). + +essl_block_non_disturbing_active_timeout_released(doc) -> + ["Check that you can block an active server. The strategy " + "non distribing means ongoing requests should be completed. " + "When the timeout occurs the block operation sohould be canceled." + "Using new of configure new SSL"]; +essl_block_non_disturbing_active_timeout_released(suite) -> + []; +essl_block_non_disturbing_active_timeout_released(Config) + when is_list(Config) -> + ssl_block_non_disturbing_active_timeout_released(essl, Config). + +ssl_block_non_disturbing_active_timeout_released(Tag, Config) + when is_list(Config) -> + Port = ?SSL_PORT, + Host = ?config(host, Config), + Node = ?config(node, Config), + httpd_block:block_non_disturbing_active_timeout_released(Tag, + Port, + Host, + Node), + ok. + %%------------------------------------------------------------------------- -ssl_block_disturbing_blocker_dies(doc) -> + +pssl_block_disturbing_blocker_dies(doc) -> + ["old SSL config"]; +pssl_block_disturbing_blocker_dies(suite) -> + []; +pssl_block_disturbing_blocker_dies(Config) when is_list(Config) -> + ssl_block_disturbing_blocker_dies(ssl, Config). + +ossl_block_disturbing_blocker_dies(doc) -> + ["using new of configure old SSL"]; +ossl_block_disturbing_blocker_dies(suite) -> []; -ssl_block_disturbing_blocker_dies(suite) -> +ossl_block_disturbing_blocker_dies(Config) when is_list(Config) -> + ssl_block_disturbing_blocker_dies(ossl, Config). + +essl_block_disturbing_blocker_dies(doc) -> + ["using new of configure new SSL"]; +essl_block_disturbing_blocker_dies(suite) -> []; -ssl_block_disturbing_blocker_dies(Config) when is_list(Config) -> - httpd_block:disturbing_blocker_dies(ssl, ?SSL_PORT, +essl_block_disturbing_blocker_dies(Config) when is_list(Config) -> + ssl_block_disturbing_blocker_dies(essl, Config). + +ssl_block_disturbing_blocker_dies(Tag, Config) -> + httpd_block:disturbing_blocker_dies(Tag, + ?SSL_PORT, ?config(host, Config), ?config(node, Config)), ok. + + %%------------------------------------------------------------------------- -ssl_block_non_disturbing_blocker_dies(doc) -> + +pssl_block_non_disturbing_blocker_dies(doc) -> + ["old SSL config"]; +pssl_block_non_disturbing_blocker_dies(suite) -> + []; +pssl_block_non_disturbing_blocker_dies(Config) when is_list(Config) -> + ssl_block_non_disturbing_blocker_dies(ssl, Config). + +ossl_block_non_disturbing_blocker_dies(doc) -> + ["using new of configure old SSL"]; +ossl_block_non_disturbing_blocker_dies(suite) -> []; -ssl_block_non_disturbing_blocker_dies(suite) -> +ossl_block_non_disturbing_blocker_dies(Config) when is_list(Config) -> + ssl_block_non_disturbing_blocker_dies(ossl, Config). + +essl_block_non_disturbing_blocker_dies(doc) -> + ["using new of configure new SSL"]; +essl_block_non_disturbing_blocker_dies(suite) -> []; -ssl_block_non_disturbing_blocker_dies(Config) when is_list(Config) -> - httpd_block:non_disturbing_blocker_dies(ssl, ?SSL_PORT, +essl_block_non_disturbing_blocker_dies(Config) when is_list(Config) -> + ssl_block_non_disturbing_blocker_dies(essl, Config). + +ssl_block_non_disturbing_blocker_dies(Tag, Config) -> + httpd_block:non_disturbing_blocker_dies(Tag, + ?SSL_PORT, ?config(host, Config), ?config(node, Config)), ok. + + %%------------------------------------------------------------------------- -ssl_restart_no_block(doc) -> - [""]; -ssl_restart_no_block(suite) -> + +pssl_restart_no_block(doc) -> + ["old SSL config"]; +pssl_restart_no_block(suite) -> + []; +pssl_restart_no_block(Config) when is_list(Config) -> + ssl_restart_no_block(ssl, Config). + +ossl_restart_no_block(doc) -> + ["using new of configure old SSL"]; +ossl_restart_no_block(suite) -> []; -ssl_restart_no_block(Config) when is_list(Config) -> - httpd_block:restart_no_block(ssl, ?SSL_PORT, ?config(host, Config), +ossl_restart_no_block(Config) when is_list(Config) -> + ssl_restart_no_block(ossl, Config). + +essl_restart_no_block(doc) -> + ["using new of configure new SSL"]; +essl_restart_no_block(suite) -> + []; +essl_restart_no_block(Config) when is_list(Config) -> + ssl_restart_no_block(essl, Config). + +ssl_restart_no_block(Tag, Config) -> + httpd_block:restart_no_block(Tag, + ?SSL_PORT, + ?config(host, Config), ?config(node, Config)), ok. + + %%------------------------------------------------------------------------- -ssl_restart_disturbing_block(doc) -> - [""]; -ssl_restart_disturbing_block(suite) -> + +pssl_restart_disturbing_block(doc) -> + ["old SSL config"]; +pssl_restart_disturbing_block(suite) -> + []; +pssl_restart_disturbing_block(Config) when is_list(Config) -> + ssl_restart_disturbing_block(ssl, Config). + +ossl_restart_disturbing_block(doc) -> + ["using new of configure old SSL"]; +ossl_restart_disturbing_block(suite) -> []; -ssl_restart_disturbing_block(Config) when is_list(Config) -> +ossl_restart_disturbing_block(Config) when is_list(Config) -> + ssl_restart_disturbing_block(ossl, Config). + +essl_restart_disturbing_block(doc) -> + ["using new of configure new SSL"]; +essl_restart_disturbing_block(suite) -> + []; +essl_restart_disturbing_block(Config) when is_list(Config) -> + ssl_restart_disturbing_block(essl, Config). + +ssl_restart_disturbing_block(Tag, Config) -> %% <CONDITIONAL-SKIP> Condition = fun() -> @@ -1336,17 +2290,36 @@ ssl_restart_disturbing_block(Config) when is_list(Config) -> ?NON_PC_TC_MAYBE_SKIP(Config, Condition), %% </CONDITIONAL-SKIP> - httpd_block:restart_disturbing_block(ssl, ?SSL_PORT, + httpd_block:restart_disturbing_block(Tag, ?SSL_PORT, ?config(host, Config), ?config(node, Config)), ok. + %%------------------------------------------------------------------------- -ssl_restart_non_disturbing_block(doc) -> - [""]; -ssl_restart_non_disturbing_block(suite) -> + +pssl_restart_non_disturbing_block(doc) -> + ["old SSL config"]; +pssl_restart_non_disturbing_block(suite) -> []; -ssl_restart_non_disturbing_block(Config) when is_list(Config) -> +pssl_restart_non_disturbing_block(Config) when is_list(Config) -> + ssl_restart_non_disturbing_block(ssl, Config). + +ossl_restart_non_disturbing_block(doc) -> + ["using new of configure old SSL"]; +ossl_restart_non_disturbing_block(suite) -> + []; +ossl_restart_non_disturbing_block(Config) when is_list(Config) -> + ssl_restart_non_disturbing_block(ossl, Config). + +essl_restart_non_disturbing_block(doc) -> + ["using new of configure new SSL"]; +essl_restart_non_disturbing_block(suite) -> + []; +essl_restart_non_disturbing_block(Config) when is_list(Config) -> + ssl_restart_non_disturbing_block(essl, Config). + +ssl_restart_non_disturbing_block(Tag, Config) -> %% <CONDITIONAL-SKIP> Condition = fun() -> @@ -1371,11 +2344,13 @@ ssl_restart_non_disturbing_block(Config) when is_list(Config) -> ?NON_PC_TC_MAYBE_SKIP(Config, Condition), %% </CONDITIONAL-SKIP> - httpd_block:restart_non_disturbing_block(ssl, ?SSL_PORT, - ?config(host, Config), - ?config(node, Config)), + httpd_block:restart_non_disturbing_block(Tag, + ?SSL_PORT, + ?config(host, Config), + ?config(node, Config)), ok. + %%------------------------------------------------------------------------- ip_host(doc) -> ["Control that the server accepts/rejects requests with/ without host"]; @@ -1665,17 +2640,29 @@ dos_hostname(Type, Port, Host, Node, Max) -> %% Other help functions create_config(Config, Access, FileName) -> ServerRoot = ?config(server_root, Config), - TcTopDir = ?config(tc_top_dir, Config), - Port = ?config(port, Config), - Type = ?config(sock_type, Config), - Host = ?config(host, Config), - Mods = io_lib:format("~p", [httpd_mod]), - Funcs = io_lib:format("~p", [ssl_password_cb]), - MaxHdrSz = io_lib:format("~p", [256]), - MaxHdrAct = io_lib:format("~p", [close]), + TcTopDir = ?config(tc_top_dir, Config), + Port = ?config(port, Config), + Type = ?config(sock_type, Config), + Host = ?config(host, Config), + Mods = io_lib:format("~p", [httpd_mod]), + Funcs = io_lib:format("~p", [ssl_password_cb]), + MaxHdrSz = io_lib:format("~p", [256]), + MaxHdrAct = io_lib:format("~p", [close]), + + io:format(user, + "create_config -> " + "~n ServerRoot: ~p" + "~n TcTopDir: ~p" + "~n Type: ~p" + "~n Port: ~p" + "~n Host: ~p" + "~n", [ServerRoot, TcTopDir, Port, Type, Host]), + SSL = - case Type of - ssl -> + if + (Type =:= ssl) orelse + (Type =:= ossl) orelse + (Type =:= essl) -> [cline(["SSLCertificateFile ", filename:join(ServerRoot, "ssl/ssl_server.pem")]), cline(["SSLCertificateKeyFile ", @@ -1686,25 +2673,25 @@ create_config(Config, Access, FileName) -> cline(["SSLPasswordCallbackFunction ", Funcs]), cline(["SSLVerifyClient 0"]), cline(["SSLVerifyDepth 1"])]; - _ -> + true -> [] end, - Mod_order = case Access of - mod_htaccess -> - "Modules mod_alias mod_htaccess mod_auth " - "mod_security " - "mod_responsecontrol mod_trace mod_esi " - "mod_actions mod_cgi mod_include mod_dir " - "mod_range mod_get " - "mod_head mod_log mod_disk_log"; - _ -> - "Modules mod_alias mod_auth mod_security " - "mod_responsecontrol mod_trace mod_esi " - "mod_actions mod_cgi mod_include mod_dir " - "mod_range mod_get " - "mod_head mod_log mod_disk_log" - end, - + ModOrder = case Access of + mod_htaccess -> + "Modules mod_alias mod_htaccess mod_auth " + "mod_security " + "mod_responsecontrol mod_trace mod_esi " + "mod_actions mod_cgi mod_include mod_dir " + "mod_range mod_get " + "mod_head mod_log mod_disk_log"; + _ -> + "Modules mod_alias mod_auth mod_security " + "mod_responsecontrol mod_trace mod_esi " + "mod_actions mod_cgi mod_include mod_dir " + "mod_range mod_get " + "mod_head mod_log mod_disk_log" + end, + %% The test suite currently does not handle an explicit BindAddress. %% They assume any has been used, that is Addr is always set to undefined! @@ -1720,7 +2707,7 @@ create_config(Config, Access, FileName) -> cline(["Port ", integer_to_list(Port)]), cline(["ServerName ", Host]), cline(["SocketType ", atom_to_list(Type)]), - cline([Mod_order]), + cline([ModOrder]), %% cline(["LogFormat ", "erlang"]), cline(["ServerAdmin [email protected]"]), cline(["BindAddress ", BindAddress]), @@ -1882,18 +2869,18 @@ start_mnesia(Node) -> ok -> ok; Other -> - test_server:fail({failed_to_cleanup_mnesia, Other}) + tsf({failed_to_cleanup_mnesia, Other}) end, - case rpc:call(Node, ?MODULE, setup_mnesia, []) of + case rpc:call(Node, ?MODULE, setup_mnesia, []) of {atomic, ok} -> ok; Other2 -> - test_server:fail({failed_to_setup_mnesia, Other2}) + tsf({failed_to_setup_mnesia, Other2}) end, ok. setup_mnesia() -> - setup_mnesia([node()]). + setup_mnesia([node()]). setup_mnesia(Nodes) -> ok = mnesia:create_schema(Nodes), @@ -2029,20 +3016,20 @@ dos_hostname_request(Host) -> get_nof_clients(Mode, Load) -> get_nof_clients(test_server:os_type(), Mode, Load). -get_nof_clients(vxworks, _, light) -> 1; +get_nof_clients(vxworks, _, light) -> 1; get_nof_clients(vxworks, ip_comm, medium) -> 3; -get_nof_clients(vxworks, ssl, medium) -> 3; +get_nof_clients(vxworks, ssl, medium) -> 3; get_nof_clients(vxworks, ip_comm, heavy) -> 5; -get_nof_clients(vxworks, ssl, heavy) -> 5; -get_nof_clients(_, ip_comm, light) -> 5; -get_nof_clients(_, ssl, light) -> 2; -get_nof_clients(_, ip_comm, medium) -> 10; -get_nof_clients(_, ssl, medium) -> 4; -get_nof_clients(_, ip_comm, heavy) -> 20; -get_nof_clients(_, ssl, heavy) -> 6. +get_nof_clients(vxworks, ssl, heavy) -> 5; +get_nof_clients(_, ip_comm, light) -> 5; +get_nof_clients(_, ssl, light) -> 2; +get_nof_clients(_, ip_comm, medium) -> 10; +get_nof_clients(_, ssl, medium) -> 4; +get_nof_clients(_, ip_comm, heavy) -> 20; +get_nof_clients(_, ssl, heavy) -> 6. %% Make a file 100 bytes long containing 012...9*10 -create_range_data(Path)-> +create_range_data(Path) -> PathAndFileName=filename:join([Path,"range.txt"]), file:write_file(PathAndFileName,list_to_binary(["12345678901234567890", "12345678901234567890", @@ -2079,3 +3066,6 @@ create_range_data(Path)-> %% {ok, Fd} = file:open(ConfigFile, [write]), %% ok = file:write(Fd, lists:flatten(HttpConfig)), %% ok = file:close(Fd). + +tsf(Reason) -> + test_server:fail(Reason). diff --git a/lib/inets/test/httpd_SUITE_data/server_root/Makefile b/lib/inets/test/httpd_SUITE_data/server_root/Makefile new file mode 100644 index 0000000000..d7a3231068 --- /dev/null +++ b/lib/inets/test/httpd_SUITE_data/server_root/Makefile @@ -0,0 +1,209 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1997-2010. All Rights Reserved. +# +# The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# %CopyrightEnd% +# +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../../vsn.mk +VSN=$(INETS_VSN) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/inets-$(VSN) + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- +MODULE= + +AUTH_FILES = auth/group \ + auth/passwd +CGI_FILES = cgi-bin/printenv.sh +CONF_FILES = conf/8080.conf \ + conf/8888.conf \ + conf/httpd.conf \ + conf/ssl.conf \ + conf/mime.types +OPEN_FILES = htdocs/open/dummy.html +MNESIA_OPEN_FILES = htdocs/mnesia_open/dummy.html +MISC_FILES = htdocs/misc/friedrich.html \ + htdocs/misc/oech.html +SECRET_FILES = htdocs/secret/dummy.html +MNESIA_SECRET_FILES = htdocs/mnesia_secret/dummy.html +HTDOCS_FILES = htdocs/index.html \ + htdocs/config.shtml \ + htdocs/echo.shtml \ + htdocs/exec.shtml \ + htdocs/flastmod.shtml \ + htdocs/fsize.shtml \ + htdocs/include.shtml +ICON_FILES = icons/README \ + icons/a.gif \ + icons/alert.black.gif \ + icons/alert.red.gif \ + icons/apache_pb.gif \ + icons/back.gif \ + icons/ball.gray.gif \ + icons/ball.red.gif \ + icons/binary.gif \ + icons/binhex.gif \ + icons/blank.gif \ + icons/bomb.gif \ + icons/box1.gif \ + icons/box2.gif \ + icons/broken.gif \ + icons/burst.gif \ + icons/button1.gif \ + icons/button10.gif \ + icons/button2.gif \ + icons/button3.gif \ + icons/button4.gif \ + icons/button5.gif \ + icons/button6.gif \ + icons/button7.gif \ + icons/button8.gif \ + icons/button9.gif \ + icons/buttonl.gif \ + icons/buttonr.gif \ + icons/c.gif \ + icons/comp.blue.gif \ + icons/comp.gray.gif \ + icons/compressed.gif \ + icons/continued.gif \ + icons/dir.gif \ + icons/down.gif \ + icons/dvi.gif \ + icons/f.gif \ + icons/folder.gif \ + icons/folder.open.gif \ + icons/folder.sec.gif \ + icons/forward.gif \ + icons/generic.gif \ + icons/generic.red.gif \ + icons/generic.sec.gif \ + icons/hand.right.gif \ + icons/hand.up.gif \ + icons/htdig.gif \ + icons/icon.sheet.gif \ + icons/image1.gif \ + icons/image2.gif \ + icons/image3.gif \ + icons/index.gif \ + icons/layout.gif \ + icons/left.gif \ + icons/link.gif \ + icons/movie.gif \ + icons/p.gif \ + icons/patch.gif \ + icons/pdf.gif \ + icons/pie0.gif \ + icons/pie1.gif \ + icons/pie2.gif \ + icons/pie3.gif \ + icons/pie4.gif \ + icons/pie5.gif \ + icons/pie6.gif \ + icons/pie7.gif \ + icons/pie8.gif \ + icons/portal.gif \ + icons/poweredby.gif \ + icons/ps.gif \ + icons/quill.gif \ + icons/right.gif \ + icons/screw1.gif \ + icons/screw2.gif \ + icons/script.gif \ + icons/sound1.gif \ + icons/sound2.gif \ + icons/sphere1.gif \ + icons/sphere2.gif \ + icons/star.gif \ + icons/star_blank.gif \ + icons/tar.gif \ + icons/tex.gif \ + icons/text.gif \ + icons/transfer.gif \ + icons/unknown.gif \ + icons/up.gif \ + icons/uu.gif \ + icons/uuencoded.gif \ + icons/world1.gif \ + icons/world2.gif + +SSL_FILES = ssl/ssl_client.pem \ + ssl/ssl_server.pem + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +ERL_COMPILE_FLAGS += + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +debug opt: + +clean: + +docs: + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/auth + $(INSTALL_DATA) $(AUTH_FILES) $(RELSYSDIR)/examples/server_root/auth + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/cgi-bin + $(INSTALL_SCRIPT) $(CGI_FILES) $(RELSYSDIR)/examples/server_root/cgi-bin + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/conf + $(INSTALL_DATA) $(CONF_FILES) $(RELSYSDIR)/examples/server_root/conf + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs/open + $(INSTALL_DATA) $(OPEN_FILES) \ + $(RELSYSDIR)/examples/server_root/htdocs/open + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs/mnesia_open + $(INSTALL_DATA) $(MNESIA_OPEN_FILES) \ + $(RELSYSDIR)/examples/server_root/htdocs/mnesia_open + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs/misc + $(INSTALL_DATA) $(MISC_FILES) \ + $(RELSYSDIR)/examples/server_root/htdocs/misc + $(INSTALL_DIR) \ + $(RELSYSDIR)/examples/server_root/htdocs/secret/top_secret + $(INSTALL_DIR) \ + $(RELSYSDIR)/examples/server_root/htdocs/mnesia_secret/top_secret + $(INSTALL_DATA) $(SECRET_FILES) \ + $(RELSYSDIR)/examples/server_root/htdocs/secret + $(INSTALL_DATA) $(MNESIA_SECRET_FILES) \ + $(RELSYSDIR)/examples/server_root/htdocs/mnesia_secret + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs + $(INSTALL_DATA) $(HTDOCS_FILES) $(RELSYSDIR)/examples/server_root/htdocs + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/icons + $(INSTALL_DATA) $(ICON_FILES) $(RELSYSDIR)/examples/server_root/icons + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/ssl + $(INSTALL_DATA) $(SSL_FILES) $(RELSYSDIR)/examples/server_root/ssl + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/logs + +release_docs_spec: + diff --git a/lib/inets/test/httpd_block.erl b/lib/inets/test/httpd_block.erl index f967d8172a..ac1bf43ff5 100644 --- a/lib/inets/test/httpd_block.erl +++ b/lib/inets/test/httpd_block.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2005-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2005-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %% @@ -36,6 +36,7 @@ ]). %% Help functions +-export([httpd_block/3, httpd_block/4, httpd_unblock/2, httpd_restart/2]). -export([do_block_server/4, do_block_nd_server/5, do_long_poll/6]). -define(report(Label, Content), @@ -47,18 +48,24 @@ %% Test cases starts here. %%------------------------------------------------------------------------- block_disturbing_idle(_Type, Port, Host, Node) -> - unblocked = get_admin_state(Node, Host, Port), + io:format("block_disturbing_idle -> entry~n", []), + validate_admin_state(Node, Host, Port, unblocked), block_server(Node, Host, Port), - blocked = get_admin_state(Node, Host, Port), + validate_admin_state(Node, Host, Port, blocked), unblock_server(Node, Host, Port), - unblocked = get_admin_state(Node, Host, Port). + validate_admin_state(Node, Host, Port, unblocked), + io:format("block_disturbing_idle -> done~n", []), + ok. + %%-------------------------------------------------------------------- block_non_disturbing_idle(_Type, Port, Host, Node) -> unblocked = get_admin_state(Node, Host, Port), block_nd_server(Node, Host, Port), blocked = get_admin_state(Node, Host, Port), unblock_server(Node, Host, Port), - unblocked = get_admin_state(Node, Host, Port). + unblocked = get_admin_state(Node, Host, Port), + ok. + %%-------------------------------------------------------------------- block_503(Type, Port, Host, Node) -> Req = "GET / HTTP/1.0\r\ndummy-host.ericsson.se:\r\n\r\n", @@ -76,6 +83,7 @@ block_503(Type, Port, Host, Node) -> ok = httpd_test_lib:verify_request(Type, Host, Port, Node, Req, [{statuscode, 200}, {version, "HTTP/1.0"}]). + %%-------------------------------------------------------------------- block_disturbing_active(Type, Port, Host, Node) -> process_flag(trap_exit, true), @@ -87,6 +95,7 @@ block_disturbing_active(Type, Port, Host, Node) -> blocked = get_admin_state(Node, Host, Port), process_flag(trap_exit, false), ok. + %%-------------------------------------------------------------------- block_non_disturbing_active(Type, Port, Host, Node) -> process_flag(trap_exit, true), @@ -219,32 +228,91 @@ do_block_nd_server(Node, Host, Port, Timeout, Reply) -> restart_server(Node, _Host, Port) -> Addr = undefined, - rpc:call(Node, httpd, restart, [Addr, Port]). + rpc:call(Node, ?MODULE, httpd_restart, [Addr, Port]). + block_server(Node, _Host, Port) -> + io:format("block_server -> entry~n", []), Addr = undefined, - rpc:call(Node, httpd, block, [Addr, Port]). + rpc:call(Node, ?MODULE, httpd_block, [Addr, Port, disturbing]). + block_server(Node, _Host, Port, Timeout) -> Addr = undefined, - rpc:call(Node, httpd, block, [Addr, Port, disturbing, Timeout]). + rpc:call(Node, ?MODULE, httpd_block, [Addr, Port, disturbing, Timeout]). + block_nd_server(Node, _Host, Port) -> Addr = undefined, - rpc:call(Node, httpd, block, [Addr, Port, non_disturbing]). + rpc:call(Node, ?MODULE, httpd_block, [Addr, Port, non_disturbing]). block_nd_server(Node, _Host, Port, Timeout) -> Addr = undefined, - rpc:call(Node, httpd, block, [Addr, Port, non_disturbing, Timeout]). + rpc:call(Node, ?MODULE, httpd_block, [Addr, Port, non_disturbing, Timeout]). unblock_server(Node, _Host, Port) -> + io:format("~p:~p:block_server -> entry~n", [node(),self()]), Addr = undefined, - rpc:call(Node, httpd, unblock, [Addr, Port]). + rpc:call(Node, ?MODULE, httpd_unblock, [Addr, Port]). + + +httpd_block(Addr, Port, Mode) -> + io:format("~p:~p:httpd_block -> entry~n", [node(),self()]), + Name = make_name(Addr, Port), + case whereis(Name) of + Pid when is_pid(Pid) -> + httpd_manager:block(Pid, Mode); + _ -> + {error, not_started} + end. + +httpd_block(Addr, Port, Mode, Timeout) -> + Name = make_name(Addr, Port), + case whereis(Name) of + Pid when is_pid(Pid) -> + httpd_manager:block(Pid, Mode, Timeout); + _ -> + {error, not_started} + end. + +httpd_unblock(Addr, Port) -> + io:format("~p:~p:httpd_unblock -> entry~n", [node(),self()]), + Name = make_name(Addr, Port), + case whereis(Name) of + Pid when is_pid(Pid) -> + httpd_manager:unblock(Pid); + _ -> + {error, not_started} + end. + +httpd_restart(Addr, Port) -> + Name = make_name(Addr, Port), + case whereis(Name) of + Pid when is_pid(Pid) -> + httpd_manager:reload(Pid, undefined); + _ -> + {error, not_started} + end. + +make_name(Addr, Port) -> + httpd_util:make_name("httpd", Addr, Port). -get_admin_state(Node,_Host,Port) -> +get_admin_state(Node, _Host, Port) -> Addr = undefined, rpc:call(Node, httpd, get_admin_state, [Addr, Port]). +validate_admin_state(Node, Host, Port, Expect) -> + io:format("try validating server admin state: ~p~n", [Expect]), + case get_admin_state(Node, Host, Port) of + Expect -> + ok; + Unexpected -> + io:format("failed validating server admin state: ~p~n", + [Unexpected]), + exit({unexpected_admin_state, Unexpected, Expect}) + end. + + await_normal_process_exit(Pid, Name, Timeout) -> receive {'EXIT', Pid, normal} -> @@ -260,6 +328,7 @@ await_normal_process_exit(Pid, Name, Timeout) -> test_server:fail("timeout while waiting for " ++ Name) end. + await_suite_failed_process_exit(Pid, Name, Timeout, Why) -> receive {'EXIT', Pid, {suite_failed, Why}} -> diff --git a/lib/inets/test/httpd_mod.erl b/lib/inets/test/httpd_mod.erl index b03f842e7c..f2c1fd6a65 100644 --- a/lib/inets/test/httpd_mod.erl +++ b/lib/inets/test/httpd_mod.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2005-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2005-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %% @@ -40,6 +40,13 @@ %% Test cases starts here. %%------------------------------------------------------------------------- alias(Type, Port, Host, Node) -> +%% io:format(user, "~w:alias -> entry with" +%% "~n Type: ~p" +%% "~n Port: ~p" +%% "~n Host: ~p" +%% "~n Node: ~p" +%% "~n", [?MODULE, Type, Port, Host, Node]), + ok = httpd_test_lib:verify_request(Type, Host, Port, Node, "GET /pics/icon.sheet.gif " "HTTP/1.0\r\n\r\n", @@ -82,14 +89,15 @@ actions(Type, Port, Host, Node) -> %%------------------------------------------------------------------------- security(ServerRoot, Type, Port, Host, Node) -> - io:format(user, "~w:security -> entry with" - "~n ServerRoot: ~p" - "~n Type: ~p" - "~n Port: ~p" - "~n Host: ~p" - "~n Node: ~p" - "~n", [?MODULE, ServerRoot, Type, Port, Host, Node]), +%% io:format(user, "~w:security -> entry with" +%% "~n ServerRoot: ~p" +%% "~n Type: ~p" +%% "~n Port: ~p" +%% "~n Host: ~p" +%% "~n Node: ~p" +%% "~n", [?MODULE, ServerRoot, Type, Port, Host, Node]), +%% io:format(user, "~w:security -> register~n", [?MODULE]), global:register_name(mod_security_test, self()), % Receive events test_server:sleep(5000), @@ -99,54 +107,71 @@ security(ServerRoot, Type, Port, Host, Node) -> %% Test blocking / unblocking of users. %% /open, require user one Aladdin +%% io:format(user, "~w:security -> remove user~n", [?MODULE]), remove_users(Node, ServerRoot, Host, Port, "open"), +%% io:format(user, "~w:security -> auth request~n", [?MODULE]), auth_request(Type, Host, Port, Node, "/open/", "one", "onePassword", [{statuscode, 401}]), +%% io:format(user, "~w:security -> await fail security event~n", [?MODULE]), receive_security_event({event, auth_fail, Port, OpenDir, [{user, "one"}, {password, "onePassword"}]}, Node, Port), +%% io:format(user, "~w:security -> auth request~n", [?MODULE]), auth_request(Type,Host,Port,Node,"/open/", "two", "twoPassword", [{statuscode, 401}]), +%% io:format(user, "~w:security -> await fail security event~n", [?MODULE]), receive_security_event({event, auth_fail, Port, OpenDir, [{user, "two"}, {password, "twoPassword"}]}, Node, Port), +%% io:format(user, "~w:security -> auth request~n", [?MODULE]), auth_request(Type, Host, Port, Node,"/open/", "Aladdin", "AladdinPassword", [{statuscode, 401}]), +%% io:format(user, "~w:security -> await fail security event~n", [?MODULE]), receive_security_event({event, auth_fail, Port, OpenDir, [{user, "Aladdin"}, {password, "AladdinPassword"}]}, Node, Port), +%% io:format(user, "~w:security -> add users~n", [?MODULE]), add_user(Node, ServerRoot, Port, "open", "one", "onePassword", []), add_user(Node, ServerRoot, Port, "open", "two", "twoPassword", []), +%% io:format(user, "~w:security -> auth request~n", [?MODULE]), auth_request(Type, Host, Port, Node,"/open/", "one", "WrongPassword", [{statuscode, 401}]), +%% io:format(user, "~w:security -> await fail security event~n", [?MODULE]), receive_security_event({event, auth_fail, Port, OpenDir, [{user, "one"}, {password, "WrongPassword"}]}, Node, Port), +%% io:format(user, "~w:security -> auth request~n", [?MODULE]), auth_request(Type, Host, Port, Node,"/open/", "one", "WrongPassword", [{statuscode, 401}]), +%% io:format(user, "~w:security -> await fail security event~n", [?MODULE]), receive_security_event({event, auth_fail, Port, OpenDir, [{user, "one"}, {password, "WrongPassword"}]}, Node, Port), +%% io:format(user, "~w:security -> await block security event~n", [?MODULE]), receive_security_event({event, user_block, Port, OpenDir, [{user, "one"}]}, Node, Port), +%% io:format(user, "~w:security -> unregister~n", [?MODULE]), global:unregister_name(mod_security_test), % No more events. +%% io:format(user, "~w:security -> auth request~n", [?MODULE]), auth_request(Type, Host, Port, Node,"/open/", "one", "WrongPassword", [{statuscode, 401}]), +%% io:format(user, "~w:security -> auth request~n", [?MODULE]), auth_request(Type, Host, Port, Node,"/open/", "one", "onePassword", [{statuscode, 403}]), %% User "one" should be blocked now.. %% [{"one",_, Port, OpenDir,_}] = list_blocked_users(Node,Port), +%% io:format(user, "~w:security -> list blocked users~n", [?MODULE]), case list_blocked_users(Node, Port) of [{"one",_, Port, OpenDir,_}] -> ok; @@ -156,35 +181,54 @@ security(ServerRoot, Type, Port, Host, Node) -> exit({unexpected_blocked, Blocked}) end, +%% io:format(user, "~w:security -> list blocked users~n", [?MODULE]), [{"one",_, Port, OpenDir,_}] = list_blocked_users(Node,Port,OpenDir), +%% io:format(user, "~w:security -> unblock user~n", [?MODULE]), true = unblock_user(Node, "one", Port, OpenDir), %% User "one" should not be blocked any more.. +%% io:format(user, "~w:security -> list blocked users~n", [?MODULE]), [] = list_blocked_users(Node, Port), +%% io:format(user, "~w:security -> list blocked users~n", [?MODULE]), [] = list_blocked_users(Node, Port, OpenDir), +%% io:format(user, "~w:security -> auth request~n", [?MODULE]), auth_request(Type, Host, Port, Node,"/open/", "one", "onePassword", [{statuscode, 200}]), %% Test list_auth_users & auth_timeout +%% io:format(user, "~w:security -> list blocked users~n", [?MODULE]), ["one"] = list_auth_users(Node, Port), +%% io:format(user, "~w:security -> list blocked users~n", [?MODULE]), ["one"] = list_auth_users(Node, Port, OpenDir), +%% io:format(user, "~w:security -> auth request~n", [?MODULE]), auth_request(Type, Host, Port, Node,"/open/", "two", "onePassword", [{statuscode, 401}]), +%% io:format(user, "~w:security -> list blocked users~n", [?MODULE]), ["one"] = list_auth_users(Node, Port), +%% io:format(user, "~w:security -> list blocked users~n", [?MODULE]), ["one"] = list_auth_users(Node, Port, OpenDir), +%% io:format(user, "~w:security -> auth request~n", [?MODULE]), auth_request(Type, Host, Port, Node,"/open/", "two", "twoPassword", [{statuscode, 401}]), +%% io:format(user, "~w:security -> list blocked users~n", [?MODULE]), ["one"] = list_auth_users(Node, Port), +%% io:format(user, "~w:security -> list blocked users~n", [?MODULE]), ["one"] = list_auth_users(Node, Port, OpenDir), %% Wait for successful auth to timeout. test_server:sleep(?AUTH_TIMEOUT*1001), +%% io:format(user, "~w:security -> list blocked users~n", [?MODULE]), [] = list_auth_users(Node, Port), +%% io:format(user, "~w:security -> list blocked users~n", [?MODULE]), [] = list_auth_users(Node, Port, OpenDir), %% "two" is blocked. +%% io:format(user, "~w:security -> unblock user~n", [?MODULE]), true = unblock_user(Node, "two", Port, OpenDir), %% Test explicit blocking. Block user 'two'. +%% io:format(user, "~w:security -> list blocked users~n", [?MODULE]), [] = list_blocked_users(Node,Port,OpenDir), +%% io:format(user, "~w:security -> block user~n", [?MODULE]), true = block_user(Node, "two", Port, OpenDir, 10), +%% io:format(user, "~w:security -> auth request~n", [?MODULE]), auth_request(Type, Host, Port, Node,"/open/", "two", "twoPassword", [{statuscode, 401}]). @@ -600,6 +644,11 @@ htaccess(Type, Port, Host, Node) -> {header, "WWW-Authenticate"}]). %%-------------------------------------------------------------------- cgi(Type, Port, Host, Node) -> +%% tsp("cgi -> entry with" +%% "~n Type: ~p" +%% "~n Port: ~p" +%% "~n Host: ~p" +%% "~n Node: ~p", []), {Script, Script2, Script3} = case test_server:os_type() of {win32, _} -> @@ -609,6 +658,7 @@ cgi(Type, Port, Host, Node) -> end, %% The length (> 100) is intentional +%% tsp("cgi -> request 01 with length > 100"), ok = httpd_test_lib: verify_request(Type, Host, Port, Node, "POST /cgi-bin/" ++ Script3 ++ @@ -636,46 +686,55 @@ cgi(Type, Port, Host, Node) -> {version, "HTTP/1.0"}, {header, "content-type", "text/plain"}]), +%% tsp("cgi -> request 02"), ok = httpd_test_lib:verify_request(Type, Host, Port, Node, "GET /cgi-bin/"++ Script ++ " HTTP/1.0\r\n\r\n", [{statuscode, 200}, {version, "HTTP/1.0"}]), +%% tsp("cgi -> request 03"), ok = httpd_test_lib:verify_request(Type, Host, Port, Node, "GET /cgi-bin/not_there " "HTTP/1.0\r\n\r\n", [{statuscode, 404},{statuscode, 500}, {version, "HTTP/1.0"}]), +%% tsp("cgi -> request 04"), ok = httpd_test_lib:verify_request(Type, Host, Port, Node, "GET /cgi-bin/"++ Script ++ "?Nisse:kkk?sss/lll HTTP/1.0\r\n\r\n", [{statuscode, 200}, {version, "HTTP/1.0"}]), +%% tsp("cgi -> request 04"), ok = httpd_test_lib:verify_request(Type, Host, Port, Node, "POST /cgi-bin/"++ Script ++ " HTTP/1.0\r\n\r\n", [{statuscode, 200}, {version, "HTTP/1.0"}]), +%% tsp("cgi -> request 05"), ok = httpd_test_lib:verify_request(Type, Host, Port, Node, "GET /htbin/"++ Script ++ " HTTP/1.0\r\n\r\n", [{statuscode, 200}, {version, "HTTP/1.0"}]), +%% tsp("cgi -> request 06"), ok = httpd_test_lib:verify_request(Type, Host, Port, Node, "GET /htbin/not_there " "HTTP/1.0\r\n\r\n", [{statuscode, 404},{statuscode, 500}, {version, "HTTP/1.0"}]), +%% tsp("cgi -> request 07"), ok = httpd_test_lib:verify_request(Type, Host, Port, Node, "GET /htbin/"++ Script ++ "?Nisse:kkk?sss/lll HTTP/1.0\r\n\r\n", [{statuscode, 200}, {version, "HTTP/1.0"}]), +%% tsp("cgi -> request 08"), ok = httpd_test_lib:verify_request(Type, Host, Port, Node, "POST /htbin/"++ Script ++ " HTTP/1.0\r\n\r\n", [{statuscode, 200}, {version, "HTTP/1.0"}]), +%% tsp("cgi -> request 09"), ok = httpd_test_lib:verify_request(Type, Host, Port, Node, "POST /htbin/"++ Script ++ " HTTP/1.0\r\n\r\n", @@ -683,19 +742,24 @@ cgi(Type, Port, Host, Node) -> {version, "HTTP/1.0"}]), %% Execute an existing, but bad CGI script.. +%% tsp("cgi -> request 10 - bad script"), ok = httpd_test_lib:verify_request(Type, Host, Port, Node, "POST /htbin/"++ Script2 ++ " HTTP/1.0\r\n\r\n", [{statuscode, 404}, {version, "HTTP/1.0"}]), +%% tsp("cgi -> request 11 - bad script"), ok = httpd_test_lib:verify_request(Type, Host, Port, Node, "POST /cgi-bin/"++ Script2 ++ " HTTP/1.0\r\n\r\n", [{statuscode, 404}, {version, "HTTP/1.0"}]), + +%% tsp("cgi -> done"), ok. + %%-------------------------------------------------------------------- esi(Type, Port, Host, Node) -> %% Check "ErlScriptAlias" and "EvalScriptAlias" directives @@ -850,25 +914,44 @@ list_users(Node, Root, _Host, Port, Dir) -> Directory = filename:join([Root, "htdocs", Dir]), rpc:call(Node, mod_auth, list_users, [Addr, Port, Directory]). + receive_security_event(Event, Node, Port) -> - io:format(user, "~w:receive_security_event -> entry with" - "~n Event: ~p" - "~n Node: ~p" - "~n Port: ~p" - "~n", [?MODULE, Event, Node, Port]), +%% io:format(user, "~w:receive_security_event -> entry with" +%% "~n Event: ~p" +%% "~n Node: ~p" +%% "~n Port: ~p" +%% "~n", [?MODULE, Event, Node, Port]), receive Event -> ok; {'EXIT', _, _} -> - receive_security_event(Event, Node, Port); - Other -> - test_server:fail({unexpected_event, - {expected, Event}, {received, Other}}) + receive_security_event(Event, Node, Port) after 5000 -> - test_server:fail(no_event_recived) + %% Flush the message queue, to see if we got something... + Msgs = inets_test_lib:flush(), + tsf({expected_event_not_received, Msgs}) end. +%% receive_security_event(Event, Node, Port) -> +%% io:format(user, "~w:receive_security_event -> entry with" +%% "~n Event: ~p" +%% "~n Node: ~p" +%% "~n Port: ~p" +%% "~n", [?MODULE, Event, Node, Port]), +%% receive +%% Event -> +%% ok; +%% {'EXIT', _, _} -> +%% receive_security_event(Event, Node, Port); +%% Other -> +%% test_server:fail({unexpected_event, +%% {expected, Event}, {received, Other}}) +%% after 5000 -> +%% test_server:fail(no_event_recived) + +%% end. + list_blocked_users(Node,Port) -> Addr = undefined, % Assumed to be on the same host rpc:call(Node, mod_security, list_blocked_users, [Addr,Port]). @@ -945,3 +1028,12 @@ check_lists_members1(L,L) -> ok; check_lists_members1(L1,L2) -> {error,{lists_not_equal,L1,L2}}. + + +%% tsp(F) -> +%% tsp(F, []). +%% tsp(F, A) -> +%% test_server:format("~p ~p:" ++ F ++ "~n", [self(), ?MODULE | A]). + +tsf(Reason) -> + test_server:fail(Reason). diff --git a/lib/inets/test/httpd_poll.erl b/lib/inets/test/httpd_poll.erl index 1cc10365a7..32335cabcf 100644 --- a/lib/inets/test/httpd_poll.erl +++ b/lib/inets/test/httpd_poll.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2000-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2000-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %% @@ -27,7 +27,8 @@ %% gen_server exports -export([init/1, - handle_call/3, handle_cast/2, handle_info/2, terminate/2]). + handle_call/3, handle_cast/2, handle_info/2, terminate/2, + code_change/3]). -define(default_verbosity,error). @@ -86,8 +87,8 @@ options(Options) -> options([], Defaults, Options) -> Options ++ Defaults; -options([{Key,Val} = Opt|Opts], Defaults, Options) -> - options(Opts, lists:keydelete(Key, 1, Defaults), [Opt|Options]). +options([{Key, _Val} = Opt|Opts], Defaults, Options) -> + options(Opts, lists:keydelete(Key, 1, Defaults), [Opt | Options]). verbosity(silence) -> @@ -134,10 +135,9 @@ uris(otp) -> uri_top_index(), uri_internal_product1(), uri_internal_product2(), - uri_p7a_test_results(), + uri_r13b03_test_results(), uri_bjorn1(), - uri_bjorn2(), - uri_top_ronja() + uri_bjorn2() ]. uri_top_index() -> @@ -149,9 +149,9 @@ uri_internal_product1() -> uri_internal_product2() -> {"product internal page (2)","/product/internal"}. -uri_p7a_test_results() -> - {"test summery index page", - "/product/internal/test/test_results/progress_P7A/index.html"}. +uri_r13b03_test_results() -> + {"daily build index page", + "/product/internal/test/daily/logs.html"}. uri_bjorn1() -> {"bjorns home page (1)","/~bjorn/"}. @@ -159,9 +159,6 @@ uri_bjorn1() -> uri_bjorn2() -> {"bjorns home page (2)","/~bjorn"}. -uri_top_ronja() -> - {"ronja top page","/ronja/"}. - handle_call(stop, _From, State) -> vlog("stop request"), @@ -199,7 +196,11 @@ handle_info(Info, State) -> {noreply, State}. -terminate(Reason,State) -> +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + + +terminate(_Reason, State) -> tcancel(State#state.tref), log_close(get(log_file)), ok. @@ -287,16 +288,16 @@ trash_the_rest(Socket,N) -> end. -add(N1,N2) when integer(N1),integer(N2) -> +add(N1, N2) when is_integer(N1) andalso is_integer(N2) -> N1 + N2; -add(N1,N2) when integer(N1) -> +add(N1, _N2) when is_integer(N1) -> N1; -add(N1,N2) when integer(N2) -> +add(_N1, N2) when is_integer(N2) -> N2. -sz(L) when list(L) -> +sz(L) when is_list(L) -> length(lists:flatten(L)); -sz(B) when binary(B) -> +sz(B) when is_binary(B) -> size(B); sz(O) -> {unknown_size,O}. @@ -307,9 +308,9 @@ sz(O) -> %% Status code to printable string %% -status_to_message(L) when list(L) -> +status_to_message(L) when is_list(L) -> case (catch list_to_integer(L)) of - I when integer(I) -> + I when is_integer(I) -> status_to_message(I); _ -> io_lib:format("UNKNOWN STATUS CODE: '~p'",[L]) @@ -470,12 +471,12 @@ vlog(F,A) -> vprint(get(verbosity),log,F,A). verror(F) -> vprint(get(verbosity),error,F,[]). verror(F,A) -> vprint(get(verbosity),error,F,A). -vprint(trace,Severity,F,A) -> vprint(Severity,F,A); -vprint(debug,trace,F,A) -> ok; -vprint(debug,Severity,F,A) -> vprint(Severity,F,A); -vprint(log,log,F,A) -> vprint(log,F,A); -vprint(log,error,F,A) -> vprint(log,F,A); -vprint(error,error,F,A) -> vprint(error,F,A); +vprint(trace, Severity, F, A) -> vprint(Severity,F,A); +vprint(debug, trace, _F, _A) -> ok; +vprint(debug, Severity, F, A) -> vprint(Severity,F,A); +vprint(log, log, F, A) -> vprint(log,F,A); +vprint(log, error, F, A) -> vprint(log,F,A); +vprint(error, error, F, A) -> vprint(error,F,A); vprint(_Verbosity,_Severity,_F,_A) -> ok. vprint(Severity,F,A) -> @@ -491,6 +492,3 @@ image_of(trace) -> "TRC: ". local_time() -> calendar:local_time(). - - - diff --git a/lib/inets/test/httpd_test_data/server_root/Makefile b/lib/inets/test/httpd_test_data/server_root/Makefile new file mode 100644 index 0000000000..d7a3231068 --- /dev/null +++ b/lib/inets/test/httpd_test_data/server_root/Makefile @@ -0,0 +1,209 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1997-2010. All Rights Reserved. +# +# The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# %CopyrightEnd% +# +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../../vsn.mk +VSN=$(INETS_VSN) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/inets-$(VSN) + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- +MODULE= + +AUTH_FILES = auth/group \ + auth/passwd +CGI_FILES = cgi-bin/printenv.sh +CONF_FILES = conf/8080.conf \ + conf/8888.conf \ + conf/httpd.conf \ + conf/ssl.conf \ + conf/mime.types +OPEN_FILES = htdocs/open/dummy.html +MNESIA_OPEN_FILES = htdocs/mnesia_open/dummy.html +MISC_FILES = htdocs/misc/friedrich.html \ + htdocs/misc/oech.html +SECRET_FILES = htdocs/secret/dummy.html +MNESIA_SECRET_FILES = htdocs/mnesia_secret/dummy.html +HTDOCS_FILES = htdocs/index.html \ + htdocs/config.shtml \ + htdocs/echo.shtml \ + htdocs/exec.shtml \ + htdocs/flastmod.shtml \ + htdocs/fsize.shtml \ + htdocs/include.shtml +ICON_FILES = icons/README \ + icons/a.gif \ + icons/alert.black.gif \ + icons/alert.red.gif \ + icons/apache_pb.gif \ + icons/back.gif \ + icons/ball.gray.gif \ + icons/ball.red.gif \ + icons/binary.gif \ + icons/binhex.gif \ + icons/blank.gif \ + icons/bomb.gif \ + icons/box1.gif \ + icons/box2.gif \ + icons/broken.gif \ + icons/burst.gif \ + icons/button1.gif \ + icons/button10.gif \ + icons/button2.gif \ + icons/button3.gif \ + icons/button4.gif \ + icons/button5.gif \ + icons/button6.gif \ + icons/button7.gif \ + icons/button8.gif \ + icons/button9.gif \ + icons/buttonl.gif \ + icons/buttonr.gif \ + icons/c.gif \ + icons/comp.blue.gif \ + icons/comp.gray.gif \ + icons/compressed.gif \ + icons/continued.gif \ + icons/dir.gif \ + icons/down.gif \ + icons/dvi.gif \ + icons/f.gif \ + icons/folder.gif \ + icons/folder.open.gif \ + icons/folder.sec.gif \ + icons/forward.gif \ + icons/generic.gif \ + icons/generic.red.gif \ + icons/generic.sec.gif \ + icons/hand.right.gif \ + icons/hand.up.gif \ + icons/htdig.gif \ + icons/icon.sheet.gif \ + icons/image1.gif \ + icons/image2.gif \ + icons/image3.gif \ + icons/index.gif \ + icons/layout.gif \ + icons/left.gif \ + icons/link.gif \ + icons/movie.gif \ + icons/p.gif \ + icons/patch.gif \ + icons/pdf.gif \ + icons/pie0.gif \ + icons/pie1.gif \ + icons/pie2.gif \ + icons/pie3.gif \ + icons/pie4.gif \ + icons/pie5.gif \ + icons/pie6.gif \ + icons/pie7.gif \ + icons/pie8.gif \ + icons/portal.gif \ + icons/poweredby.gif \ + icons/ps.gif \ + icons/quill.gif \ + icons/right.gif \ + icons/screw1.gif \ + icons/screw2.gif \ + icons/script.gif \ + icons/sound1.gif \ + icons/sound2.gif \ + icons/sphere1.gif \ + icons/sphere2.gif \ + icons/star.gif \ + icons/star_blank.gif \ + icons/tar.gif \ + icons/tex.gif \ + icons/text.gif \ + icons/transfer.gif \ + icons/unknown.gif \ + icons/up.gif \ + icons/uu.gif \ + icons/uuencoded.gif \ + icons/world1.gif \ + icons/world2.gif + +SSL_FILES = ssl/ssl_client.pem \ + ssl/ssl_server.pem + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +ERL_COMPILE_FLAGS += + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +debug opt: + +clean: + +docs: + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/auth + $(INSTALL_DATA) $(AUTH_FILES) $(RELSYSDIR)/examples/server_root/auth + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/cgi-bin + $(INSTALL_SCRIPT) $(CGI_FILES) $(RELSYSDIR)/examples/server_root/cgi-bin + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/conf + $(INSTALL_DATA) $(CONF_FILES) $(RELSYSDIR)/examples/server_root/conf + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs/open + $(INSTALL_DATA) $(OPEN_FILES) \ + $(RELSYSDIR)/examples/server_root/htdocs/open + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs/mnesia_open + $(INSTALL_DATA) $(MNESIA_OPEN_FILES) \ + $(RELSYSDIR)/examples/server_root/htdocs/mnesia_open + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs/misc + $(INSTALL_DATA) $(MISC_FILES) \ + $(RELSYSDIR)/examples/server_root/htdocs/misc + $(INSTALL_DIR) \ + $(RELSYSDIR)/examples/server_root/htdocs/secret/top_secret + $(INSTALL_DIR) \ + $(RELSYSDIR)/examples/server_root/htdocs/mnesia_secret/top_secret + $(INSTALL_DATA) $(SECRET_FILES) \ + $(RELSYSDIR)/examples/server_root/htdocs/secret + $(INSTALL_DATA) $(MNESIA_SECRET_FILES) \ + $(RELSYSDIR)/examples/server_root/htdocs/mnesia_secret + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs + $(INSTALL_DATA) $(HTDOCS_FILES) $(RELSYSDIR)/examples/server_root/htdocs + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/icons + $(INSTALL_DATA) $(ICON_FILES) $(RELSYSDIR)/examples/server_root/icons + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/ssl + $(INSTALL_DATA) $(SSL_FILES) $(RELSYSDIR)/examples/server_root/ssl + $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/logs + +release_docs_spec: + diff --git a/lib/inets/test/httpd_test_lib.erl b/lib/inets/test/httpd_test_lib.erl index 6abee5be2c..3189a758a5 100644 --- a/lib/inets/test/httpd_test_lib.erl +++ b/lib/inets/test/httpd_test_lib.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2001-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2001-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %% @@ -72,6 +72,8 @@ 'last-modified', other=[] % list() - Key/Value list with other headers }). + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%-------------------------------------------------------------------- @@ -81,7 +83,8 @@ verify_request(SocketType, Host, Port, Node, RequestStr, Options) -> verify_request(SocketType, Host, Port, Node, RequestStr, Options, 30000). verify_request(SocketType, Host, Port, Node, RequestStr, Options, TimeOut) -> {ok, Socket} = inets_test_lib:connect_bin(SocketType, Host, Port), - inets_test_lib:send(SocketType, Socket, RequestStr), + + _SendRes = inets_test_lib:send(SocketType, Socket, RequestStr), State = case inets_regexp:match(RequestStr, "printenv") of nomatch -> @@ -90,18 +93,26 @@ verify_request(SocketType, Host, Port, Node, RequestStr, Options, TimeOut) -> #state{print = true} end, - case request(State#state{request = RequestStr, socket = Socket}, TimeOut) of - {error, Reson} -> - {error, Reson}; + case request(State#state{request = RequestStr, + socket = Socket}, TimeOut) of + {error, Reason} -> + tsp("request failed: " + "~n Reason: ~p", [Reason]), + {error, Reason}; NewState -> + tsp("validate reply: " + "~n NewState: ~p", [NewState]), ValidateResult = validate(RequestStr, NewState, Options, Node, Port), + tsp("validation result: " + "~n ~p", [ValidateResult]), inets_test_lib:close(SocketType, Socket), ValidateResult end. request(#state{mfa = {Module, Function, Args}, request = RequestStr, socket = Socket} = State, TimeOut) -> + HeadRequest = lists:sublist(RequestStr, 1, 4), receive {tcp, Socket, Data} -> @@ -109,12 +120,12 @@ request(#state{mfa = {Module, Function, Args}, case Module:Function([Data | Args]) of {ok, Parsed} -> handle_http_msg(Parsed, State); - {_, whole_body, _} when HeadRequest == "HEAD" -> + {_, whole_body, _} when HeadRequest =:= "HEAD" -> State#state{body = <<>>}; NewMFA -> request(State#state{mfa = NewMFA}, TimeOut) end; - {tcp_closed, Socket} when Function == whole_body -> + {tcp_closed, Socket} when Function =:= whole_body -> print(tcp, "closed", State), State#state{body = hd(Args)}; {tcp_closed, Socket} -> @@ -126,12 +137,12 @@ request(#state{mfa = {Module, Function, Args}, case Module:Function([Data | Args]) of {ok, Parsed} -> handle_http_msg(Parsed, State); - {_, whole_body, _} when HeadRequest == "HEAD" -> + {_, whole_body, _} when HeadRequest =:= "HEAD" -> State#state{body = <<>>}; NewMFA -> request(State#state{mfa = NewMFA}, TimeOut) end; - {ssl_closed, Socket} when Function == whole_body -> + {ssl_closed, Socket} when Function =:= whole_body -> print(ssl, "closed", State), State#state{body = hd(Args)}; {ssl_closed, Socket} -> @@ -330,3 +341,9 @@ print(Proto, Data, #state{print = true}) -> print(_, _, #state{print = false}) -> ok. + +%% tsp(F) -> +%% tsp(F, []). +tsp(F, A) -> + test_server:format("~p ~p:" ++ F ++ "~n", [self(), ?MODULE | A]). + diff --git a/lib/inets/test/httpd_time_test.erl b/lib/inets/test/httpd_time_test.erl index 7d6aa08542..f39f9faff0 100644 --- a/lib/inets/test/httpd_time_test.erl +++ b/lib/inets/test/httpd_time_test.erl @@ -1,25 +1,25 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2001-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2001-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %% -module(httpd_time_test). --export([t/3, t1/2, t2/2]). +-export([t/3, t1/2, t2/2, t3/2, t4/2]). -export([do/1, do/2, do/3, do/4, do/5]). @@ -29,6 +29,9 @@ -record(stat, {pid, time = undefined, count = undefined, res}). +%% -define(NUM_POLLERS, 10). +-define(NUM_POLLERS, 1). + %%% ----------------------------------------------------------------- %%% Test suite interface @@ -42,9 +45,17 @@ t2(Host, Port) -> t(ssl, Host, Port). +t3(Host, Port) -> + t(ossl, Host, Port). + + +t4(Host, Port) -> + t(essl, Host, Port). + + t(SocketType, Host, Port) -> %% put(dbg,true), - main(1, SocketType, Host, Port, 60000). + main(?NUM_POLLERS, SocketType, Host, Port, 60000). @@ -111,28 +122,40 @@ loop(Pollers, Timeout) -> "~n Timeout: ~p", [Timeout]), Start = t(), receive - {'EXIT', Pid, {poller_stat_failure, Time, Reason}} -> + {'EXIT', Pid, {poller_stat_failure, SocketType, Host, Port, Time, Reason}} -> case is_poller(Pid, Pollers) of true -> error_msg("received unexpected exit from poller ~p~n" "befor completion of test " - "(after ~p micro sec):~n" - "~p~n", [Pid,Time,Reason]), - exit({fail, {poller_exit, Pid, Reason}}); + "after ~p micro sec" + "~n SocketType: ~p" + "~n Host: ~p" + "~n Port: ~p" + "~n~p~n", + [Pid, SocketType, Host, Port, Time, Reason]), + exit({fail, {poller_exit, Pid, Time, Reason}}); false -> error_msg("received unexpected ~p from ~p" "befor completion of test", [Reason, Pid]), loop(Pollers, to(Timeout, Start)) end; - {poller_stat_failure, Pid, {Time, Reason}} -> + {poller_stat_failure, Pid, {SocketType, Host, Port, Time, Reason}} -> error_msg("received stat failure ~p from poller ~p after ~p " - "befor completion of test", [Reason, Pid, Time]), - exit({fail, {poller_failure, Pid, Reason}}); - - {poller_stat_failure, Pid, Reason} -> + "befor completion of test" + "~n SocketType: ~p" + "~n Host: ~p" + "~n Port: ~p", + [Reason, Pid, Time, SocketType, Host, Port]), + exit({fail, {poller_failure, Pid, Time, Reason}}); + + {poller_stat_failure, Pid, SocketType, Host, Port, Reason} -> error_msg("received stat failure ~p from poller ~p " - "befor completion of test", [Reason, Pid]), + "befor completion of test" + "~n SocketType: ~p" + "~n Host: ~p" + "~n Port: ~p", + [Reason, Pid, SocketType, Host, Port]), exit({fail, {poller_failure, Pid, Reason}}); Any -> @@ -250,16 +273,16 @@ is_poller(Pid, [_|Rest]) -> poller_main(Parent, SocketType, Host, Port) -> process_flag(trap_exit,true), - put(sname,poller), + put(sname, poller), case timer:tc(?MODULE, poller_loop, [SocketType, Host, Port, uris()]) of {Time, Count} when is_integer(Time) andalso is_integer(Count) -> Parent ! {poller_statistics, self(), {Time, Count}}; {Time, {'EXIT', Reason}} when is_integer(Time) -> - exit({poller_stat_failure, Time, Reason}); + exit({poller_stat_failure, SocketType, Host, Port, Time, Reason}); {Time, Other} when is_integer(Time) -> - Parent ! {poller_stat_failure, self(), {Time, Other}}; + Parent ! {poller_stat_failure, self(), {SocketType, Host, Port, Time, Other}}; Else -> - Parent ! {poller_stat_failure, self(), Else} + Parent ! {poller_stat_failure, self(), SocketType, Host, Port, Else} end. diff --git a/lib/inets/test/inets_sup_SUITE.erl b/lib/inets/test/inets_sup_SUITE.erl index ba41e0960c..1e701bc074 100644 --- a/lib/inets/test/inets_sup_SUITE.erl +++ b/lib/inets/test/inets_sup_SUITE.erl @@ -372,11 +372,11 @@ httpc_subtree(Config) when is_list(Config) -> "~n Config: ~p", [Config]), tsp("httpc_subtree -> start inets service httpc with profile foo"), - {ok, Foo} = inets:start(httpc, [{profile, foo}]), + {ok, _Foo} = inets:start(httpc, [{profile, foo}]), tsp("httpc_subtree -> " "start stand-alone inets service httpc with profile bar"), - {ok, Bar} = inets:start(httpc, [{profile, bar}], stand_alone), + {ok, _Bar} = inets:start(httpc, [{profile, bar}], stand_alone), tsp("httpc_subtree -> retreive list of httpc instances"), HttpcChildren = supervisor:which_children(httpc_profile_sup), diff --git a/lib/inets/test/inets_test_lib.erl b/lib/inets/test/inets_test_lib.erl index 6af2ad32f7..707b8c026a 100644 --- a/lib/inets/test/inets_test_lib.erl +++ b/lib/inets/test/inets_test_lib.erl @@ -1,28 +1,30 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2001-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2001-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %% -module(inets_test_lib). -include("inets_test_lib.hrl"). +-include_lib("inets/src/http_lib/http_internal.hrl"). %% Various small utility functions --export([start_http_server/1, start_http_server_ssl/1]). +-export([start_http_server/1, start_http_server/2]). +-export([start_http_server_ssl/1, start_http_server_ssl/2]). -export([hostname/0]). -export([connect_bin/3, connect_byte/3, send/3, close/2]). -export([copy_file/3, copy_files/2, copy_dirs/2, del_dirs/1]). @@ -30,15 +32,99 @@ -export([check_body/1]). -export([millis/0, millis_diff/2, hours/1, minutes/1, seconds/1, sleep/1]). -export([non_pc_tc_maybe_skip/4, os_based_skip/1]). +-export([flush/0]). +-export([start_node/1, stop_node/1]). + +%% -- Misc node operation wrapper functions -- + +start_node(Name) -> + Pa = filename:dirname(code:which(?MODULE)), + Args = case init:get_argument('CC_TEST') of + {ok, [[]]} -> + " -pa /clearcase/otp/libraries/snmp/ebin "; + {ok, [[Path]]} -> + " -pa " ++ Path; + error -> + "" + end, + A = Args ++ " -pa " ++ Pa, + Opts = [{cleanup,false}, {args, A}], + case (catch test_server:start_node(Name, slave, Opts)) of + {ok, Node} -> + Node; + Else -> + exit({failed_starting_node, Name, Else}) + end. + +stop_node(Node) -> + rpc:cast(Node, erlang, halt, []), + await_stopped(Node, 5). + +await_stopped(_, 0) -> + ok; +await_stopped(Node, N) -> + Nodes = erlang:nodes(), + case lists:member(Node, Nodes) of + true -> + sleep(1000), + await_stopped(Node, N-1); + false -> + ok + end. + + +%% ---------------------------------------------------------------- +%% HTTPD starter functions +%% start_http_server(Conf) -> + start_http_server(Conf, ?HTTP_DEFAULT_SSL_KIND). + +start_http_server(Conf, essl = _SslTag) -> + application:start(crypto), + do_start_http_server(Conf); +start_http_server(Conf, _SslTag) -> + do_start_http_server(Conf). + +do_start_http_server(Conf) -> + tsp("start http server with " + "~n Conf: ~p" + "~n", [Conf]), application:load(inets), - ok = application:set_env(inets, services, [{httpd, Conf}]), - ok = application:start(inets). - + case application:set_env(inets, services, [{httpd, Conf}]) of + ok -> + case application:start(inets) of + ok -> + ok; + Error1 -> + test_server:format("<ERROR> Failed starting application: " + "~n Error: ~p" + "~n", [Error1]), + Error1 + end; + Error2 -> + test_server:format("<ERROR> Failed set application env: " + "~n Error: ~p" + "~n", [Error2]), + Error2 + end. + start_http_server_ssl(FileName) -> + start_http_server_ssl(FileName, ?HTTP_DEFAULT_SSL_KIND). + +start_http_server_ssl(FileName, essl = _SslTag) -> + application:start(crypto), + do_start_http_server_ssl(FileName); +start_http_server_ssl(FileName, _SslTag) -> + do_start_http_server_ssl(FileName). + +do_start_http_server_ssl(FileName) -> + tsp("start (ssl) http server with " + "~n FileName: ~p" + "~n", [FileName]), application:start(ssl), - catch start_http_server(FileName). + catch do_start_http_server(FileName). + %% ---------------------------------------------------------------------- %% print functions @@ -84,27 +170,17 @@ copy_files(FromDir, ToDir) -> copy_dirs(FromDirRoot, ToDirRoot) -> -%% io:format("~w:copy_dirs -> entry with" -%% "~n FromDirRoot: ~p" -%% "~n ToDirRoot: ~p" -%% "~n", [?MODULE, FromDirRoot, ToDirRoot]), {ok, Files} = file:list_dir(FromDirRoot), lists:foreach( fun(FileOrDir) -> %% Check if it's a directory or a file -%% io:format("~w:copy_dirs -> check ~p" -%% "~n", [?MODULE, FileOrDir]), case filelib:is_dir(filename:join(FromDirRoot, FileOrDir)) of true -> -%% io:format("~w:copy_dirs -> ~p is a directory" -%% "~n", [?MODULE, FileOrDir]), FromDir = filename:join([FromDirRoot, FileOrDir]), ToDir = filename:join([ToDirRoot, FileOrDir]), ok = file:make_dir(ToDir), copy_dirs(FromDir, ToDir); false -> -%% io:format("~w:copy_dirs -> ~p is a file" -%% "~n", [?MODULE, FileOrDir]), copy_file(FileOrDir, FromDirRoot, ToDirRoot) end end, Files). @@ -133,8 +209,8 @@ check_body(Body) -> 0 -> case string:rstr(Body, "</HTML>") of 0 -> - test_server:format("Body ~p~n", [Body]), - test_server:fail(did_not_receive_whole_body); + tsp("Body ~p", [Body]), + tsf(did_not_receive_whole_body); _ -> ok end; @@ -204,9 +280,31 @@ os_based_skip(_) -> %% Port -> integer() connect_bin(ssl, Host, Port) -> + connect(ssl, Host, Port, [binary, {packet,0}]); +connect_bin(ossl, Host, Port) -> + connect(ssl, Host, Port, [{ssl_imp, old}, binary, {packet,0}]); +connect_bin(essl, Host, Port) -> + connect(ssl, Host, Port, [{ssl_imp, new}, binary, {packet,0}, {reuseaddr, true}]); +connect_bin(ip_comm, Host, Port) -> + Opts = [inet6, binary, {packet,0}], + connect(ip_comm, Host, Port, Opts). + + +connect_byte(ssl, Host, Port) -> + connect(ssl, Host, Port, [{packet,0}]); +connect_byte(ossl, Host, Port) -> + connect(ssl, Host, Port, [{ssl_imp, old}, {packet,0}]); +connect_byte(essl, Host, Port) -> + connect(ssl, Host, Port, [{ssl_imp, new}, {packet,0}]); +connect_byte(ip_comm, Host, Port) -> + Opts = [inet6, {packet,0}], + connect(ip_comm, Host, Port, Opts). + + +connect(ssl, Host, Port, Opts) -> ssl:start(), %% Does not support ipv6 in old ssl - case ssl:connect(Host, Port, [binary, {packet,0}]) of + case ssl:connect(Host, Port, Opts) of {ok, Socket} -> {ok, Socket}; {error, Reason} -> @@ -214,61 +312,48 @@ connect_bin(ssl, Host, Port) -> Error -> Error end; -connect_bin(ip_comm, Host, Port) -> - Opts = [inet6, binary, {packet,0}], - connect(ip_comm, Host, Port, Opts). - - connect(ip_comm, Host, Port, Opts) -> - test_server:format("gen_tcp:connect(~p, ~p, ~p) ~n", [Host, Port, Opts]), case gen_tcp:connect(Host,Port, Opts) of {ok, Socket} -> - test_server:format("connect success~n", []), + %% tsp("connect success"), {ok, Socket}; {error, nxdomain} -> - test_server:format("nxdomain opts: ~p~n", [Opts]), + tsp("nxdomain opts: ~p", [Opts]), connect(ip_comm, Host, Port, lists:delete(inet6, Opts)); {error, eafnosupport} -> - test_server:format("eafnosupport opts: ~p~n", [Opts]), + tsp("eafnosupport opts: ~p", [Opts]), connect(ip_comm, Host, Port, lists:delete(inet6, Opts)); {error, {enfile,_}} -> - test_server:format("Error enfile~n", []), + tsp("Error enfile"), {error, enfile}; Error -> - test_server:format("Unexpected error: " - "~n Error: ~p" - "~nwhen" - "~n Host: ~p" - "~n Port: ~p" - "~n Opts: ~p" - "~n", [Error, Host, Port, Opts]), + tsp("Unexpected error: " + "~n Error: ~p" + "~nwhen" + "~n Host: ~p" + "~n Port: ~p" + "~n Opts: ~p" + "~n", [Error, Host, Port, Opts]), Error end. -connect_byte(ip_comm, Host, Port) -> - Opts = [inet6, {packet,0}], - connect(ip_comm, Host, Port, Opts); - -connect_byte(ssl, Host, Port) -> - ssl:start(), - %% Does not support ipv6 in old ssl - case ssl:connect(Host,Port,[{packet,0}]) of - {ok,Socket} -> - {ok,Socket}; - {error,{enfile,_}} -> - {error, enfile}; - Error -> - Error - end. send(ssl, Socket, Data) -> ssl:send(Socket, Data); +send(ossl, Socket, Data) -> + ssl:send(Socket, Data); +send(essl, Socket, Data) -> + ssl:send(Socket, Data); send(ip_comm,Socket,Data) -> gen_tcp:send(Socket,Data). close(ssl,Socket) -> catch ssl:close(Socket); +close(ossl,Socket) -> + catch ssl:close(Socket); +close(essl,Socket) -> + catch ssl:close(Socket); close(ip_comm,Socket) -> catch gen_tcp:close(Socket). @@ -300,3 +385,20 @@ sleep(MSecs) -> skip(Reason, File, Line) -> exit({skipped, {Reason, File, Line}}). + +flush() -> + receive + Msg -> + [Msg | flush()] + after 1000 -> + [] + end. + + +tsp(F) -> + tsp(F, []). +tsp(F, A) -> + test_server:format("~p ~p:" ++ F ++ "~n", [self(), ?MODULE | A]). + +tsf(Reason) -> + test_server:fail(Reason). diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk index ac20fa7bb7..57c87e7036 100644 --- a/lib/inets/vsn.mk +++ b/lib/inets/vsn.mk @@ -18,11 +18,16 @@ # %CopyrightEnd% APPLICATION = inets -INETS_VSN = 5.3.3 +INETS_VSN = 5.4 PRE_VSN = APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)" -TICKETS = OTP-8609 OTP-8610 OTP-8624 +TICKETS = OTP-7907 OTP-8564 OTP-8573 + +TICKETS_5_3_3 = \ + OTP-8609 \ + OTP-8610 \ + OTP-8624 TICKETS_5_3_2 = \ OTP-8542 \ diff --git a/lib/kernel/doc/src/file.xml b/lib/kernel/doc/src/file.xml index 382262d1ee..a9ceac0bcf 100644 --- a/lib/kernel/doc/src/file.xml +++ b/lib/kernel/doc/src/file.xml @@ -603,7 +603,7 @@ f.txt: {person, "kalle", 25}. <type> <v>Filename = name()</v> <v>Modes = [Mode]</v> - <v> Mode = read | write | append | raw | binary | {delayed_write, Size, Delay} | delayed_write | {read_ahead, Size} | read_ahead | compressed</v> + <v> Mode = read | write | append | exclusive | raw | binary | {delayed_write, Size, Delay} | delayed_write | {read_ahead, Size} | read_ahead | compressed</v> <v> Size = Delay = int()</v> <v>IoDevice = io_device()</v> <v>Reason = ext_posix() | system_limit</v> @@ -630,6 +630,17 @@ f.txt: {person, "kalle", 25}. file opened with <c>append</c> will take place at the end of the file.</p> </item> + <tag><c>exclusive</c></tag> + <item> + <p>The file, when opened for writing, is created if it + does not exist. If the file exists, open will return + <c>{error, eexist}</c>.</p> + <warning><p>This option does not guarantee exclusiveness on + file systems that do not support O_EXCL properly, + such as NFS. Do not depend on this option unless you + know that the file system supports it (in general, local + file systems should be safe).</p></warning> + </item> <tag><c>raw</c></tag> <item> <p>The <c>raw</c> option allows faster access to a file, diff --git a/lib/kernel/src/code.erl b/lib/kernel/src/code.erl index affa5fc0fd..42d4818f08 100644 --- a/lib/kernel/src/code.erl +++ b/lib/kernel/src/code.erl @@ -66,6 +66,8 @@ set_primary_archive/3, clash/0]). +-export_type([load_error_rsn/0, load_ret/0]). + -include_lib("kernel/include/file.hrl"). %% User interface. diff --git a/lib/kernel/src/dist_util.erl b/lib/kernel/src/dist_util.erl index a2937d60b8..f0d54a2f3e 100644 --- a/lib/kernel/src/dist_util.erl +++ b/lib/kernel/src/dist_util.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1999-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1999-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %%%---------------------------------------------------------------------- @@ -564,7 +564,7 @@ recv_challenge(#hs_data{socket=Socket,other_node=Node, case Recv(Socket, 0, infinity) of {ok,[$n,V1,V0,Fl1,Fl2,Fl3,Fl4,CA3,CA2,CA1,CA0 | Ns]} -> Flags = ?u32(Fl1,Fl2,Fl3,Fl4), - case {list_to_existing_atom(Ns),?u16(V1,V0)} of + try {list_to_existing_atom(Ns),?u16(V1,V0)} of {Node,Version} -> Challenge = ?u32(CA3,CA2,CA1,CA0), ?trace("recv: node=~w, challenge=~w version=~w\n", @@ -572,6 +572,9 @@ recv_challenge(#hs_data{socket=Socket,other_node=Node, {Flags,Challenge}; _ -> ?shutdown(no_node) + catch + error:badarg -> + ?shutdown(no_node) end; _ -> ?shutdown(no_node) diff --git a/lib/kernel/src/file.erl b/lib/kernel/src/file.erl index a694ed0708..cfdd7045bd 100644 --- a/lib/kernel/src/file.erl +++ b/lib/kernel/src/file.erl @@ -61,6 +61,9 @@ -export([ipread_s32bu_p32bu_int/3]). +%% Types that can be used from other modules -- alphabetically ordered. +-export_type([date_time/0, fd/0, file_info/0, filename/0, io_device/0, + name/0, posix/0]). %%% Includes and defines -include("file.hrl"). @@ -81,7 +84,7 @@ -type mode() :: 'read' | 'write' | 'append' | 'raw' | 'binary' | {'delayed_write', non_neg_integer(), non_neg_integer()} | 'delayed_write' | {'read_ahead', pos_integer()} | - 'read_ahead' | 'compressed'. + 'read_ahead' | 'compressed' | 'exclusive'. -type name() :: string() | atom() | [name()]. -type posix() :: atom(). -type bindings() :: any(). @@ -369,7 +372,7 @@ advise(_, _, _, _) -> -spec read(File :: io_device(), Size :: non_neg_integer()) -> 'eof' | {'ok', [char()] | binary()} | {'error', posix()}. -read(File, Sz) when is_pid(File), is_integer(Sz), Sz >= 0 -> +read(File, Sz) when (is_pid(File) orelse is_atom(File)), is_integer(Sz), Sz >= 0 -> case io:request(File, {get_chars, '', Sz}) of Data when is_list(Data); is_binary(Data) -> {ok, Data}; @@ -385,7 +388,7 @@ read(_, _) -> -spec read_line(File :: io_device()) -> 'eof' | {'ok', [char()] | binary()} | {'error', posix()}. -read_line(File) when is_pid(File) -> +read_line(File) when (is_pid(File) orelse is_atom(File)) -> case io:request(File, {get_line, ''}) of Data when is_list(Data); is_binary(Data) -> {ok, Data}; @@ -439,7 +442,7 @@ pread(_, _, _) -> -spec write(File :: io_device(), Byte :: iodata()) -> 'ok' | {'error', posix()}. -write(File, Bytes) when is_pid(File) -> +write(File, Bytes) when (is_pid(File) orelse is_atom(File)) -> case make_binary(Bytes) of Bin when is_binary(Bin) -> io:request(File, {put_chars,Bin}); diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl index eb503235d8..93d75321ba 100644 --- a/lib/kernel/src/inet.erl +++ b/lib/kernel/src/inet.erl @@ -62,6 +62,8 @@ %% timer interface -export([start_timer/1, timeout/1, timeout/2, stop_timer/1]). +-export_type([ip_address/0, socket/0]). + %% imports -import(lists, [append/1, duplicate/2, filter/2, foldl/3]). diff --git a/lib/kernel/src/inet_dns.erl b/lib/kernel/src/inet_dns.erl index 669a361c9d..1289e176c7 100644 --- a/lib/kernel/src/inet_dns.erl +++ b/lib/kernel/src/inet_dns.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1997-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% -module(inet_dns). @@ -129,27 +129,33 @@ do_decode(<<Id:16, RA:1,PR:1,_:2,Rcode:4, QdCount:16,AnCount:16,NsCount:16,ArCount:16, QdBuf/binary>>=Buffer) -> - {AnBuf,QdList} = decode_query_section(QdBuf,QdCount,Buffer), - {NsBuf,AnList} = decode_rr_section(AnBuf,AnCount,Buffer), - {ArBuf,NsList} = decode_rr_section(NsBuf,NsCount,Buffer), - {Rest,ArList} = decode_rr_section(ArBuf,ArCount,Buffer), + {AnBuf,QdList,QdTC} = decode_query_section(QdBuf,QdCount,Buffer), + {NsBuf,AnList,AnTC} = decode_rr_section(AnBuf,AnCount,Buffer), + {ArBuf,NsList,NsTC} = decode_rr_section(NsBuf,NsCount,Buffer), + {Rest,ArList,ArTC} = decode_rr_section(ArBuf,ArCount,Buffer), case Rest of <<>> -> + HdrTC = decode_boolean(TC), DnsHdr = #dns_header{id=Id, qr=decode_boolean(QR), opcode=decode_opcode(Opcode), aa=decode_boolean(AA), - tc=decode_boolean(TC), + tc=HdrTC, rd=decode_boolean(RD), ra=decode_boolean(RA), pr=decode_boolean(PR), rcode=Rcode}, - #dns_rec{header=DnsHdr, - qdlist=QdList, - anlist=AnList, - nslist=NsList, - arlist=ArList}; + case QdTC or AnTC or NsTC or ArTC of + true when not HdrTC -> + throw(?DECODE_ERROR); + _ -> + #dns_rec{header=DnsHdr, + qdlist=QdList, + anlist=AnList, + nslist=NsList, + arlist=ArList} + end; _ -> %% Garbage data after DNS message throw(?DECODE_ERROR) @@ -161,8 +167,10 @@ do_decode(_) -> decode_query_section(Bin, N, Buffer) -> decode_query_section(Bin, N, Buffer, []). +decode_query_section(<<>>=Rest, N, _Buffer, Qs) -> + {Rest,reverse(Qs),N =/= 0}; decode_query_section(Rest, 0, _Buffer, Qs) -> - {Rest,reverse(Qs)}; + {Rest,reverse(Qs),false}; decode_query_section(Bin, N, Buffer, Qs) -> case decode_name(Bin, Buffer) of {<<Type:16,Class:16,Rest/binary>>,Name} -> @@ -179,8 +187,10 @@ decode_query_section(Bin, N, Buffer, Qs) -> decode_rr_section(Bin, N, Buffer) -> decode_rr_section(Bin, N, Buffer, []). +decode_rr_section(<<>>=Rest, N, _Buffer, RRs) -> + {Rest,reverse(RRs),N =/= 0}; decode_rr_section(Rest, 0, _Buffer, RRs) -> - {Rest,reverse(RRs)}; + {Rest,reverse(RRs),false}; decode_rr_section(Bin, N, Buffer, RRs) -> case decode_name(Bin, Buffer) of {<<T:16/unsigned,C:16/unsigned,TTL:4/binary, diff --git a/lib/kernel/src/inet_res.erl b/lib/kernel/src/inet_res.erl index 9b9e078898..de0f23bf24 100644 --- a/lib/kernel/src/inet_res.erl +++ b/lib/kernel/src/inet_res.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1997-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %% RFC 1035, 2671, 2782, 2915. @@ -592,6 +592,7 @@ query_retries(_Q, _NSs, _Timer, Retry, Retry, S) -> query_retries(Q, NSs, Timer, Retry, I, S0) -> Num = length(NSs), if Num =:= 0 -> + udp_close(S0), {error,timeout}; true -> case query_nss(Q, NSs, Timer, Retry, I, S0, []) of diff --git a/lib/kernel/src/net_kernel.erl b/lib/kernel/src/net_kernel.erl index 3afaedf274..0e17c059e5 100644 --- a/lib/kernel/src/net_kernel.erl +++ b/lib/kernel/src/net_kernel.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1996-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% -module(net_kernel). @@ -72,7 +72,7 @@ -export([publish_on_node/1, update_publish_nodes/1]). -%% Internal Exports +%% Internal Exports -export([do_spawn/3, spawn_func/6, ticker/2, @@ -94,7 +94,7 @@ connecttime, %% the connection setuptime. connections, %% table of connections conn_owners = [], %% List of connection owner pids, - pend_owners = [], %% List of potential owners + pend_owners = [], %% List of potential owners listen, %% list of #listen allowed, %% list of allowed nodes in a restricted system verbose = 0, %% level of verboseness @@ -232,7 +232,7 @@ do_connect(Node, Type, WaitForBarred) -> %% Type = normal | hidden %% "connected from other end.~n",[Node]), true; {Pid, false} -> - ?connect_failure(Node,{barred_connection, + ?connect_failure(Node,{barred_connection, ets:lookup(sys_dist, Node)}), %%io:format("Net Kernel: barred connection (~p) " %% "- failure.~n",[Node]), @@ -244,12 +244,12 @@ do_connect(Node, Type, WaitForBarred) -> %% Type = normal | hidden {ok, never} -> ?connect_failure(Node,{dist_auto_connect,never}), false; - % This might happen due to connection close + % This might happen due to connection close % not beeing propagated to user space yet. - % Save the day by just not connecting... + % Save the day by just not connecting... {ok, once} when Else =/= [], (hd(Else))#connection.state =:= up -> - ?connect_failure(Node,{barred_connection, + ?connect_failure(Node,{barred_connection, ets:lookup(sys_dist, Node)}), false; _ -> @@ -276,8 +276,8 @@ passive_connect_monitor(Parent, Node) -> Parent ! {self(),true} end end. - -%% If the net_kernel isn't running we ignore all requests to the + +%% If the net_kernel isn't running we ignore all requests to the %% kernel, thus basically accepting them :-) request(Req) -> case whereis(net_kernel) of @@ -302,7 +302,7 @@ start_link([Name, LongOrShortNames]) -> start_link([Name, LongOrShortNames, 15000]); start_link([Name, LongOrShortNames, Ticktime]) -> - case gen_server:start_link({local, net_kernel}, net_kernel, + case gen_server:start_link({local, net_kernel}, net_kernel, {Name, LongOrShortNames, Ticktime}, []) of {ok, Pid} -> {ok, Pid}; @@ -313,7 +313,7 @@ start_link([Name, LongOrShortNames, Ticktime]) -> end. %% auth:get_cookie should only be able to return an atom -%% tuple cookies are unknowns +%% tuple cookies are unknowns init({Name, LongOrShortNames, TickT}) -> process_flag(trap_exit,true), @@ -354,13 +354,13 @@ init({Name, LongOrShortNames, TickT}) -> %% The response is delayed until the connection is up and %% running. %% -handle_call({connect, _, Node}, _From, State) when Node =:= node() -> - {reply, true, State}; +handle_call({connect, _, Node}, From, State) when Node =:= node() -> + async_reply({reply, true, State}, From); handle_call({connect, Type, Node}, From, State) -> verbose({connect, Type, Node}, 1, State), case ets:lookup(sys_dist, Node) of [Conn] when Conn#connection.state =:= up -> - {reply, true, State}; + async_reply({reply, true, State}, From); [Conn] when Conn#connection.state =:= pending -> Waiting = Conn#connection.waiting, ets:insert(sys_dist, Conn#connection{waiting = [From|Waiting]}), @@ -376,74 +376,75 @@ handle_call({connect, Type, Node}, From, State) -> {noreply,State#state{conn_owners=Owners}}; _ -> ?connect_failure(Node, {setup_call, failed}), - {reply, false, State} + async_reply({reply, false, State}, From) end end; %% %% Close the connection to Node. %% -handle_call({disconnect, Node}, _From, State) when Node =:= node() -> - {reply, false, State}; -handle_call({disconnect, Node}, _From, State) -> +handle_call({disconnect, Node}, From, State) when Node =:= node() -> + async_reply({reply, false, State}, From); +handle_call({disconnect, Node}, From, State) -> verbose({disconnect, Node}, 1, State), {Reply, State1} = do_disconnect(Node, State), - {reply, Reply, State1}; + async_reply({reply, Reply, State1}, From); -%% +%% %% The spawn/4 BIF ends up here. -%% +%% handle_call({spawn,M,F,A,Gleader},{From,Tag},State) when is_pid(From) -> do_spawn([no_link,{From,Tag},M,F,A,Gleader],[],State); -%% +%% %% The spawn_link/4 BIF ends up here. -%% +%% handle_call({spawn_link,M,F,A,Gleader},{From,Tag},State) when is_pid(From) -> do_spawn([link,{From,Tag},M,F,A,Gleader],[],State); -%% +%% %% The spawn_opt/5 BIF ends up here. -%% +%% handle_call({spawn_opt,M,F,A,O,L,Gleader},{From,Tag},State) when is_pid(From) -> do_spawn([L,{From,Tag},M,F,A,Gleader],O,State); -%% +%% %% Only allow certain nodes. -%% -handle_call({allow, Nodes}, _From, State) -> +%% +handle_call({allow, Nodes}, From, State) -> case all_atoms(Nodes) of true -> Allowed = State#state.allowed, - {reply,ok,State#state{allowed = Allowed ++ Nodes}}; + async_reply({reply,ok,State#state{allowed = Allowed ++ Nodes}}, + From); false -> - {reply,error,State} + async_reply({reply,error,State}, From) end; -%% +%% %% authentication, used by auth. Simply works as this: %% if the message comes through, the other node IS authorized. -%% -handle_call({is_auth, _Node}, _From, State) -> - {reply,yes,State}; +%% +handle_call({is_auth, _Node}, From, State) -> + async_reply({reply,yes,State}, From); -%% +%% %% Not applicable any longer !? -%% -handle_call({apply,_Mod,_Fun,_Args}, {From,Tag}, State) +%% +handle_call({apply,_Mod,_Fun,_Args}, {From,Tag}, State) when is_pid(From), node(From) =:= node() -> - gen_server:reply({From,Tag}, not_implemented), + async_gen_server_reply({From,Tag}, not_implemented), % Port = State#state.port, % catch apply(Mod,Fun,[Port|Args]), {noreply,State}; -handle_call(longnames, _From, State) -> - {reply, get(longnames), State}; +handle_call(longnames, From, State) -> + async_reply({reply, get(longnames), State}, From); -handle_call({update_publish_nodes, Ns}, _From, State) -> - {reply, ok, State#state{publish_on_nodes = Ns}}; +handle_call({update_publish_nodes, Ns}, From, State) -> + async_reply({reply, ok, State#state{publish_on_nodes = Ns}}, From); -handle_call({publish_on_node, Node}, _From, State) -> +handle_call({publish_on_node, Node}, From, State) -> NewState = case State#state.publish_on_nodes of undefined -> State#state{publish_on_nodes = @@ -457,11 +458,12 @@ handle_call({publish_on_node, Node}, _From, State) -> Nodes -> lists:member(Node, Nodes) end, - {reply, Publish, NewState}; + async_reply({reply, Publish, NewState}, From); -handle_call({verbose, Level}, _From, State) -> - {reply, State#state.verbose, State#state{verbose = Level}}; +handle_call({verbose, Level}, From, State) -> + async_reply({reply, State#state.verbose, State#state{verbose = Level}}, + From); %% %% Set new ticktime @@ -471,16 +473,16 @@ handle_call({verbose, Level}, _From, State) -> %% #tick_change{} record if the ticker process has been upgraded; %% otherwise, an integer or an atom. -handle_call(ticktime, _, #state{tick = #tick{time = T}} = State) -> - {reply, T, State}; -handle_call(ticktime, _, #state{tick = #tick_change{time = T}} = State) -> - {reply, {ongoing_change_to, T}, State}; +handle_call(ticktime, From, #state{tick = #tick{time = T}} = State) -> + async_reply({reply, T, State}, From); +handle_call(ticktime, From, #state{tick = #tick_change{time = T}} = State) -> + async_reply({reply, {ongoing_change_to, T}, State}, From); -handle_call({new_ticktime,T,_TP}, _, #state{tick = #tick{time = T}} = State) -> +handle_call({new_ticktime,T,_TP}, From, #state{tick = #tick{time = T}} = State) -> ?tckr_dbg(no_tick_change), - {reply, unchanged, State}; + async_reply({reply, unchanged, State}, From); -handle_call({new_ticktime,T,TP}, _, #state{tick = #tick{ticker = Tckr, +handle_call({new_ticktime,T,TP}, From, #state{tick = #tick{ticker = Tckr, time = OT}} = State) -> ?tckr_dbg(initiating_tick_change), start_aux_ticker(T, OT, TP), @@ -493,14 +495,18 @@ handle_call({new_ticktime,T,TP}, _, #state{tick = #tick{ticker = Tckr, ?tckr_dbg(shorter_ticktime), shorter end, - {reply, change_initiated, State#state{tick = #tick_change{ticker = Tckr, - time = T, - how = How}}}; + async_reply({reply, change_initiated, + State#state{tick = #tick_change{ticker = Tckr, + time = T, + how = How}}}, From); -handle_call({new_ticktime,_,_}, +handle_call({new_ticktime,From,_}, _, #state{tick = #tick_change{time = T}} = State) -> - {reply, {ongoing_change_to, T}, State}. + async_reply({reply, {ongoing_change_to, T}, State}, From); + +handle_call(_Msg, _From, State) -> + {noreply, State}. %% ------------------------------------------------------------ %% handle_cast. @@ -568,7 +574,7 @@ handle_info({accept,AcceptPid,Socket,Family,Proto}, State) -> %% %% A node has successfully been connected. %% -handle_info({SetupPid, {nodeup,Node,Address,Type,Immediate}}, +handle_info({SetupPid, {nodeup,Node,Address,Type,Immediate}}, State) -> case {Immediate, ets:lookup(sys_dist, Node)} of {true, [Conn]} when Conn#connection.state =:= pending, @@ -656,7 +662,7 @@ handle_info({From,registered_send,To,Mess},State) -> send(From,To,Mess), {noreply,State}; -%% badcookies SHOULD not be sent +%% badcookies SHOULD not be sent %% (if someone does erlang:set_cookie(node(),foo) this may be) handle_info({From,badcookie,_To,_Mess}, State) -> error_logger:error_msg("~n** Got OLD cookie from ~w~n", @@ -704,7 +710,7 @@ handle_info(X, State) -> %% 4. The ticker process. %% (5. Garbage pid.) %% -%% The process type function that handled the process throws +%% The process type function that handled the process throws %% the handle_info return value ! %% ----------------------------------------------------------- @@ -994,9 +1000,9 @@ ticker(Kernel, Tick) when is_integer(Tick) -> ticker_loop(Kernel, Tick). to_integer(T) when is_integer(T) -> T; -to_integer(T) when is_atom(T) -> +to_integer(T) when is_atom(T) -> list_to_integer(atom_to_list(T)); -to_integer(T) when is_list(T) -> +to_integer(T) when is_list(T) -> list_to_integer(T). ticker_loop(Kernel, Tick) -> @@ -1004,7 +1010,7 @@ ticker_loop(Kernel, Tick) -> {new_ticktime, NewTick} -> ?tckr_dbg({ticker_changed_time, Tick, NewTick}), ?MODULE:ticker_loop(Kernel, NewTick) - after Tick -> + after Tick -> Kernel ! tick, ?MODULE:ticker_loop(Kernel, Tick) end. @@ -1052,7 +1058,7 @@ send(_From,To,Mess) -> -ifdef(UNUSED). safesend(Name,Mess) when is_atom(Name) -> - case whereis(Name) of + case whereis(Name) of undefined -> Mess; P when is_pid(P) -> @@ -1063,11 +1069,12 @@ safesend(Pid, Mess) -> Pid ! Mess. -endif. do_spawn(SpawnFuncArgs, SpawnOpts, State) -> + [_,From|_] = SpawnFuncArgs, case catch spawn_opt(?MODULE, spawn_func, SpawnFuncArgs, SpawnOpts) of - {'EXIT', {Reason,_}} -> - {reply, {'EXIT', {Reason,[]}}, State}; - {'EXIT', Reason} -> - {reply, {'EXIT', {Reason,[]}}, State}; + {'EXIT', {Reason,_}} -> + async_reply({reply, {'EXIT', {Reason,[]}}, State}, From); + {'EXIT', Reason} -> + async_reply({reply, {'EXIT', {Reason,[]}}, State}, From); _ -> {noreply,State} end. @@ -1079,11 +1086,11 @@ do_spawn(SpawnFuncArgs, SpawnOpts, State) -> spawn_func(link,{From,Tag},M,F,A,Gleader) -> link(From), - gen_server:reply({From,Tag},self()), %% ahhh + async_gen_server_reply({From,Tag},self()), %% ahhh group_leader(Gleader,self()), apply(M,F,A); spawn_func(_,{From,Tag},M,F,A,Gleader) -> - gen_server:reply({From,Tag},self()), %% ahhh + async_gen_server_reply({From,Tag},self()), %% ahhh group_leader(Gleader,self()), apply(M,F,A). @@ -1145,7 +1152,7 @@ get_proto_mod(Family,Protocol,[L|Ls]) -> true -> get_proto_mod(Family,Protocol,Ls) end; -get_proto_mod(_Family, _Protocol, []) -> +get_proto_mod(_Family, _Protocol, []) -> error. %% -------- Initialisation functions ------------------------ @@ -1156,9 +1163,9 @@ init_node(Name, LongOrShortNames) -> case create_name(Name, LongOrShortNames, 1) of {ok,Node} -> case start_protos(list_to_atom(NameWithoutHost),Node) of - {ok, Ls} -> + {ok, Ls} -> {ok, Node, Ls}; - Error -> + Error -> Error end; Error -> @@ -1167,9 +1174,9 @@ init_node(Name, LongOrShortNames) -> %% Create the node name create_name(Name, LongOrShortNames, Try) -> - put(longnames, case LongOrShortNames of - shortnames -> false; - longnames -> true + put(longnames, case LongOrShortNames of + shortnames -> false; + longnames -> true end), {Head,Host1} = create_hostpart(Name, LongOrShortNames), case Host1 of @@ -1218,7 +1225,7 @@ create_hostpart(Name, LongOrShortNames) -> {Head,Host1}. %% -%% +%% %% protocol_childspecs() -> case init:get_argument(proto_dist) of @@ -1228,7 +1235,7 @@ protocol_childspecs() -> protocol_childspecs(["inet_tcp"]) end. -protocol_childspecs([]) -> +protocol_childspecs([]) -> []; protocol_childspecs([H|T]) -> Mod = list_to_atom(H ++ "_dist"), @@ -1238,15 +1245,15 @@ protocol_childspecs([H|T]) -> _ -> protocol_childspecs(T) end. - - + + %% %% epmd_module() -> module_name of erl_epmd or similar gen_server_module. %% epmd_module() -> case init:get_argument(epmd_module) of - {ok,[[Module]]} -> + {ok,[[Module]]} -> Module; _ -> erl_epmd @@ -1293,7 +1300,7 @@ start_protos(Name, [Proto | Ps], Node, Ls) -> error_logger:info_msg("Protocol: ~p: not supported~n", [Proto]), start_protos(Name,Ps, Node, Ls); {'EXIT', Reason} -> - error_logger:info_msg("Protocol: ~p: register error: ~p~n", + error_logger:info_msg("Protocol: ~p: register error: ~p~n", [Proto, Reason]), start_protos(Name,Ps, Node, Ls); {error, duplicate_name} -> @@ -1303,7 +1310,7 @@ start_protos(Name, [Proto | Ps], Node, Ls) -> [Proto]), start_protos(Name,Ps, Node, Ls); {error, Reason} -> - error_logger:info_msg("Protocol: ~p: register/listen error: ~p~n", + error_logger:info_msg("Protocol: ~p: register/listen error: ~p~n", [Proto, Reason]), start_protos(Name,Ps, Node, Ls) end; @@ -1409,7 +1416,7 @@ reply_waiting(_Node, Waiting, Rep) -> reply_waiting1(lists:reverse(Waiting), Rep). reply_waiting1([From|W], Rep) -> - gen_server:reply(From, Rep), + async_gen_server_reply(From, Rep), reply_waiting1(W, Rep); reply_waiting1([], _) -> ok. @@ -1455,7 +1462,7 @@ display_info({Node, Info}, {I,O}) -> integer_to_list(In), integer_to_list(Out), Address), {I+In,O+Out}. -fmt_address(undefined) -> +fmt_address(undefined) -> "-"; fmt_address(A) -> case A#net_address.family of @@ -1511,3 +1518,19 @@ verbose(_, _, _) -> getnode(P) when is_pid(P) -> node(P); getnode(P) -> P. + +async_reply({reply, Msg, State}, From) -> + async_gen_server_reply(From, Msg), + {noreply, State}. + +async_gen_server_reply(From, Msg) -> + {Pid, Tag} = From, + M = {Tag, Msg}, + case catch erlang:send(Pid, M, [nosuspend, noconnect]) of + true -> + M; + false -> + spawn(fun() -> gen_server:reply(From, Msg) end); + EXIT -> + EXIT + end. diff --git a/lib/kernel/src/os.erl b/lib/kernel/src/os.erl index d0b498edc9..75a11a8afd 100644 --- a/lib/kernel/src/os.erl +++ b/lib/kernel/src/os.erl @@ -50,7 +50,7 @@ find_executable(Name, Path) -> relative -> find_executable1(Name, split_path(Path), Extensions); _ -> - case verify_executable(Name, Extensions) of + case verify_executable(Name, Extensions, Extensions) of {ok, Complete} -> Complete; error -> @@ -60,7 +60,7 @@ find_executable(Name, Path) -> find_executable1(Name, [Base|Rest], Extensions) -> Complete0 = filename:join(Base, Name), - case verify_executable(Complete0, Extensions) of + case verify_executable(Complete0, Extensions, Extensions) of {ok, Complete} -> Complete; error -> @@ -69,7 +69,7 @@ find_executable1(Name, [Base|Rest], Extensions) -> find_executable1(_Name, [], _Extensions) -> false. -verify_executable(Name0, [Ext|Rest]) -> +verify_executable(Name0, [Ext|Rest], OrigExtensions) -> Name1 = Name0 ++ Ext, case os:type() of vxworks -> @@ -78,7 +78,7 @@ verify_executable(Name0, [Ext|Rest]) -> {ok, _} -> {ok, Name1}; _ -> - verify_executable(Name0, Rest) + verify_executable(Name0, Rest, OrigExtensions) end; _ -> case file:read_file_info(Name1) of @@ -87,12 +87,30 @@ verify_executable(Name0, [Ext|Rest]) -> %% on Unix, since we test if any execution bit is set. {ok, Name1}; _ -> - verify_executable(Name0, Rest) + verify_executable(Name0, Rest, OrigExtensions) end end; -verify_executable(_, []) -> +verify_executable(Name, [], OrigExtensions) when OrigExtensions =/= [""] -> %% Windows + %% Will only happen on windows, hence case insensitivity + case can_be_full_name(string:to_lower(Name),OrigExtensions) of + true -> + verify_executable(Name,[""],[""]); + _ -> + error + end; +verify_executable(_, [], _) -> error. +can_be_full_name(_Name,[]) -> + false; +can_be_full_name(Name,[H|T]) -> + case lists:suffix(H,Name) of %% Name is in lowercase, cause this is a windows thing + true -> + true; + _ -> + can_be_full_name(Name,T) + end. + split_path(Path) -> case type() of {win32, _} -> @@ -119,6 +137,7 @@ reverse_element(List) -> lists:reverse(List). -spec extensions() -> [string()]. +%% Extensions in lower case extensions() -> case type() of {win32, _} -> [".exe",".com",".cmd",".bat"]; diff --git a/lib/kernel/src/pg2.erl b/lib/kernel/src/pg2.erl index cb9fec2ffe..956a900adc 100644 --- a/lib/kernel/src/pg2.erl +++ b/lib/kernel/src/pg2.erl @@ -251,7 +251,9 @@ terminate(_Reason, _S) -> %%% Pid is a member of group Name. store(List) -> - _ = [assure_group(Name) andalso [join_group(Name, P) || P <- Members] || + _ = [(assure_group(Name) + andalso + [join_group(Name, P) || P <- Members -- group_members(Name)]) || [Name, Members] <- List], ok. diff --git a/lib/kernel/test/file_SUITE.erl b/lib/kernel/test/file_SUITE.erl index 1d652679b0..17c47f871d 100644 --- a/lib/kernel/test/file_SUITE.erl +++ b/lib/kernel/test/file_SUITE.erl @@ -53,7 +53,7 @@ -export([file_info/1, file_info_basic_file/1, file_info_basic_directory/1, file_info_bad/1, file_info_times/1, file_write_file_info/1]). -export([rename/1, access/1, truncate/1, datasync/1, sync/1, - read_write/1, pread_write/1, append/1]). + read_write/1, pread_write/1, append/1, exclusive/1]). -export([errors/1, e_delete/1, e_rename/1, e_make_dir/1, e_del_dir/1]). -export([otp_5814/1]). @@ -84,6 +84,8 @@ -export([advise/1]). +-export([standard_io/1,mini_server/1]). + %% Debug exports -export([create_file_slow/2, create_file/2, create_bin/2]). -export([verify_file/2, verify_bin/3]). @@ -103,7 +105,8 @@ all(suite) -> compression, links, copy, delayed_write, read_ahead, segment_read, segment_write, ipread, pid2name, interleaved_read_write, - otp_5814, large_file, read_line_1, read_line_2, read_line_3, read_line_4], + otp_5814, large_file, read_line_1, read_line_2, read_line_3, read_line_4, + standard_io], fini}. init(Config) when is_list(Config) -> @@ -172,6 +175,85 @@ time_dist({_D1, _T1} = DT1, {_D2, _T2} = DT2) -> - calendar:datetime_to_gregorian_seconds(DT1). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +mini_server(Parent) -> + receive + die -> + ok; + {io_request,From,To,{put_chars,Data}} -> + Parent ! {io_request,From,To,{put_chars,Data}}, + From ! {io_reply, To, ok}, + mini_server(Parent); + {io_request,From,To,{get_chars,'',N}} -> + Parent ! {io_request,From,To,{get_chars,'',N}}, + From ! {io_reply, To, {ok, lists:duplicate(N,$a)}}, + mini_server(Parent); + {io_request,From,To,{get_line,''}} -> + Parent ! {io_request,From,To,{get_line,''}}, + From ! {io_reply, To, {ok, "hej\n"}}, + mini_server(Parent) + end. + +standard_io(suite) -> + []; +standard_io(doc) -> + ["Test that standard i/o-servers work with file module"]; +standard_io(Config) when is_list(Config) -> + %% Really just a smoke test + ?line Pid = spawn(?MODULE,mini_server,[self()]), + ?line register(mini_server,Pid), + ?line ok = file:write(mini_server,<<"hej\n">>), + ?line receive + {io_request,_,_,{put_chars,<<"hej\n">>}} -> + ok + after 1000 -> + exit(noreply) + end, + ?line {ok,"aaaaa"} = file:read(mini_server,5), + ?line receive + {io_request,_,_,{get_chars,'',5}} -> + ok + after 1000 -> + exit(noreply) + end, + ?line {ok,"hej\n"} = file:read_line(mini_server), + ?line receive + {io_request,_,_,{get_line,''}} -> + ok + after 1000 -> + exit(noreply) + end, + ?line OldGL = group_leader(), + ?line group_leader(Pid,self()), + ?line ok = file:write(standard_io,<<"hej\n">>), + ?line group_leader(OldGL,self()), + ?line receive + {io_request,_,_,{put_chars,<<"hej\n">>}} -> + ok + after 1000 -> + exit(noreply) + end, + ?line group_leader(Pid,self()), + ?line {ok,"aaaaa"} = file:read(standard_io,5), + ?line group_leader(OldGL,self()), + ?line receive + {io_request,_,_,{get_chars,'',5}} -> + ok + after 1000 -> + exit(noreply) + end, + ?line group_leader(Pid,self()), + ?line {ok,"hej\n"} = file:read_line(standard_io), + ?line group_leader(OldGL,self()), + ?line receive + {io_request,_,_,{get_line,''}} -> + ok + after 1000 -> + exit(noreply) + end, + Pid ! die, + receive after 1000 -> ok end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% read_write_file(suite) -> []; read_write_file(doc) -> []; @@ -384,7 +466,7 @@ files(suite) -> sync,datasync,advise]. open(suite) -> [open1,old_modes,new_modes,path_open,close,access,read_write, - pread_write,append,open_errors]. + pread_write,append,open_errors,exclusive]. open1(suite) -> []; open1(doc) -> []; @@ -758,6 +840,22 @@ open_errors(Config) when is_list(Config) -> ?line test_server:timetrap_cancel(Dog), ok. +exclusive(suite) -> []; +exclusive(doc) -> "Test exclusive access to a file."; +exclusive(Config) when is_list(Config) -> + ?line Dog = test_server:timetrap(test_server:seconds(5)), + ?line RootDir = ?config(priv_dir,Config), + ?line NewDir = filename:join(RootDir, + atom_to_list(?MODULE) + ++"_exclusive"), + ?line ok = ?FILE_MODULE:make_dir(NewDir), + ?line Name = filename:join(NewDir, "ex_file.txt"), + ?line {ok, Fd} = ?FILE_MODULE:open(Name, [write, exclusive]), + ?line {error, eexist} = ?FILE_MODULE:open(Name, [write, exclusive]), + ?line ok = ?FILE_MODULE:close(Fd), + ?line test_server:timetrap_cancel(Dog), + ok. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% pos(suite) -> [pos1,pos2]. diff --git a/lib/kernel/test/os_SUITE.erl b/lib/kernel/test/os_SUITE.erl index 6a3534b094..ace9501d18 100644 --- a/lib/kernel/test/os_SUITE.erl +++ b/lib/kernel/test/os_SUITE.erl @@ -137,6 +137,13 @@ find_executable(Config) when is_list(Config) -> ?line find_exe(Abin, "my_ar", ".exe", Path), ?line find_exe(Abin, "my_ascii", ".com", Path), ?line find_exe(Abin, "my_adb", ".bat", Path), + %% OTP-3626 find names of executables given with extension + ?line find_exe(Abin, "my_ar.exe", "", Path), + ?line find_exe(Abin, "my_ascii.com", "", Path), + ?line find_exe(Abin, "my_adb.bat", "", Path), + ?line find_exe(Abin, "my_ar.EXE", "", Path), + ?line find_exe(Abin, "my_ascii.COM", "", Path), + ?line find_exe(Abin, "MY_ADB.BAT", "", Path), %% Search for programs in Abin (second element in PATH). ?line find_exe(Abin, "my_ar", ".exe", Path), diff --git a/lib/kernel/test/pg2_SUITE.erl b/lib/kernel/test/pg2_SUITE.erl index 8eb1a7ca19..df28dcf447 100644 --- a/lib/kernel/test/pg2_SUITE.erl +++ b/lib/kernel/test/pg2_SUITE.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2008-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2008-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %%---------------------------------------------------------------- %% Purpose:Test Suite for the 'pg2' module. @@ -26,8 +26,8 @@ -export([all/1, init_per_testcase/2, fin_per_testcase/2]). --export([tickets/1, - otp_7277/1, otp_8259/1, +-export([tickets/1, + otp_7277/1, otp_8259/1, otp_8653/1, compat/1, basic/1]). % Default timetrap timeout (set in init_per_testcase). @@ -37,7 +37,8 @@ -define(testcase, ?config(?TESTCASE, Config)). %% Internal export. --export([mk_part_node/3, part1/5, p_init/3, start_proc/1, sane/0]). +-export([mk_part_node_and_group/3, part2/4, + mk_part_node/3, part1/5, p_init/3, start_proc/1, sane/0]). init_per_testcase(Case, Config) -> ?line Dog = ?t:timetrap(?default_timeout), @@ -48,11 +49,11 @@ fin_per_testcase(_Case, _Config) -> test_server:timetrap_cancel(Dog), ok. -all(suite) -> +all(suite) -> [tickets]. tickets(suite) -> - [otp_7277, otp_8259, compat, basic]. + [otp_7277, otp_8259, otp_8653, compat, basic]. otp_7277(doc) -> "OTP-7277. Bugfix leave()."; @@ -65,9 +66,9 @@ otp_7277(Config) when is_list(Config) -> ?line ok = pg2:leave(b, P), ?line true = exit(P, kill), case {pg2:get_members(a), pg2:get_local_members(a)} of - {[], []} -> + {[], []} -> ok; - _ -> + _ -> timer:sleep(100), ?line [] = pg2:get_members(a), ?line [] = pg2:get_local_members(a) @@ -79,6 +80,63 @@ otp_7277(Config) when is_list(Config) -> -define(UNTIL(Seq), loop_until_true(fun() -> Seq end, Config)). -define(UNTIL_LOOP, 300). +otp_8653(suite) -> []; +otp_8653(doc) -> + ["OTP-8259. Member was not removed after being killed."]; +otp_8653(Config) when is_list(Config) -> + Timeout = 15, + ?line Dog = test_server:timetrap({seconds,Timeout}), + + ?line [A, B, C] = start_nodes([a, b, c], peer, Config), + + ?line wait_for_ready_net(Config), + + % make b and c connected, partitioned from node() and a + ?line rpc_cast(B, ?MODULE, part2, [Config, node(), A, C]), + ?line ?UNTIL(is_ready_partition(Config)), + + % Connect to the other partition. + ?line pong = net_adm:ping(B), + timer:sleep(100), + ?line pong = net_adm:ping(C), + ?line _ = global:sync(), + ?line [A, B, C] = lists:sort(nodes()), + + G = pg2_otp_8653, + ?line ?UNTIL(begin + GA = lists:sort(rpc:call(A, pg2, get_members, [G])), + GB = lists:sort(rpc:call(B, pg2, get_members, [G])), + GC = lists:sort(rpc:call(C, pg2, get_members, [G])), + GT = lists:sort(pg2:get_members(G)), + GA =:= GB andalso + GB =:= GC andalso + GC =:= GT andalso + 8 =:= length(GA) + end), + ?line ok = pg2:delete(G), + ?line stop_nodes([A,B,C]), + ?line test_server:timetrap_cancel(Dog), + ok. + +part2(Config, Main, A, C) -> + Function = mk_part_node_and_group, + case catch begin + make_partition(Config, [Main, A], [node(), C], Function) + end + of + ok -> ok + end. + +mk_part_node_and_group(File, MyPart0, Config) -> + touch(File, "start"), % debug + MyPart = lists:sort(MyPart0), + ?UNTIL(is_node_in_part(File, MyPart)), + G = pg2_otp_8653, + Pid = spawn(forever()), + ok = pg2:create(G), + _ = [ok = pg2:join(G, Pid) || _ <- [1,1]], + touch(File, "done"). + otp_8259(suite) -> []; otp_8259(doc) -> ["OTP-8259. Member was not removed after being killed."]; @@ -102,7 +160,7 @@ otp_8259(Config) when is_list(Config) -> % make b and c connected, partitioned from node() and a ?line rpc_cast(B, ?MODULE, part1, [Config, node(), A, C, Name]), ?line ?UNTIL(is_ready_partition(Config)), - + % Connect to the other partition. % The resolver on node b will be called. ?line pong = net_adm:ping(B), @@ -140,9 +198,9 @@ start_proc(Name) -> p_init(Parent, Name, TestServer) -> Resolve = fun(_Name, Pid1, Pid2) -> %% The pid on node a will be chosen. - [{_,Min}, {_,Max}] = + [{_,Min}, {_,Max}] = lists:sort([{node(Pid1),Pid1}, {node(Pid2),Pid2}]), - %% b is connected to test_server. + %% b is connected to test_server. %% exit(Min, kill), % would ping a rpc:cast(TestServer, erlang, exit, [Min, kill]), Max @@ -165,7 +223,7 @@ compat(Config) when is_list(Config) -> true -> Timeout = 15, ?line Dog = test_server:timetrap({seconds,Timeout}), - Pid = spawn(forever()), + Pid = spawn(forever()), G = a, ?line ok = pg2:create(G), ?line ok = pg2:join(G, Pid), @@ -365,7 +423,7 @@ killit(N, P, Ps, Ns) -> timer:sleep(100), sane(Ns), lists:keydelete(P, 1, Ps). - + pr(Node, C) -> _ = [?t:format("~p: ", [Node]) || Node =/= node()], ?t:format("do ~p~n", [C]). @@ -412,27 +470,27 @@ sane(Ns) -> wsane(Ns) -> %% Same members on all nodes: - {[_],gs} = + {[_],gs} = {lists:usort([rpc:call(N, pg2, which_groups, []) || N <- Ns]),gs}, - _ = [{[_],ms,G} = {lists:usort([rpc:call(N, pg2, get_members, [G]) || + _ = [{[_],ms,G} = {lists:usort([rpc:call(N, pg2, get_members, [G]) || N <- Ns]),ms,G} || G <- pg2:which_groups()], %% The local members are a partitioning of the members: - [begin - LocalMembers = + [begin + LocalMembers = lists:sort(lists:append( - [rpc:call(N, pg2, get_local_members, [G]) || + [rpc:call(N, pg2, get_local_members, [G]) || N <- Ns])), {part, LocalMembers} = {part, lists:sort(pg2:get_members(G))} end || G <- pg2:which_groups()], %% The closest pid should run on the local node, if possible. [[case rpc:call(N, pg2, get_closest_pid, [G]) of Pid when is_pid(Pid), node(Pid) =:= N -> - true = + true = lists:member(Pid, rpc:call(N, pg2, get_local_members, [G])); %% FIXME. Om annan nod: member, local = []. _ -> [] = rpc:call(N, pg2, get_local_members, [G]) - end || N <- Ns] + end || N <- Ns] || G <- pg2:which_groups()]. %% Look inside the pg2_table. @@ -482,9 +540,9 @@ start_node_rel(Name, Rel, How) -> {RelList, ""} end, ?line Pa = filename:dirname(code:which(?MODULE)), - ?line Res = test_server:start_node(Name, How, + ?line Res = test_server:start_node(Name, How, [{args, - Compat ++ + Compat ++ " -kernel net_setuptime 100 " " -pa " ++ Pa}, {erl, Release}]), @@ -575,29 +633,30 @@ get_known(Node) -> case catch gen_server:call({global_name_server,Node},get_known,infinity) of {'EXIT', _} -> [list, without, nodenames]; - Known when is_list(Known) -> + Known when is_list(Known) -> lists:sort([Node | Known]) end. node_name(Name, Config) -> U = "_", {{Y,M,D}, {H,Min,S}} = calendar:now_to_local_time(now()), - Date = io_lib:format("~4w_~2..0w_~2..0w__~2..0w_~2..0w_~2..0w", + Date = io_lib:format("~4w_~2..0w_~2..0w__~2..0w_~2..0w_~2..0w", [Y,M,D, H,Min,S]), L = lists:flatten(Date), lists:concat([Name,U,?testcase,U,U,L]). -%% this one runs on one node in Part2 -%% The partition is ready when is_ready_partition(Config) returns (true). -%% this one runs on one node in Part2 +%% This one runs on one node in Part2. %% The partition is ready when is_ready_partition(Config) returns (true). make_partition(Config, Part1, Part2) -> + make_partition(Config, Part1, Part2, mk_part_node). + +make_partition(Config, Part1, Part2, Function) -> Dir = ?config(priv_dir, Config), - Ns = [begin + Ns = [begin Name = lists:concat([atom_to_list(N),"_",msec(),".part"]), File = filename:join([Dir, Name]), file:delete(File), - rpc_cast(N, ?MODULE, mk_part_node, [File, Part, Config], File), + rpc_cast(N, ?MODULE, Function, [File, Part, Config], File), {N, File} end || Part <- [Part1, Part2], N <- Part], all_nodes_files(Ns, "done", Config), @@ -614,10 +673,10 @@ mk_part_node(File, MyPart0, Config) -> %% The calls to append_to_file are for debugging. is_node_in_part(File, MyPart) -> - lists:foreach(fun(N) -> + lists:foreach(fun(N) -> _ = erlang:disconnect_node(N) end, nodes() -- MyPart), - case {(Known = get_known(node())) =:= MyPart, + case {(Known = get_known(node())) =:= MyPart, (Nodes = lists:sort([node() | nodes()])) =:= MyPart} of {true, true} -> %% Make sure the resolvers have been terminated, @@ -649,7 +708,7 @@ wait_for_ready_net(Nodes0, Config) -> ?t:format("wait_for_ready_net ~p~n", [Nodes]), ?UNTIL(begin lists:all(fun(N) -> Nodes =:= get_known(N) end, Nodes) and - lists:all(fun(N) -> + lists:all(fun(N) -> LNs = rpc:call(N, erlang, nodes, []), Nodes =:= lists:sort([N | LNs]) end, Nodes) @@ -688,11 +747,11 @@ file_contents(File, ContentsList, Config) -> file_contents(File, ContentsList, Config, no_log_file). file_contents(File, ContentsList, Config, LogFile) -> - Contents = list_to_binary(ContentsList), + Contents = list_to_binary(ContentsList), Sz = size(Contents), ?UNTIL(begin case file:read_file(File) of - {ok, FileContents}=Reply -> + {ok, FileContents}=Reply -> case catch split_binary(FileContents, Sz) of {Contents,_} -> true; diff --git a/lib/kernel/test/prim_file_SUITE.erl b/lib/kernel/test/prim_file_SUITE.erl index 21bdc06fdc..1688ec45ca 100644 --- a/lib/kernel/test/prim_file_SUITE.erl +++ b/lib/kernel/test/prim_file_SUITE.erl @@ -35,7 +35,7 @@ file_write_file_info_a/1, file_write_file_info_b/1]). -export([rename_a/1, rename_b/1, access/1, truncate/1, datasync/1, sync/1, - read_write/1, pread_write/1, append/1]). + read_write/1, pread_write/1, append/1, exclusive/1]). -export([errors/1, e_delete/1, e_rename/1, e_make_dir/1, e_del_dir/1]). -export([compression/1, read_not_really_compressed/1, @@ -385,7 +385,7 @@ win_cur_dir_1(_Config, Handle) -> files(suite) -> [open,pos,file_info,truncate,sync,datasync,advise]. open(suite) -> [open1,modes,close,access,read_write, - pread_write,append]. + pread_write,append,exclusive]. open1(suite) -> []; open1(doc) -> []; @@ -610,6 +610,22 @@ append(Config) when is_list(Config) -> ?line test_server:timetrap_cancel(Dog), ok. +exclusive(suite) -> []; +exclusive(doc) -> "Test exclusive access to a file."; +exclusive(Config) when is_list(Config) -> + ?line Dog = test_server:timetrap(test_server:seconds(5)), + ?line RootDir = ?config(priv_dir,Config), + ?line NewDir = filename:join(RootDir, + atom_to_list(?MODULE) + ++"_exclusive"), + ?line ok = ?PRIM_FILE:make_dir(NewDir), + ?line Name = filename:join(NewDir, "ex_file.txt"), + ?line {ok,Fd} = ?PRIM_FILE:open(Name, [write, exclusive]), + ?line {error, eexist} = ?PRIM_FILE:open(Name, [write, exclusive]), + ?line ok = ?PRIM_FILE:close(Fd), + ?line test_server:timetrap_cancel(Dog), + ok. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% pos(suite) -> [pos1,pos2]. diff --git a/lib/megaco/doc/src/megaco.xml b/lib/megaco/doc/src/megaco.xml index 0fb9d5aac6..ae9e250965 100644 --- a/lib/megaco/doc/src/megaco.xml +++ b/lib/megaco/doc/src/megaco.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>2000</year><year>2009</year> + <year>2000</year><year>2010</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -13,12 +13,12 @@ compliance with the License. You should have received a copy of the Erlang Public License along with this software. If not, it can be retrieved online at http://www.erlang.org/. - + Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - + </legalnotice> <title>megaco</title> @@ -40,6 +40,16 @@ <section> <title>DATA TYPES</title> <code type="none"><![CDATA[ +megaco_mid() = ip4Address() | ip6Address() | + domainName() | deviceName() | + mtpAddress() +ip4Address() = #'IP4Address'{} +ip6Address() = #'IP6Address'{} +domainName() = #'DomainName'{} +deviceName() = pathName() +pathName() = ia5String(1..64) +mtpAddress() = octetString(2..4) + action_request() = #'ActionRequest'{} action_reply() = #'ActionReply'{} error_desc() = #'ErrorDescriptor'{} diff --git a/lib/megaco/doc/src/notes.xml b/lib/megaco/doc/src/notes.xml index ab17dd50ca..99a3784402 100644 --- a/lib/megaco/doc/src/notes.xml +++ b/lib/megaco/doc/src/notes.xml @@ -66,6 +66,16 @@ <list type="bulleted"> <item> + <p>A raise condition when, during high load, processing + both the original and a resent message and delivering + this as two separate messages to the user. </p> + <p>Note that this solution only protects against multiple + reply deliveries! </p> + <p>Own Id: OTP-8529</p> + <p>Aux Id: Seq 10915</p> + </item> + + <item> <p>Fix shared libraries installation. </p> <p>The flex shared lib(s) were incorrectly installed as data files. </p> @@ -73,6 +83,13 @@ <p>Own Id: OTP-8627</p> </item> + <item> + <p>Eliminated a possible raise condition while creating + pending counters. </p> + <p>Own Id: OTP-8634</p> + <p>Aux Id: Seq 11579</p> + </item> + </list> </section> diff --git a/lib/megaco/src/app/megaco.appup.src b/lib/megaco/src/app/megaco.appup.src index 5df31f2923..f939f5e6cf 100644 --- a/lib/megaco/src/app/megaco.appup.src +++ b/lib/megaco/src/app/megaco.appup.src @@ -133,13 +133,16 @@ [ {"3.14", [ + {load_module, megaco_messenger, soft_purge, soft_purge, [megaco_monitor]}, + {update, megaco_monitor, soft, soft_purge, soft_purge, []}, {update, megaco_config, soft, soft_purge, soft_purge, []} ] }, {"3.13", [ - {load_module, megaco_messenger, soft_purge, soft_purge, []}, + {load_module, megaco_messenger, soft_purge, soft_purge, [megaco_monitor]}, {load_module, megaco_filter, soft_purge, soft_purge, []}, + {update, megaco_monitor, soft, soft_purge, soft_purge, []}, {update, megaco_config, soft, soft_purge, soft_purge, []}, {update, megaco_flex_scanner_handler, {advanced, downgrade_to_pre_3_13_1}, soft_purge, soft_purge, []} @@ -173,13 +176,16 @@ [ {"3.14", [ + {load_module, megaco_messenger, soft_purge, soft_purge, [megaco_monitor]}, + {update, megaco_monitor, soft, soft_purge, soft_purge, []}, {update, megaco_config, soft, soft_purge, soft_purge, []} ] }, {"3.13", [ - {load_module, megaco_messenger, soft_purge, soft_purge, []}, + {load_module, megaco_messenger, soft_purge, soft_purge, [megaco_monitor]}, {load_module, megaco_filter, soft_purge, soft_purge, []}, + {update, megaco_monitor, soft, soft_purge, soft_purge, []}, {update, megaco_config, soft, soft_purge, soft_purge, []}, {update, megaco_flex_scanner_handler, {advanced, upgrade_from_pre_3_13_1}, soft_purge, soft_purge, []} diff --git a/lib/megaco/src/app/megaco_internal.hrl b/lib/megaco/src/app/megaco_internal.hrl index adbaacacef..2c124e9060 100644 --- a/lib/megaco/src/app/megaco_internal.hrl +++ b/lib/megaco/src/app/megaco_internal.hrl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1999-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1999-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -139,6 +139,22 @@ [?APPLICATION, ?MODULE, self()|A]))). +-define(megaco_ereport(Label, Report), + ?megaco_report(error_report, Label, Report)). + +-define(megaco_wreport(Label, Report), + ?megaco_report(warning_report, Label, Report)). + +-define(megaco_ireport(Label, Report), + ?megaco_report(info_report, Label, Report)). + +-define(megaco_report(Func, Label, Report), + (catch error_logger:Func([{label, Label}, + {application, ?APPLICATION}, + {module, ?MODULE}, + {process, self()} | Report]))). + + %%%---------------------------------------------------------------------- %%% Default (ignore) value of the Extra argument to the %%% megaco:receive_message/5 and process_received_message functions/5. diff --git a/lib/megaco/src/engine/megaco_config.erl b/lib/megaco/src/engine/megaco_config.erl index 0445f10838..6805db790d 100644 --- a/lib/megaco/src/engine/megaco_config.erl +++ b/lib/megaco/src/engine/megaco_config.erl @@ -628,31 +628,19 @@ incr_counter(Item, Incr) -> end catch error:_ -> + %% Counter does not exist, so try creat it try begin cre_counter(Item, Incr) end catch exit:_ -> - %% Ok, some other process got there before us, - %% so try again + %% This is a raise condition. + %% When we tried to update the counter above, it + %% did not exist, but now it does... ets:update_counter(megaco_config, Item, Incr) end end. -%% incr_counter(Item, Incr) -> -%% case (catch ets:update_counter(megaco_config, Item, Incr)) of -%% {'EXIT', _} -> -%% case (catch cre_counter(Item, Incr)) of -%% {'EXIT', _} -> -%% %% Ok, some other process got there before us, -%% %% so try again -%% ets:update_counter(megaco_config, Item, Incr); -%% NewVal -> -%% NewVal -%% end; -%% NewVal -> -%% NewVal -%% end. cre_counter(Item, Initial) -> case whereis(?SERVER) =:= self() of @@ -660,8 +648,8 @@ cre_counter(Item, Initial) -> case call({cre_counter, Item, Initial}) of {ok, Value} -> Value; - Error -> - exit(Error) + {error, Reason} -> + exit({failed_creating_counter, Item, Initial, Reason}) end; true -> %% Check that the counter does not already exists @@ -671,7 +659,7 @@ cre_counter(Item, Initial) -> ets:insert(megaco_config, {Item, Initial}), {ok, Initial}; [_] -> - %% Ouch, now what? + %% Possibly a raise condition {error, already_exists} end diff --git a/lib/megaco/src/engine/megaco_messenger.erl b/lib/megaco/src/engine/megaco_messenger.erl index 5756e8e896..5fad29931b 100644 --- a/lib/megaco/src/engine/megaco_messenger.erl +++ b/lib/megaco/src/engine/megaco_messenger.erl @@ -1541,30 +1541,6 @@ check_pending_limit(Limit, Direction, TransId) -> aborted end. -%% check_pending_limit(infinity, _, _) -> -%% {ok, 0}; -%% check_pending_limit(Limit, Direction, TransId) -> -%% ?rt2("check pending limit", [Direction, Limit, TransId]), -%% case (catch megaco_config:get_pending_counter(Direction, TransId)) of -%% {'EXIT', _} -> -%% %% This function is only called when we "know" the -%% %% counter to exist. So, the only reason that this -%% %% would happen is of the counter has been removed. -%% %% This only happen if the pending limit has been -%% %% reached. In any case, this is basically the same -%% %% as aborted! -%% ?rt2("check pending limit - exit", []), -%% aborted; -%% Val when Val =< Limit -> -%% %% Since we have no intention to increment here, it -%% %% is ok to be _at_ the limit -%% ?rt2("check pending limit - ok", [Val]), -%% {ok, Val}; -%% _Val -> -%% ?rt2("check pending limit - aborted", [_Val]), -%% aborted -%% end. - check_and_maybe_incr_pending_limit(infinity, _, _) -> ok; @@ -1572,59 +1548,42 @@ check_and_maybe_incr_pending_limit(Limit, Direction, TransId) -> %% %% We need this kind of test to detect when we _pass_ the limit %% - ?rt2("check and maybe incr pending limit", [Direction, Limit, TransId]), + ?rt2("check and maybe incr pending limit", [{direction, Direction}, + {transaction_id, TransId}, + {counter_limit, Limit}]), try megaco_config:get_pending_counter(Direction, TransId) of Val when Val > Limit -> - ?rt2("check and maybe incr - aborted", [Direction, Val, Limit]), + ?rt2("check and maybe incr - aborted", [{counter_value, Val}]), aborted; % Already passed the limit Val -> - ?rt2("check and maybe incr - incr", [Direction, Val, Limit]), + ?rt2("check and maybe incr - incr", [{counter_value, Val}]), megaco_config:incr_pending_counter(Direction, TransId), if Val < Limit -> ok; % Still within the limit true -> ?rt2("check and maybe incr - error", - [Direction, Val, Limit]), + [{counter_value, Val}]), error % Passed the limit end catch _:_ -> %% Has not been created yet (connect). - megaco_config:cre_pending_counter(Direction, TransId, 1), - ok + %% Try create it, but bevare of possible raise condition + try + begin + megaco_config:cre_pending_counter(Direction, TransId, 1), + ok + end + catch + _:_ -> + %% Ouch, raise condition, increment instead... + megaco_config:incr_pending_counter(Direction, TransId), + ok + end end. -%% check_and_maybe_incr_pending_limit(infinity, _, _) -> -%% ok; -%% check_and_maybe_incr_pending_limit(Limit, Direction, TransId) -> -%% %% -%% %% We need this kind of test to detect when we _pass_ the limit -%% %% -%% ?rt2("check and maybe incr pending limit", [Direction, Limit, TransId]), -%% case (catch megaco_config:get_pending_counter(Direction, TransId)) of -%% {'EXIT', _} -> -%% %% Has not been created yet (connect). -%% megaco_config:cre_pending_counter(Direction, TransId, 1), -%% ok; -%% Val when Val > Limit -> -%% ?rt2("check and maybe incr - aborted", [Direction, Val, Limit]), -%% aborted; % Already passed the limit -%% Val -> -%% ?rt2("check and maybe incr - incr", [Direction, Val, Limit]), -%% megaco_config:incr_pending_counter(Direction, TransId), -%% if -%% Val < Limit -> -%% ok; % Still within the limit -%% true -> -%% ?rt2("check and maybe incr - error", -%% [Direction, Val, Limit]), -%% error % Passed the limit -%% end -%% end. - - %% BUGBUG BUGBUG BUGBUG %% %% Do we know that the Rep is still valid? A previous transaction @@ -2648,33 +2607,84 @@ handle_reply( handle_reply(#conn_data{conn_handle = CH} = CD, T, Extra) -> TransId = to_local_trans_id(CD), ?rt2("handle reply", [T, TransId]), - case megaco_monitor:lookup_request(TransId) of - [Req] when (is_record(Req, request) andalso - (CD#conn_data.cancel =:= true)) -> + case {megaco_monitor:request_lockcnt_inc(TransId), + megaco_monitor:lookup_request(TransId)} of + {_Cnt, [Req]} when (is_record(Req, request) andalso + (CD#conn_data.cancel =:= true)) -> ?TC_AWAIT_REPLY_EVENT(true), + ?report_trace(CD, "trans reply - cancel(1)", [T]), do_handle_reply_cancel(CD, Req, T); - [#request{remote_mid = RMid} = Req] when ((RMid =:= preliminary_mid) orelse - (RMid =:= CH#megaco_conn_handle.remote_mid)) -> + {Cnt, [#request{remote_mid = RMid} = Req]} when + ((Cnt =:= 1) andalso + ((RMid =:= preliminary_mid) orelse + (RMid =:= CH#megaco_conn_handle.remote_mid))) -> + ?TC_AWAIT_REPLY_EVENT(false), + %% Just in case conn_data got update after our lookup + %% but before we looked up the request record, we + %% check the cancel field again. + case megaco_config:conn_info(CD, cancel) of + true -> + ?report_trace(CD, "trans reply - cancel(2)", [T]), + megaco_monitor:request_lockcnt_del(TransId), + do_handle_reply_cancel(CD, Req, T); + false -> + ?report_trace(CD, "trans reply", [T]), + do_handle_reply(CD, Req, TransId, T, Extra) + end; + + {Cnt, [#request{remote_mid = RMid} = _Req]} when + (is_integer(Cnt) andalso + ((RMid =:= preliminary_mid) orelse + (RMid =:= CH#megaco_conn_handle.remote_mid))) -> + ?TC_AWAIT_REPLY_EVENT(false), + %% Ok, someone got there before me, now what? + %% This is a plain old raise condition + ?report_important(CD, "trans reply - raise condition", + [T, {request_lockcnt, Cnt}]), + megaco_monitor:request_lockcnt_dec(TransId); + + %% no counter + {_Cnt, [#request{remote_mid = RMid} = Req]} when + ((RMid =:= preliminary_mid) orelse + (RMid =:= CH#megaco_conn_handle.remote_mid)) -> ?TC_AWAIT_REPLY_EVENT(false), + %% The counter does not exist. + %% This can only mean a code upgrade raise condition. + %% That is, this request record was created before + %% this feature (the counters) was instroduced. + %% The simples solution is this is to behave exactly as + %% before, that is pass it along, and leave it to the + %% user to figure out. + %% Just in case conn_data got update after our lookup %% but before we looked up the request record, we %% check the cancel field again. + ?report_verbose(CD, "trans reply - old style", [T]), case megaco_config:conn_info(CD, cancel) of true -> + megaco_monitor:request_lockcnt_del(TransId), do_handle_reply_cancel(CD, Req, T); false -> do_handle_reply(CD, Req, TransId, T, Extra) end; - [#request{user_mod = UserMod, - user_args = UserArgs, - reply_action = Action, - reply_data = UserData, - remote_mid = RMid}] -> + {Cnt, [#request{user_mod = UserMod, + user_args = UserArgs, + reply_action = Action, + reply_data = UserData, + remote_mid = RMid}]} -> ?report_trace(CD, "received trans reply with invalid remote mid", - [T, RMid]), + [{transaction, T}, + {remote_mid, RMid}, + {request_lockcnt, Cnt}]), + if + is_integer(Cnt) -> + megaco_monitor:request_lockcnt_dec(TransId); + true -> + ok + end, WrongMid = CH#megaco_conn_handle.remote_mid, T2 = transform_transaction_reply_enc(CD#conn_data.protocol_version, T), @@ -2685,7 +2695,15 @@ handle_reply(#conn_data{conn_handle = CH} = CD, T, Extra) -> reply_data = UserData}, return_reply(CD2, TransId, UserReply, Extra); - [] -> + {Cnt, []} when is_integer(Cnt) -> + ?TC_AWAIT_REPLY_EVENT(undefined), + ?report_trace(CD, "trans reply (no receiver)", + [T, {request_lockcnt, Cnt}]), + megaco_monitor:request_lockcnt_dec(TransId), + return_unexpected_trans(CD, T, Extra); + + %% No counter + {_Cnt, []} -> ?TC_AWAIT_REPLY_EVENT(undefined), ?report_trace(CD, "trans reply (no receiver)", [T]), return_unexpected_trans(CD, T, Extra) @@ -2716,6 +2734,7 @@ do_handle_reply(CD, %% This is the first reply (maybe of many) megaco_monitor:delete_request(TransId), + megaco_monitor:request_lockcnt_del(TransId), megaco_monitor:cancel_apply_after(Ref), % OTP-4843 megaco_config:del_pending_counter(recv, TransId), % OTP-7189 @@ -3739,6 +3758,11 @@ insert_requests(ConnData, ConnHandle, insert_request(ConnData, ConnHandle, TransId, Action, Data, InitTimer, LongTimer) -> + %% We dont check the result of the lock-counter creation because + %% the only way it could already exist is if the transaction-id + %% range has wrapped and an old counter was not deleted. + megaco_monitor:request_lockcnt_cre(TransId), + #megaco_conn_handle{remote_mid = RemoteMid} = ConnHandle, #conn_data{protocol_version = Version, user_mod = UserMod, @@ -4323,6 +4347,7 @@ cancel_request(ConnData, Req, Reason) -> cancel_request2(ConnData, TransId, UserReply) -> megaco_monitor:delete_request(TransId), + megaco_monitor:request_lockcnt_del(TransId), megaco_config:del_pending_counter(recv, TransId), % OTP-7189 Serial = TransId#trans_id.serial, ConnData2 = ConnData#conn_data{serial = Serial}, @@ -4380,29 +4405,67 @@ receive_reply_remote(ConnData, UserReply) -> receive_reply_remote(ConnData, UserReply, Extra) -> TransId = to_local_trans_id(ConnData), - case (catch megaco_monitor:lookup_request(TransId)) of - [#request{timer_ref = {_Type, Ref}} = Req] -> %% OTP-4843 + case {megaco_monitor:request_lockcnt_inc(TransId), + (catch megaco_monitor:lookup_request(TransId))} of + {Cnt, [Req]} when (Cnt =:= 1) andalso is_record(Req, request) -> %% Don't care about Req and Rep version diff - megaco_monitor:delete_request(TransId), - megaco_monitor:cancel_apply_after(Ref), % OTP-4843 - megaco_config:del_pending_counter(recv, TransId), % OTP-7189 - - UserMod = Req#request.user_mod, - UserArgs = Req#request.user_args, - Action = Req#request.reply_action, - UserData = Req#request.reply_data, - ConnData2 = ConnData#conn_data{user_mod = UserMod, - user_args = UserArgs, - reply_action = Action, - reply_data = UserData}, - return_reply(ConnData2, TransId, UserReply, Extra); - + do_receive_reply_remote(ConnData, TransId, Req, UserReply, Extra); + + {Cnt, [Req]} when is_integer(Cnt) andalso is_record(Req, request) -> + %% Another process is accessing, handle as unexpected + %% (so it has a possibillity to get logged). + ?report_important(ConnData, "trans reply (no receiver)", + [{user_reply, UserReply}, + {request_lockcnt, Cnt}]), + megaco_monitor:request_lockcnt_dec(TransId), + return_unexpected_trans_reply(ConnData, TransId, UserReply, Extra); + + %% no counter + {_Cnt, [Req]} when is_record(Req, request) -> + %% The counter does not exist. + %% This can only mean a code upgrade raise condition. + %% That is, this request record was created before + %% this feature (the counters) was instroduced. + %% The simples solution to this is to behave exactly as + %% before, that is, pass it along, and leave it to the + %% user to figure out. + ?report_trace(ConnData, + "remote reply - " + "code upgrade raise condition", + [{user_reply, UserReply}]), + do_receive_reply_remote(ConnData, TransId, Req, UserReply, Extra); + + {Cnt, _} when is_integer(Cnt) -> + ?report_trace(ConnData, "trans reply (no receiver)", + [{user_reply, UserReply}, {request_lockcnt, Cnt}]), + megaco_monitor:request_lockcnt_dec(TransId), + return_unexpected_trans_reply(ConnData, TransId, UserReply, Extra); + _ -> ?report_trace(ConnData, "remote reply (no receiver)", - [UserReply]), + [{user_reply, UserReply}]), return_unexpected_trans_reply(ConnData, TransId, UserReply, Extra) end. +do_receive_reply_remote(ConnData, TransId, + #request{timer_ref = {_Type, Ref}, + user_mod = UserMod, + user_args = UserArgs, + reply_action = Action, + reply_data = UserData} = _Req, + UserReply, Extra) -> + megaco_monitor:delete_request(TransId), + megaco_monitor:request_lockcnt_del(TransId), + megaco_monitor:cancel_apply_after(Ref), % OTP-4843 + megaco_config:del_pending_counter(recv, TransId), % OTP-7189 + + ConnData2 = ConnData#conn_data{user_mod = UserMod, + user_args = UserArgs, + reply_action = Action, + reply_data = UserData}, + return_reply(ConnData2, TransId, UserReply, Extra). + + cancel_reply(ConnData, #reply{state = waiting_for_ack, user_mod = UserMod, user_args = UserArgs} = Rep, Reason) -> diff --git a/lib/megaco/src/engine/megaco_monitor.erl b/lib/megaco/src/engine/megaco_monitor.erl index f95a20cf58..29275371be 100644 --- a/lib/megaco/src/engine/megaco_monitor.erl +++ b/lib/megaco/src/engine/megaco_monitor.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2000-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2000-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -51,6 +51,11 @@ update_request_field/3, update_request_fields/2, delete_request/1, + request_lockcnt_cre/1, + request_lockcnt_del/1, + request_lockcnt_inc/1, + request_lockcnt_dec/1, + lookup_reply/1, lookup_reply_field/2, match_replies/1, @@ -115,6 +120,24 @@ update_request_fields(Key, NewFields) when is_list(NewFields) -> delete_request(Key) -> ets:delete(megaco_requests, Key). + +request_lockcnt_cre(TransId) -> + Key = {TransId, lockcnt}, + ets:insert_new(megaco_requests, {Key, 1}). + +request_lockcnt_del(TransId) -> + Key = {TransId, lockcnt}, + ets:delete(megaco_requests, Key). + +request_lockcnt_inc(TransId) -> + Key = {TransId, lockcnt}, + (catch ets:update_counter(megaco_requests, Key, 1)). + +request_lockcnt_dec(TransId) -> + Key = {TransId, lockcnt}, + (catch ets:update_counter(megaco_requests, Key, -1)). + + lookup_reply(Key) -> ets:lookup(megaco_replies, Key). diff --git a/lib/megaco/vsn.mk b/lib/megaco/vsn.mk index cf5957460d..4ef0ed8f18 100644 --- a/lib/megaco/vsn.mk +++ b/lib/megaco/vsn.mk @@ -22,7 +22,7 @@ MEGACO_VSN = 3.14.1 PRE_VSN = APP_VSN = "$(APPLICATION)-$(MEGACO_VSN)$(PRE_VSN)" -TICKETS = OTP-8561 OTP-8627 +TICKETS = OTP-8529 OTP-8561 OTP-8627 OTP-8634 TICKETS_3_14 = OTP-8317 OTP-8323 OTP-8328 OTP-8362 OTP-8403 diff --git a/lib/mnesia/examples/mnesia_meter.erl b/lib/mnesia/examples/mnesia_meter.erl index ea74d8691b..68094c4431 100644 --- a/lib/mnesia/examples/mnesia_meter.erl +++ b/lib/mnesia/examples/mnesia_meter.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1997-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -407,7 +407,7 @@ run(Nodes, Config, FunOverhead) -> stop(Nodes), Res. -run_meter(M, Nodes, FunOverhead) when record(M, meter) -> +run_meter(M, Nodes, FunOverhead) when is_record(M, meter) -> io:format(".", []), case catch init_records(M#meter.init, ?TIMES) of {atomic, ok} -> diff --git a/lib/mnesia/src/mnesia_controller.erl b/lib/mnesia/src/mnesia_controller.erl index 9bc480e619..0298b382a6 100644 --- a/lib/mnesia/src/mnesia_controller.erl +++ b/lib/mnesia/src/mnesia_controller.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1996-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -52,6 +52,7 @@ async_dump_log/1, sync_dump_log/1, connect_nodes/1, + connect_nodes/2, wait_for_schema_commit_lock/0, release_schema_commit_lock/0, create_table/1, @@ -94,7 +95,7 @@ load_and_reply/2, send_and_reply/2, wait_for_tables_init/2, - connect_nodes2/2 + connect_nodes2/3 ]). -import(mnesia_lib, [set/2, add/2]). @@ -420,12 +421,15 @@ try_schedule_late_disc_load(Tabs, Reason, MsgTag) -> [[Tabs, Reason, MsgTag], AbortReason]) end. -connect_nodes(Ns) -> +connect_nodes(Ns) -> + connect_nodes(Ns, fun default_merge/1). + +connect_nodes(Ns, UserFun) -> case mnesia:system_info(is_running) of no -> {error, {node_not_running, node()}}; yes -> - Pid = spawn_link(?MODULE,connect_nodes2,[self(),Ns]), + Pid = spawn_link(?MODULE,connect_nodes2,[self(),Ns, UserFun]), receive {?MODULE, Pid, Res, New} -> case Res of @@ -443,7 +447,7 @@ connect_nodes(Ns) -> end end. -connect_nodes2(Father, Ns) -> +connect_nodes2(Father, Ns, UserFun) -> Current = val({current, db_nodes}), abcast([node()|Ns], {merging_schema, node()}), {NewC, OldC} = mnesia_recover:connect_nodes(Ns), @@ -451,7 +455,7 @@ connect_nodes2(Father, Ns) -> New1 = mnesia_lib:intersect(Ns, Connected), New = New1 -- Current, process_flag(trap_exit, true), - Res = try_merge_schema(New), + Res = try_merge_schema(New, UserFun), Msg = {schema_is_merged, [], late_merge, []}, multicall([node()|Ns], Msg), After = val({current, db_nodes}), @@ -465,7 +469,7 @@ connect_nodes2(Father, Ns) -> merge_schema() -> AllNodes = mnesia_lib:all_nodes(), - case try_merge_schema(AllNodes) of + case try_merge_schema(AllNodes, fun default_merge/1) of ok -> schema_is_merged(); {aborted, {throw, Str}} when is_list(Str) -> @@ -474,8 +478,11 @@ merge_schema() -> fatal("Failed to merge schema: ~p~n", [Else]) end. -try_merge_schema(Nodes) -> - case mnesia_schema:merge_schema() of +default_merge(F) -> + F([]). + +try_merge_schema(Nodes, UserFun) -> + case mnesia_schema:merge_schema(UserFun) of {atomic, not_merged} -> %% No more nodes that we need to merge the schema with ok; @@ -488,11 +495,11 @@ try_merge_schema(Nodes) -> im_running(OldFriends, NewFriends), im_running(NewFriends, OldFriends), - try_merge_schema(Nodes); + try_merge_schema(Nodes, UserFun); {atomic, {"Cannot get cstructs", Node, Reason}} -> dbg_out("Cannot get cstructs, Node ~p ~p~n", [Node, Reason]), timer:sleep(1000), % Avoid a endless loop look alike - try_merge_schema(Nodes); + try_merge_schema(Nodes, UserFun); Other -> Other end. @@ -1842,17 +1849,20 @@ reply(ReplyTo, Reply) -> add_worker(Worker = #dump_log{}, State) -> InitBy = Worker#dump_log.initiated_by, Queue = State#state.dumper_queue, - case lists:keymember(InitBy, #dump_log.initiated_by, Queue) of - true when Worker#dump_log.opt_reply_to == undefined -> - %% The same threshold has been exceeded again, - %% before we have had the possibility to - %% process the older one. - DetectedBy = {dump_log, InitBy}, - Event = {mnesia_overload, DetectedBy}, - mnesia_lib:report_system_event(Event); - _ -> - ignore - end, + Status = + case lists:keymember(InitBy, #dump_log.initiated_by, Queue) of + true when Worker#dump_log.opt_reply_to == undefined -> + %% The same threshold has been exceeded again, + %% before we have had the possibility to + %% process the older one. + DetectedBy = {dump_log, InitBy}, + Event = {mnesia_overload, DetectedBy}, + mnesia_lib:report_system_event(Event), + true; + _ -> + false + end, + mnesia_recover:log_dump_overload(Status), Queue2 = Queue ++ [Worker], State2 = State#state{dumper_queue = Queue2}, opt_start_worker(State2); diff --git a/lib/mnesia/src/mnesia_lib.erl b/lib/mnesia/src/mnesia_lib.erl index dba808e66e..3da3dd2f5c 100644 --- a/lib/mnesia/src/mnesia_lib.erl +++ b/lib/mnesia/src/mnesia_lib.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1996-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -113,6 +113,9 @@ mkcore/1, not_active_here/1, other_val/2, + overload_read/0, + overload_read/1, + overload_set/2, pad_name/3, random_time/2, read_counter/1, @@ -551,6 +554,33 @@ cs_to_nodes(Cs) -> Cs#cstruct.disc_only_copies ++ Cs#cstruct.disc_copies ++ Cs#cstruct.ram_copies. + +overload_types() -> + [mnesia_tm, mnesia_dump_log]. + +valid_overload_type(T) -> + case lists:member(T, overload_types()) of + false -> + erlang:error(bad_type); + true -> + true + end. + +overload_set(Type, Bool) when is_boolean(Bool) -> + valid_overload_type(Type), + set({overload, Type}, Bool). + +overload_read() -> + [{T, overload_read(T)} || T <- overload_types()]. + +overload_read(T) -> + case ?catch_val({overload, T}) of + {'EXIT',_} -> + valid_overload_type(T), + false; + Flag when is_boolean(Flag) -> + Flag + end. dist_coredump() -> dist_coredump(all_nodes()). diff --git a/lib/mnesia/src/mnesia_recover.erl b/lib/mnesia/src/mnesia_recover.erl index 6c53c2e752..0ca7bf3f7f 100644 --- a/lib/mnesia/src/mnesia_recover.erl +++ b/lib/mnesia/src/mnesia_recover.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1997-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -36,6 +36,7 @@ incr_trans_tid_serial/0, init/0, log_decision/1, + log_dump_overload/1, log_master_nodes/3, log_mnesia_down/1, log_mnesia_up/1, @@ -70,6 +71,7 @@ unclear_decision, unclear_waitfor, tm_queue_len = 0, + log_dump_overload = false, initiated = false, early_msgs = [] }). @@ -277,6 +279,9 @@ mnesia_down(Node) -> cast({mnesia_down, Node}) end. +log_dump_overload(Flag) when is_boolean(Flag) -> + cast({log_dump_overload, Flag}). + log_master_nodes(Args, UseDir, IsRunning) -> if IsRunning == yes -> @@ -818,6 +823,12 @@ handle_cast({announce_all, Nodes}, State) -> announce_all(Nodes), {noreply, State}; +handle_cast({log_dump_overload, Flag}, State) when is_boolean(Flag) -> + Prev = State#state.log_dump_overload, + Overload = Prev orelse Flag, + mnesia_lib:overload_set(mnesia_dump_log, Overload), + {noreply, State#state{log_dump_overload = Flag}}; + handle_cast(Msg, State) -> error("~p got unexpected cast: ~p~n", [?MODULE, Msg]), {noreply, State}. @@ -851,12 +862,14 @@ handle_info(check_overload, S) -> Len > Threshold, Prev > Threshold -> What = {mnesia_tm, message_queue_len, [Prev, Len]}, mnesia_lib:report_system_event({mnesia_overload, What}), + mnesia_lib:overload_set(mnesia_tm, true), {noreply, S#state{tm_queue_len = 0}}; Len > Threshold -> {noreply, S#state{tm_queue_len = Len}}; true -> + mnesia_lib:overload_set(mnesia_tm, false), {noreply, S#state{tm_queue_len = 0}} end; undefined -> @@ -905,7 +918,23 @@ terminate(Reason, State) -> %% Purpose: Upgrade process when its code is to be changed %% Returns: {ok, NewState} %%---------------------------------------------------------------------- -code_change(_OldVsn, State, _Extra) -> +code_change(_OldVsn, {state, + Supervisor, + Unclear_pid, + Unclear_decision, + Unclear_waitfor, + Tm_queue_len, + Initiated, + Early_msgs + }, _Extra) -> + {ok, #state{supervisor = Supervisor, + unclear_pid = Unclear_pid, + unclear_decision = Unclear_decision, + unclear_waitfor = Unclear_waitfor, + tm_queue_len = Tm_queue_len, + initiated = Initiated, + early_msgs = Early_msgs}}; +code_change(_OldVsn, #state{} = State, _Extra) -> {ok, State}. %%%---------------------------------------------------------------------- diff --git a/lib/mnesia/src/mnesia_schema.erl b/lib/mnesia/src/mnesia_schema.erl index 354431a296..17e570b881 100644 --- a/lib/mnesia/src/mnesia_schema.erl +++ b/lib/mnesia/src/mnesia_schema.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1996-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -62,6 +62,7 @@ list2cs/1, lock_schema/0, merge_schema/0, + merge_schema/1, move_table/3, opt_create_dir/2, prepare_commit/3, @@ -2650,10 +2651,16 @@ make_dump_tables([]) -> %% Merge the local schema with the schema on other nodes merge_schema() -> - schema_transaction(fun() -> do_merge_schema() end). + schema_transaction(fun() -> do_merge_schema([]) end). + +merge_schema(UserFun) -> + schema_transaction(fun() -> UserFun(fun(Arg) -> do_merge_schema(Arg) end) end). -do_merge_schema() -> + +do_merge_schema(LockTabs0) -> {_Mod, Tid, Ts} = get_tid_ts_and_lock(schema, write), + LockTabs = [{T, tab_to_nodes(T)} || T <- LockTabs0], + [get_tid_ts_and_lock(T,write) || {T,_} <- LockTabs], Connected = val(recover_nodes), Running = val({current, db_nodes}), Store = Ts#tidstore.store, @@ -2665,9 +2672,12 @@ do_merge_schema() -> mnesia:abort({bad_commit, {missing_lock, Miss}}) end, case Connected -- Running of - [Node | _] -> + [Node | _] = OtherNodes -> %% Time for a schema merging party! mnesia_locker:wlock_no_exist(Tid, Store, schema, [Node]), + [mnesia_locker:wlock_no_exist( + Tid, Store, T, mnesia_lib:intersect(Ns, OtherNodes)) + || {T,Ns} <- LockTabs], case rpc:call(Node, mnesia_controller, get_cstructs, []) of {cstructs, Cstructs, RemoteRunning1} -> LockedAlready = Running ++ [Node], @@ -2681,6 +2691,9 @@ do_merge_schema() -> end, NeedsLock = RemoteRunning -- LockedAlready, mnesia_locker:wlock_no_exist(Tid, Store, schema, NeedsLock), + [mnesia_locker:wlock_no_exist(Tid, Store, T, + mnesia_lib:intersect(Ns,NeedsLock)) + || {T,Ns} <- LockTabs], {value, SchemaCs} = lists:keysearch(schema, #cstruct.name, Cstructs), @@ -2714,6 +2727,10 @@ do_merge_schema() -> not_merged end. +tab_to_nodes(Tab) when is_atom(Tab) -> + Cs = val({Tab, cstruct}), + mnesia_lib:cs_to_nodes(Cs). + make_merge_schema(Node, [Cs | Cstructs]) -> Ops = do_make_merge_schema(Node, Cs), Ops ++ make_merge_schema(Node, Cstructs); diff --git a/lib/mnesia/test/Makefile b/lib/mnesia/test/Makefile new file mode 100644 index 0000000000..a4f32e3f78 --- /dev/null +++ b/lib/mnesia/test/Makefile @@ -0,0 +1,118 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1996-2009. All Rights Reserved. +# +# The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# %CopyrightEnd% +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- + +MODULES= \ + mt \ + mnesia_SUITE \ + mnesia_test_lib \ + mnesia_install_test \ + mnesia_registry_test \ + mnesia_config_test \ + mnesia_frag_test \ + mnesia_inconsistent_database_test \ + mnesia_config_backup \ + mnesia_config_event \ + mnesia_examples_test \ + mnesia_nice_coverage_test \ + mnesia_evil_coverage_test \ + mnesia_evil_backup \ + mnesia_trans_access_test \ + mnesia_dirty_access_test \ + mnesia_atomicity_test \ + mnesia_consistency_test \ + mnesia_isolation_test \ + mnesia_durability_test \ + mnesia_recovery_test \ + mnesia_qlc_test \ + mnesia_schema_recovery_test \ + mnesia_measure_test \ + mnesia_cost \ + mnesia_dbn_meters + +MnesiaExamplesDir := ../examples + +ExampleModules = \ + company \ + company_o \ + bup \ + mnesia_meter \ + mnesia_tpcb +ExamplesHrl = \ + company.hrl \ + company_o.hrl + +ERL_FILES= $(MODULES:%=%.erl) $(ExampleModules:%=$(MnesiaExamplesDir)/%.erl) + +HRL_FILES= mnesia_test_lib.hrl $(ExamplesHrl:%=$(MnesiaExamplesDir)/%) + +TARGET_FILES= \ + $(MODULES:%=$(EBIN)/%.$(EMULATOR)) $(ExampleModules:%=$(EBIN)/%.$(EMULATOR)) + +INSTALL_PROGS= $(TARGET_FILES) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/mnesia_test + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +#ERL_COMPILE_FLAGS += + +EBIN = . + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +tests debug opt: $(TARGET_FILES) + +$(EBIN)/%.beam: $(MnesiaExamplesDir)/%.erl + $(ERLC) -bbeam $(ERL_COMPILE_FLAGS) -o$(EBIN) $< + +clean: + rm -f $(TARGET_FILES) + rm -f core + +docs: + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + +release_tests_spec: opt + $(INSTALL_DIR) $(RELSYSDIR) + $(INSTALL_DATA) mnesia.spec mnesia.spec.vxworks $(ERL_FILES) $(HRL_FILES) $(RELSYSDIR) + $(INSTALL_PROGRAM) mt $(INSTALL_PROGS) $(RELSYSDIR) +# chmod -f -R u+w $(RELSYSDIR) +# @tar cf - *_SUITE_data | (cd $(RELSYSDIR); tar xf -) + +release_docs_spec: + + diff --git a/lib/mnesia/test/README b/lib/mnesia/test/README new file mode 100644 index 0000000000..e0ced7399d --- /dev/null +++ b/lib/mnesia/test/README @@ -0,0 +1,107 @@ +This directory contains the test suite of Mnesia. +Compile it with "erl -make". + +Test cases are identified with a {Mod, Fun} tuple that maps +to a function Mod:Fun(Config), where the test case hopefully +is implemented. The test suite is organized in a hierarchy +with {mnesia_SUITE, all} as the top. + +The module called mt, implements various convenience functions +to ease up the execution of test cases. It does also provide +aliases for some test cases. For example the atom Mod is an +alias for {Mod, all}, the atom all for {mnesia_SUITE, all}, +evil for mnesia_evil_coverage_test etc. + + mt:struct(TestCase) + + Displays the test case structure from TestCase + and downwards the hierarchy. E.g. mt:struct(all) + will display the entire test suite. + + mt:t(TestCase), mt:t(TestCase, Config) + + Runs a single test case or a hierarchy of test cases. + mt:t(silly) is be a good starter, but you may also + try mt:t(all) directly if you feel lucky. + + The identity of the last run test case and the outcome of + it is stored on file. mt:t() will re-run the last test case. + + The Config argument contains various configuration + parameters for the test cases, such as which nodes that + are available for running the test suite. The default + settings should be enough for the most. Use mt:read_config() + to get the current default setting and change it with + mt:write_config(Config). + + mt:doc(TestCase) + + Generates html documentation for the test suite. + +In order to be able to run the test suite, the Erlang node must +be started with the distribution enabled and the code path must +be set to the mnesia/ebin, mnesia/examples, and mnesia/test +directories. E.g. the following would do: + + erl -sname a -pa $top/examples -pa $top/src -pa $top/ebin + +where $top is the path to the Mnesia installation. Many test +cases needs 2 or 3 nodes. The node names may explicitly be +stated as test suite configuration parameters, but by default +the extra node names are generated. In this example the names +will be: a, a1 and a2. It is enough to start the first node +manually, the extra nodes will automatically be started if +neccessary. + +The attached UNIX shell script mt, does not work on all +platforms, but it may be used as a source for inspiration. It +starts three Erlang nodes in one xterm's each. The main xterm +(a@localhost) logs all output in the Erlang shell to a +file. The file is piped thru grep to easily find successful +test cases (i.e. test cases that encountered an error). + +During development we want to be able to run the test cases +in the debugger. This demands a little bit of preparations: + + - Start the neccessary number of nodes (normally 3). + This may either be done by running the mt script or + by starting the main node and then invoke mt:start_nodes() + to start the extra nodes with slave. + + - Ensure that the nodes are connected. The easiest way to do + this is by invoking mt:ping(). + + - Load all files that needs to be interpreted. This is typically + all Mnesia files plus the test case. By invoking mnesia:ni() + and mnesia:ni([TestModule]) the neccessary modules will be + loaded on all CONNECTED nodes. + +The test case execution is supervised in order to ensure that no test +case exceeds its maximum time limit, which by default is 5 minutes. +When the limit is reached, the running test case gets aborted and the +test server runs the next test case in line. This behaviour is useful +when running the entire test suite during the night, but it is really +annoying during debugging. + + Use the "erl -mnesia_test_timeout" flag to disable the test case + time limit mechanism. + +Some mechanisms in Mnesia are almost impossible to test with a +white box technique. In order to be able to write predictable +test cases which tests the same thing every time it is run, +Mnesia has been instrumented with debug functions. These may be +controlled from a test program. For example to verify that the +commit protocols work it is essential that it is possible to +ensure that we are able to kill Mnesia in the most critical +situations. Normally Mnesia is compiled with the debug +functions disabled and this means that test cases which +requires this functionality will be skipped. The mnesia:ni(), +mentioned above, functions ensures that the interpreted code is +instrumented with Mnesia's debug functionality. The mnesia:nc() +functions compiles Mnesia with the debug setting enabled. + +Happy bug hunting! + + Hakan Mattsson <[email protected]> + + diff --git a/lib/mnesia/test/mnesia.spec b/lib/mnesia/test/mnesia.spec new file mode 100644 index 0000000000..596f8b917d --- /dev/null +++ b/lib/mnesia/test/mnesia.spec @@ -0,0 +1,23 @@ +{topcase, {dir, "../mnesia_test"}}. +{require_nodenames, 2}. +{skip, {mnesia_measure_test, ram_meter, "Takes to long time"}}. +{skip, {mnesia_measure_test, disc_meter, "Takes to long time"}}. +{skip, {mnesia_measure_test, disc_only_meter, "Takes to long time"}}. +{skip, {mnesia_measure_test, cost, "Takes to long time"}}. +{skip, {mnesia_measure_test, dbn_meters, "Takes to long time"}}. +{skip, {mnesia_measure_test, tpcb, "Takes to long time"}}. +{skip, {mnesia_measure_test, prediction, "Not yet implemented"}}. +{skip, {mnesia_measure_test, consumption, "Not yet implemented"}}. +{skip, {mnesia_measure_test, scalability, "Not yet implemented"}}. +{skip, {mnesia_measure_test, tpcb, "Takes too much time and memory"}}. +{skip, {mnesia_measure_test, measure_all_api_functions, "Not yet implemented"}}. +{skip, {mnesia_measure_test, mnemosyne_vs_mnesia_kernel, "Not yet implemented"}}. +{skip, {mnesia_examples_test, company, "Not yet implemented"}}. +{skip, {mnesia_config_test, ignore_fallback_at_startup, "Not yet implemented"}}. +{skip, {mnesia_evil_backup, local_backup_checkpoint, "Not yet implemented"}}. +{skip, {mnesia_config_test, max_wait_for_decision, "Not yet implemented"}}. +{skip, {mnesia_recovery_test, after_full_disc_partition, "Not yet implemented"}}. +{skip, {mnesia_recovery_test, system_upgrade, "Not yet implemented"}}. +{skip, {mnesia_consistency_test, consistency_after_change_table_copy_type, "Not yet implemented"}}. +{skip, {mnesia_consistency_test, consistency_after_transform_table, "Not yet implemented"}}. +{skip, {mnesia_consistency_test, consistency_after_rename_of_node, "Not yet implemented"}}. diff --git a/lib/mnesia/test/mnesia.spec.vxworks b/lib/mnesia/test/mnesia.spec.vxworks new file mode 100644 index 0000000000..11c01ea3fe --- /dev/null +++ b/lib/mnesia/test/mnesia.spec.vxworks @@ -0,0 +1,362 @@ +{topcase, {dir, "../mnesia_test"}}. +{require_nodenames, 3}. +{diskless, true}. +{skip, {mnesia_measure_test, all, "Too heavy"}}. +%{mnesia_install_test, silly_durability} 'IMPL' +%{mnesia_install_test, silly_move} 'IMPL' +{skip, {mnesia_install_test, silly_upgrade, "Uses disk"}}. +%{mnesia_install_test, conflict} 'IMPL' +%{mnesia_install_test, dist} 'IMPL' +{skip, {mnesia_examples_test, all, "Uses disk"}}. +{skip, {mnesia_nice_coverage_test, all, "Uses disk"}}. + +%{mnesia_evil_coverage_test, system_info} 'IMPL' +%{mnesia_evil_coverage_test, table_info} 'IMPL' +%{mnesia_evil_coverage_test, error_description} 'IMPL' +{skip, {mnesia_evil_coverage_test, db_node_lifecycle, "Uses disk"}}. +{skip, {mnesia_evil_coverage_test, local_content, "Uses disk"}}. +%{mnesia_evil_coverage_test, start_and_stop} 'IMPL' +%{mnesia_evil_coverage_test, transaction} 'IMPL' +{skip, {mnesia_evil_coverage_test, checkpoint, "Uses disk"}}. +{skip, {mnesia_evil_backup, backup, "Uses disk"}}. +{skip, {mnesia_evil_backup, global_backup_checkpoint, "Uses disk"}}. +{skip, {mnesia_evil_backup, incremental_backup_checkpoint, "Uses disk"}}. +{skip, {mnesia_evil_backup, local_backup_checkpoint, "Uses disk"}}. +{skip, {mnesia_evil_backup, selective_backup_checkpoint, "Uses disk"}}. +{skip, {mnesia_evil_backup, restore_errors, "Uses disk"}}. +{skip, {mnesia_evil_backup, restore_clear, "Uses disk"}}. +{skip, {mnesia_evil_backup, restore_keep, "Uses disk"}}. +{skip, {mnesia_evil_backup, restore_recreate, "Uses disk"}}. +{skip, {mnesia_evil_backup, traverse_backup, "Uses disk"}}. +{skip, {mnesia_evil_backup, install_fallback, "Uses disk"}}. +{skip, {mnesia_evil_backup, uninstall_fallback, "Uses disk"}}. +{skip, {mnesia_evil_backup, local_fallback, "Uses disk"}}. +%{mnesia_evil_coverage_test, table_lifecycle} 'IMPL' +{skip, {mnesia_evil_coverage_test, replica_management, "Uses disk"}}. +%{mnesia_evil_coverage_test, change_table_access_mode} 'IMPL' +%{mnesia_evil_coverage_test, change_table_load_order} 'IMPL' +{skip, {mnesia_evil_coverage_test, set_master_nodes, "Uses disk"}}. +{skip, {mnesia_evil_coverage_test, offline_set_master_nodes, "Uses disk"}}. +{skip, {mnesia_evil_coverage_test, replica_location, "Uses disk"}}. +%{mnesia_evil_coverage_test, add_table_index_ram} 'IMPL' +{skip, {mnesia_trans_access_test, add_table_index_disc, "Uses disc"}}. +{skip, {mnesia_trans_access_test, add_table_index_disc_only, "Uses disc"}}. +%{mnesia_evil_coverage_test, create_live_table_index_ram} 'IMPL' +{skip, {mnesia_trans_access_test, create_live_table_index_disc, "Uses disc"}}. +{skip, {mnesia_trans_access_test, create_live_table_index_disc_only, "Uses disc"}}. +%{mnesia_evil_coverage_test, del_table_index_ram} 'IMPL' +{skip, {mnesia_trans_access_test, del_table_index_disc, "Uses disc"}}. +{skip, {mnesia_trans_access_test, del_table_index_disc_only, "Uses disc"}}. +{skip, {mnesia_trans_access_test, idx_schema_changes_ram, "Uses disk"}}. +{skip, {mnesia_trans_access_test, idx_schema_changes_disc, "Uses disc"}}. +{skip, {mnesia_trans_access_test, idx_schema_changes_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_write_ram} 'IMPL' + +{skip, {mnesia_dirty_access_test, dirty_write_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_write_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_read_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_read_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_read_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_update_counter_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_update_counter_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_update_counter_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_delete_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_delete_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_delete_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_delete_object_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_delete_object_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_delete_object_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_match_object_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_match_object_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_match_object_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_index_match_object_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_index_match_object_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_index_match_object_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_index_read_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_index_read_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_index_read_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_index_update_set_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_index_update_set_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_index_update_set_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_index_update_bag_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_index_update_bag_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_index_update_bag_disc_only, "Uses disc"}}. +%{mnesia_dirty_access_test, dirty_iter_ram} 'IMPL' +{skip, {mnesia_dirty_access_test, dirty_iter_disc, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, dirty_iter_disc_only, "Uses disc"}}. +{skip, {mnesia_dirty_access_test, admin_tests, "Uses disk"}}. + +%{mnesia_trans_access_test, write} 'IMPL' +%{mnesia_trans_access_test, read} 'IMPL' +%{mnesia_trans_access_test, wread} 'IMPL' +%{mnesia_trans_access_test, delete} 'IMPL' +%{mnesia_trans_access_test, delete_object} 'IMPL' +%{mnesia_trans_access_test, match_object} 'IMPL' +%{mnesia_trans_access_test, all_keys} 'IMPL' +%{mnesia_trans_access_test, index_match_object} 'IMPL' +%{mnesia_trans_access_test, index_read} 'IMPL' +%{mnesia_trans_access_test, index_update_set} 'IMPL' +%{mnesia_trans_access_test, index_update_bag} 'IMPL' +{skip, {mnesia_evil_coverage_test, dump_tables, "Uses disk"}}. +{skip, {mnesia_evil_coverage_test, dump_log, "Uses disk"}}. +%{mnesia_evil_coverage_test, wait_for_tables} 'IMPL' +{skip, {mnesia_evil_coverage_test, force_load_table, "Uses disk"}}. +%{mnesia_evil_coverage_test, user_properties} 'IMPL' +%{mnesia_evil_coverage_test, record_name_dirty_access_ram} 'IMPL' +{skip, {mnesia_evil_coverage_test, record_name_dirty_access_disc, "Uses disc"}}. +{skip, {mnesia_evil_coverage_test, record_name_dirty_access_disc_only, "Uses disc"}}. +%{mnesia_evil_coverage_test, snmp_open_table} 'IMPL' +%{mnesia_evil_coverage_test, snmp_close_table} 'IMPL' +%{mnesia_evil_coverage_test, snmp_get_next_index} 'IMPL' +%{mnesia_evil_coverage_test, snmp_get_row} 'IMPL' +%{mnesia_evil_coverage_test, snmp_get_mnesia_key} 'IMPL' +%{mnesia_evil_coverage_test, snmp_update_counter} 'IMPL' +%{mnesia_evil_coverage_test, info} 'IMPL' +%{mnesia_evil_coverage_test, schema_0} 'IMPL' +%{mnesia_evil_coverage_test, schema_1} 'IMPL' +%{mnesia_evil_coverage_test, view_0} 'IMPL' +{skip, {mnesia_evil_coverage_test, view_1, "Uses disk"}}. +{skip, {mnesia_evil_coverage_test, view_2, "Uses disk"}}. +%{mnesia_evil_coverage_test, lkill} 'IMPL' +%{mnesia_evil_coverage_test, kill} 'IMPL' + +%{mnesia_config_test, access_module} 'IMPL' +%{mnesia_config_test, auto_repair} 'IMPL' +{skip, {mnesia_config_test, backup_module, "Uses disk"}}. +{skip, {mnesia_config_test, dynamic_connect, "Uses disk"}}. +%{mnesia_config_test, debug} 'IMPL' +%{mnesia_config_test, dir} 'IMPL' +{skip, {mnesia_config_test, dump_log_load_regulation, "Uses disk"}}. +{skip, {mnesia_config_test, dump_log_time_threshold, "Uses disk"}}. +{skip, {mnesia_config_test, dump_log_write_threshold, "Uses disk"}}. +{skip, {mnesia_config_test, dump_log_update_in_place, "Uses disk"}}. +{skip, {mnesia_config_test, embedded_mnemosyne, "Uses Mnemosyne"}}. +%{mnesia_config_test, event_module} 'IMPL' +{skip, {mnesia_config_test, ignore_fallback_at_startup, "Not Yet impl"}}. +%{mnesia_config_test, inconsistent_database} 'IMPL' +{skip, {mnesia_config_test, max_wait_for_decision, "Not Yet impl"}}. +{skip, {mnesia_config_test, start_one_disc_full_then_one_disc_less, "Uses disc"}}. +{skip, {mnesia_config_test, start_first_one_disc_less_then_one_disc_full, "Uses disc"}}. +%%{skip, {mnesia_config_test, start_first_one_disc_less_then_two_more_disc_less, "Uses disc"}}. +{skip, {mnesia_config_test, schema_location_and_extra_db_nodes_combinations, "Uses disk"}}. +{skip, {mnesia_config_test, table_load_to_disc_less_nodes, "Uses disc"}}. +{skip, {mnesia_config_test, schema_merge, "Uses Disc"}}. +%{mnesia_config_test, unknown_config} 'IMPL' +%{mnesia_registry_test, good_dump} 'IMPL' +%{mnesia_registry_test, bad_dump} 'IMPL' + +%{mnesia_atomicity_test, explicit_abort_in_middle_of_trans} 'IMPL' +%{mnesia_atomicity_test, runtime_error_in_middle_of_trans} 'IMPL' +%{mnesia_atomicity_test, kill_self_in_middle_of_trans} 'IMPL' +%{mnesia_atomicity_test, throw_in_middle_of_trans} 'IMPL' +%{mnesia_atomicity_test, mnesia_down_during_infinite_trans} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_sw_rt} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_sw_wt} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wr_r} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_sw_sw} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_sw_w} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_sw_wr} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wr_wt} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wr_sw} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wr_w} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_r_sw} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_r_w} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_r_wt} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_rt_sw} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_rt_w} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_rt_wt} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wt_r} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wt_w} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wt_rt} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wt_wt} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wt_wr} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_wt_sw} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_w_wr} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_w_sw} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_w_r} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_w_w} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_w_rt} 'IMPL' +%{mnesia_atomicity_test, lock_waiter_w_wt} 'IMPL' +%{mnesia_atomicity_test, restart_r_one} 'IMPL' +%{mnesia_atomicity_test, restart_w_one} 'IMPL' +%{mnesia_atomicity_test, restart_rt_one} 'IMPL' +%{mnesia_atomicity_test, restart_wt_one} 'IMPL' +%{mnesia_atomicity_test, restart_wr_one} 'IMPL' +%{mnesia_atomicity_test, restart_sw_one} 'IMPL' +%{mnesia_atomicity_test, restart_r_two} 'IMPL' +%{mnesia_atomicity_test, restart_w_two} 'IMPL' +%{mnesia_atomicity_test, restart_rt_two} 'IMPL' +%{mnesia_atomicity_test, restart_wt_two} 'IMPL' +%{mnesia_atomicity_test, restart_wr_two} 'IMPL' +%{mnesia_atomicity_test, restart_sw_two} 'IMPL' + +%{mnesia_isolation_test, no_conflict} 'IMPL' +%{mnesia_isolation_test, simple_queue_conflict} 'IMPL' +%{mnesia_isolation_test, advanced_queue_conflict} 'IMPL' +%{mnesia_isolation_test, simple_deadlock_conflict} 'IMPL' +%{mnesia_isolation_test, advanced_deadlock_conflict} 'IMPL' +%{mnesia_isolation_test, lock_burst} 'IMPL' +%{mnesia_isolation_test, basic_sticky_functionality} 'IMPL' +%{mnesia_isolation_test, create_table} 'IMPL' +%{mnesia_isolation_test, delete_table} 'IMPL' +%{mnesia_isolation_test, move_table_copy} 'IMPL' +%{mnesia_isolation_test, add_table_index} 'IMPL' +%{mnesia_isolation_test, del_table_index} 'IMPL' +%{mnesia_isolation_test, transform_table} 'IMPL' +%{mnesia_isolation_test, snmp_open_table} 'IMPL' +%{mnesia_isolation_test, snmp_close_table} 'IMPL' +{skip, {mnesia_isolation_test, change_table_copy_type, "Uses disk"}}. +%{mnesia_isolation_test, change_table_access} 'IMPL' +%{mnesia_isolation_test, add_table_copy} 'IMPL' +%{mnesia_isolation_test, del_table_copy} 'IMPL' +{skip, {mnesia_isolation_test, dump_tables, "Uses disk"}}. +{skip, {mnesia_isolation_test, extra_admin_tests, "Uses disk"}}. +%{mnesia_isolation_test, del_table_copy_1} 'IMPL' +%{mnesia_isolation_test, del_table_copy_2} 'IMPL' +%{mnesia_isolation_test, del_table_copy_3} 'IMPL' +%{mnesia_isolation_test, add_table_copy_1} 'IMPL' +%{mnesia_isolation_test, add_table_copy_2} 'IMPL' +%{mnesia_isolation_test, add_table_copy_3} 'IMPL' +%{mnesia_isolation_test, add_table_copy_4} 'IMPL' +%{mnesia_isolation_test, move_table_copy_1} 'IMPL' +%{mnesia_isolation_test, move_table_copy_2} 'IMPL' +%{mnesia_isolation_test, move_table_copy_3} 'IMPL' +%{mnesia_isolation_test, move_table_copy_4} 'IMPL' +%{mnesia_isolation_test, dirty_updates_visible_direct} 'IMPL' +%{mnesia_isolation_test, dirty_reads_regardless_of_trans} 'IMPL' +%{mnesia_isolation_test, trans_update_invisibible_outside_trans} 'IMPL' +%{mnesia_isolation_test, trans_update_visible_inside_trans} 'IMPL' +%{mnesia_isolation_test, write_shadows} 'IMPL' +%{mnesia_isolation_test, delete_shadows} 'IMPL' +%{mnesia_isolation_test, write_delete_shadows_bag} 'IMPL' + +{skip, {mnesia_durability_test, all, "Uses disk "}}. +%{mnesia_durability_test, load_local_contents_directly} 'IMPL' +%{mnesia_durability_test, load_directly_when_all_are_ram_copiesA} 'IMPL' +%{mnesia_durability_test, load_directly_when_all_are_ram_copiesB} 'IMPL' +%{skip, {mnesia_durability_test, late_load_when_all_are_ram_copies_on_ram_nodes1, "Uses disk schema"}}. +%{skip, {mnesia_durability_test, late_load_when_all_are_ram_copies_on_ram_nodes2, "Uses disk schema"}}. +%{skip, {mnesia_durability_test, load_when_last_replica_becomes_available, "Uses disk"}}. +%{skip, {mnesia_durability_test, load_when_we_have_down_from_all_other_replica_nodes, "Uses disk"}}. +%{skip, {mnesia_durability_test, late_load_transforms_into_disc_load, "Uses disc"}}. +%{mnesia_durability_test, late_load_leads_to_hanging} 'IMPL' +%{mnesia_durability_test, force_load_when_nobody_intents_to_load} 'IMPL' +%{mnesia_durability_test, force_load_when_someone_has_decided_to_load} 'IMPL' +%{mnesia_durability_test, force_load_when_someone_else_already_has_loaded} 'IMPL' +%{mnesia_durability_test, force_load_when_we_has_loaded} 'IMPL' +%{mnesia_durability_test, force_load_on_a_non_local_table} 'IMPL' +%{mnesia_durability_test, force_load_when_the_table_does_not_exist} 'IMPL' +%{mnesia_durability_test, master_nodes} 'IMPL' +%{mnesia_durability_test, master_on_non_local_tables} 'IMPL' +%{mnesia_durability_test, remote_force_load_with_local_master_node} 'IMPL' +%{mnesia_durability_test, dump_ram_copies} 'IMPL' +%{skip, {mnesia_durability_test, dump_disc_copies, "Uses disc"}}. +%{skip, {mnesia_durability_test, dump_disc_only, "Uses disc"}}. +%{skip, {mnesia_durability_test, durability_of_disc_copies, "Uses disc"}}. +%{skip, {mnesia_durability_test, durability_of_disc_only_copies, "Uses disc"}}. + +{skip, {mnesia_recovery_test, mnesia_down, "Uses Disk"}}. +%{mnesia_recovery_test, no_master_2} 'IMPL' +%{mnesia_recovery_test, no_master_3} 'IMPL' +%{mnesia_recovery_test, one_master_2} 'IMPL' +%{mnesia_recovery_test, one_master_3} 'IMPL' +%{mnesia_recovery_test, two_master_2} 'IMPL' +%{mnesia_recovery_test, two_master_3} 'IMPL' +%{mnesia_recovery_test, all_master_2} 'IMPL' +%{mnesia_recovery_test, all_master_3} 'IMPL' +{skip, {mnesia_recovery_test, mnesia_down_during_startup_disk_ram, "Uses disk"}}. +%{mnesia_recovery_test, mnesia_down_during_startup_init_ram} 'IMPL' +{skip, {mnesia_recovery_test, mnesia_down_during_startup_init_disc, "Uses disc"}}. +{skip, {mnesia_recovery_test, mnesia_down_during_startup_init_disc_only, "Uses disc"}}. +%{mnesia_recovery_test, mnesia_down_during_startup_tm_ram} 'IMPL' +{skip, {mnesia_recovery_test, mnesia_down_during_startup_tm_disc, "Uses disc"}}. +{skip, {mnesia_recovery_test, mnesia_down_during_startup_tm_disc_only, "Uses disc"}}. +%{mnesia_recovery_test, explicit_stop_during_snmp} 'IMPL' + +{skip, {mnesia_recovery_test, schema_trans, "Uses Disk, needs disk log"}}. +{skip, {mnesia_recovery_test, async_dirty, "Uses disc"}}. +{skip, {mnesia_recovery_test, sync_dirty, "Uses disc"}}. +{skip, {mnesia_recovery_test, sym_trans, "Uses disc"}}. +{skip, {mnesia_recovery_test, asym_trans, "Uses disc"}}. + +{skip, {mnesia_recovery_test, after_full_disc_partition, "Not Yet impl"}}. +{skip, {mnesia_recovery_test, after_corrupt_files, "Uses disk"}}. + +%{mnesia_evil_coverage_test, subscriptions} 'IMPL' +%{mnesia_evil_coverage_test, nested_trans_both_ok} 'IMPL' +%{mnesia_evil_coverage_test, nested_trans_child_dies} 'IMPL' +%{mnesia_evil_coverage_test, nested_trans_parent_dies} 'IMPL' +%{mnesia_evil_coverage_test, nested_trans_both_dies} 'IMPL' +%{mnesia_evil_coverage_test, mix_of_trans_sync_dirty} 'IMPL' +%{mnesia_evil_coverage_test, mix_of_trans_async_dirty} 'IMPL' +%{mnesia_evil_coverage_test, mix_of_trans_ets} 'IMPL' + +{skip, {mnesia_recovery_test, disc_less, "Uses disc (on the other nodes)"}}. +{skip, {mnesia_recovery_test, system_upgrade, "Not Yet impl"}}. +%{mnesia_consistency_test, consistency_after_restart_1_ram} 'IMPL' +{skip, {mnesia_consistency_test, consistency_after_restart_1_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_restart_1_disc_only, "Uses disc"}}. +%{mnesia_consistency_test, consistency_after_restart_2_ram} 'IMPL' +{skip, {mnesia_consistency_test, consistency_after_restart_2_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_restart_2_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_dump_tables_1_ram, "Uses disk"}}. +{skip, {mnesia_consistency_test, consistency_after_dump_tables_2_ram, "Uses disk"}}. +%{mnesia_consistency_test, consistency_after_add_replica_2_ram} 'IMPL' +{skip, {mnesia_consistency_test, consistency_after_add_replica_2_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_add_replica_2_disc_only, "Uses disc"}}. +%{mnesia_consistency_test, consistency_after_add_replica_3_ram} 'IMPL' +{skip, {mnesia_consistency_test, consistency_after_add_replica_3_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_add_replica_3_disc_only, "Uses disc"}}. +%{mnesia_consistency_test, consistency_after_del_replica_2_ram} 'IMPL' +{skip, {mnesia_consistency_test, consistency_after_del_replica_2_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_del_replica_2_disc_only, "Uses disc"}}. +%{mnesia_consistency_test, consistency_after_del_replica_3_ram} 'IMPL' +{skip, {mnesia_consistency_test, consistency_after_del_replica_3_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_del_replica_3_disc_only, "Uses disc"}}. +%{mnesia_consistency_test, consistency_after_move_replica_2_ram} 'IMPL' +{skip, {mnesia_consistency_test, consistency_after_move_replica_2_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_move_replica_2_disc_only, "Uses disc"}}. +%{mnesia_consistency_test, consistency_after_move_replica_3_ram} 'IMPL' +{skip, {mnesia_consistency_test, consistency_after_move_replica_3_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_move_replica_3_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_transform_table, "Not yet implemented"}}. +{skip, {mnesia_consistency_test, consistency_after_change_table_copy_type, "Not yet implemented"}}. +{skip, {mnesia_consistency_test, consistency_after_fallback_2_ram, "Uses disk"}}. +{skip, {mnesia_consistency_test, consistency_after_fallback_2_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_fallback_2_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_fallback_3_ram, "Uses disk"}}. +{skip, {mnesia_consistency_test, consistency_after_fallback_3_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_fallback_3_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_restore_clear_ram, "Uses disk"}}. +{skip, {mnesia_consistency_test, consistency_after_restore_clear_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_restore_clear_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_restore_recreate_ram, "Uses disk"}}. +{skip, {mnesia_consistency_test, consistency_after_restore_recreate_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_restore_recreate_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, consistency_after_rename_of_node, "Not yet implemented"}}. +{skip, {mnesia_consistency_test, updates_during_checkpoint_activation, "Uses disk"}}. +%{skip, {mnesia_consistency_test, updates_during_checkpoint_activation_2_disc, "Uses disc"}}. +%{skip, {mnesia_consistency_test, updates_during_checkpoint_activation_2_disc_only, "Uses disc"}}. +%%{mnesia_consistency_test, updates_during_checkpoint_activation_3_ram} 'IMPL' +%{skip, {mnesia_consistency_test, updates_during_checkpoint_activation_3_disc, "Uses disc"}}. +%{skip, {mnesia_consistency_test, updates_during_checkpoint_activation_3_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, updates_during_checkpoint_iteration, "Uses disk"}}. +%{skip, {mnesia_consistency_test, updates_during_checkpoint_iteration_2_disc, "Uses disc"}}. +%{skip, {mnesia_consistency_test, updates_during_checkpoint_iteration_2_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, load_table_with_activated_checkpoint_ram, "Uses disk"}}. +{skip, {mnesia_consistency_test, load_table_with_activated_checkpoint_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, load_table_with_activated_checkpoint_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, add_table_copy_to_table_with_activated_checkpoint_ram, "Uses disk"}}. +{skip, {mnesia_consistency_test, add_table_copy_to_table_with_activated_checkpoint_disc, "Uses disc"}}. +{skip, {mnesia_consistency_test, add_table_copy_to_table_with_activated_checkpoint_disc_only, "Uses disc"}}. +{skip, {mnesia_consistency_test, inst_fallback_process_dies, "Uses disk"}}. +{skip, {mnesia_consistency_test, fatal_when_inconsistency, "Uses disk"}}. +{skip, {mnesia_consistency_test, after_delete, "Uses disk"}}. +{skip, {mnesia_consistency_test, mnesia_down_during_backup_causes_switch, "Uses disk"}}. +{skip, {mnesia_consistency_test, mnesia_down_during_backup_causes_abort, "Uses disk"}}. +%{mnesia_consistency_test, cause_switch_after} 'IMPL' +%{mnesia_consistency_test, cause_abort_before} 'IMPL' +%{mnesia_consistency_test, cause_abort_after} 'IMPL' +%{mnesia_consistency_test, change_schema_before} 'IMPL' +%{mnesia_consistency_test, change_schema_after} 'IMPL' + diff --git a/lib/mnesia/test/mnesia_SUITE.erl b/lib/mnesia/test/mnesia_SUITE.erl new file mode 100644 index 0000000000..b28deaf330 --- /dev/null +++ b/lib/mnesia/test/mnesia_SUITE.erl @@ -0,0 +1,203 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_SUITE). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Verify that Mnesia really is a distributed real-time DBMS", + "This is the test suite of the Mnesia DBMS. The test suite", + "covers many aspects of usage and is indended to be developed", + "incrementally. The test suite is divided into a hierarchy of test", + "suites where the leafs actually implements the test cases.", + "The intention of each test case and sub test suite can be", + "read in comments where they are implemented or in worst cases", + "from their long mnemonic names. ", + "", + "The most simple test case of them all is called 'silly'", + "and is useful to run now and then, e.g. when some new fatal", + "bug has been introduced. It may be run even if Mnesia is in", + "such a bad shape that the test machinery cannot be used.", + "NB! Invoke the function directly with mnesia_SUITE:silly()", + "and do not involve the normal test machinery."]; +all(suite) -> + [ + light, + medium, + heavy, + clean_up_suite + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +silly() -> + mnesia_install_test:silly(). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +light(doc) -> + ["The 'light' test suite runs a selected set of test suites and is", + "intended to be the smallest test suite that is meaningful", + "to run. It starts with an installation test (which in essence is the", + "'silly' test case) and then it covers all functions in the API in", + "various depths. All configuration parameters and examples are also", + "covered."]; +light(suite) -> + [ + install, + nice, + evil, + {mnesia_frag_test, light}, + qlc, + registry, + config, + examples + ]. + +install(suite) -> + [{mnesia_install_test, all}]. + +nice(suite) -> + [{mnesia_nice_coverage_test, all}]. + +evil(suite) -> + [{mnesia_evil_coverage_test, all}]. + +qlc(suite) -> + [{mnesia_qlc_test, all}]. + +registry(suite) -> + [{mnesia_registry_test, all}]. + +config(suite) -> + [{mnesia_config_test, all}]. + +examples(suite) -> + [{mnesia_examples_test, all}]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +medium(doc) -> + ["The 'medium' test suite verfies the ACID (atomicity, consistency", + "isolation and durability) properties and various recovery scenarios", + "These tests may take quite while to run."]; +medium(suite) -> + [ + install, + atomicity, + isolation, + durability, + recovery, + consistency, + {mnesia_frag_test, medium} + ]. + +atomicity(suite) -> + [{mnesia_atomicity_test, all}]. + +isolation(suite) -> + [{mnesia_isolation_test, all}]. + +durability(suite) -> + [{mnesia_durability_test, all}]. + +recovery(suite) -> + [{mnesia_recovery_test, all}]. + +consistency(suite) -> + [{mnesia_consistency_test, all}]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +heavy(doc) -> + ["The 'heavy' test suite runs some resource consuming tests and", + "benchmarks"]; +heavy(suite) -> + [measure]. + +measure(suite) -> + [{mnesia_measure_test, all}]. + +prediction(suite) -> + [{mnesia_measure_test, prediction}]. + +fairness(suite) -> + [{mnesia_measure_test, fairness}]. + +benchmarks(suite) -> + [{mnesia_measure_test, benchmarks}]. + +consumption(suite) -> + [{mnesia_measure_test, consumption}]. + +scalability(suite) -> + [{mnesia_measure_test, scalability}]. + + +clean_up_suite(doc) -> ["Not a test case only kills mnesia and nodes, that where" + "started during the tests"]; +clean_up_suite(suite) -> + []; +clean_up_suite(Config) when is_list(Config)-> + mnesia:kill(), + Slaves = mnesia_test_lib:lookup_config(nodenames, Config), + Nodes = lists:delete(node(), Slaves), + rpc:multicall(Nodes, erlang, halt, []), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +otp_r4b(doc) -> + ["This test suite is an extract of the grand Mnesia suite", + "it contains OTP R4B specific test cases"]; +otp_r4b(suite) -> + [ + {mnesia_config_test, access_module}, + {mnesia_config_test, dump_log_load_regulation}, + {mnesia_config_test, embedded_mnemosyne}, + {mnesia_config_test, ignore_fallback_at_startup}, + {mnesia_config_test, max_wait_for_decision}, + {mnesia_consistency_test, consistency_after_restore}, + {mnesia_evil_backup, restore}, + {mnesia_evil_coverage_test, offline_set_master_nodes}, + {mnesia_evil_coverage_test, record_name}, + {mnesia_evil_coverage_test, user_properties}, + {mnesia_registry_test, all}, + otp_2363 + ]. + +otp_2363(doc) -> + ["Index on disc only tables"]; +otp_2363(suite) -> + [ + {mnesia_dirty_access_test, dirty_index_match_object_disc_only}, + {mnesia_dirty_access_test,dirty_index_read_disc_only}, + {mnesia_dirty_access_test,dirty_index_update_bag_disc_only}, + {mnesia_dirty_access_test,dirty_index_update_set_disc_only}, + {mnesia_evil_coverage_test, create_live_table_index_disc_only} + ]. + + + diff --git a/lib/mnesia/test/mnesia_atomicity_test.erl b/lib/mnesia/test/mnesia_atomicity_test.erl new file mode 100644 index 0000000000..645c203a91 --- /dev/null +++ b/lib/mnesia/test/mnesia_atomicity_test.erl @@ -0,0 +1,839 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_atomicity_test). +-author('[email protected]'). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Verify atomicity of transactions", + "Verify that transactions are atomic, i.e. either all operations", + "in a transaction will be performed or none of them. It must be", + "assured that no partitially completed operations leaves any", + "effects in the database."]; +all(suite) -> + [ + explicit_abort_in_middle_of_trans, + runtime_error_in_middle_of_trans, + kill_self_in_middle_of_trans, + throw_in_middle_of_trans, + mnesia_down_in_middle_of_trans + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +explicit_abort_in_middle_of_trans(suite) -> []; +explicit_abort_in_middle_of_trans(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = explicit_abort_in_middle_of_trans, + + Rec1A = {Tab, 1, a}, + Rec1B = {Tab, 1, b}, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}])), + %% Start a transaction on one node + {success, [A]} = ?start_activities([Node1]), + + %% store an object in the Tab - first tranaction + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1A) % returns ok when successful + end, + ?match_receive({A, ok}), + A ! end_trans, + ?match_receive({A, {atomic, end_trans}}), + + %% second transaction: store some new objects and abort before the + %% transaction is finished -> the new changes should be invisable + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1B), + exit(abort_by_purpose) %does that stop the process A ??? + end, + ?match_receive({A, {aborted, abort_by_purpose}}), + + + %?match_receive({A, {'EXIT', Pid, normal}}), % A died and sends EXIT + + + %% Start a second transactionprocess, after the first failed + {success, [B]} = ?start_activities([Node1]), + + %% check, whether the interupted transaction had no influence on the db + ?start_transactions([B]), + B ! fun() -> + ?match([Rec1A], mnesia:read({Tab, 1})), + ok + end, + ?match_receive({B, ok}), + B ! end_trans, + ?match_receive({B, {atomic, end_trans}}), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +runtime_error_in_middle_of_trans(suite) -> []; +runtime_error_in_middle_of_trans(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = runtime_error_in_middle_of_trans, + + Rec1A = {Tab, 1, a}, + Rec1B = {Tab, 1, b}, + Rec1C = {Tab, 1, c}, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}])), + %% Start a transaction on one node + {success, [A]} = ?start_activities([Node1]), + + %% store an object in the Tab - first tranaction + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1A) % returns ok when successful + end, + ?match_receive({A, ok}), + A ! end_trans, + ?match_receive({A, {atomic, end_trans}}), + + %% second transaction: store some new objects and abort before the + %% transaction is finished -> the new changes should be invisable + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1B), + erlang:error(foo), % that should provoke a runtime error + mnesia:write(Rec1C) + end, + ?match_receive({A, {aborted, _Reason}}), + + %?match_receive({A, {'EXIT', Msg1}), % A died and sends EXIT + + + %% Start a second transactionprocess, after the first failed + {success, [B]} = ?start_activities([Node1]), + + %% check, whether the interupted transaction had no influence on the db + ?start_transactions([B]), + B ! fun() -> + ?match([Rec1A], mnesia:read({Tab, 1})), + ok + end, + ?match_receive({B, ok}), + B ! end_trans, + ?match_receive({B, {atomic, end_trans}}), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +kill_self_in_middle_of_trans(suite) -> []; +kill_self_in_middle_of_trans(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = kill_self_in_middle_of_trans, + + Rec1A = {Tab, 1, a}, + Rec1B = {Tab, 1, b}, + Rec1C = {Tab, 1, c}, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}])), + %% Start a transaction on one node + {success, [A]} = ?start_activities([Node1]), + + %% store an object in the Tab - first tranaction + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1A) % returns ok when successful + end, + ?match_receive({A, ok}), + A ! end_trans, + ?match_receive({A, {atomic, end_trans}}), + + %% second transaction: store some new objects and abort before the + %% transaction is finished -> the new changes should be invisable + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1B), + exit(self(), kill), % that should kill the process himself + % - poor guy ! + mnesia:write(Rec1C) + end, + %% + %% exit(.., kill) : the transaction can't trap this error - thus no + %% proper result can be send by the test server + + % ?match_receive({A, {aborted, Reason}}), + + ?match_receive({'EXIT', _Pid, killed}), % A is killed and sends EXIT + + %% Start a second transactionprocess, after the first failed + {success, [B]} = ?start_activities([Node1]), + + %% check, whether the interupted transaction had no influence on the db + ?start_transactions([B]), + B ! fun() -> + ?match([Rec1A], mnesia:read({Tab, 1})), + ok + end, + ?match_receive({B, ok}), + B ! end_trans, + ?match_receive({B, {atomic, end_trans}}), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +throw_in_middle_of_trans(suite) -> []; +throw_in_middle_of_trans(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = throw_in_middle_of_trans, + + Rec1A = {Tab, 1, a}, + Rec1B = {Tab, 1, b}, + Rec1C = {Tab, 1, c}, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}])), + %% Start a transaction on one node + {success, [A]} = ?start_activities([Node1]), + + %% store an object in the Tab - first tranaction + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1A) % returns ok when successful + end, + ?match_receive({A, ok}), + A ! end_trans, + ?match_receive({A, {atomic, end_trans}}), + + %% second transaction: store some new objects and abort before the + %% transaction is finished -> the new changes should be invisable + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1B), + throw(exit_transactian_by_a_throw), + mnesia:write(Rec1C) + end, + ?match_receive({A, {aborted, {throw, exit_transactian_by_a_throw}}}), + % A ! end_trans, % is A still alive ? + % ?match_receive({A, {atomic, end_trans}}), % {'EXIT', Pid, normal} + + %?match_receive({A, {'EXIT', Pid, normal}}), % A died and sends EXIT + + %% Start a second transactionprocess, after the first failed + {success, [B]} = ?start_activities([Node1]), + + %% check, whether the interupted transaction had no influence on the db + ?start_transactions([B]), + B ! fun() -> + ?match([Rec1A], mnesia:read({Tab, 1})), + ok + end, + ?match_receive({B, ok}), + B ! end_trans, + ?match_receive({B, {atomic, end_trans}}), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +mnesia_down_in_middle_of_trans(suite) -> + [ + mnesia_down_during_infinite_trans, + lock_waiter, + restart_check + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +mnesia_down_during_infinite_trans(suite) -> []; +mnesia_down_during_infinite_trans(Config) when is_list(Config) -> + [Node1, Node2] = ?acquire_nodes(2, Config), + Tab = mnesia_down_during_infinite_trans, + + ?match({atomic, ok}, + mnesia:create_table([{name, Tab}, {ram_copies, [Node1, Node2]}])), + %% Start a transaction on one node + {success, [A2, A1]} = ?start_activities([Node2, Node1]), + %% Start order of the transactions are important + %% We also needs to sync the tid counter + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, 1, test_ok}) end)), + mnesia_test_lib:start_sync_transactions([A2, A1]), + + %% Obtain a write lock and wait forever + RecA = {Tab, 1, test_not_ok}, + A1 ! fun() -> mnesia:write(RecA) end, + ?match_receive({A1, ok}), + + A1 ! fun() -> process_flag(trap_exit, true), timer:sleep(infinity) end, + ?match_receive(timeout), + + %% Try to get read lock, but gets queued + A2 ! fun() -> mnesia:read({Tab, 1}) end, + ?match_receive(timeout), + + %% Kill Mnesia on other node + mnesia_test_lib:kill_mnesia([Node1]), + + %% Second transaction gets the read lock + ?match_receive({A2, [{Tab, 1, test_ok}]}), + exit(A1, kill), % Needed since we trap exit + + ?verify_mnesia([Node2], [Node1]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +lock_waiter(doc) -> + ["The purpose of this test case is to test the following situation:", + "process B locks an object, process A accesses that object as", + "well, but A has to wait for the lock to be released. Then", + "mnesia of B goes down. Question: will A get the lock ?", + "important: the transaction of A is the oldest one !!! (= a little tricky)", + "", + "several different access operations shall be tested", + "rt = read_lock_table, wt = write_lock_table, r = read,", + "sw = s_write, w = write, wr = wread"]; +lock_waiter(suite) -> + [ + lock_waiter_sw_r, + lock_waiter_sw_rt, + lock_waiter_sw_wt, + lock_waiter_wr_r, + lock_waiter_srw_r, + lock_waiter_sw_sw, + lock_waiter_sw_w, + lock_waiter_sw_wr, + lock_waiter_sw_srw, + lock_waiter_wr_wt, + lock_waiter_srw_wt, + lock_waiter_wr_sw, + lock_waiter_srw_sw, + lock_waiter_wr_w, + lock_waiter_srw_w, + lock_waiter_r_sw, + lock_waiter_r_w, + lock_waiter_r_wt, + lock_waiter_rt_sw, + lock_waiter_rt_w, + lock_waiter_rt_wt, + lock_waiter_wr_wr, + lock_waiter_srw_srw, + lock_waiter_wt_r, + lock_waiter_wt_w, + lock_waiter_wt_rt, + lock_waiter_wt_wt, + lock_waiter_wt_wr, + lock_waiter_wt_srw, + lock_waiter_wt_sw, + lock_waiter_w_wr, + lock_waiter_w_srw, + lock_waiter_w_sw, + lock_waiter_w_r, + lock_waiter_w_w, + lock_waiter_w_rt, + lock_waiter_w_wt + ]. + +lock_waiter_sw_r(suite) -> []; +lock_waiter_sw_r(Config) when is_list(Config) -> + start_lock_waiter(sw, r, Config). + +lock_waiter_sw_rt(suite) -> []; +lock_waiter_sw_rt(Config) when is_list(Config) -> + start_lock_waiter(sw, rt, Config). + +lock_waiter_sw_wt(suite) -> []; +lock_waiter_sw_wt(Config) when is_list(Config) -> + start_lock_waiter(sw, wt,Config). + +lock_waiter_wr_r(suite) -> []; +lock_waiter_wr_r(Config) when is_list(Config) -> + start_lock_waiter(wr, r, Config). + +lock_waiter_srw_r(suite) -> []; +lock_waiter_srw_r(Config) when is_list(Config) -> + start_lock_waiter(srw, r, Config). + +lock_waiter_sw_sw(suite) -> []; +lock_waiter_sw_sw(Config) when is_list(Config) -> + start_lock_waiter(sw, sw,Config). + +lock_waiter_srw_srw(suite) -> []; +lock_waiter_srw_srw(Config) when is_list(Config) -> + start_lock_waiter(srw, srw,Config). + +lock_waiter_wr_wr(suite) -> []; +lock_waiter_wr_wr(Config) when is_list(Config) -> + start_lock_waiter(wr, wr,Config). + +lock_waiter_sw_w(suite) -> []; +lock_waiter_sw_w(Config) when is_list(Config) -> + start_lock_waiter(sw, w,Config). + +lock_waiter_sw_wr(suite) -> []; +lock_waiter_sw_wr(Config) when is_list(Config) -> + start_lock_waiter(sw, wr,Config). + +lock_waiter_sw_srw(suite) -> []; +lock_waiter_sw_srw(Config) when is_list(Config) -> + start_lock_waiter(sw, srw,Config). + +lock_waiter_wr_wt(suite) -> []; +lock_waiter_wr_wt(Config) when is_list(Config) -> + start_lock_waiter(wr, wt,Config). + +lock_waiter_srw_wt(suite) -> []; +lock_waiter_srw_wt(Config) when is_list(Config) -> + start_lock_waiter(srw, wt,Config). + +lock_waiter_wr_sw(suite) -> []; +lock_waiter_wr_sw(Config) when is_list(Config) -> + start_lock_waiter(wr, sw,Config). + +lock_waiter_srw_sw(suite) -> []; +lock_waiter_srw_sw(Config) when is_list(Config) -> + start_lock_waiter(srw, sw,Config). + +lock_waiter_wr_w(suite) -> []; +lock_waiter_wr_w(Config) when is_list(Config) -> + start_lock_waiter(wr, w,Config). + +lock_waiter_srw_w(suite) -> []; +lock_waiter_srw_w(Config) when is_list(Config) -> + start_lock_waiter(srw, w,Config). + +lock_waiter_r_sw(suite) -> []; +lock_waiter_r_sw(Config) when is_list(Config) -> + start_lock_waiter(r, sw,Config). + +lock_waiter_r_w(suite) -> []; +lock_waiter_r_w(Config) when is_list(Config) -> + start_lock_waiter(r, w,Config). + +lock_waiter_r_wt(suite) -> []; +lock_waiter_r_wt(Config) when is_list(Config) -> + start_lock_waiter(r, wt,Config). + +lock_waiter_rt_sw(suite) -> []; +lock_waiter_rt_sw(Config) when is_list(Config) -> + start_lock_waiter(rt, sw,Config). + +lock_waiter_rt_w(suite) -> []; +lock_waiter_rt_w(Config) when is_list(Config) -> + start_lock_waiter(rt, w,Config). + +lock_waiter_rt_wt(suite) -> []; +lock_waiter_rt_wt(Config) when is_list(Config) -> + start_lock_waiter(rt, wt,Config). + +lock_waiter_wt_r(suite) -> []; +lock_waiter_wt_r(Config) when is_list(Config) -> + start_lock_waiter(wt, r,Config). + +lock_waiter_wt_w(suite) -> []; +lock_waiter_wt_w(Config) when is_list(Config) -> + start_lock_waiter(wt, w,Config). + +lock_waiter_wt_rt(suite) -> []; +lock_waiter_wt_rt(Config) when is_list(Config) -> + start_lock_waiter(wt, rt,Config). + +lock_waiter_wt_wt(suite) -> []; +lock_waiter_wt_wt(Config) when is_list(Config) -> + start_lock_waiter(wt, wt,Config). + +lock_waiter_wt_wr(suite) -> []; +lock_waiter_wt_wr(Config) when is_list(Config) -> + start_lock_waiter(wt, wr,Config). + +lock_waiter_wt_srw(suite) -> []; +lock_waiter_wt_srw(Config) when is_list(Config) -> + start_lock_waiter(wt, srw,Config). + +lock_waiter_wt_sw(suite) -> []; +lock_waiter_wt_sw(Config) when is_list(Config) -> + start_lock_waiter(wt, sw,Config). + +lock_waiter_w_wr(suite) -> []; +lock_waiter_w_wr(Config) when is_list(Config) -> + start_lock_waiter(w, wr, Config). + +lock_waiter_w_srw(suite) -> []; +lock_waiter_w_srw(Config) when is_list(Config) -> + start_lock_waiter(w, srw, Config). + +lock_waiter_w_sw(suite) -> []; +lock_waiter_w_sw(Config) when is_list(Config) -> + start_lock_waiter(w, sw, Config). + +lock_waiter_w_r(suite) -> []; +lock_waiter_w_r(Config) when is_list(Config) -> + start_lock_waiter(w, r, Config). + +lock_waiter_w_w(suite) -> []; +lock_waiter_w_w(Config) when is_list(Config) -> + start_lock_waiter(w, w, Config). + +lock_waiter_w_rt(suite) -> []; +lock_waiter_w_rt(Config) when is_list(Config) -> + start_lock_waiter(w, rt, Config). + +lock_waiter_w_wt(suite) -> []; +lock_waiter_w_wt(Config) when is_list(Config) -> + start_lock_waiter(w, wt, Config). + +start_lock_waiter(BlockOpA, BlockOpB, Config) -> + [N1, N2] = Nodes = ?acquire_nodes(2, Config), + + TabName = mk_tab_name(lock_waiter_), + ?match({atomic, ok}, mnesia:create_table(TabName, + [{ram_copies, [N1, N2]}])), + + %% initialize the table with object {1, c} - when there + %% is a read transaction, the read will find that value + ?match({atomic, ok}, mnesia:sync_transaction(fun() -> mnesia:write({TabName, 1, c}) end)), + rpc:call(N2, ?MODULE, sync_tid_release, []), + + Tester = self(), + Fun_A =fun() -> + NewCounter = incr_restart_counter(), + if + NewCounter == 1 -> + Tester ! go_ahead_test, + receive go_ahead -> ok end; + true -> ok + end, + lock_waiter_fun(BlockOpA, TabName, a), + NewCounter + end, + + %% it's not possible to just spawn the transaction, because + %% the result shall be evaluated + A = spawn_link(N1, ?MODULE, perform_restarted_transaction, [Fun_A]), + + ?match(ok, receive go_ahead_test -> ok after 10000 -> timeout end), + + mnesia_test_lib:sync_trans_tid_serial([N1, N2]), + + Fun_B = fun() -> + lock_waiter_fun(BlockOpB, TabName, b), + A ! go_ahead, + wait(infinity) + end, + + B = spawn_link(N2, mnesia, transaction, [Fun_B, 100]), + + io:format("waiting for A (~p on ~p) to be in the queue ~n", [A, [N1, N2]]), + wait_for_a(A, [N1, N2]), + + io:format("Queus ~p~n", + [[{N,rpc:call(N, mnesia, system_info, [lock_queue])} || N <- Nodes]]), + + KillNode = node(B), + io:format("A was in the queue, time to kill Mnesia on B's node (~p on ~p)~n", + [B, KillNode]), + + mnesia_test_lib:kill_mnesia([KillNode]), % kill mnesia of fun B + + %% Read Ops does not need to be restarted + ExpectedCounter = + if + BlockOpA == sw, BlockOpB == w -> 1; + BlockOpA == sw, BlockOpB == wt -> 1; + BlockOpA == sw, BlockOpB == wr -> 1; + BlockOpA == srw, BlockOpB == w -> 1; + BlockOpA == srw, BlockOpB == wt -> 1; + BlockOpA == srw, BlockOpB == wr -> 1; + BlockOpA == r, BlockOpB /= sw -> 1; + BlockOpA == rt, BlockOpB /= sw -> 1; + true -> 2 + end, + ?match_multi_receive([{'EXIT', A, {atomic, ExpectedCounter}}, + {'EXIT', B, killed}]), + + %% the expected result depends on the transaction of + %% fun A - when that doesn't change the object in the + %% table (e.g. it is a read) then the predefined + %% value {Tabname, 1, c} is expected to be the result here + ExpectedResult = + case BlockOpA of + w -> {TabName, 1, a}; + sw ->{TabName, 1, a}; + _all_other -> {TabName, 1, c} + end, + + ?match({atomic, [ExpectedResult]}, + mnesia:transaction(fun() -> mnesia:read({TabName, 1}) end, 100)), + ?verify_mnesia([N1], [N2]). + +mk_tab_name(Prefix) -> + {Mega, Sec, Micro} = erlang:now(), + list_to_atom(lists:concat([Prefix , Mega, '_', Sec, '_', Micro])). + +lock_waiter_fun(Op, TabName, Val) -> + case Op of + rt -> mnesia:read_lock_table(TabName); + wt -> mnesia:write_lock_table(TabName); + r -> mnesia:read({TabName, 1}); + w -> mnesia:write({TabName, 1, Val}); + wr -> mnesia:wread({TabName, 1}); + srw -> mnesia:read(TabName, 1, sticky_write); + sw -> mnesia:s_write({TabName, 1, Val}) + end. + +wait_for_a(Pid, Nodes) -> + wait_for_a(Pid, Nodes, 5). + +wait_for_a(_P, _N, 0) -> + ?error("Timeout while waiting for lock on a~n", []); + +wait_for_a(Pid, Nodes, Count) -> + %% io:format("WAIT_FOR_A ~p ON ~w ~n", [Pid, Nodes]), + List = [rpc:call(N, mnesia, system_info, [lock_queue]) || N <- Nodes], + Q = lists:append(List), + check_q(Pid, Q, Nodes, Count). + +check_q(Pid, [{{_Oid,_Tid}, _Op, Pid, _WFT} | _Tail], _N, _Count) -> + ok; +check_q(Pid, [{_Oid, _Op, Pid, _Tid, _WFT} | _Tail], _N, _Count) -> + ok; +check_q(Pid, [_ | Tail], N, Count) -> + check_q(Pid, Tail, N, Count); +check_q(Pid, [], N, Count) -> + timer:sleep(500), + wait_for_a(Pid, N, Count - 1). + +perform_restarted_transaction (Fun_Trans) -> + %% the result of the transaction shall be: + %% - undefined (if the transaction was never executed) + %% - Times ( number of times that the transaction has been executed) + + Result = mnesia:transaction(Fun_Trans, 100), + exit(Result). + +%% Returns new val +incr_restart_counter() -> + NewCount = + case get(count_restart_of_transaction) of + undefined -> 1; + OldCount -> OldCount + 1 + end, + put(count_restart_of_transaction, NewCount), + NewCount. + +wait(Mseconds) -> + receive + after Mseconds -> ok + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +restart_check (doc) -> + [ + "test case:'A' performs a transaction on a table which", + "is only replicated on node B. During that transaction", + "mnesia on node B is killed. The transaction of A should", + "be stopped, since there is no further replica", + "rt = read_lock_table, wt = write_lock_table, r = read,", + "sw = s_write, w = write, wr = wread,"]; +restart_check(suite) -> + [ + restart_r_one, + restart_w_one, + restart_rt_one, + restart_wt_one, + restart_wr_one, + restart_sw_one, + restart_r_two, + restart_w_two, + restart_rt_two, + restart_wt_two, + restart_wr_two, + restart_sw_two + ]. + +restart_r_one(suite) -> []; +restart_r_one(Config) when is_list(Config) -> + start_restart_check(r, one, Config). + +restart_w_one(suite) -> []; +restart_w_one(Config) when is_list(Config) -> + start_restart_check(w, one, Config). + +restart_rt_one(suite) -> []; +restart_rt_one(Config) when is_list(Config) -> + start_restart_check(rt, one, Config). + +restart_wt_one(suite) -> []; +restart_wt_one(Config) when is_list(Config) -> + start_restart_check(wt, one, Config). + +restart_wr_one(suite) -> []; +restart_wr_one(Config) when is_list(Config) -> + start_restart_check(wr, one, Config). + +restart_sw_one(suite) -> []; +restart_sw_one(Config) when is_list(Config) -> + start_restart_check(sw, one, Config). + +restart_r_two(suite) -> []; +restart_r_two(Config) when is_list(Config) -> + start_restart_check(r, two, Config). + +restart_w_two(suite) -> []; +restart_w_two(Config) when is_list(Config) -> + start_restart_check(w, two, Config). + +restart_rt_two(suite) -> []; +restart_rt_two(Config) when is_list(Config) -> + start_restart_check(rt, two, Config). + +restart_wt_two(suite) -> []; +restart_wt_two(Config) when is_list(Config) -> + start_restart_check(wt, two, Config). + +restart_wr_two(suite) -> []; +restart_wr_two(Config) when is_list(Config) -> + start_restart_check(wr, two, Config). + +restart_sw_two(suite) -> []; +restart_sw_two(Config) when is_list(Config) -> + start_restart_check(sw, two, Config). + +start_restart_check(RestartOp, ReplicaNeed, Config) -> + [N1, N2, N3] = Nodes = ?acquire_nodes(3, Config), + + {TabName, _TabNodes} = create_restart_table(ReplicaNeed, Nodes), + + %% initialize the table with object {1, c} - when there + %% is a read transaction, the read will find that value + ?match({atomic, ok}, mnesia:sync_transaction(fun() -> mnesia:write({TabName, 1, c}) end)), + + %% Really sync tid_release + rpc:multicall([N2,N3], ?MODULE, sync_tid_release, []), + Coord = self(), + + Fun_A = fun() -> + NewCounter = incr_restart_counter(), + case NewCounter of + 1 -> + mnesia:write({TabName, 1, d}), + %% send a message to the test proc + Coord ! {self(),fun_a_is_blocked}, + receive go_ahead -> ok end; + _ -> + %% the fun will NOT be blocked here + restart_fun_A(RestartOp, TabName) + end, + NewCounter + end, + + A = spawn_link(N1, ?MODULE, perform_restarted_transaction, [Fun_A]), + ?match_receive({A,fun_a_is_blocked}), + + %% mnesia shall be killed at that node, where A is reading + %% the information from + kill_where_to_read(TabName, N1, [N2, N3]), + + %% wait some time to let mnesia go down and spread those news around + %% fun A shall be able to finish its job before being restarted + wait(500), + A ! go_ahead, + + %% the sticky write doesnt work on remote nodes !!! + ExpectedMsg = + case RestartOp of + sw when ReplicaNeed == two -> + {'EXIT',A,{aborted, {not_local, TabName}}}; + _all_other -> + case ReplicaNeed of + one -> + {'EXIT',A,{aborted, {no_exists, TabName}}}; + two -> + {'EXIT',A,{atomic, 2}} + end + end, + + ?match_receive(ExpectedMsg), + + %% now mnesia has to be started again on the node KillNode + %% because the next test suite will need it + ?match([], mnesia_test_lib:start_mnesia(Nodes, [TabName])), + + + %% the expected result depends on the transaction of + %% fun A - when that doesnt change the object in the + %% table (e.g. it is a read) then the predefined + %% value {Tabname, 1, c} is expected to be the result here + + ExpectedResult = + case ReplicaNeed of + one -> + []; + two -> + case RestartOp of + w -> [{TabName, 1, a}]; + _ ->[ {TabName, 1, c}] + end + end, + + ?match({atomic, ExpectedResult}, + mnesia:transaction(fun() -> mnesia:read({TabName, 1}) end,100)), + ?verify_mnesia(Nodes, []). + +create_restart_table(ReplicaNeed, [_N1, N2, N3]) -> + TabNodes = + case ReplicaNeed of + one -> [N2]; + two -> [N2, N3] + end, + TabName = mk_tab_name(restart_check_), + ?match({atomic, ok}, mnesia:create_table(TabName, [{ram_copies, TabNodes}])), + {TabName, TabNodes}. + +restart_fun_A(Op, TabName) -> + case Op of + rt -> mnesia:read_lock_table(TabName); + wt -> mnesia:write_lock_table(TabName); + r -> mnesia:read( {TabName, 1}); + w -> mnesia:write({TabName, 1, a}); + wr -> mnesia:wread({TabName, 1}); + sw -> mnesia:s_write({TabName, 1, a}) + end. + +kill_where_to_read(TabName, N1, Nodes) -> + Read = rpc:call(N1,mnesia,table_info, [TabName, where_to_read]), + case lists:member(Read, Nodes) of + true -> + mnesia_test_lib:kill_mnesia([Read]); + false -> + ?error("Fault while killing Mnesia: ~p~n", [Read]), + mnesia_test_lib:kill_mnesia(Nodes) + end. + +sync_tid_release() -> + sys:get_status(whereis(mnesia_tm)), + sys:get_status(whereis(mnesia_locker)), + ok. + diff --git a/lib/mnesia/test/mnesia_config_backup.erl b/lib/mnesia/test/mnesia_config_backup.erl new file mode 100644 index 0000000000..a33ec6ac5c --- /dev/null +++ b/lib/mnesia/test/mnesia_config_backup.erl @@ -0,0 +1,105 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_config_backup). +-author('[email protected]'). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% +%% This module is used for testing the backup module config parameter. +%% +%% This module is an impostor for the mnesia_backup module. +%% +%% +%% Original doc below: +%% +%% This module contains one implementation of callback functions +%% used by Mnesia at backup and restore. The user may however +%% write an own module the same interface as mnesia_backup and +%% configure Mnesia so the alternate module performs the actual +%% accesses to the backup media. This means that the user may put +%% the backup on medias that Mnesia does not know about, possibly +%% on hosts where Erlang is not running. +%% +%% The OpaqueData argument is never interpreted by other parts of +%% Mnesia. It is the property of this module. Alternate implementations +%% of this module may have different interpretations of OpaqueData. +%% The OpaqueData argument given to open_write/1 and open_read/1 +%% are forwarded directly from the user. +%% +%% All functions must return {ok, NewOpaqueData} or {error, Reason}. +%% +%% The NewOpaqueData arguments returned by backup callback functions will +%% be given as input when the next backup callback function is invoked. +%% If any return value does not match {ok, _} the backup will be aborted. +%% +%% The NewOpaqueData arguments returned by restore callback functions will +%% be given as input when the next restore callback function is invoked +%% If any return value does not match {ok, _} the restore will be aborted. +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-export([ + open_write/1, write/2, commit_write/1, abort_write/1, + open_read/1, read/1, close_read/1 + ]). + +-record(backup, {name, mode, items}). + +open_write(Name) -> + file:delete(Name), + {ok, #backup{name = Name, mode = write, items = []}}. + +write(Opaque, Item) when Opaque#backup.mode == write -> + %% Build the list in reverse order + {ok, Opaque#backup{items = [Item | Opaque#backup.items]}}. + +commit_write(Opaque) when Opaque#backup.mode == write -> + Bin = term_to_binary(Opaque#backup.items), + case file:write_file(Opaque#backup.name, Bin) of + ok -> + {ok, Opaque#backup{mode = closed, items = []}}; + {error, Reason} -> + {error, {commit_write, Reason}} + end. + +abort_write(Opaque) -> + {ok, Opaque#backup{mode = closed, items = []}}. + +open_read(Name) -> + case file:read_file(Name) of + {ok, Bin} -> + ReverseList = binary_to_term(Bin), + List = lists:reverse(ReverseList), + {ok, #backup{name = Name, mode = read, items = List}}; + {error, Reason} -> + {error, {open_read, Reason}} + end. + +read(Opaque) when Opaque#backup.mode == read -> + case Opaque#backup.items of + [Head | Tail] -> + {ok, Opaque#backup{items = Tail}, Head}; + [] -> + {ok, Opaque#backup{mode = closed}, []} + end. + +close_read(Opaque) -> + {ok, Opaque#backup{mode = closed, items = []}}. diff --git a/lib/mnesia/test/mnesia_config_event.erl b/lib/mnesia/test/mnesia_config_event.erl new file mode 100644 index 0000000000..6c1dea7ed5 --- /dev/null +++ b/lib/mnesia/test/mnesia_config_event.erl @@ -0,0 +1,74 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_config_event). +-author('[email protected]'). + +-behaviour(gen_event). + +%% +%% This module was stolen from Mnesia +%% + + +%% gen_event callback interface +-export([init/1, handle_event/2, handle_call/2, handle_info/2, + terminate/2, code_change/3]). + + +init(_Args) -> + {ok, []}. + +handle_event(Msg, State) -> + handle_any_event(Msg, State). + +handle_info(Msg, State) -> + handle_any_event(Msg, State). + + +handle_call(Msg, State) -> + handle_any_event(Msg, State). + + +%% The main... + +handle_any_event({get_log, Pid}, State) -> + Pid ! {log, State}, + {ok, State}; +handle_any_event(Msg, State) -> + io:format("Got event: ~p~n", [Msg]), + {ok, [Msg | State]}. + +%%----------------------------------------------------------------- +%% terminate(Reason, State) -> +%% AnyVal +%%----------------------------------------------------------------- + +terminate(_Reason, _State) -> + ok. + +%%---------------------------------------------------------------------- +%% Func: code_change/3 +%% Purpose: Upgrade process when its code is to be changed +%% Returns: {ok, NewState} +%%---------------------------------------------------------------------- +code_change(_OldVsn, _State, _Extra) -> + exit(not_supported). + diff --git a/lib/mnesia/test/mnesia_config_test.erl b/lib/mnesia/test/mnesia_config_test.erl new file mode 100644 index 0000000000..7b62c63a62 --- /dev/null +++ b/lib/mnesia/test/mnesia_config_test.erl @@ -0,0 +1,1466 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_config_test). +-author('[email protected]'). + +-include("mnesia_test_lib.hrl"). + +-record(test_table,{i,a1,a2,a3}). +-record(test_table2,{i, b}). + +-export([ + all/1, + access_module/1, + auto_repair/1, + backup_module/1, + debug/1, + dir/1, + dump_log_load_regulation/1, + dump_log_thresholds/1, + dump_log_update_in_place/1, + embedded_mnemosyne/1, + event_module/1, + ignore_fallback_at_startup/1, + inconsistent_database/1, + max_wait_for_decision/1, + send_compressed/1, + + app_test/1, + schema_config/1, + schema_merge/1, + unknown_config/1, + + dump_log_time_threshold/1, + dump_log_write_threshold/1, + + start_one_disc_full_then_one_disc_less/1, + start_first_one_disc_less_then_one_disc_full/1, + start_first_one_disc_less_then_two_more_disc_less/1, + schema_location_and_extra_db_nodes_combinations/1, + table_load_to_disc_less_nodes/1, + dynamic_connect/1, + dynamic_basic/1, + dynamic_ext/1, + dynamic_bad/1, + + init_per_testcase/2, + fin_per_testcase/2, + c_nodes/0 + ]). + +-export([check_logs/1]). + +-define(init(N, Config), + mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}, + delete_schema, + {reload_appls, [mnesia]}], + N, Config, ?FILE, ?LINE)). +-define(acquire(N, Config), + mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}, + delete_schema, + {reload_appls, [mnesia]}, + create_schema, + {start_appls, [mnesia]}], + N, Config, ?FILE, ?LINE)). +-define(acquire_schema(N, Config), + mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}, + delete_schema, + {reload_appls, [mnesia]}, + create_schema], + N, Config, ?FILE, ?LINE)). +-define(cleanup(N, Config), + mnesia_test_lib:prepare_test_case([{reload_appls, [mnesia]}], + N, Config, ?FILE, ?LINE)). +-define(trans(Fun), + ?match({atomic, ok}, mnesia:transaction(Fun))). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +all(doc) -> + [ + "Test all configuration parameters", + "Perform an exhaustive test of all the various parameters that", + "may be used to configure the Mnesia application.", + "", + "Hint: Check out the unofficial function mnesia:start/1.", + " But be careful to cleanup all configuration parameters", + " afterwards since the rest of the test suite may rely on", + " these default configurations. Perhaps it is best to run", + " these tests in a separate node which is dropped afterwards.", + "Are really all configuration parameters covered?"]; + +all(suite) -> + [ + access_module, + auto_repair, + backup_module, + debug, + dir, + dump_log_load_regulation, + dump_log_thresholds, + dump_log_update_in_place, + embedded_mnemosyne, + event_module, + ignore_fallback_at_startup, + inconsistent_database, + max_wait_for_decision, + send_compressed, + + app_test, + schema_config, + unknown_config + ]. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +access_module(doc) -> + ["Replace the activity access module with another module and ", + "use it to read and write to some alternate table storage"]; +access_module(suite) -> []; +access_module(Config) when is_list(Config) -> + Nodes = ?acquire_schema(1, Config), + ?match(ok, mnesia:start([{access_module, mnesia_frag}])), + + ?match(mnesia_frag, mnesia:system_info(access_module)), + + access_tab(ram_copies, Nodes), + case mnesia_test_lib:diskless(Config) of + true -> skip; + false -> + access_tab(disc_copies, Nodes) + , access_tab(disc_only_copies, Nodes) + end, + + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config). + +access_tab(Storage, Nodes) -> + Tab = list_to_atom(lists:concat([access_tab_, Storage])), + RecName = some_access, + Attr = val, + TabDef = [{Storage, Nodes}, + {type, bag}, + {index, [Attr]}, + {record_name, RecName}], + ?match({atomic,ok}, mnesia:create_table(Tab, TabDef)), + + Activity = fun(Kind) -> + A = [Kind, Tab, RecName, Attr, Nodes], + io:format("kind: ~w, storage: ~w~n", [Kind, Storage]), + mnesia:activity(Kind, fun do_access/5, A) + end, + ModActivity = fun(Kind, M) -> + io:format("kind: ~w, storage: ~w. module: ~w~n", + [Kind, Storage, M]), + A = [Kind, Tab, RecName, Attr, Nodes], + mnesia:activity(Kind, fun do_access/5, A, M) + end, + ?match(ok, Activity(transaction)), + ?match(ok, Activity({transaction, 47})), + ?match(ok, ModActivity(transaction, mnesia)), + ?match(ok, ModActivity(transaction, mnesia_frag)), + + ?match(ok, Activity(async_dirty)), + ?match(ok, Activity(sync_dirty)), + case Storage of + ram_copies -> + ?match(ok, Activity(ets)); + _ -> + ignore + end. + +do_access(Kind, Tab, RecName, Attr, Nodes) -> + Tens = lists:sort([{RecName, 1, 10}, {RecName, 3, 10}]), + {OptNodes, OptTens} = + case Kind of + transaction -> {Nodes, Tens}; + {transaction, _} -> {Nodes, Tens}; + async_dirty -> {[], Tens}; + sync_dirty -> {[], Tens}; + ets -> {[], []} + end, + ?match(RecName, mnesia:table_info(Tab, record_name)), + + ?match(ok, mnesia:write(Tab, {RecName, 1, 10}, write)), + ?match(ok, mnesia:write(Tab, {RecName, 2, 20}, sticky_write)), + ?match(ok, mnesia:write(Tab, {RecName, 2, 21}, sticky_write)), + ?match(ok, mnesia:write(Tab, {RecName, 2, 22}, write)), + ?match(ok, mnesia:write(Tab, {RecName, 3, 10}, write)), + + Twos = [{RecName, 2, 20}, {RecName, 2, 21}, {RecName, 2, 22}], + ?match(Twos, lists:sort(mnesia:read(Tab, 2, read))), + + ?match(ok, mnesia:delete_object(Tab, {RecName, 2, 21}, sticky_write)), + + TenPat = {RecName, '_', 10}, + ?match(Tens, lists:sort(mnesia:match_object(Tab, TenPat, read))), + ?match(OptTens, lists:sort(mnesia:index_match_object(Tab, TenPat, Attr, read) )), + ?match(OptTens, lists:sort(mnesia:index_read(Tab, 10, Attr))), + Keys = [1, 2, 3], + ?match(Keys, lists:sort(mnesia:all_keys(Tab))), + + First = mnesia:first(Tab), + Mid = mnesia:next(Tab, First), + Last = mnesia:next(Tab, Mid), + ?match('$end_of_table', mnesia:next(Tab, Last)), + ?match(Keys, lists:sort([First,Mid,Last])), + + %% For set and bag these last, prev works as first and next + First2 = mnesia:last(Tab), + Mid2 = mnesia:prev(Tab, First2), + Last2 = mnesia:prev(Tab, Mid2), + ?match('$end_of_table', mnesia:prev(Tab, Last2)), + ?match(Keys, lists:sort([First2,Mid2,Last2])), + + ?match([ok, ok, ok], [mnesia:delete(Tab, K, write) || K <- Keys]), + W = wild_pattern, + ?match([], mnesia:match_object(Tab, mnesia:table_info(Tab, W), read)), + ?log("Safe fixed ~p~n", [catch ets:info(Tab, safe_fixed)]), + ?log("Fixed ~p ~n", [catch ets:info(Tab, fixed)]), + + ?match(OptNodes, mnesia:lock({global, some_lock_item, Nodes}, write)), + ?match(OptNodes, mnesia:lock({global, some_lock_item, Nodes}, read)), + ?match(OptNodes, mnesia:lock({table, Tab}, read)), + ?match(OptNodes, mnesia:lock({table, Tab}, write)), + + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +auto_repair(doc) -> + ["Try the auto_repair mechanism on the various disk_logs and dets files.", + "", + "The case tests both normal values of the parameter, and also", + "one crazy value.", + "The test of the real auto_repair functionality is made in the", + "dets suite" + ]; +auto_repair(suite) -> []; +auto_repair(Config) when is_list(Config) -> + ?init(1, Config), + ?match(ok, mnesia:start()), % Check default true + ?match(true, mnesia:system_info(auto_repair)), + ?match(stopped, mnesia:stop()), + ?match(ok, mnesia:start([{auto_repair, true}])), + ?match(true, mnesia:system_info(auto_repair)), + ?match(stopped, mnesia:stop()), + ?match(ok, mnesia:start([{auto_repair, false}])), + ?match(false, mnesia:system_info(auto_repair)), + ?match(stopped, mnesia:stop()), + ?match({error, {bad_type, auto_repair, your_mama}}, + mnesia:start([{auto_repair, your_mama}])), + ?match(stopped, mnesia:stop()), + ?cleanup(1, Config), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +backup_module(doc) -> + ["Replace the backup module with another module and use it to", + "read and write to an alternate backup media, e.g stored in", + "the internal state of a simple process."]; +backup_module(suite) -> []; +backup_module(Config) when is_list(Config) -> + Nodes = ?acquire_schema(1, Config), + ?match(ok, mnesia:start([{backup_module, mnesia_config_backup}])), + ?match({atomic,ok}, + mnesia:create_table(test_table, + [{disc_copies, Nodes}, + {attributes, + record_info(fields,test_table)}])), + + ?match({atomic,ok}, + mnesia:create_table(test_table2, + [{disc_copies, Nodes}, + {attributes, + record_info(fields,test_table2)}])), + %% Write in test table + ?trans(fun() -> mnesia:write(#test_table{i=1}) end), + ?trans(fun() -> mnesia:write(#test_table{i=2}) end), + + %% Write in test table 2 + ?trans(fun() -> mnesia:write(#test_table2{i=3}) end), + ?trans(fun() -> mnesia:write(#test_table2{i=4}) end), + mnesia_test_lib:sync_tables(Nodes, [test_table, test_table2]), + + File = whow, + %% Now make a backup + ?match(ok, mnesia:backup(File)), + + ?match(ok, mnesia:install_fallback(File)), + + %% Now add things + ?trans(fun() -> mnesia:write(#test_table{i=2.5}) end), + ?trans(fun() -> mnesia:write(#test_table2{i=3.5}) end), + + mnesia_test_lib:kill_mnesia(Nodes), + receive after 2000 -> ok end, + ?match([], mnesia_test_lib:start_mnesia(Nodes, [test_table, test_table2])), + + %% Now check newly started tables + ?match({atomic, [1,2]}, + mnesia:transaction(fun() -> lists:sort(mnesia:all_keys(test_table)) end)), + ?match({atomic, [3,4]}, + mnesia:transaction(fun() -> lists:sort(mnesia:all_keys(test_table2)) end)), + + file:delete(File), + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +debug(doc) -> + ["Try out the four debug levels and ensure that the", + "expected events are generated."]; +debug(suite) -> []; +debug(Config) when is_list(Config) -> + Nodes = ?init(1, Config), + case application:get_env(mnesia,debug) of + undefined -> + ?match(none, mnesia:system_info(debug)); + {ok, false} -> + ?match(none, mnesia:system_info(debug)); + {ok, true} -> + ?match(debug, mnesia:system_info(debug)); + {ok, Env} -> + ?match(Env, mnesia:system_info(debug)) + end, + + ?match(ok, mnesia:start([{debug, verbose}])), + ?match(verbose, mnesia:system_info(debug)), + mnesia_test_lib:kill_mnesia(Nodes), + receive after 2000 -> ok end, + + ?match(ok, mnesia:start([{debug, debug}])), + ?match(debug, mnesia:system_info(debug)), + mnesia_test_lib:kill_mnesia(Nodes), + receive after 2000 -> ok end, + + ?match(ok, mnesia:start([{debug, trace}])), + ?match(trace, mnesia:system_info(debug)), + mnesia_test_lib:kill_mnesia(Nodes), + receive after 2000 -> ok end, + + ?match(ok, mnesia:start([{debug, true}])), + ?match(debug, mnesia:system_info(debug)), + mnesia_test_lib:kill_mnesia(Nodes), + receive after 2000 -> ok end, + + ?match(ok, mnesia:start([{debug, false}])), + ?match(none, mnesia:system_info(debug)), + + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +dir(doc) -> + ["Try to use alternate Mnesia directories"]; +dir(suite) -> []; +dir(Config) when is_list(Config) -> + Nodes = ?init(1, Config), + + ?match(ok, mnesia:start([{dir, tuff}])), + Dir = filename:join([element(2, file:get_cwd()), "tuff"]), + ?match(Dir, mnesia:system_info(directory)), + mnesia_test_lib:kill_mnesia(Nodes), + + ?cleanup(1, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +dump_log_update_in_place(doc) -> + ["Change the update in place policy for the transaction log dumper."]; +dump_log_update_in_place(suite) -> []; +dump_log_update_in_place(Config) when is_list(Config) -> + Nodes = ?acquire(1, Config), + ?match(true, mnesia:system_info(dump_log_update_in_place)), + ?match({atomic,ok}, + mnesia:create_table(test_table, + [{disc_copies, Nodes}, + {attributes, + record_info(fields,test_table)}])), + + mnesia_test_lib:kill_mnesia(Nodes), + receive after 2000 -> ok end, + + ?match(ok, mnesia:start([{dump_log_update_in_place, false}])), + ?match(false, mnesia:system_info(dump_log_update_in_place)), + + mnesia_test_lib:sync_tables(Nodes, [schema, test_table]), + + %% Now provoke some log dumps + + L = lists:map( + fun(Num) -> + %% Write something on one end ... + mnesia:transaction( + fun() -> + mnesia:write(#test_table{i=Num}) end + ) end, + lists:seq(1, 110)), + + L2 = lists:duplicate(110, {atomic, ok}), + + %% If this fails then some of the 110 writes above failed + ?match(true, L==L2), + if L==L2 -> ok; + true -> + ?verbose("***** List1 len: ~p, List2 len: ~p~n", + [length(L), length(L2)]), + ?verbose("L: ~p~nL2:~p~n", [L, L2]) + end, + + %% If we still can write, then Mnesia is probably alive + ?trans(fun() -> mnesia:write(#test_table{i=115}) end), + + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +dump_log_thresholds(doc) -> + ["Elaborate with various values of the dump log thresholds and how", + "they affects each others. Both the dump_log_time_threshold and the", + "dump_log_write_threshold must be covered. Do also check that both", + "kinds of overload events are generated as expected.", + "", + "Logs are checked by first doing whatever has to be done to trigger ", + "a dump, and then stopping Mnesia and then look in the ", + "data files and see that the correct amount of transactions ", + "have been done."]; +dump_log_thresholds(suite) -> + [ + dump_log_time_threshold, + dump_log_write_threshold + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +dump_log_write_threshold(doc)-> + ["This test case must be rewritten.", + "Dump logs are tested by doing transactions, then killing Mnesia and ", + "then examining the table data files and see if they are correct.", + "The test_table is used as a counter, test_table. is stepped once ", + "for each transaction."]; +dump_log_write_threshold(suite)->[]; +dump_log_write_threshold(Config) when is_list(Config) -> + [N1] = ?acquire_schema(1, Config), + + Threshold = 3, + ?match(ok,mnesia:start([{dump_log_write_threshold, Threshold}])), + + ?match({atomic,ok}, + mnesia:create_table(test_table, + [{disc_copies, [N1]}, + {attributes, + record_info(fields,test_table)}])), + ?match(dumped, mnesia:dump_log()), + + ?match(ok, do_trans(2)), % Shall not have dumped + check_logs(0), + + ?match(ok, do_trans(Threshold - 2)), % Trigger a dump + receive after 1000 -> ok end, + check_logs(Threshold), + + + ?match(ok, do_trans(Threshold - 1)), + ?match(dumped, mnesia:dump_log()), %% This should trigger ets2dcd dump + check_logs(0), %% and leave no dcl file + + ?match(stopped, mnesia:stop()), + + %% Check bad threshold value + ?match({error,{bad_type,dump_log_write_threshold,0}}, + mnesia:start([{dump_log_write_threshold,0}])), + + ?verify_mnesia([], [N1]), + ?cleanup(1, Config), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +dump_log_time_threshold(doc)-> + ["See doc on above."]; +dump_log_time_threshold(suite)->[]; +dump_log_time_threshold(Config) when is_list(Config) -> + Nodes = ?acquire_schema(1, Config), + Time = 4000, + + %% Check bad threshold value + ?match({error,{bad_type,dump_log_time_threshold,0}}, + mnesia:start([{dump_log_time_threshold,0}])), + + + ?match(ok,mnesia:start([{dump_log_write_threshold,100}, + {dump_log_time_threshold, Time}])), + + ?match({atomic,ok},mnesia:create_table(test_table, + [{disc_copies, Nodes}, + {attributes, + record_info(fields, + test_table)}])), + + %% Check that nothing is dumped when within time threshold + ?match(ok, do_trans(1)), + check_logs(0), + + ?match(Time, mnesia:system_info(dump_log_time_threshold)), + + %% Check that things get dumped when time threshold exceeded + ?match(ok, do_trans(5)), + receive after Time+2000 -> ok end, + check_logs(6), + + ?verify_mnesia([node()], []), + ?cleanup(1, Config), + ok. + +%%%%%%%% +%% +%% Help functions for dump log + +%% Do a transaction N times +do_trans(0) -> ok; +do_trans(N) -> + Fun = fun() -> + XX=incr(), + mnesia:write(#test_table{i=XX}) + end, + {atomic, ok} = mnesia:transaction(Fun), + do_trans(N-1). + +%% An increasing number +incr() -> + case get(bloody_counter) of + undefined -> put(bloody_counter, 2), 1; + Num -> put(bloody_counter, Num+1) + end. + +%% +%% Check that the correct number of transactions have been recorded. +%%-record(test_table,{i,a1,a2,a3}). +check_logs(N) -> + File = mnesia_lib:tab2dcl(test_table), + Args = [{file, File}, {name, testing}, {repair, true}, {mode, read_only}], + + if N == 0 -> + ?match(false, mnesia_lib:exists(File)); + true -> + ?match(true, mnesia_lib:exists(File)), + ?match({ok, _Log}, disk_log:open(Args)), + + {Cont, Terms} = disk_log:chunk(testing, start), + ?match(eof, disk_log:chunk(testing, Cont)), + %%?verbose("N: ~p, L: ~p~n", [N, L]), + disk_log:close(testing), + + %% Correct number of records in file + ?match({N, N}, {N, length(Terms) -1 }) %% Ignore Header + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +dump_log_load_regulation(doc) -> + ["Test the load regulation of the dumper"]; +dump_log_load_regulation(suite) -> + []; +dump_log_load_regulation(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + Param = dump_log_load_regulation, + + %% Normal + NoReg = false, + ?match(NoReg, mnesia:system_info(Param)), + ?match([], mnesia_test_lib:stop_mnesia(Nodes)), + + %% Bad + Bad = arne_anka, + ?match({error, {bad_type, Param, Bad}}, + mnesia:start([{Param, Bad}])), + + %% Regulation activated + Reg = true, + ?match(ok,mnesia:start([{Param, Reg}])), + ?match(Reg, mnesia:system_info(Param)), + + Args = + [{db_nodes, Nodes}, + {driver_nodes, Nodes}, + {replica_nodes, Nodes}, + {n_drivers_per_node, 5}, + {n_branches, length(Nodes) * 10}, + {n_accounts_per_branch, 5}, + {replica_type, disc_copies}, + {stop_after, timer:seconds(30)}, + {report_interval, timer:seconds(10)}, + {use_running_mnesia, true}, + {reuse_history_id, true}], + + ?match({ok, _}, mnesia_tpcb:start(Args)), + + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +embedded_mnemosyne(doc) -> + ["Start Mnemosyne as an embedded part of Mnesia", + "on some of the nodes"]; +embedded_mnemosyne(suite) -> + []; +embedded_mnemosyne(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + Param = embedded_mnemosyne, + + %% Normal + NoMnem = false, + ?match(NoMnem, mnesia:system_info(Param)), + ?match(undefined, whereis(mnemosyne_catalog)), + ?match([], mnesia_test_lib:stop_mnesia(Nodes)), + + %% Bad + Bad = arne_anka, + ?match({error, {bad_type, Param, Bad}}, + mnesia:start([{Param, Bad}])), + + case code:priv_dir(mnemosyne) of + {error, _} -> %% No mnemosyne on later systems + ok; + _ -> + %% Mnemosyne as embedded application + Mnem = true, + ?match(undefined, whereis(mnemosyne_catalog)), + ?match(ok,mnesia:start([{Param, Mnem}])), + ?match(Mnem, mnesia:system_info(Param)), + ?match(Pid when is_pid(Pid), whereis(mnemosyne_catalog)), + ?match([], mnesia_test_lib:stop_mnesia(Nodes)), + ?match(undefined, whereis(mnemosyne_catalog)) + end, + ?verify_mnesia([], Nodes), + ?cleanup(1, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +ignore_fallback_at_startup(doc) -> + ["Start Mnesia without rollback of the database to the fallback. ", + "Once Mnesia has been (re)started the installed fallback should", + "be handled as a normal active fallback.", + "Install a customized event module which disables the termination", + "of Mnesia when mnesia_down occurrs with an active fallback."]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +max_wait_for_decision(doc) -> + ["Provoke Mnesia to make a forced decision of the outome", + "of a heavy weight transaction."]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +send_compressed(doc) -> []; +send_compressed(suite) -> []; +send_compressed(Config) -> + [N1,N2] = Nodes = ?acquire_nodes(2, Config), + ?match({atomic,ok}, mnesia:create_table(t0, [{ram_copies,[N1,N2]}])), + ?match({atomic,ok}, mnesia:create_table(t1, [{disc_copies,[N1,N2]}])), + ?match({atomic,ok}, mnesia:create_table(t2, [{disc_only_copies,[N1,N2]}])), + + Max = 1000, + Create = fun(Tab) -> [mnesia:write({Tab, N, {N, "FILLER-123490878345asdasd"}}) + || N <- lists:seq(1, Max)], + ok + end, + + ?match([], mnesia_test_lib:kill_mnesia([N2])), + + ?match([], mnesia_test_lib:kill_mnesia([N1])), + ?match(ok, mnesia:start([{send_compressed, 9}])), + ?match(ok, mnesia:wait_for_tables([t0,t1,t2], 5000)), + + ?match({atomic, ok}, mnesia:transaction(Create, [t0])), + ?match({atomic, ok}, mnesia:transaction(Create, [t1])), + ?match({atomic, ok}, mnesia:transaction(Create, [t2])), + + ?match([], mnesia_test_lib:start_mnesia([N2], [t0,t1,t2])), + + Verify = fun(Tab) -> + [ [{Tab,N,{N,_}}] = mnesia:read(Tab, N) || N <- lists:seq(1, Max)], + ok + end, + ?match({atomic, ok}, rpc:call(N1, mnesia, transaction, [Verify, [t0]])), + ?match({atomic, ok}, rpc:call(N1, mnesia, transaction, [Verify, [t1]])), + ?match({atomic, ok}, rpc:call(N1, mnesia, transaction, [Verify, [t2]])), + + ?match({atomic, ok}, rpc:call(N2, mnesia, transaction, [Verify, [t0]])), + ?match({atomic, ok}, rpc:call(N2, mnesia, transaction, [Verify, [t1]])), + ?match({atomic, ok}, rpc:call(N2, mnesia, transaction, [Verify, [t2]])), + + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config), + ok. + +app_test(doc) -> []; +app_test(suite) -> []; +app_test(_Config) -> + ?match(ok,test_server:app_test(mnesia)), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +event_module(doc) -> + ["Replace the event module with another module and use it as", + "receiver of the various system and table events. Provoke", + "coverage of all kinds of events."]; +event_module(suite) -> []; +event_module(Config) when is_list(Config) -> + Filter = fun({mnesia_system_event,{mnesia_info, _, _}}) -> false; + (_) -> true + end, + + [_N1, N2]=Nodes=?acquire_schema(2, Config), + + Def = case mnesia_test_lib:diskless(Config) of + true -> [{event_module, mnesia_config_event}, + {extra_db_nodes, Nodes}]; + false -> + [{event_module, mnesia_config_event}] + end, + + ?match({[ok, ok], []}, rpc:multicall(Nodes, mnesia, start, [Def])), + receive after 1000 -> ok end, + mnesia_event ! {get_log, self()}, + DebugLog1 = receive + {log, L1} -> L1 + after 10000 -> [timeout] + end, + ?match([{mnesia_system_event,{mnesia_up,N2}}], + lists:filter(Filter, DebugLog1)), + mnesia_test_lib:kill_mnesia([N2]), + receive after 2000 -> ok end, + + ?match({[ok], []}, rpc:multicall([N2], mnesia, start, [])), + + receive after 1000 -> ok end, + mnesia_event ! {get_log, self()}, + DebugLog = receive + {log, L} -> L + after 10000 -> [timeout] + end, + ?match([{mnesia_system_event,{mnesia_up,N2}}, + {mnesia_system_event,{mnesia_down,N2}}, + {mnesia_system_event,{mnesia_up, N2}}], + lists:filter(Filter, DebugLog)), + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +schema_config(doc) -> + ["Try many configurations with various schema_location's with and", + "without explicit extra_db_nodes. Do also provoke various schema merge", + "situations. Most of the other test suites focusses on tests where the", + "schema is residing on disc. Now it is time to perform an exhaustive", + "elaboration with various disc less configurations."]; +schema_config(suite) -> + [ + start_one_disc_full_then_one_disc_less, + start_first_one_disc_less_then_one_disc_full, + start_first_one_disc_less_then_two_more_disc_less, + schema_location_and_extra_db_nodes_combinations, + table_load_to_disc_less_nodes, + schema_merge, + dynamic_connect + ]. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +start_one_disc_full_then_one_disc_less(doc)-> + ["Start a disk node and then a disk less one. Distribute some", + "tables between them."]; +start_one_disc_full_then_one_disc_less(suite) -> []; +start_one_disc_full_then_one_disc_less(Config) when is_list(Config) -> + [N1, N2] = ?init(2, Config), + ?match(ok, mnesia:create_schema([N1])), + ?match([], mnesia_test_lib:start_mnesia([N1])), + + ?match({atomic, ok}, mnesia:add_table_copy(schema, N2, ram_copies)), + + ?match(ok, rpc:call(N2, mnesia, start, [[{schema_location, ram}, + {extra_db_nodes, [N1]}]])), + mnesia_test_lib:sync_tables([N1, N2], [schema]), + + %% Now create some tables + ?match({atomic,ok}, + mnesia:create_table(test_table, + [{ram_copies, [N1, N2]}, + {attributes, + record_info(fields,test_table)}])), + + ?match({atomic,ok}, + rpc:call( + N2, mnesia,create_table, [test_table2, + [{ram_copies, [N1, N2]}, + {attributes, + record_info(fields,test_table2)}]])), + + %% Write something on one end ... + Rec = #test_table{i=55}, + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec) end)), + + %% ... and read it in the other + ?match({atomic, [Rec]}, + rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:read({test_table, 55}) end])), + + + %% Then do the same but start at the other end + Rec2 = #test_table2{i=155}, + ?match({atomic, ok}, + rpc:call(N2, mnesia, transaction, + [fun() -> + mnesia:write(Rec2) end + ])), + + ?match({atomic, [Rec2]}, + mnesia:transaction(fun() -> mnesia:read({test_table2, 155}) end)), + + ?verify_mnesia([N1, N2], []), + ?cleanup(2, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +start_first_one_disc_less_then_one_disc_full(doc)-> + ["no_doc"]; +start_first_one_disc_less_then_one_disc_full(suite) -> []; +start_first_one_disc_less_then_one_disc_full(Config) when is_list(Config) -> + [N1, N2] = Nodes = ?init(2, Config), + ?match(ok, mnesia:create_schema([N1])), + ?match([], mnesia_test_lib:start_mnesia([N1])), + + ?match({atomic, ok}, mnesia:add_table_copy(schema, N2, ram_copies)), + + ?match(ok, rpc:call(N2, mnesia, start, [[{schema_location, ram}, + {extra_db_nodes, Nodes}]])), + + mnesia_test_lib:sync_tables([N1, N2], [schema]), + + mnesia_test_lib:kill_mnesia(Nodes), + receive after 2000 -> ok end, + ?match([], mnesia_test_lib:start_mnesia(Nodes)), + + mnesia_test_lib:sync_tables([N1, N2], [schema]), + + %% Now create some tables + ?match({atomic,ok}, + rpc:call( + N1, mnesia,create_table, [test_table, + [%%{disc_copies, [node()]}, + {ram_copies, [N1, N2]}, + {attributes, + record_info(fields,test_table)}]])), + mnesia_test_lib:sync_tables([N1, N2], [test_table]), + + ?match({atomic,ok}, + rpc:call( + N2, mnesia,create_table, [test_table2, + [%%{disc_copies, [node()]}, + {ram_copies, [N1, N2]}, + {attributes, + record_info(fields,test_table2)}]])), + + mnesia_test_lib:sync_tables([N1, N2], [test_table, test_table2]), + + %% Assure tables loaded + ?match({[ok, ok], []}, + rpc:multicall([N1, N2], mnesia, wait_for_tables, + [[schema, test_table, test_table2], 10000])), + + %% Write something on one end ... + Rec = #test_table{i=55}, + ?match({atomic, ok}, + rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:write(Rec) end])), + + %% ... and read it in the other + ?match({atomic, [Rec]}, + rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:read({test_table, 55}) end])), + + %% Then do the same but start at the other end + Rec2 = #test_table2{i=155}, + ?match({atomic, ok}, + rpc:call(N2, mnesia, transaction, + [fun() -> + mnesia:write(Rec2) end + ])), + + ?match({atomic, [Rec2]}, + rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:read({test_table2, 155}) end])), + + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +start_first_one_disc_less_then_two_more_disc_less(doc)-> + ["no doc"]; +start_first_one_disc_less_then_two_more_disc_less(suite) -> []; +start_first_one_disc_less_then_two_more_disc_less(Config) when is_list(Config) -> + Nodes = [N1, N2, N3] = ?init(3, Config), + + ?match(ok, rpc:call(N1, mnesia, start, [[{schema_location, ram}]])), + + %% Really should use test_lib:mnesia_start for these ones but ... + ?match({atomic, ok}, + rpc:call(N1, mnesia,add_table_copy, [schema, N2, ram_copies])), + ?match({atomic, ok}, + rpc:call(N1, mnesia,add_table_copy, [schema, N3, ram_copies])), + + ?match(ok, rpc:call(N2, mnesia, start, [[{schema_location, ram}, + {extra_db_nodes, [N1]}]])), + ?match(ok, rpc:call(N3, mnesia, start, [[{schema_location, ram}, + {extra_db_nodes, [N1, N2]}]])), + + %% Now create some tables + ?match({atomic,ok}, + rpc:call( + N1, mnesia,create_table, [test_table, + [%%{disc_copies, [node()]}, + {ram_copies, [N1, N2, N3]}, + {attributes, + record_info(fields,test_table)}]])), + + %% Assure tables loaded + ?match({[ok, ok, ok], []}, + rpc:multicall([N1, N2, N3], mnesia, wait_for_tables, + [[test_table], 1000])), + + %% Write something on one end ... + ?match({atomic, ok}, + rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:write(#test_table{i=44}) end])), + + %% Force synchronicity + ?match({atomic, ok}, + rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:write_lock_table(test_table) end])), + + %% ... and read it in the others + ?match({[{atomic, [{test_table, 44, _, _, _}]}, + {atomic, [{test_table, 44, _, _, _}]}], []}, + rpc:multicall([N2, N3], mnesia, transaction, + [fun() -> mnesia:read({test_table, 44}) end])), + + %% Then do the other way around + ?match({atomic, ok}, + rpc:call(N3, mnesia, transaction, + [fun() -> mnesia:write(#test_table{i=33}) end])), + %% Force synchronicity + ?match({atomic, ok}, + rpc:call(N3, mnesia, transaction, + [fun() -> mnesia:write_lock_table(test_table) end])), + + ?match({[{atomic, [{test_table, 44, _, _, _}]}, + {atomic, [{test_table, 44, _, _, _}]}], []}, + rpc:multicall([N1, N2], mnesia, transaction, + [fun() -> mnesia:read({test_table, 44}) end])), + + mnesia_test_lib:reload_appls([mnesia], Nodes), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +schema_location_and_extra_db_nodes_combinations(doc)-> + ["Test schema loaction and extra_db_nodes combinations."]; +schema_location_and_extra_db_nodes_combinations(suite) -> []; +schema_location_and_extra_db_nodes_combinations(Config) when is_list(Config) -> + [N1, N2] = Nodes = ?init(2, Config), + ?match(ok, mnesia:create_schema([N1])), + ?match([], mnesia_test_lib:start_mnesia([N1])), + + %% Really should use test_lib:mnesia_start for these ones but ... + ?match({atomic, ok}, + rpc:call(N1, mnesia,add_table_copy, [schema, N2, ram_copies])), + + ?match(ok, rpc:call(N2, mnesia, start, [[{schema_location, ram}, + {extra_db_nodes, [N1]}]])), + + %% Assure tables loaded + ?match({[ok, ok], []}, + rpc:multicall([N1, N2], mnesia, wait_for_tables, + [[schema], 10000])), + + ?verify_mnesia(Nodes, []), + ?cleanup(2, Config), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +table_load_to_disc_less_nodes(doc)-> + ["Load tables to disc less nodes"]; +table_load_to_disc_less_nodes(suite) -> []; +table_load_to_disc_less_nodes(Config) when is_list(Config) -> + [N1, N2] = ?init(2, Config), + + ?match(ok, rpc:call(N1, mnesia, start, [[{schema_location, ram}]])), + + %% Really should use test_lib:mnesia_start for these ones but ... + ?match({atomic, ok}, + rpc:call(N1, mnesia,add_table_copy, [schema, N2, ram_copies])), + + ?match(ok, rpc:call(N2, mnesia, start, [[{schema_location, ram}, + {extra_db_nodes, [N1]}]])), + + %% Now create some tables + ?match({atomic,ok}, + rpc:call( + N1, mnesia,create_table, [test_table, + [%%{disc_copies, [node()]}, + {ram_copies, [N1, N2]}, + {attributes, + record_info(fields,test_table)}]])), + + %% Assure tables loaded + ?match({[ok, ok], []}, + rpc:multicall([N1, N2], mnesia, wait_for_tables, + [[test_table], 1000])), + + %% Write something on one end ... + ?match({atomic, ok}, + rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:write(#test_table{i=44}) end])), + + %% Force synchronicity + ?match({atomic, ok}, + rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:write_lock_table(test_table) end])), + + %% ... and read it in the others + ?match({atomic, [{test_table, 44, _, _, _}]}, + rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:read({test_table, 44}) end])), + + ?cleanup(2, Config), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +schema_merge(doc) -> + ["Provoke various schema merge situations.", + "Perform various schema updates while some nodes are down,", + "stop the started nodes, start the stopped nodes and perform", + "schema updates. Now we have a situation were some of the table", + "definitions have been changed on two or more nodes independently", + "of each other and when Mnesia on the nodes tries to connect", + "to each other at restart the schema will be merged.", + "Do also try to provoke schema merge situations were the", + "schema cannot be merged."]; + +schema_merge(suite) -> []; + +schema_merge(Config) when is_list(Config) -> + [N1, N2]=Nodes=?acquire(2,Config), + + mnesia_test_lib:kill_mnesia([N2]), + receive after 1000 -> ok end, + + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + ?match({atomic,ok}, + rpc:call( + N1, mnesia,create_table, + [test_table, + [{Storage, [N1]}, + {attributes, + record_info(fields,test_table)}]])), + + ?match({atomic, ok}, + rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:write(#test_table{i=44}) end])), + + mnesia_test_lib:kill_mnesia([N1]), + receive after 2000 -> ok end, + %% Can't use std start because it waits for schema + ?match(ok, rpc:call(N2, mnesia, start, [])), + + ?match({atomic,ok}, + rpc:call( + N2, mnesia,create_table, + [test_table2, + [{Storage, [N2]}, + {attributes, + record_info(fields,test_table2)}]])), + + receive after 5000 -> ok end, + + ?match({atomic, ok}, + rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:write(#test_table2{i=33}) end])), + + %% Can't use std start because it waits for schema + ?match(ok, rpc:call(N1, mnesia, start, [])), + + %% Assure tables loaded + ?match({[ok, ok], []}, + rpc:multicall([N1, N2], mnesia, wait_for_tables, + [[schema, test_table, test_table2], 10000])), + + %% ... and read it in the others + ?match({[{atomic, [{test_table, 44, _, _, _}]}, + {atomic, [{test_table, 44, _, _, _}]}], []}, + rpc:multicall([N1, N2], mnesia, transaction, + [fun() -> mnesia:read({test_table, 44}) end])), + + ?match({[{atomic, [{test_table2, 33, _}]}, + {atomic, [{test_table2, 33, _}]}], []}, + rpc:multicall([N1, N2], mnesia, transaction, + [fun() -> mnesia:read({test_table2, 33}) end])), + + ?verify_mnesia(Nodes, []), + ?cleanup(2, Config), + ok. + + +-define(connect(Nodes), mnesia:change_config(extra_db_nodes, Nodes)). +-define(rpc_connect(From, Nodes), + rpc:call(From, mnesia, change_config, [extra_db_nodes, Nodes])). + + +sort({ok, NS}) -> + {ok, lists:sort(NS)}; +sort(Ns) when is_tuple(Ns) -> + Ns; +sort(NS) when is_list(NS) -> + lists:sort(NS). + + +dynamic_connect(doc) -> + ["Test the new functionality where we start mnesia first and then " + "connect to the other mnesia nodes"]; +dynamic_connect(suite) -> + [ + dynamic_basic, + dynamic_ext, + dynamic_bad + ]. + + +dynamic_basic(suite) -> []; +dynamic_basic(Config) when is_list(Config) -> + Nodes = [N1, N2, N3] = ?acquire_nodes(3, Config), + SNs = lists:sort(Nodes), + + ?match({atomic, ok}, mnesia:create_table(tab1, [{ram_copies, Nodes--[N1]}, {disc_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab2, [{disc_copies, Nodes}])), + + ?match({ok, SNs}, sort(?rpc_connect(N1, Nodes))), %% What shall happen? + ?match({ok, []}, sort(?rpc_connect(N1, [nonode@nothosted]))), %% What shall happen? + + ?match([], mnesia_test_lib:kill_mnesia([N2])), + ?match(ok, mnesia:delete_schema([N2])), + + ?match(ok, mnesia:dirty_write({tab1, 1, 1})), + ?match(ok, mnesia:dirty_write({tab2, 1, 1})), + + ?match(ok, rpc:call(N2, mnesia, start, [[{extra_db_nodes, [N1]}]])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab1,tab2],5000])), + io:format("Here ~p ~n",[?LINE]), + check_storage(N2, N1, [N3]), + ?match(SNs, sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))), + + ?match([], mnesia_test_lib:kill_mnesia([N3])), + ?match(ok, mnesia:delete_schema([N3])), + + io:format("T1 ~p ~n",[rpc:call(N3,?MODULE,c_nodes,[])]), + ?match(ok, rpc:call(N3, mnesia, start, [])), + io:format("T2 ~p ~n",[rpc:call(N3,?MODULE,c_nodes,[])]), + timer:sleep(2000), + io:format("T3 ~p ~n",[rpc:call(N3,?MODULE,c_nodes,[])]), + ?match({ok, [N1]}, sort(?rpc_connect(N3, [N1]))), + io:format("T4 ~p ~n",[rpc:call(N3,?MODULE,c_nodes,[])]), + ?match(ok, rpc:call(N3, mnesia, wait_for_tables, [[tab1,tab2],5000])), + io:format("Here ~p ~n",[?LINE]), + check_storage(N3, N1, [N2]), + ?match(SNs, sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))), + + ?match([], mnesia_test_lib:kill_mnesia([N3])), + ?match(ok, mnesia:delete_schema([N3])), + + ?match(ok, rpc:call(N3, mnesia, start, [])), + ?match({ok, [N3]}, sort(?rpc_connect(N1, [N3]))), + ?match(ok, rpc:call(N3, mnesia, wait_for_tables, [[tab1,tab2],5000])), + io:format("Here ~p ~n",[?LINE]), + check_storage(N3, N1, [N2]), + ?match(SNs, sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))), + + mnesia_test_lib:kill_mnesia([N2]), + ?match(ok, mnesia:delete_schema([N2])), + ?match({atomic, ok}, mnesia:del_table_copy(schema, N2)), + + % Ok, we have now removed references to node N2 from the other nodes + % mnesia should come up now. + ?match({atomic, ok}, mnesia:add_table_copy(tab1, N2, ram_copies)), + + ?match(ok, rpc:call(N2, mnesia, start, [])), + ?match({ok, _}, sort(?rpc_connect(N2, [N3]))), + + ?match(SNs, sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N3, mnesia, system_info, [running_db_nodes]))), + + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab1], 1000])), + ?match([{tab1, 1, 1}], rpc:call(N2, mnesia, dirty_read, [tab1, 1])), + + mnesia_test_lib:kill_mnesia([N2]), + + %%% SYNC!!! + timer:sleep(1000), + + ?match([N3,N1], sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))), + ?match([N3,N1], sort(rpc:call(N3, mnesia, system_info, [running_db_nodes]))), + + ?match(ok, rpc:call(N2, mnesia, start, [])), + ?match({ok, _}, sort(?rpc_connect(N3, [N2]))), + + ?match(SNs, sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N3, mnesia, system_info, [running_db_nodes]))), + + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab1], 1000])), + ?match([{tab1, 1, 1}], rpc:call(N2, mnesia, dirty_read, [tab1, 1])), + + ?verify_mnesia(Nodes, []), +%% ?cleanup(3, Config). + ok. + +c_nodes() -> + {mnesia_lib:val({current, db_nodes}),mnesia_lib:val(recover_nodes)}. + + +dynamic_ext(suite) -> []; +dynamic_ext(Config) when is_list(Config) -> + Ns = [N1,N2] = ?acquire_nodes(2, Config), + SNs = lists:sort([N1,N2]), + + ?match({atomic, ok}, mnesia:create_table(tab0, [{disc_copies, [N1,N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab1, [{ram_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab2, [{disc_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab3, [{disc_only_copies, [N2]}])), + + mnesia_test_lib:kill_mnesia([N2]), + ?match(ok, mnesia:delete_schema([N2])), + ?match(ok, rpc:call(N2, mnesia, start, [[{extra_db_nodes, [N1]}]])), + + ?match(SNs, sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))), + + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab0,tab1,tab2,tab3], 2000])), + + Check = fun({Tab,Storage}) -> + ?match(Storage, rpc:call(N2, mnesia, table_info, [Tab, storage_type])), + ?match([{N2,Storage}], + lists:sort(rpc:call(N2, mnesia, table_info, [Tab, where_to_commit]))) + end, + [Check(Test) || Test <- [{tab1, ram_copies},{tab2, disc_copies},{tab3, disc_only_copies}]], + + T = now(), + ?match(ok, mnesia:dirty_write({tab0, 42, T})), + ?match(ok, mnesia:dirty_write({tab1, 42, T})), + ?match(ok, mnesia:dirty_write({tab2, 42, T})), + ?match(ok, mnesia:dirty_write({tab3, 42, T})), + + ?match(stopped, rpc:call(N2, mnesia, stop, [])), + ?match(ok, rpc:call(N2, mnesia, start, [])), + ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))), + ?match(ok, mnesia:wait_for_tables([tab0,tab1,tab2,tab3], 10000)), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab1,tab2,tab3], 100])), + ?match([], mnesia:dirty_read({tab1, 41})), + ?match([{tab2,42,T}], mnesia:dirty_read({tab2, 42})), + ?match([{tab3,42,T}], mnesia:dirty_read({tab3, 42})), + + mnesia_test_lib:kill_mnesia([N2]), + ?match(ok, mnesia:delete_schema([N2])), + + ?match(stopped, rpc:call(N1, mnesia, stop, [])), + + ?match(ok, rpc:call(N2, mnesia, start, [[{extra_db_nodes,[N1,N2]}]])), + ?match({timeout,[tab0]}, rpc:call(N2, mnesia, wait_for_tables, [[tab0], 500])), + + ?match(ok, rpc:call(N1, mnesia, start, [[{extra_db_nodes, [N1,N2]}]])), + ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[tab0], 1500])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab0], 1500])), + ?match([{tab0,42,T}], mnesia:dirty_read({tab0, 42})), + ?match([{tab0,42,T}], rpc:call(N2, mnesia,dirty_read,[{tab0,42}])), + + ?match(stopped, rpc:call(N1, mnesia, stop, [])), + mnesia_test_lib:kill_mnesia([N2]), + ?match(ok, mnesia:delete_schema([N2])), + ?match(ok, rpc:call(N1, mnesia, start, [[{extra_db_nodes, [N1,N2]}]])), + ?match({timeout,[tab0]}, rpc:call(N1, mnesia, wait_for_tables, [[tab0], 500])), + + ?match(ok, rpc:call(N2, mnesia, start, [[{extra_db_nodes,[N1,N2]}]])), + ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[tab0], 1500])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab0], 1500])), + ?match([{tab0,42,T}], mnesia:dirty_read({tab0, 42})), + ?match([{tab0,42,T}], rpc:call(N2,mnesia,dirty_read,[{tab0,42}])), + + ?verify_mnesia(Ns, []), + ok. + +check_storage(Me, Orig, Other) -> + io:format("Nodes ~p ~p ~p~n",[Me,Orig,Other]), + rpc:multicall(Other, sys, status, [mnesia_locker]), + rpc:call(Me, sys, status, [mnesia_locker]), + rpc:call(Orig, sys, status, [mnesia_locker]), + rpc:multicall(Other, sys, status, [mnesia_controller]), + rpc:call(Me, sys, status, [mnesia_controller]), + rpc:call(Orig, sys, status, [mnesia_controller]), + %% Verify disc_copies + W2C = lists:sort([{Node,disc_copies} || Node <- [Me,Orig|Other]]), + W2W = lists:sort([Me,Orig|Other]), + ?match(disc_copies, rpc:call(Orig, mnesia, table_info, [schema, storage_type])), + ?match(disc_copies, rpc:call(Me, mnesia, table_info, [schema, storage_type])), + ?match(W2C, lists:sort(rpc:call(Orig, mnesia, table_info, [schema, where_to_commit]))), + ?match(W2C, lists:sort(rpc:call(Me, mnesia, table_info, [schema, where_to_commit]))), + + ?match(disc_copies, rpc:call(Orig, mnesia, table_info, [tab2, storage_type])), + ?match(disc_copies, rpc:call(Me, mnesia, table_info, [tab2, storage_type])), + ?match(W2W, lists:sort(rpc:call(Me, mnesia, table_info, [tab2, where_to_write]))), + ?match(Me, rpc:call(Me, mnesia, table_info, [tab2, where_to_read])), + + ?match(W2C, lists:sort(rpc:call(Orig, mnesia, table_info, [tab2, where_to_commit]))), + ?match(W2C, lists:sort(rpc:call(Me, mnesia, table_info, [tab2, where_to_commit]))), + + ?match([{tab1,1,1}], mnesia:dirty_read(tab1,1)), + ?match([{tab2,1,1}], mnesia:dirty_read(tab2,1)), + ?match([{tab1,1,1}], rpc:call(Me, mnesia, dirty_read, [tab1,1])), + ?match([{tab2,1,1}], rpc:call(Me, mnesia, dirty_read, [tab2,1])), + + ?match(true, rpc:call(Me, mnesia_monitor, use_dir, [])), + ?match(disc_copies, rpc:call(Me, mnesia_lib, val, [{schema, storage_type}])), + + mnesia_test_lib:kill_mnesia([Orig]), + mnesia_test_lib:kill_mnesia(Other), + T = now(), + ?match(ok, rpc:call(Me, mnesia, dirty_write, [{tab2, 42, T}])), + ?match(stopped, rpc:call(Me, mnesia, stop, [])), + ?match(ok, rpc:call(Me, mnesia, start, [])), + ?match([], mnesia_test_lib:start_mnesia([Orig|Other], [tab1,tab2])), + ?match([{tab2,42,T}], rpc:call(Me, mnesia, dirty_read, [{tab2, 42}])), + ?match([{tab2,42,T}], rpc:call(Orig, mnesia, dirty_read, [{tab2, 42}])), + + ?match([{tab1,1,1}], mnesia:dirty_read(tab1,1)), + ?match([{tab2,1,1}], mnesia:dirty_read(tab2,1)), + ?match([{tab1,1,1}], rpc:call(Me, mnesia, dirty_read, [tab1,1])), + ?match([{tab2,1,1}], rpc:call(Me, mnesia, dirty_read, [tab2,1])), + ok. + + +dynamic_bad(suite) -> []; +dynamic_bad(Config) when is_list(Config) -> + Ns = [N1, N2, N3] = ?acquire_nodes(3, Config), + SNs = lists:sort([N2,N3]), + + ?match({atomic, ok}, mnesia:change_table_copy_type(schema, N2, ram_copies)), + ?match({atomic, ok}, mnesia:change_table_copy_type(schema, N3, ram_copies)), + ?match({atomic, ok}, mnesia:create_table(tab1, [{ram_copies, Ns -- [N1]}, + {disc_copies, [N1]}])), + ?match(ok, mnesia:dirty_write({tab1, 1, 1})), + + mnesia_test_lib:kill_mnesia(Ns), + ?match({[ok, ok], []}, rpc:multicall(Ns -- [N1], mnesia, start, [])), + ?match({ok, [N2]}, ?rpc_connect(N3, [N2])), + ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))), + ?match(SNs, sort(rpc:call(N3, mnesia, system_info, [running_db_nodes]))), + ?match({badrpc, {'EXIT', {aborted, {no_exists, _, _}}}}, + rpc:call(N2, mnesia, table_info, [tab1, where_to_read])), + + ?match(ok, mnesia:start()), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab1], 1000])), + ?match(N2, rpc:call(N2, mnesia, table_info, [tab1, where_to_read])), + ?match([{tab1, 1, 1}], rpc:call(N2, mnesia, dirty_read, [tab1, 1])), + + mnesia_test_lib:kill_mnesia(Ns), + ?match({[ok, ok], []}, rpc:multicall(Ns -- [N1], mnesia, start, [])), + ?match({ok, [N2]}, ?rpc_connect(N3, [N2])), + % Make a merge conflict + ?match({atomic, ok}, rpc:call(N3, mnesia, create_table, [tab1, []])), + + io:format("We expect a mnesia crash here~n", []), + ?match({error,{_, _}}, mnesia:start()), + + ?verify_mnesia(Ns -- [N1], []), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +unknown_config(doc) -> + ["Try some unknown configuration parameters and see that expected", + "things happens."]; +unknown_config(suite)-> []; +unknown_config(Config) when is_list(Config) -> + ?init(1, Config), + %% NOTE: case 1 & 2 below do not respond the same + ?match({error, Res} when element(1, Res) == bad_type, + mnesia:start([{undefined_config,[]}])), + %% Below does not work, but the "correct" behaviour would be to have + %% case 1 above to behave as the one below. + + %% in mnesia-1.3 {error,{bad_type,{[],undefined_config}}} + ?match({error, Res} when element(1, Res) == bad_type, + mnesia:start([{[],undefined_config}])), + ?cleanup(1, Config), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +inconsistent_database(doc) -> + ["Replace the event module with another module and use it as", + "receiver of the various system and table events. Provoke", + "coverage of all kinds of events."]; +inconsistent_database(suite) -> []; +inconsistent_database(Config) when is_list(Config) -> + Nodes = mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}], + 2, Config, ?FILE, ?LINE), + KillAfter = length(Nodes) * timer:minutes(5), + ?acquire_schema(2, Config ++ [{tc_timeout, KillAfter}]), + + Ok = [ok || _N <- Nodes], + StartArgs = [{event_module, mnesia_inconsistent_database_test}], + ?match({Ok, []}, rpc:multicall(Nodes, mnesia, start, [StartArgs])), + ?match([], mnesia_test_lib:kill_mnesia(Nodes)), + + ?match(ok, mnesia_meter:go(ram_copies, Nodes)), + + mnesia_test_lib:reload_appls([mnesia], Nodes), + ok. + diff --git a/lib/mnesia/test/mnesia_consistency_test.erl b/lib/mnesia/test/mnesia_consistency_test.erl new file mode 100644 index 0000000000..ffe8ab7ac3 --- /dev/null +++ b/lib/mnesia/test/mnesia_consistency_test.erl @@ -0,0 +1,1612 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_consistency_test). +-author('[email protected]'). +-compile([export_all]). + +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Verify transaction consistency", + "Consistency is the property of the application that requires any", + "execution of the transaction to take the database from one", + "consistent state to another. Verify that the database is", + "consistent at any point in time.", + "Verify for various configurations.", + " Verify for both set and bag"]; +all(suite) -> + [ + consistency_after_restart, + consistency_after_dump_tables, + consistency_after_add_replica, + consistency_after_del_replica, + consistency_after_move_replica, + consistency_after_transform_table, + consistency_after_change_table_copy_type, + consistency_after_fallback, + consistency_after_restore, + consistency_after_rename_of_node, + checkpoint_retainer_consistency, + backup_consistency + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% stolen from mnesia_tpcb.erl: + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Account record, total size must be at least 100 bytes + +-define(ACCOUNT_FILLER, + {123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234}). + +-record(account, + { + id = 0, %% Unique account id + branch_id = 0, %% Branch where the account is held + balance = 0, %% Account balance + filler = ?ACCOUNT_FILLER %% Gap filler to ensure size >= 100 bytes + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Branch record, total size must be at least 100 bytes + +-define(BRANCH_FILLER, + {123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234567890}). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Teller record, total size must be at least 100 bytes + +-define(TELLER_FILLER, + {123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234567890, + 1234567890123456789012345678901234567890123456789012345678}). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% History record, total size must be at least 50 bytes + +-define(HISTORY_FILLER, 1234567890). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-record(tab_config, + { + db_nodes = [node()], + replica_nodes = [node()], + replica_type = ram_copies, + use_running_mnesia = false, + n_branches = 1, + n_tellers_per_branch = 10, %% Must be 10 + n_accounts_per_branch = 100000, %% Must be 100000 + branch_filler = ?BRANCH_FILLER, + account_filler = ?ACCOUNT_FILLER, + teller_filler = ?TELLER_FILLER + }). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% stolen from mnesia_tpcb.erl: + +list2rec(List, Fields, DefaultTuple) -> + [Name|Defaults] = tuple_to_list(DefaultTuple), + List2 = list2rec(List, Fields, Defaults, []), + list_to_tuple([Name] ++ List2). + +list2rec(_List, [], [], Acc) -> + Acc; +list2rec(List, [F|Fields], [D|Defaults], Acc) -> + {Val, List2} = + case lists:keysearch(F, 1, List) of + false -> + {D, List}; + {value, {F, NewVal}} -> + {NewVal, lists:keydelete(F, 1, List)} + end, + list2rec(List2, Fields, Defaults, Acc ++ [Val]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +tpcb_config(ReplicaType, _NodeConfig, Nodes, NoDriverNodes) -> + [{n_branches, 10}, + {n_drivers_per_node, 10}, + {replica_nodes, Nodes}, + {driver_nodes, Nodes -- NoDriverNodes}, + {use_running_mnesia, true}, + {report_interval, infinity}, + {n_accounts_per_branch, 100}, + {replica_type, ReplicaType}, + {reuse_history_id, true}]. + +%% Stolen from mnesia_tpcb:dist +tpcb_config_dist(ReplicaType, _NodeConfig, Nodes, _Config) -> + [{db_nodes, Nodes}, + {driver_nodes, Nodes}, + {replica_nodes, Nodes}, + {n_drivers_per_node, 10}, + {n_branches, 1}, + {use_running_mnesia, true}, + {n_accounts_per_branch, 10}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(15)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true}]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% stolen from mnesia_recovery_test.erl: + +receive_messages([]) -> []; +receive_messages(ListOfMsgs) -> + receive + {Pid, Msg} -> + case lists:member(Msg, ListOfMsgs) of + false -> + ?warning("I (~p) have received unexpected msg~n ~p ~n", + [self(),{Pid, Msg}]), + receive_messages(ListOfMsgs); + true -> + ?verbose("I (~p) got msg ~p from ~p ~n", [self(),Msg, Pid]), + [{Pid, Msg} | receive_messages(ListOfMsgs -- [Msg])] + end; + Else -> ?warning("Recevied unexpected Msg~n ~p ~n", [Else]) + after timer:minutes(3) -> + ?error("Timeout in receive msgs while waiting for ~p~n", + [ListOfMsgs]) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_restart(suite) -> + [ + consistency_after_restart_1_ram, + consistency_after_restart_1_disc, + consistency_after_restart_1_disc_only, + consistency_after_restart_2_ram, + consistency_after_restart_2_disc, + consistency_after_restart_2_disc_only + ]. + +consistency_after_restart_1_ram(suite) -> []; +consistency_after_restart_1_ram(Config) when is_list(Config) -> + consistency_after_restart(ram_copies, 2, Config). + +consistency_after_restart_1_disc(suite) -> []; +consistency_after_restart_1_disc(Config) when is_list(Config) -> + consistency_after_restart(disc_copies, 2, Config). + +consistency_after_restart_1_disc_only(suite) -> []; +consistency_after_restart_1_disc_only(Config) when is_list(Config) -> + consistency_after_restart(disc_only_copies, 2, Config). + +consistency_after_restart_2_ram(suite) -> []; +consistency_after_restart_2_ram(Config) when is_list(Config) -> + consistency_after_restart(ram_copies, 3, Config). + +consistency_after_restart_2_disc(suite) -> []; +consistency_after_restart_2_disc(Config) when is_list(Config) -> + consistency_after_restart(disc_copies, 3, Config). + +consistency_after_restart_2_disc_only(suite) -> []; +consistency_after_restart_2_disc_only(Config) when is_list(Config) -> + consistency_after_restart(disc_only_copies, 3, Config). + +consistency_after_restart(ReplicaType, NodeConfig, Config) -> + [Node1 | _] = Nodes = ?acquire_nodes(NodeConfig, Config), + {success, [A]} = ?start_activities([Node1]), + ?log("consistency_after_restart with ~p on ~p~n", + [ReplicaType, Nodes]), + TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes, [Node1]), + mnesia_tpcb:init(TpcbConfig), + A ! fun () -> mnesia_tpcb:run(TpcbConfig) end, + timer:sleep(timer:seconds(10)), + mnesia_test_lib:kill_mnesia([Node1]), + %% Start and wait for tables to be loaded on all nodes + timer:sleep(timer:seconds(3)), + ?match([], mnesia_test_lib:start_mnesia(Nodes,[account,branch,teller, history])), + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_dump_tables(suite) -> + [ + consistency_after_dump_tables_1_ram, + consistency_after_dump_tables_2_ram + ]. + +consistency_after_dump_tables_1_ram(suite) -> []; +consistency_after_dump_tables_1_ram(Config) when is_list(Config) -> + consistency_after_dump_tables(ram_copies, 1, Config). + +consistency_after_dump_tables_2_ram(suite) -> []; +consistency_after_dump_tables_2_ram(Config) when is_list(Config) -> + consistency_after_dump_tables(ram_copies, 2, Config). + +consistency_after_dump_tables(ReplicaType, NodeConfig, Config) -> + [Node1 | _] = Nodes = ?acquire_nodes(NodeConfig, Config), + {success, [A]} = ?start_activities([Node1]), + ?log("consistency_after_dump_tables with ~p on ~p~n", + [ReplicaType, Nodes]), + TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes, []), + mnesia_tpcb:init(TpcbConfig), + A ! fun() -> mnesia_tpcb:run(TpcbConfig) end, + timer:sleep(timer:seconds(10)), + ?match({atomic, ok}, rpc:call(Node1, mnesia, dump_tables, + [[branch, teller, account, history]])), + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + + mnesia_test_lib:kill_mnesia(Nodes), + timer:sleep(timer:seconds(1)), + ?match([], mnesia_test_lib:start_mnesia(Nodes,[account, branch, + teller, history])), + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_add_replica(suite) -> + [ + consistency_after_add_replica_2_ram, + consistency_after_add_replica_2_disc, + consistency_after_add_replica_2_disc_only, + consistency_after_add_replica_3_ram, + consistency_after_add_replica_3_disc, + consistency_after_add_replica_3_disc_only + ]. + +consistency_after_add_replica_2_ram(suite) -> []; +consistency_after_add_replica_2_ram(Config) when is_list(Config) -> + consistency_after_add_replica(ram_copies, 2, Config). + +consistency_after_add_replica_2_disc(suite) -> []; +consistency_after_add_replica_2_disc(Config) when is_list(Config) -> + consistency_after_add_replica(disc_copies, 2, Config). + +consistency_after_add_replica_2_disc_only(suite) -> []; +consistency_after_add_replica_2_disc_only(Config) when is_list(Config) -> + consistency_after_add_replica(disc_only_copies, 2, Config). + +consistency_after_add_replica_3_ram(suite) -> []; +consistency_after_add_replica_3_ram(Config) when is_list(Config) -> + consistency_after_add_replica(ram_copies, 3, Config). + +consistency_after_add_replica_3_disc(suite) -> []; +consistency_after_add_replica_3_disc(Config) when is_list(Config) -> + consistency_after_add_replica(disc_copies, 3, Config). + +consistency_after_add_replica_3_disc_only(suite) -> []; +consistency_after_add_replica_3_disc_only(Config) when is_list(Config) -> + consistency_after_add_replica(disc_only_copies, 3, Config). + +consistency_after_add_replica(ReplicaType, NodeConfig, Config) -> + Nodes0 = ?acquire_nodes(NodeConfig, Config), + AddNode = lists:last(Nodes0), + Nodes = Nodes0 -- [AddNode], + Node1 = hd(Nodes), + {success, [A]} = ?start_activities([Node1]), + ?log("consistency_after_add_replica with ~p on ~p~n", + [ReplicaType, Nodes0]), + TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes, []), + mnesia_tpcb:init(TpcbConfig), + A ! fun () -> mnesia_tpcb:run(TpcbConfig) end, + timer:sleep(timer:seconds(10)), + ?match({atomic, ok}, mnesia:add_table_copy(account, AddNode, ReplicaType)), + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + ?verify_mnesia(Nodes0, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_del_replica(suite) -> + [ + consistency_after_del_replica_2_ram, + consistency_after_del_replica_2_disc, + consistency_after_del_replica_2_disc_only, + consistency_after_del_replica_3_ram, + consistency_after_del_replica_3_disc, + consistency_after_del_replica_3_disc_only + ]. + +consistency_after_del_replica_2_ram(suite) -> []; +consistency_after_del_replica_2_ram(Config) when is_list(Config) -> + consistency_after_del_replica(ram_copies, 2, Config). + +consistency_after_del_replica_2_disc(suite) -> []; +consistency_after_del_replica_2_disc(Config) when is_list(Config) -> + consistency_after_del_replica(disc_copies, 2, Config). + +consistency_after_del_replica_2_disc_only(suite) -> []; +consistency_after_del_replica_2_disc_only(Config) when is_list(Config) -> + consistency_after_del_replica(disc_only_copies, 2, Config). + +consistency_after_del_replica_3_ram(suite) -> []; +consistency_after_del_replica_3_ram(Config) when is_list(Config) -> + consistency_after_del_replica(ram_copies, 3, Config). + +consistency_after_del_replica_3_disc(suite) -> []; +consistency_after_del_replica_3_disc(Config) when is_list(Config) -> + consistency_after_del_replica(disc_copies, 3, Config). + +consistency_after_del_replica_3_disc_only(suite) -> []; +consistency_after_del_replica_3_disc_only(Config) when is_list(Config) -> + consistency_after_del_replica(disc_only_copies, 3, Config). + +consistency_after_del_replica(ReplicaType, NodeConfig, Config) -> + Nodes = ?acquire_nodes(NodeConfig, Config), + Node1 = hd(Nodes), + Node2 = lists:last(Nodes), + {success, [A]} = ?start_activities([Node1]), + ?log("consistency_after_del_replica with ~p on ~p~n", + [ReplicaType, Nodes]), + TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes, []), + mnesia_tpcb:init(TpcbConfig), + A ! fun () -> mnesia_tpcb:run(TpcbConfig) end, + timer:sleep(timer:seconds(10)), + ?match({atomic, ok}, mnesia:del_table_copy(account, Node2)), + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_move_replica(suite) -> + [ + consistency_after_move_replica_2_ram, + consistency_after_move_replica_2_disc, + consistency_after_move_replica_2_disc_only, + consistency_after_move_replica_3_ram, + consistency_after_move_replica_3_disc, + consistency_after_move_replica_3_disc_only + ]. + +consistency_after_move_replica_2_ram(suite) -> []; +consistency_after_move_replica_2_ram(Config) when is_list(Config) -> + consistency_after_move_replica(ram_copies, 2, Config). + +consistency_after_move_replica_2_disc(suite) -> []; +consistency_after_move_replica_2_disc(Config) when is_list(Config) -> + consistency_after_move_replica(disc_copies, 2, Config). + +consistency_after_move_replica_2_disc_only(suite) -> []; +consistency_after_move_replica_2_disc_only(Config) when is_list(Config) -> + consistency_after_move_replica(disc_only_copies, 2, Config). + +consistency_after_move_replica_3_ram(suite) -> []; +consistency_after_move_replica_3_ram(Config) when is_list(Config) -> + consistency_after_move_replica(ram_copies, 3, Config). + +consistency_after_move_replica_3_disc(suite) -> []; +consistency_after_move_replica_3_disc(Config) when is_list(Config) -> + consistency_after_move_replica(disc_copies, 3, Config). + +consistency_after_move_replica_3_disc_only(suite) -> []; +consistency_after_move_replica_3_disc_only(Config) when is_list(Config) -> + consistency_after_move_replica(disc_only_copies, 3, Config). + +consistency_after_move_replica(ReplicaType, NodeConfig, Config) -> + Nodes = ?acquire_nodes(NodeConfig, Config ++ [{tc_timeout, timer:minutes(10)}]), + Node1 = hd(Nodes), + Node2 = lists:last(Nodes), + {success, [A]} = ?start_activities([Node1]), + ?log("consistency_after_move_replica with ~p on ~p~n", + [ReplicaType, Nodes]), + TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes -- [Node2], []), + mnesia_tpcb:init(TpcbConfig), + A ! fun () -> mnesia_tpcb:run(TpcbConfig) end, + timer:sleep(timer:seconds(10)), + ?match({atomic, ok}, mnesia:move_table_copy(account, Node1, Node2)), + ?log("First move completed from node ~p to ~p ~n", [Node1, Node2]), + ?match({atomic, ok}, mnesia:move_table_copy(account, Node2, Node1)), + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_transform_table(doc) -> + ["Check that the database is consistent after transform_table.", + " While applications are updating the involved tables. "]; + +consistency_after_transform_table(suite) -> + [ + consistency_after_transform_table_ram, + consistency_after_transform_table_disc, + consistency_after_transform_table_disc_only + ]. + + +consistency_after_transform_table_ram(suite) -> []; +consistency_after_transform_table_ram(Config) when is_list(Config) -> + consistency_after_transform_table(ram_copies, Config). + +consistency_after_transform_table_disc(suite) -> []; +consistency_after_transform_table_disc(Config) when is_list(Config) -> + consistency_after_transform_table(disc_copies, Config). + +consistency_after_transform_table_disc_only(suite) -> []; +consistency_after_transform_table_disc_only(Config) when is_list(Config) -> + consistency_after_transform_table(disc_only_copies, Config). + +consistency_after_transform_table(Type, Config) -> + Nodes = [N1, N2,_N3] = ?acquire_nodes(3, Config), + + ?match({atomic, ok}, mnesia:create_table(tab1, [{index, [3]}, {Type, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab2, [{index, [3]}, {Type, [N1,N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab3, [{index, [3]}, {Type, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(empty, [{index, [3]},{Type, Nodes}])), + + Tabs = lists:sort([tab1, tab2, tab3, empty]), + + [[mnesia:dirty_write({Tab, N, N}) || N <- lists:seq(1,10)] || + Tab <- Tabs -- [empty, tab4]], + mnesia:dump_log(), + + Ok = lists:duplicate(4, {atomic, ok}), + ?match(Ok, [mnesia:transform_table(Tab, fun({T, N, N}) -> {T, N, N, ok} end, + [k,a,n]) || Tab <- Tabs]), + [?match([k,a,n], mnesia:table_info(Tab, attributes)) || Tab <- Tabs], + + Filter = fun(Tab) -> mnesia:foldl(fun(A, Acc) when size(A) == 3 -> [A|Acc]; + (A, Acc) when size(A) == 4 -> Acc + end, [], Tab) + end, + + ?match([[],[],[],[]], [element(2,mnesia:transaction(Filter, [Tab])) || Tab <- Tabs]), + + mnesia_test_lib:kill_mnesia(Nodes), + mnesia_test_lib:start_mnesia(Nodes, Tabs), + + ?match([Tabs, Tabs, Tabs], + [lists:sort(rpc:call(Node, mnesia,system_info, [tables]) -- [schema]) || Node <- Nodes]), + + ?match([[],[],[],[]], [element(2,mnesia:transaction(Filter, [Tab])) || Tab <- Tabs]), + [?match([k,a,n], mnesia:table_info(Tab, attributes)) || Tab <- Tabs], + + ?verify_mnesia(Nodes, []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_change_table_copy_type(doc) -> + ["Check that the database is consistent after change of copy type.", + " While applications are updating the involved tables. "]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_fallback(doc) -> + ["Check that installed fallbacks are consistent. Check this by starting ", + "some nodes, run tpcb on them, take a backup at any time, install it ", + "as a fallback, kill all nodes, start mnesia again and check for ", + "any inconsistencies"]; +consistency_after_fallback(suite) -> + [ + consistency_after_fallback_2_ram, + consistency_after_fallback_2_disc, + consistency_after_fallback_2_disc_only, + consistency_after_fallback_3_ram, + consistency_after_fallback_3_disc + , consistency_after_fallback_3_disc_only + ]. + +consistency_after_fallback_2_ram(suite) -> []; +consistency_after_fallback_2_ram(Config) when is_list(Config) -> + consistency_after_fallback(ram_copies, 2, Config). + +consistency_after_fallback_2_disc(suite) -> []; +consistency_after_fallback_2_disc(Config) when is_list(Config) -> + consistency_after_fallback(disc_copies, 2, Config). + +consistency_after_fallback_2_disc_only(suite) -> []; +consistency_after_fallback_2_disc_only(Config) when is_list(Config) -> + consistency_after_fallback(disc_only_copies, 2, Config). + +consistency_after_fallback_3_ram(suite) -> []; +consistency_after_fallback_3_ram(Config) when is_list(Config) -> + consistency_after_fallback(ram_copies, 3, Config). + +consistency_after_fallback_3_disc(suite) -> []; +consistency_after_fallback_3_disc(Config) when is_list(Config) -> + consistency_after_fallback(disc_copies, 3, Config). + +consistency_after_fallback_3_disc_only(suite) -> []; +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) -> + %%?verbose("Starting consistency_after_fallback2 at ~p~n", [self()]), + Delay = 5, + Nodes = ?acquire_nodes(NodeConfig, [{tc_timeout, timer:minutes(10)} | Config]), + Node1 = hd(Nodes), + %%?verbose("Mnesia info: ~p~n", [mnesia:info()]), + + {success, [A]} = ?start_activities([Node1]), + ?log("consistency_after_fallback with ~p on ~p~n", + [ReplicaType, Nodes]), + TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes, []), + mnesia_tpcb:init(TpcbConfig), + A ! fun () -> mnesia_tpcb:run(TpcbConfig) end, + timer:sleep(timer:seconds(Delay)), + + %% Make a backup + ?verbose("Doing backup~n", []), + ?match(ok, mnesia:backup(consistency_after_fallback2)), + + %% Install the backup as a fallback + ?verbose("Doing fallback~n", []), + ?match(ok, mnesia:install_fallback(consistency_after_fallback2)), + timer:sleep(timer:seconds(Delay)), + + %% Stop tpcb + ?verbose("Stopping TPC-B~n", []), + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + + %% Stop and then start mnesia and check table consistency + %%?verbose("Restarting Mnesia~n", []), + mnesia_test_lib:kill_mnesia(Nodes), + mnesia_test_lib:start_mnesia(Nodes,[account,branch,teller,history]), + + ?match(ok, mnesia_tpcb:verify_tabs()), + if + ReplicaType == ram_copies -> + %% Test that change_table_copy work i.e. no account.dcd file exists. + ?match({atomic, ok}, mnesia:change_table_copy_type(account, node(), disc_copies)); + true -> + ignore + end, + file:delete(consistency_after_fallback2), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_restore(doc) -> + ["Verify consistency after restore operations."]; + +consistency_after_restore(suite) -> + [ + consistency_after_restore_clear_ram, + consistency_after_restore_clear_disc, + consistency_after_restore_clear_disc_only, + consistency_after_restore_recreate_ram, + consistency_after_restore_recreate_disc, + consistency_after_restore_recreate_disc_only + ]. + +consistency_after_restore_clear_ram(suite) -> []; +consistency_after_restore_clear_ram(Config) when is_list(Config) -> + consistency_after_restore(ram_copies, clear_tables, Config). + +consistency_after_restore_clear_disc(suite) -> []; +consistency_after_restore_clear_disc(Config) when is_list(Config) -> + consistency_after_restore(disc_copies, clear_tables, Config). + +consistency_after_restore_clear_disc_only(suite) -> []; +consistency_after_restore_clear_disc_only(Config) when is_list(Config) -> + consistency_after_restore(disc_only_copies, clear_tables, Config). + +consistency_after_restore_recreate_ram(suite) -> []; +consistency_after_restore_recreate_ram(Config) when is_list(Config) -> + consistency_after_restore(ram_copies, recreate_tables, Config). + +consistency_after_restore_recreate_disc(suite) -> []; +consistency_after_restore_recreate_disc(Config) when is_list(Config) -> + consistency_after_restore(disc_copies, recreate_tables, Config). + +consistency_after_restore_recreate_disc_only(suite) -> []; +consistency_after_restore_recreate_disc_only(Config) when is_list(Config) -> + consistency_after_restore(disc_only_copies, recreate_tables, Config). + +consistency_after_restore(ReplicaType, Op, Config) -> + Delay = 1, + Nodes = ?acquire_nodes(3, [{tc_timeout, timer:minutes(10)} | Config]), + [Node1, Node2, _Node3] = Nodes, + File = "cons_backup_restore", + + ?log("consistency_after_restore with ~p on ~p~n", + [ReplicaType, Nodes]), + Tabs = [carA, carB, carC, carD], + + ?match({atomic, ok}, mnesia:create_table(carA, [{ReplicaType, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(carB, [{ReplicaType, Nodes -- [Node1]}])), + ?match({atomic, ok}, mnesia:create_table(carC, [{ReplicaType, Nodes -- [Node2]}])), + ?match({atomic, ok}, mnesia:create_table(carD, [{ReplicaType, [Node2]}])), + + NList = lists:seq(0, 20), + [lists:foreach(fun(E) -> ok = mnesia:dirty_write({Tab, E, 1}) end, NList) || + Tab <- Tabs], + + {ok, Name, _} = mnesia:activate_checkpoint([{max, [schema | Tabs]}, + {ram_overrides_dump, true}]), + ?verbose("Doing backup~n", []), + ?match(ok, mnesia:backup_checkpoint(Name, File)), + ?match(ok, mnesia:deactivate_checkpoint(Name)), + + [lists:foreach(fun(E) -> ok = mnesia:dirty_write({Tab, E, 2}) end, NList) || + Tab <- Tabs], + + Pids1 = [{'EXIT', spawn_link(?MODULE, change_tab, [self(), carA, Op]), ok} || _ <- lists:seq(1, 5)], + Pids2 = [{'EXIT', spawn_link(?MODULE, change_tab, [self(), carB, Op]), ok} || _ <- lists:seq(1, 5)], + Pids3 = [{'EXIT', spawn_link(?MODULE, change_tab, [self(), carC, Op]), ok} || _ <- lists:seq(1, 5)], + Pids4 = [{'EXIT', spawn_link(?MODULE, change_tab, [self(), carD, Op]), ok} || _ <- lists:seq(1, 5)], + + AllPids = Pids1 ++ Pids2 ++ Pids3 ++ Pids4, + + Restore = fun(F, Args) -> + case mnesia:restore(F, Args) of + {atomic, List} -> lists:sort(List); + Else -> Else + end + end, + + timer:sleep(timer:seconds(Delay)), %% Let changers grab locks + ?verbose("Doing restore~n", []), + ?match(Tabs, Restore(File, [{default_op, Op}])), + + timer:sleep(timer:seconds(Delay)), %% Let em die + + ?match_multi_receive(AllPids), + + case ?match(ok, restore_verify_tabs(Tabs)) of + {success, ok} -> + file:delete(File); + _ -> + {T, M, S} = time(), + File2 = ?flat_format("consistency_error~w~w~w.BUP", [T, M, S]), + file:rename(File, File2) + end, + ?verify_mnesia(Nodes, []). + +change_tab(Father, Tab, Test) -> + Key = random:uniform(20), + Update = fun() -> + case mnesia:read({Tab, Key}) of + [{Tab, Key, 1}] -> + quit; + [{Tab, Key, _N}] -> + mnesia:write({Tab, Key, 3}) + end + end, + case mnesia:transaction(Update) of + {atomic, quit} -> + exit(ok); + {aborted, {no_exists, Tab}} when Test == recreate_tables ->%% I'll allow this + change_tab(Father, Tab, Test); + {atomic, ok} -> + change_tab(Father, Tab, Test) + end. + +restore_verify_tabs([Tab | R]) -> + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:foldl(fun({_, _, 1}, ok) -> + ok; + (Else, Acc) -> + [Else|Acc] + end, ok, Tab) + end)), + restore_verify_tabs(R); +restore_verify_tabs([]) -> + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consistency_after_rename_of_node(doc) -> + ["Skipped because it is an unimportant case."]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +checkpoint_retainer_consistency(doc) -> + ["Verify that the contents of a checkpoint retainer has the expected", + "contents in various situations."]; +checkpoint_retainer_consistency(suite) -> + [ + updates_during_checkpoint_activation, + updates_during_checkpoint_iteration, + load_table_with_activated_checkpoint, + add_table_copy_to_table_with_activated_checkpoint + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +updates_during_checkpoint_activation(doc) -> + ["Perform updates while the checkpoint getting activated", + "and verify that all checkpoint retainers associated with", + "different replicas of the same table really has the same", + "contents."]; +updates_during_checkpoint_activation(suite) -> + [ + updates_during_checkpoint_activation_2_ram, + updates_during_checkpoint_activation_2_disc, + updates_during_checkpoint_activation_2_disc_only, + updates_during_checkpoint_activation_3_ram, + updates_during_checkpoint_activation_3_disc + , updates_during_checkpoint_activation_3_disc_only + ]. + +updates_during_checkpoint_activation_2_ram(suite) -> []; +updates_during_checkpoint_activation_2_ram(Config) when is_list(Config) -> + updates_during_checkpoint_activation(ram_copies, 2, Config). + +updates_during_checkpoint_activation_2_disc(suite) -> []; +updates_during_checkpoint_activation_2_disc(Config) when is_list(Config) -> + updates_during_checkpoint_activation(disc_copies, 2, Config). + +updates_during_checkpoint_activation_2_disc_only(suite) -> []; +updates_during_checkpoint_activation_2_disc_only(Config) when is_list(Config) -> + updates_during_checkpoint_activation(disc_only_copies, 2, Config). + +updates_during_checkpoint_activation_3_ram(suite) -> []; +updates_during_checkpoint_activation_3_ram(Config) when is_list(Config) -> + updates_during_checkpoint_activation(ram_copies, 3, Config). + +updates_during_checkpoint_activation_3_disc(suite) -> []; +updates_during_checkpoint_activation_3_disc(Config) when is_list(Config) -> + updates_during_checkpoint_activation(disc_copies, 3, Config). + +updates_during_checkpoint_activation_3_disc_only(suite) -> []; +updates_during_checkpoint_activation_3_disc_only(Config) when is_list(Config) -> + updates_during_checkpoint_activation(disc_only_copies, 3, Config). + +updates_during_checkpoint_activation(ReplicaType,NodeConfig,Config) -> + %%?verbose("updates_during_checkpoint_activation2 at ~p~n", [self()]), + Delay = 5, + Nodes = ?acquire_nodes(NodeConfig, Config), + Node1 = hd(Nodes), + %%?verbose("Mnesia info: ~p~n", [mnesia:info()]), + + {success, [A]} = ?start_activities([Node1]), + ?log("consistency_after_fallback with ~p on ~p~n", + [ReplicaType, Nodes]), + TpcbConfig = tpcb_config_dist(ReplicaType, NodeConfig, Nodes, Config), + %%TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes), + mnesia_tpcb:init(TpcbConfig), + A ! fun () -> mnesia_tpcb:run(TpcbConfig) end, + timer:sleep(timer:seconds(Delay)), + + {ok, CPName, _NodeList} = + mnesia:activate_checkpoint([{max, mnesia:system_info(tables)}]), + timer:sleep(timer:seconds(Delay)), + + %% Stop tpcb + ?verbose("Stopping TPC-B~n", []), + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + + ?match(ok, mnesia:backup_checkpoint(CPName, + updates_during_checkpoint_activation2)), + timer:sleep(timer:seconds(Delay)), + + ?match(ok, mnesia:install_fallback(updates_during_checkpoint_activation2)), + + %% Stop and then start mnesia and check table consistency + %%?verbose("Restarting Mnesia~n", []), + mnesia_test_lib:kill_mnesia(Nodes), + file:delete(updates_during_checkpoint_activation2), + mnesia_test_lib:start_mnesia(Nodes,[account,branch,teller, history]), + + ?match(ok, mnesia_tpcb:verify_tabs()), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +updates_during_checkpoint_iteration(doc) -> + ["Perform updates while someone is iterating over a checkpoint", + "and verify that the iterator really finds the expected data", + "regardless of ongoing upates."]; + +updates_during_checkpoint_iteration(suite) -> + [ + updates_during_checkpoint_iteration_2_ram, + updates_during_checkpoint_iteration_2_disc + , updates_during_checkpoint_iteration_2_disc_only + ]. + +updates_during_checkpoint_iteration_2_ram(suite) -> []; +updates_during_checkpoint_iteration_2_ram(Config) when is_list(Config) -> + updates_during_checkpoint_iteration(ram_copies, 2, Config). + +updates_during_checkpoint_iteration_2_disc(suite) -> []; +updates_during_checkpoint_iteration_2_disc(Config) when is_list(Config) -> + updates_during_checkpoint_iteration(disc_copies, 2, Config). + +updates_during_checkpoint_iteration_2_disc_only(suite) -> []; +updates_during_checkpoint_iteration_2_disc_only(Config) when is_list(Config) -> + updates_during_checkpoint_iteration(disc_only_copies, 2, Config). + +updates_during_checkpoint_iteration(ReplicaType,NodeConfig,Config) -> + %?verbose("updates_during_checkpoint_iteration2 at ~p~n", [self()]), + Delay = 5, + Nodes = ?acquire_nodes(NodeConfig, Config), + Node1 = hd(Nodes), + %?verbose("Mnesia info: ~p~n", [mnesia:info()]), + File = updates_during_checkpoint_iteration2, + {success, [A]} = ?start_activities([Node1]), + ?log("updates_during_checkpoint_iteration with ~p on ~p~n", + [ReplicaType, Nodes]), + TpcbConfig = tpcb_config_dist(ReplicaType, NodeConfig, Nodes, Config), + %%TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes), + TpcbConfigRec = list2rec(TpcbConfig, + record_info(fields,tab_config), + #tab_config{}), + mnesia_tpcb:init(TpcbConfig), + ?match(ok, mnesia_tpcb:verify_tabs()), + + {ok, CPName, _NodeList} = + mnesia:activate_checkpoint([{max, mnesia:system_info(tables)}, + {ram_overrides_dump,true}]), + A ! fun () -> mnesia:backup_checkpoint(CPName, File) end, + + do_changes_during_backup(TpcbConfigRec), + + ?match_receive({A,ok}), + + timer:sleep(timer:seconds(Delay)), + ?match(ok, mnesia:install_fallback(File)), + timer:sleep(timer:seconds(Delay)), + + ?match({error,{"Bad balance",_,_}}, mnesia_tpcb:verify_tabs()), + + mnesia_test_lib:kill_mnesia(Nodes), + mnesia_test_lib:start_mnesia(Nodes,[account,branch,teller, history]), + + ?match(ok, mnesia_tpcb:verify_tabs()), + + ?match(ok, file:delete(File)), + ?verify_mnesia(Nodes, []). + +do_changes_during_backup(TpcbConfig) -> + loop_branches(TpcbConfig#tab_config.n_branches, + TpcbConfig#tab_config.n_accounts_per_branch). + +loop_branches(N_br,N_acc) when N_br >= 1 -> + loop_accounts(N_br,N_acc), + loop_branches(N_br-1,N_acc); +loop_branches(_,_) -> done. + +loop_accounts(N_br, N_acc) when N_acc >= 1 -> + A = #account{id=N_acc, branch_id=N_br, balance = 4711}, + ok = mnesia:dirty_write(A), + loop_accounts(N_br, N_acc-1); + +loop_accounts(_,_) -> done. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +load_table_with_activated_checkpoint(doc) -> + ["Load a table with a checkpoint attached to it and verify that the", + "newly loaded replica also gets a checkpoint retainer attached to it", + "and that it is consistent with the original retainer."]; + +load_table_with_activated_checkpoint(suite) -> + [ + load_table_with_activated_checkpoint_ram, + load_table_with_activated_checkpoint_disc, + load_table_with_activated_checkpoint_disc_only + ]. + +load_table_with_activated_checkpoint_ram(suite) -> []; +load_table_with_activated_checkpoint_ram(Config) when is_list(Config) -> + load_table_with_activated_checkpoint(ram_copies, Config). + +load_table_with_activated_checkpoint_disc(suite) -> []; +load_table_with_activated_checkpoint_disc(Config) when is_list(Config) -> + load_table_with_activated_checkpoint(disc_copies, Config). + +load_table_with_activated_checkpoint_disc_only(suite) -> []; +load_table_with_activated_checkpoint_disc_only(Config) when is_list(Config) -> + load_table_with_activated_checkpoint(disc_only_copies, Config). + +load_table_with_activated_checkpoint(Type, Config) -> + Nodes = ?acquire_nodes(2, Config), + Node1 = hd(Nodes), + Tab = load_test, + Def = [{attributes, [key, value]}, + {Type, Nodes}], %% ??? important that RAM ??? + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 2, 42})), + ?match(ok, mnesia:dirty_write({Tab, 3, 256})), + + timer:sleep(timer:seconds(1)), + + {ok, CPName, _NodeList} = + mnesia:activate_checkpoint([{max, mnesia:system_info(tables)}, + {ram_overrides_dump,true}]), + + mnesia_test_lib:stop_mnesia([Node1]), + mnesia_test_lib:start_mnesia([Node1],[Tab]), + %%--- check, whether the checkpiont is attached to both replicas + {success, [A,B]} = ?start_activities(Nodes), + + A ! fun () -> + mnesia:table_info(Tab,checkpoints) + end, + ?match_receive({A,[CPName]}), + + B ! fun () -> + mnesia:table_info(Tab,checkpoints) + end, + ?match_receive({B,[CPName]}), + + %%--- check, whether both retainers are consistent + ?match(ok, mnesia:dirty_write({Tab, 1, 815})), + A ! fun () -> + mnesia:backup_checkpoint(CPName, load_table_a) + end, + ?match_receive({A,ok}), + B ! fun () -> + mnesia:backup_checkpoint(CPName, load_table_b) + end, + ?match_receive({B,ok}), + + Mod = mnesia_backup, %% Assume local files + List_a = view(load_table_a, Mod), + List_b = view(load_table_b, Mod), + + ?match(List_a, List_b), + + ?match(ok,file:delete(load_table_a)), + ?match(ok,file:delete(load_table_b)), + ?verify_mnesia(Nodes, []). + +view(Source, Mod) -> + View = fun(Item, Acc) -> + ?verbose("tab - item : ~p ~n",[Item]), + case Item of + {schema, Tab, Cs} -> %% Remove cookie information + NewCs = lists:keyreplace(cookie, 1, Cs, + {cookie, skip_cookie}), + Item2 = {schema, Tab, NewCs}, + {[Item], [Item2|Acc]}; + _ -> + {[Item], [Item|Acc]} + end + end, + {ok,TabList} = + mnesia:traverse_backup(Source, Mod, dummy, read_only, View, []), + lists:sort(TabList). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +add_table_copy_to_table_with_activated_checkpoint(doc) -> + ["Add a replica to a table with a checkpoint attached to it", + "and verify that the new replica also gets a checkpoint", + "retainer attached to it and that it is consistent with the", + "original retainer."]; + +add_table_copy_to_table_with_activated_checkpoint(suite) -> + [ + add_table_copy_to_table_with_activated_checkpoint_ram, + add_table_copy_to_table_with_activated_checkpoint_disc, + add_table_copy_to_table_with_activated_checkpoint_disc_only + ]. + +add_table_copy_to_table_with_activated_checkpoint_ram(suite) -> []; +add_table_copy_to_table_with_activated_checkpoint_ram(Config) when is_list(Config) -> + add_table_copy_to_table_with_activated_checkpoint(ram_copies, Config). + +add_table_copy_to_table_with_activated_checkpoint_disc(suite) -> []; +add_table_copy_to_table_with_activated_checkpoint_disc(Config) when is_list(Config) -> + add_table_copy_to_table_with_activated_checkpoint(disc_copies, Config). + +add_table_copy_to_table_with_activated_checkpoint_disc_only(suite) -> []; +add_table_copy_to_table_with_activated_checkpoint_disc_only(Config) when is_list(Config) -> + add_table_copy_to_table_with_activated_checkpoint(disc_only_copies, Config). + +add_table_copy_to_table_with_activated_checkpoint(Type,Config) -> + Nodes = ?acquire_nodes(2, Config), + %?verbose("NODES = ~p ~n",[Nodes]), + [Node1,Node2] = Nodes, + + Tab = add_test, + Def = [{attributes, [key, value]}, + {Type, [Node1]}], %% ??? important that RAM ??? + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 2, 42})), + ?match(ok, mnesia:dirty_write({Tab, 3, 256})), + + {ok, CPName, _NodeList} = + mnesia:activate_checkpoint([{max, mnesia:system_info(tables)}, + {ram_overrides_dump,true}]), + + ?match({atomic,ok},mnesia:add_table_copy(Tab,Node2,ram_copies)), + + %%--- check, whether the checkpiont is attached to both replicas + {success, [A,B]} = ?start_activities(Nodes), + + A ! fun () -> + mnesia:table_info(Tab,checkpoints) + end, + ?match_receive({A,[CPName]}), + + B ! fun () -> + mnesia:table_info(Tab,checkpoints) + end, + ?match_receive({B,[CPName]}), + + %%--- check, whether both retainers are consistent + + ?match(ok, mnesia:dirty_write({Tab, 1, 815})), + ?match(ok, mnesia:dirty_write({Tab, 2, 815})), + + A ! fun () -> + mnesia:backup_checkpoint(CPName, add_table_a) + end, + ?match_receive({A,ok}), + B ! fun () -> + mnesia:backup_checkpoint(CPName, add_table_b) + end, + ?match_receive({B,ok}), + + Mod = mnesia_backup, %% Assume local files + + List_a = view(add_table_a, Mod), + List_b = view(add_table_b, Mod), + + ?match(List_a, List_b), + + ?match(ok,file:delete(add_table_a)), + ?match(ok, file:delete(add_table_b)), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +backup_consistency(suite) -> + [ + interupted_install_fallback, + interupted_uninstall_fallback, + mnesia_down_during_backup_causes_switch, + mnesia_down_during_backup_causes_abort, + schema_transactions_during_backup + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +interupted_install_fallback(doc) -> + ["Verify that a interrupted install_fallback really", + "is performed on all nodes or none"]; + +interupted_install_fallback(suite) -> + [ + inst_fallback_process_dies, + fatal_when_inconsistency + ]. + +inst_fallback_process_dies(suite) -> + []; +inst_fallback_process_dies(Config) when is_list(Config) -> + ?is_debug_compiled, + + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + {success, [A,_B,_C]} = ?start_activities(Nodes), + + TestPid = self(), + DebugId = {mnesia_bup, fallback_receiver_loop, pre_swap}, + DebugFun = + fun(PrevContext, _EvalContext) -> + ?verbose("fallback_receiver_loop - pre_swap pid ~p #~p~n", + [self(),PrevContext]), + TestPid ! {self(),fallback_preswap}, + case receive_messages([fallback_continue]) of + [{TestPid,fallback_continue}] -> + ?deactivate_debug_fun(DebugId), + PrevContext+1 + end + end, + ?activate_debug_fun(DebugId, DebugFun, 1), + + Tab = install_table, + Def = [{attributes, [key, value]}, {disc_copies, Nodes}], + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match(ok, mnesia:dirty_write({Tab, 1, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 2, 42})), + ?match(ok, mnesia:dirty_write({Tab, 3, 256})), + + {ok, CPName, _NodeList} = + mnesia:activate_checkpoint([{max, mnesia:system_info(tables)}, + {ram_overrides_dump,true}]), + + ?match(ok, mnesia:backup_checkpoint(CPName, install_backup)), + + A ! fun() -> mnesia:install_fallback(install_backup) end, + [{AnsPid,fallback_preswap}] = receive_messages([fallback_preswap]), + exit(A, kill), + AnsPid ! {self(), fallback_continue}, + ?match_receive({'EXIT', A, killed}), + timer:sleep(2000), %% Wait till fallback is installed everywhere + + mnesia_test_lib:kill_mnesia(Nodes), + ?verbose("~n---->Mnesia is stopped everywhere<-----~n", []), + ?match([], mnesia_test_lib:start_mnesia(Nodes,[Tab])), + + check_data(Nodes, Tab), + ?match(ok, file:delete(install_backup)), + ?verify_mnesia(Nodes, []). + +check_data([N1 | R], Tab) -> + ?match([{Tab, 1, 4711}], rpc:call(N1, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 2, 42}], rpc:call(N1, mnesia, dirty_read, [{Tab, 2}])), + ?match([{Tab, 3, 256}], rpc:call(N1, mnesia, dirty_read, [{Tab, 3}])), + check_data(R, Tab); +check_data([], _Tab) -> + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +fatal_when_inconsistency(suite) -> + []; +fatal_when_inconsistency(Config) when is_list(Config) -> + ?is_debug_compiled, + + [Node1, Node2, Node3] = Nodes = + ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + {success, [A,_B,_C]} = ?start_activities(Nodes), + + TestPid = self(), + DebugId = {mnesia_bup, fallback_receiver_loop, pre_swap}, + DebugFun = + fun(PrevContext, _EvalContext) -> + ?verbose("fallback_receiver_loop - pre_swap pid ~p #~p~n", + [self(),PrevContext]), + TestPid ! {self(),fallback_preswap}, + case receive_messages([fallback_continue]) of + [{TestPid,fallback_continue}] -> + ?deactivate_debug_fun(DebugId), + PrevContext+1 + end + end, + ?activate_debug_fun(DebugId, DebugFun, 1), + + Tab = install_table, + Def = [{attributes, [key, value]}, {disc_copies, Nodes}], + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match(ok, mnesia:dirty_write({Tab, 1, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 2, 42})), + ?match(ok, mnesia:dirty_write({Tab, 3, 256})), + + {ok, CPName, _NodeList} = + mnesia:activate_checkpoint([{max, mnesia:system_info(tables)}, + {ram_overrides_dump,true}]), + + ?match(ok, mnesia:backup_checkpoint(CPName, install_backup)), + ?match(ok, mnesia:dirty_write({Tab, 2, 42424242})), + + A ! fun() -> + mnesia:install_fallback(install_backup) + end, + + [{AnsPid,fallback_preswap}] = receive_messages([fallback_preswap]), + exit(AnsPid, kill), %% Kill install-fallback on local node will + AnsPid ! {self(), fallback_continue}, + ?deactivate_debug_fun(DebugId), + + ?match_receive({A,{error,{"Cannot install fallback", + {'EXIT',AnsPid,killed}}}}), + mnesia_test_lib:kill_mnesia(Nodes), + ?verbose("EXPECTING FATAL from 2 nodes WITH CORE DUMP~n", []), + + ?match([], mnesia_test_lib:start_mnesia([Node1],[])), + is_running(Node1, yes), + ?match([{Node2, mnesia, _}], mnesia_test_lib:start_mnesia([Node2],[])), + is_running(Node2, no), + ?match([{Node3, mnesia, _}], mnesia_test_lib:start_mnesia([Node3],[])), + is_running(Node3, no), + mnesia_test_lib:kill_mnesia(Nodes), + + ?match(ok, mnesia:install_fallback(install_backup)), + mnesia_test_lib:start_mnesia(Nodes,[Tab]), + + check_data(Nodes, Tab), + + ?match(ok,file:delete(install_backup)), + ?verify_mnesia(Nodes, []). + +is_running(Node, Shouldbe) -> + timer:sleep(1000), + Running = rpc:call(Node, mnesia, system_info, [is_running]), + case Running of + Shouldbe -> ok; + _ -> is_running(Node, Shouldbe) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +interupted_uninstall_fallback(doc) -> + ["Verify that a interrupted uninstall_fallback really", + "is performed on all nodes or none"]; +interupted_uninstall_fallback(suite) -> + [ + after_delete + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +after_delete(doc) -> + ["interrupt the uninstall after deletion of ", + "fallback files - there shall be no fallback"]; +after_delete(suite) -> []; +after_delete(Config) when is_list(Config) -> + do_uninstall(Config, post_delete). + +%%%%%%%%%%%%%%%%%%%%%%%%% + +do_uninstall(Config,DebugPoint) -> + ?is_debug_compiled, + + Nodes = ?acquire_nodes(3, Config), + %%?verbose("NODES = ~p ~n",[Nodes]), + + {success, [P1,P2,P3]} = ?start_activities(Nodes), + + NP1 = node(P1), + NP2 = node(P2), + + {A,B,C} = case node() of + NP1 -> + %%?verbose("first case ~n"), + {P3,P2,P1}; + NP2 -> + %%?verbose("second case ~n"), + {P3, P1, P2}; + _ -> + { P1, P2, P3} + end, + + Node1 = node(A), + Node2 = node(B), + Node3 = node(C), + + ?verbose(" A pid:~p node:~p ~n",[A,Node1]), + ?verbose(" B pid:~p node:~p ~n",[B,Node2]), + ?verbose(" C pid:~p node:~p ~n",[C,Node3]), + + + TestPid = self(), + %%?verbose("TestPid : ~p~n",[TestPid]), + DebugId = {mnesia_bup, uninstall_fallback2, DebugPoint}, + DebugFun = fun(PrevContext, _EvalContext) -> + ?verbose("uninstall_fallback pid ~p #~p~n" + ,[self(),PrevContext]), + TestPid ! {self(),uninstall_predelete}, + case receive_messages([uninstall_continue]) of + [{TestPid,uninstall_continue}] -> + ?deactivate_debug_fun(DebugId), + %%?verbose("uninstall_fallback continues~n"), + PrevContext+1 + end + end, + ?remote_activate_debug_fun(Node1,DebugId, DebugFun, 1), + + Tab = install_table, + Def = [{attributes, [key, value]}, + {ram_copies, Nodes}], %% necessary to test different types ??? + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match(ok, mnesia:dirty_write({Tab, 1, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 2, 42})), + ?match(ok, mnesia:dirty_write({Tab, 3, 256})), + + {ok, CPName, _NodeList} = + mnesia:activate_checkpoint([{max, mnesia:system_info(tables)}, + {ram_overrides_dump,true}]), + + ?match(ok, mnesia:backup_checkpoint(CPName,install_backup)), + timer:sleep(timer:seconds(1)), + + A ! fun () -> + mnesia:install_fallback(install_backup) + end, + ?match_receive({A,ok}), + + A ! fun () -> + mnesia:uninstall_fallback() + end, + %% + %% catch the debug entry in mnesia and kill one Mnesia node + %% + + + [{AnsPid,uninstall_predelete}] = receive_messages([uninstall_predelete]), + + ?verbose("AnsPid : ~p~n",[AnsPid]), + + mnesia_test_lib:kill_mnesia([Node2]), + timer:sleep(timer:seconds(1)), + + AnsPid ! {self(),uninstall_continue}, + + ?match_receive({A,ok}), + + mnesia_test_lib:kill_mnesia(Nodes) , + mnesia_test_lib:start_mnesia(Nodes,[Tab]), + + A ! fun () -> + R1 = mnesia:dirty_read({Tab,1}), + R2 = mnesia:dirty_read({Tab,2}), + R3 = mnesia:dirty_read({Tab,3}), + {R1,R2,R3} + end, + ?match_receive({ A, {[],[],[]} }), + + B ! fun () -> + R1 = mnesia:dirty_read({Tab,1}), + R2 = mnesia:dirty_read({Tab,2}), + R3 = mnesia:dirty_read({Tab,3}), + {R1,R2,R3} + end, + ?match_receive({ B, {[],[],[]} }), + + C ! fun () -> + R1 = mnesia:dirty_read({Tab,1}), + R2 = mnesia:dirty_read({Tab,2}), + R3 = mnesia:dirty_read({Tab,3}), + {R1,R2,R3} + end, + ?match_receive({ C, {[],[],[]} }), + + ?match(ok,file:delete(install_backup)), + ?verify_mnesia(Nodes, []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +mnesia_down_during_backup_causes_switch(doc) -> + ["Verify that an ongoing backup is not disturbed", + "even if the node hosting the replica that currently", + "is being backup'ed is stopped. The backup utility", + "is expected to switch over to another replica and", + "fulfill the backup."]; +mnesia_down_during_backup_causes_switch(suite) -> + [ + cause_switch_before, + cause_switch_after + ]. + +%%%%%%%%%%%%%%% + +cause_switch_before(doc) -> + ["interrupt the backup before iterating the retainer"]; +cause_switch_before(suite) -> []; +cause_switch_before(Config) when is_list(Config) -> + do_something_during_backup(cause_switch,pre,Config). + +%%%%%%%%%%%%%%% + +cause_switch_after(doc) -> + ["interrupt the backup after iterating the retainer"]; +cause_switch_after(suite) -> []; +cause_switch_after(Config) when is_list(Config) -> + do_something_during_backup(cause_switch,post,Config). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +mnesia_down_during_backup_causes_abort(doc) -> + ["Verify that an ongoing backup is aborted nicely", + "without leaving any backup file if the last replica", + "of a table becomes unavailable due to a node down", + "or some crash."]; +mnesia_down_during_backup_causes_abort(suite) -> + [ + cause_abort_before, + cause_abort_after + ]. + +%%%%%%%%%%%%%%%%%% + +cause_abort_before(doc) -> + ["interrupt the backup before iterating the retainer"]; + +cause_abort_before(suite) -> []; +cause_abort_before(Config) when is_list(Config) -> + do_something_during_backup(cause_abort,pre,Config). + +%%%%%%%%%%%%%%%%%% + +cause_abort_after(doc) -> + ["interrupt the backup after iterating the retainer"]; + +cause_abort_after(suite) -> []; +cause_abort_after(Config) when is_list(Config) -> + do_something_during_backup(cause_abort,post,Config). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +schema_transactions_during_backup(doc) -> + ["Verify that an schema transactions does not", + "affect an ongoing backup."]; +schema_transactions_during_backup(suite) -> + [ + change_schema_before, + change_schema_after + ]. + +%%%%%%%%%%%%% + +change_schema_before(doc) -> + ["interrupt the backup before iterating the retainer"]; +change_schema_before(suite) -> []; +change_schema_before(Config) when is_list(Config) -> + do_something_during_backup(change_schema,pre,Config). + +%%%%%%%%%%%%%%%% + +change_schema_after(doc) -> + ["interrupt the backup after iterating the retainer"]; +change_schema_after(suite) -> []; +change_schema_after(Config) when is_list(Config) -> + do_something_during_backup(change_schema,post,Config). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +do_something_during_backup(Action,DebugPoint,Config) -> + ?is_debug_compiled, + + Nodes = ?acquire_nodes(3, Config), + + {success, [A,B,C]} = ?start_activities(Nodes), + + Node1 = node(A), + Node2 = node(B), + Node3 = node(C), + + TestPid = self(), + %%?verbose("TestPid : ~p~n",[TestPid]), + + Tab = interrupt_table, + Bak = interrupt_backup, + Def = [{attributes, [key, value]}, + {ram_copies, [Node2,Node3]}], + %% necessary to test different types ??? + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + + + DebugId = {mnesia_log, tab_copier, DebugPoint}, + DebugFun = fun(PrevContext, EvalContext) -> + ?verbose("interrupt backup pid ~p #~p ~n context ~p ~n" + ,[self(),PrevContext,EvalContext]), + TestPid ! {self(),interrupt_backup_pre}, + global:set_lock({{lock_for_backup, Tab}, self()}, + Nodes, + infinity), + + %%?verbose("interrupt backup - continues ~n"), + ?deactivate_debug_fun(DebugId), + PrevContext+1 + end, + ?remote_activate_debug_fun(Node1,DebugId, DebugFun, 1), + + ?match(ok, mnesia:dirty_write({Tab, 1, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 2, 42})), + ?match(ok, mnesia:dirty_write({Tab, 3, 256})), + + {ok, CPName, _NodeList} = + mnesia:activate_checkpoint([{max, mnesia:system_info(tables)}, + {ram_overrides_dump,true}]), + + A ! fun () -> + %%?verbose("node: ~p pid: ~p ~n",[node(),self()]), + mnesia:table_info(Tab,where_to_read) + end, + + ReadNode_a = receive { A, ReadNode_a_tmp } -> ReadNode_a_tmp end, + ?verbose("ReadNode ~p ~n",[ReadNode_a]), + + global:set_lock({{lock_for_backup, Tab}, self()}, Nodes, infinity), + + A ! fun () -> %% A shall perform the backup, so the test proc is + %% able to do further actions in between + mnesia:backup_checkpoint(CPName, Bak) + end, + + %% catch the debug function of mnesia, stop the backup process + %% kill the node ReadNode_a and continue the backup process + %% As there is a second replica of the table, the backup shall continue + + case receive_messages([interrupt_backup_pre]) of + [{_AnsPid,interrupt_backup_pre}] -> ok + end, + + case Action of + cause_switch -> + mnesia_test_lib:kill_mnesia([ReadNode_a]), + timer:sleep(timer:seconds(1)); + cause_abort -> + mnesia_test_lib:kill_mnesia([Node2,Node3]), + timer:sleep(timer:seconds(1)); + change_schema -> + Tab2 = second_interrupt_table, + Def2 = [{attributes, [key, value]}, + {ram_copies, Nodes}], + + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)) + end, + + %% AnsPid ! {self(),interrupt_backup_continue}, + global:del_lock({{lock_for_backup, Tab}, self()}, Nodes), + + case Action of + cause_abort -> + + %% answer of A when finishing the backup + ?match_receive({A,{error, _}}), + + ?match({error,{"Cannot install fallback",_}}, + mnesia:install_fallback(Bak)); + _ -> %% cause_switch, change_schema + + ?match_receive({A,ok}), %% answer of A when finishing the backup + + %% send a fun to that node where mnesia is still running + WritePid = case ReadNode_a of + Node2 -> C; %% node(C) == Node3 + Node3 -> B + end, + WritePid ! fun () -> + ?match(ok, mnesia:dirty_write({Tab, 1, 815})), + ?match(ok, mnesia:dirty_write({Tab, 2, 816})), + ok + end, + ?match_receive({ WritePid, ok }), + ?match(ok, mnesia:install_fallback(Bak)) + end, + + %% Stop and then start mnesia and check table consistency + %%?verbose("Restarting Mnesia~n", []), + mnesia_test_lib:kill_mnesia(Nodes), + mnesia_test_lib:start_mnesia(Nodes,[Tab]), + + case Action of + cause_switch -> + %% the backup should exist + cross_check_tables([A,B,C],Tab,{[{Tab,1,4711}], + [{Tab,2,42}], + [{Tab,3,256}] }), + ?match(ok,file:delete(Bak)); + cause_abort -> + %% the backup should NOT exist + cross_check_tables([A,B,C],Tab,{[],[],[]}), + %% file does not exist + ?match({error, _},file:delete(Bak)); + change_schema -> + %% the backup should exist + cross_check_tables([A,B,C],Tab,{[{Tab,1,4711}], + [{Tab,2,42}], + [{Tab,3,256}] }), + ?match(ok,file:delete(Bak)) + end, + ?verify_mnesia(Nodes, []). + +%% check the contents of the table +cross_check_tables([],_tab,_elements) -> ok; +cross_check_tables([Pid|Rest],Tab,{Val1,Val2,Val3}) -> + Pid ! fun () -> + R1 = mnesia:dirty_read({Tab,1}), + R2 = mnesia:dirty_read({Tab,2}), + R3 = mnesia:dirty_read({Tab,3}), + {R1,R2,R3} + end, + ?match_receive({ Pid, {Val1, Val2, Val3 } }), + cross_check_tables(Rest,Tab,{Val1,Val2,Val3} ). diff --git a/lib/mnesia/test/mnesia_cost.erl b/lib/mnesia/test/mnesia_cost.erl new file mode 100644 index 0000000000..54cb2b3064 --- /dev/null +++ b/lib/mnesia/test/mnesia_cost.erl @@ -0,0 +1,222 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_cost). +-compile(export_all). + +%% This code exercises the mnesia system and produces a bunch +%% of measurements on what various things cost + +-define(TIMES, 1000). %% set to at least 1000 when running for real !! + +%% This is the record we perform all ops on in this test + +-record(item, {a = 1234, + b = foobar, + c = "1.2.3.4", + d = {'Lennart', 'Hyland'}, + e = true + }). + +go() -> + go([node() | nodes()]). + +go(Nodes) when hd(Nodes) == node() -> + {ok, Out} = file:open("MNESIA_COST", write), + put(out, Out), + + rpc:multicall(Nodes, mnesia, lkill, []), + ok = mnesia:delete_schema(Nodes), + ok = mnesia:create_schema(Nodes), + rpc:multicall(Nodes, mnesia, start, []), + TabDef = [{attributes, record_info(fields, item)}], + {atomic, ok} = mnesia:create_table(item, TabDef), + + round("single ram copy", "no index"), + {atomic, ok} = mnesia:add_table_index(item, #item.e), + round("single ram copy", "One index"), + + {atomic, ok} = mnesia:add_table_index(item, #item.c), + round("single ram copy", "Two indexes"), + + {atomic, ok} = mnesia:del_table_index(item, #item.e), + {atomic, ok} = mnesia:del_table_index(item, #item.c), + + {atomic, ok} = mnesia:change_table_copy_type(item, node(), disc_copies), + round("single disc copy", "no index"), + + {atomic, ok} = mnesia:change_table_copy_type(item, node(), ram_copies), + + case length(Nodes) of + Len when Len < 2 -> + format("<WARNING> replication skipped. Too few nodes.", []); + _Len -> + N2 = lists:nth(2, Nodes), + {atomic, ok} = mnesia:add_table_copy(item, N2, ram_copies), + round("2 replicated ram copy", "no index") + end, + file:close(Out), + erase(out), + ok. + +round(Replication, Index) -> + run(Replication, Index, [write], + fun() -> mnesia:write(#item{}) end), + + + run(Replication, Index, [read], + fun() -> mnesia:read({item, 1234}) end), + + run(Replication, Index, [read, write], + fun() -> mnesia:read({item, 1234}), + mnesia:write(#item{}) end), + + run(Replication, Index, [wread, write], + fun() -> mnesia:wread({item, 1234}), + mnesia:write(#item{}) end), + + + run(Replication, Index, [match, write, write, write], + fun() -> mnesia:match_object({item, 1, '_', '_', '_', true}), + mnesia:write(#item{a =1}), + mnesia:write(#item{a =2}), + mnesia:write(#item{a =3}) end). + + +format(F, As) -> + io:format(get(out), F, As). + +run(What, OtherInfo, Ops, F) -> + run(t, What, OtherInfo, Ops, F). + +run(How, What, OtherInfo, Ops, F) -> + T1 = erlang:now(), + statistics(runtime), + do_times(How, ?TIMES, F), + {_, RunTime} = statistics(runtime), + T2 = erlang:now(), + RealTime = subtr(T1, T2), + report(How, What, OtherInfo, Ops, RunTime, RealTime). + +report(t, What, OtherInfo, Ops, RunTime, RealTime) -> + format("~s, ~s, transaction call ", [What, OtherInfo]), + format("Ops is ", []), + lists:foreach(fun(Op) -> format("~w-", [Op]) end, Ops), + + format("~n ~w/~w Millisecs/Trans ~w/~w MilliSecs/Operation ~n~n", + [RunTime/?TIMES, + RealTime/?TIMES, + RunTime/(?TIMES*length(Ops)), + RealTime/(?TIMES*length(Ops))]); + +report(dirty, What, OtherInfo, Ops, RunTime, RealTime) -> + format("~s, ~s, dirty calls ", [What, OtherInfo]), + format("Ops is ", []), + lists:foreach(fun(Op) -> format("~w-", [Op]) end, Ops), + + format("~n ~w/~w Millisecs/Bunch ~w/~w MilliSecs/Operation ~n~n", + [RunTime/?TIMES, + RealTime/?TIMES, + RunTime/(?TIMES*length(Ops)), + RealTime/(?TIMES*length(Ops))]). + + +subtr(Before, After) -> + E =(element(1,After)*1000000000000 + +element(2,After)*1000000+element(3,After)) - + (element(1,Before)*1000000000000 + +element(2,Before)*1000000+element(3,Before)), + E div 1000. + +do_times(t, I, F) -> + do_trans_times(I, F); +do_times(dirty, I, F) -> + do_dirty(I, F). + +do_trans_times(I, F) when I /= 0 -> + {atomic, _} = mnesia:transaction(F), + do_trans_times(I-1, F); +do_trans_times(_,_) -> ok. + +do_dirty(I, F) when I /= 0 -> + F(), + do_dirty(I-1, F); +do_dirty(_,_) -> ok. + + + +table_load([N1,N2| _ ] = Ns) -> + Nodes = [N1,N2], + rpc:multicall(Ns, mnesia, lkill, []), + ok = mnesia:delete_schema(Ns), + ok = mnesia:create_schema(Nodes), + rpc:multicall(Nodes, mnesia, start, []), + TabDef = [{disc_copies,[N1]},{ram_copies,[N2]}, + {attributes,record_info(fields,item)},{record_name,item}], + Tabs = [list_to_atom("tab" ++ integer_to_list(I)) || I <- lists:seq(1,400)], + + [mnesia:create_table(Tab,TabDef) || Tab <- Tabs], + +%% InitTab = fun(Tab) -> +%% mnesia:write_lock_table(Tab), +%% InitRec = fun(Key) -> mnesia:write(Tab,#item{a=Key},write) end, +%% lists:foreach(InitRec, lists:seq(1,100)) +%% end, +%% +%% {Time,{atomic,ok}} = timer:tc(mnesia,transaction, [fun() ->lists:foreach(InitTab, Tabs) end]), + mnesia:dump_log(), +%% io:format("Init took ~p msec ~n", [Time/1000]), + rpc:call(N2, mnesia, stop, []), timer:sleep(1000), + mnesia:stop(), timer:sleep(500), + %% Warmup + ok = mnesia:start([{no_table_loaders, 1}]), + timer:tc(mnesia, wait_for_tables, [Tabs, infinity]), + mnesia:dump_log(), + rpc:call(N2, mnesia, dump_log, []), + io:format("Initialized ~n",[]), + + mnesia:stop(), timer:sleep(1000), + ok = mnesia:start([{no_table_loaders, 1}]), + {T1, ok} = timer:tc(mnesia, wait_for_tables, [Tabs, infinity]), + io:format("Loading from disc with 1 loader ~p msec~n",[T1/1000]), + mnesia:stop(), timer:sleep(1000), + ok = mnesia:start([{no_table_loaders, 4}]), + {T2, ok} = timer:tc(mnesia, wait_for_tables, [Tabs, infinity]), + io:format("Loading from disc with 4 loader ~p msec~n",[T2/1000]), + + %% Warmup + rpc:call(N2, ?MODULE, remote_load, [Tabs,4]), + io:format("Initialized ~n",[]), + + + T3 = rpc:call(N2, ?MODULE, remote_load, [Tabs,1]), + io:format("Loading from net with 1 loader ~p msec~n",[T3/1000]), + + T4 = rpc:call(N2, ?MODULE, remote_load, [Tabs,4]), + io:format("Loading from net with 4 loader ~p msec~n",[T4/1000]), + + ok. + +remote_load(Tabs,Loaders) -> + ok = mnesia:start([{no_table_loaders, Loaders}]), +%% io:format("~p ~n", [mnesia_controller:get_info(500)]), + {Time, ok} = timer:tc(mnesia, wait_for_tables, [Tabs, infinity]), + timer:sleep(1000), mnesia:stop(), timer:sleep(1000), + Time. diff --git a/lib/mnesia/test/mnesia_dbn_meters.erl b/lib/mnesia/test/mnesia_dbn_meters.erl new file mode 100644 index 0000000000..feaf90ee75 --- /dev/null +++ b/lib/mnesia/test/mnesia_dbn_meters.erl @@ -0,0 +1,242 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% + +-module(mnesia_dbn_meters). +-export([ + start/0, + local_start/0, + distr_start/1, + start/3 + ]). + +-record(simple,{key,val=0}). +-define(key,1). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Configuration and start + +start() -> + local_start(), + distr_start(nodes()). + +local_start() -> + start(one_ram_only,[node()],some_meters()), + start(one_disc_only,[node()],some_meters()). + +distr_start([]) -> + local_only; +distr_start(OtherNodes) when is_list(OtherNodes) -> + start(ram_and_ram,[node()|OtherNodes],some_meters()), + start(disc_and_disc,[node()|OtherNodes],some_meters()). + +start(Config,Nodes,Meters) -> + Attrs = record_info(fields,simple), + Schema = [{name,simple},{type,set},{attributes,Attrs}] ++ config(Config,Nodes), + L = '====================', + io:format("~n~p dbn_meters: ~p ~p~nSchema = ~p.~n~n",[L,Config,L,Schema]), + ok = mnesia:delete_schema(Nodes), + ok = mnesia:create_schema(Nodes), + rpc:multicall(Nodes, mnesia, start, []), + {atomic,_} = mnesia:create_table(Schema), + lists:foreach(fun report_meter/1,Meters), + {atomic, ok} = mnesia:delete_table(simple), + rpc:multicall(Nodes, mnesia, stop, []), + ok. + +config(one_ram_only,[Single|_]) -> + [{ram_copies,[Single]}]; +config(ram_and_ram,[Master|[Slave|_]]) -> + [{ram_copies,[Master,Slave]}]; +config(one_disc_only,[Single|_]) -> + [{disc_copies,[Single]}]; +config(disc_and_disc,[Master|[Slave|_]]) -> + [{disc_copies,[Master,Slave]}]; +config(Config,Nodes) -> + io:format("<ERROR> Config ~p not supported or too few nodes ~p given~n",[Config,Nodes]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% The various DBN meters +some_meters() -> + [create, + open_safe_read, + open_dirty_read, + get_int, + open_update, + put_int, + put_int_and_copy, + dirty_put_int_and_copy, + start_trans, + commit_one_update, + delete, + dirty_delete + ]. + +report_meter(Meter) -> + Times = 100, + Micros = repeat_meter(Meter,{atomic,{0,ignore}},Times) div Times, + io:format("\t~-30w ~-10w micro seconds (mean of ~p repetitions)~n",[Meter,Micros,Times]). + +repeat_meter(_Meter,{atomic,{Micros,_Result}},0) -> + Micros; +repeat_meter(Meter,{atomic,{Micros,_Result}},Times) when Times > 0 -> + repeat_meter(Meter,catch meter(Meter),Times-1) + Micros; +repeat_meter(Meter,{aborted,Reason},Times) when Times > 0 -> + io:format("<ERROR>\t~-20w\t,aborted, because ~p~n",[Meter,Reason]), + 0; +repeat_meter(Meter,{'EXIT',Reason},Times) when Times > 0 -> + io:format("<ERROR>\t~-20w\tcrashed, because ~p~n",[Meter,Reason]), + 0. + +meter(create) -> + Key = 1, + mnesia:transaction(fun() -> mnesia:delete({simple,Key}) end), + Fun = fun() -> + BeforeT = erlang:now(), + R = mnesia:write(#simple{key=Key}), + AfterT = erlang:now(), + elapsed_time(BeforeT,AfterT,R) + end, + mnesia:transaction(Fun); + +meter(open_safe_read) -> + Key = 2, + mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end), + Fun = fun() -> + BeforeT = erlang:now(), + R = mnesia:read({simple,Key}), + AfterT = erlang:now(), + elapsed_time(BeforeT,AfterT,R) + end, + mnesia:transaction(Fun); + +meter(open_dirty_read) -> + Key = 21, + mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end), + Fun = fun() -> + BeforeT = erlang:now(), + R = mnesia:dirty_read({simple,Key}), + AfterT = erlang:now(), + elapsed_time(BeforeT,AfterT,R) + end, + mnesia:transaction(Fun); + +meter(get_int) -> + Key = 3, + mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end), + Fun = fun() -> + [Simple] = mnesia:read({simple,Key}), + BeforeT = erlang:now(), + Int = Simple#simple.val, + AfterT = erlang:now(), + elapsed_time(BeforeT,AfterT,Int) + end, + mnesia:transaction(Fun); + +meter(open_update) -> + Key = 3, + mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end), + Fun = fun() -> + BeforeT = erlang:now(), + R = mnesia:wread({simple,Key}), + AfterT = erlang:now(), + elapsed_time(BeforeT,AfterT,R) + end, + mnesia:transaction(Fun); + +meter(put_int) -> + Key = 4, + mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end), + Fun = fun() -> + [Simple] = mnesia:wread({simple,Key}), + BeforeT = erlang:now(), + R = Simple#simple{val=7}, + AfterT = erlang:now(), + elapsed_time(BeforeT,AfterT,R) + end, + mnesia:transaction(Fun); + +meter(put_int_and_copy) -> + Key = 5, + mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end), + Fun = fun() -> + [Simple] = mnesia:wread({simple,Key}), + BeforeT = erlang:now(), + Simple2 = Simple#simple{val=17}, + R = mnesia:write(Simple2), + AfterT = erlang:now(), + elapsed_time(BeforeT,AfterT,R) + end, + mnesia:transaction(Fun); + +meter(dirty_put_int_and_copy) -> + Key = 55, + mnesia:dirty_write(#simple{key=Key}), + [Simple] = mnesia:dirty_read({simple,Key}), + BeforeT = erlang:now(), + Simple2 = Simple#simple{val=17}, + R = mnesia:dirty_write(Simple2), + AfterT = erlang:now(), + {atomic,elapsed_time(BeforeT,AfterT,R)}; + +meter(start_trans) -> + BeforeT = erlang:now(), + {atomic,AfterT} = mnesia:transaction(fun() -> erlang:now() end), + {atomic,elapsed_time(BeforeT,AfterT,ok)}; + +meter(commit_one_update) -> + Key = 6, + mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end), + Fun = fun() -> + [Simple] = mnesia:wread({simple,Key}), + Simple2 = Simple#simple{val=27}, + _R = mnesia:write(Simple2), + erlang:now() + end, + {atomic,BeforeT} = mnesia:transaction(Fun), + AfterT = erlang:now(), + {atomic,elapsed_time(BeforeT,AfterT,ok)}; + +meter(delete) -> + Key = 7, + mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end), + Fun = fun() -> + BeforeT = erlang:now(), + R = mnesia:delete({simple,Key}), + AfterT = erlang:now(), + elapsed_time(BeforeT,AfterT,R) + end, + mnesia:transaction(Fun); + +meter(dirty_delete) -> + Key = 75, + mnesia:dirty_write(#simple{key=Key}), + BeforeT = erlang:now(), + R = mnesia:dirty_delete({simple,Key}), + AfterT = erlang:now(), + {atomic, elapsed_time(BeforeT,AfterT,R)}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Calculate the elapsed time +elapsed_time(BeforeT,AfterT,Result) -> + {(element(1,AfterT)*1000000000000 + +element(2,AfterT)*1000000+element(3,AfterT)) - + (element(1,BeforeT)*1000000000000 + +element(2,BeforeT)*1000000+element(3,BeforeT)),Result}. diff --git a/lib/mnesia/test/mnesia_dirty_access_test.erl b/lib/mnesia/test/mnesia_dirty_access_test.erl new file mode 100644 index 0000000000..5f9f2a9733 --- /dev/null +++ b/lib/mnesia/test/mnesia_dirty_access_test.erl @@ -0,0 +1,927 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_dirty_access_test). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Evil dirty access, regardless of transaction scope.", + "Invoke all functions in the API and try to cover all legal uses", + "cases as well the illegal dito. This is a complement to the", + "other more explicit test cases."]; +all(suite) -> + [ + dirty_write, + dirty_read, + dirty_update_counter, + dirty_delete, + dirty_delete_object, + dirty_match_object, + dirty_index, + dirty_iter, + admin_tests + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Write records dirty + +dirty_write(suite) -> + [ + dirty_write_ram, + dirty_write_disc, + dirty_write_disc_only + ]. + +dirty_write_ram(suite) -> []; +dirty_write_ram(Config) when is_list(Config) -> + dirty_write(Config, ram_copies). + +dirty_write_disc(suite) -> []; +dirty_write_disc(Config) when is_list(Config) -> + dirty_write(Config, disc_copies). + +dirty_write_disc_only(suite) -> []; +dirty_write_disc_only(Config) when is_list(Config) -> + dirty_write(Config, disc_only_copies). + +dirty_write(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_write, + Def = [{attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match({'EXIT', _}, mnesia:dirty_write([])), + ?match({'EXIT', _}, mnesia:dirty_write({Tab, 2})), + ?match({'EXIT', _}, mnesia:dirty_write({foo, 2})), + ?match(ok, mnesia:dirty_write({Tab, 1, 2})), + + ?match({atomic, ok}, mnesia:transaction(fun() -> + mnesia:dirty_write({Tab, 1, 2}) end)), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Read records dirty + +dirty_read(suite) -> + [ + dirty_read_ram, + dirty_read_disc, + dirty_read_disc_only + ]. + +dirty_read_ram(suite) -> []; +dirty_read_ram(Config) when is_list(Config) -> + dirty_read(Config, ram_copies). + +dirty_read_disc(suite) -> []; +dirty_read_disc(Config) when is_list(Config) -> + dirty_read(Config, disc_copies). + +dirty_read_disc_only(suite) -> []; +dirty_read_disc_only(Config) when is_list(Config) -> + dirty_read(Config, disc_only_copies). + +dirty_read(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_read, + Def = [{type, bag}, {attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match({'EXIT', _}, mnesia:dirty_read([])), + ?match({'EXIT', _}, mnesia:dirty_read({Tab})), + ?match({'EXIT', _}, mnesia:dirty_read({Tab, 1, 2})), + ?match([], mnesia:dirty_read({Tab, 1})), + ?match(ok, mnesia:dirty_write({Tab, 1, 2})), + ?match([{Tab, 1, 2}], mnesia:dirty_read({Tab, 1})), + ?match(ok, mnesia:dirty_write({Tab, 1, 3})), + ?match([{Tab, 1, 2}, {Tab, 1, 3}], mnesia:dirty_read({Tab, 1})), + + ?match({atomic, [{Tab, 1, 2}, {Tab, 1, 3}]}, + mnesia:transaction(fun() -> mnesia:dirty_read({Tab, 1}) end)), + + ?match(false, mnesia:async_dirty(fun() -> mnesia:is_transaction() end)), + ?match(false, mnesia:sync_dirty(fun() -> mnesia:is_transaction() end)), + ?match(false, mnesia:ets(fun() -> mnesia:is_transaction() end)), + ?match(false, mnesia:activity(async_dirty, fun() -> mnesia:is_transaction() end)), + ?match(false, mnesia:activity(sync_dirty, fun() -> mnesia:is_transaction() end)), + ?match(false, mnesia:activity(ets, fun() -> mnesia:is_transaction() end)), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Update counter record dirty + +dirty_update_counter(suite) -> + [ + dirty_update_counter_ram, + dirty_update_counter_disc, + dirty_update_counter_disc_only + ]. + +dirty_update_counter_ram(suite) -> []; +dirty_update_counter_ram(Config) when is_list(Config) -> + dirty_update_counter(Config, ram_copies). + +dirty_update_counter_disc(suite) -> []; +dirty_update_counter_disc(Config) when is_list(Config) -> + dirty_update_counter(Config, disc_copies). + +dirty_update_counter_disc_only(suite) -> []; +dirty_update_counter_disc_only(Config) when is_list(Config) -> + dirty_update_counter(Config, disc_only_copies). + +dirty_update_counter(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_update_counter, + Def = [{attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, 2})), + + ?match({'EXIT', _}, mnesia:dirty_update_counter({Tab, 1}, [])), + ?match({'EXIT', _}, mnesia:dirty_update_counter({Tab}, 3)), + ?match({'EXIT', _}, mnesia:dirty_update_counter({foo, 1}, 3)), + ?match(5, mnesia:dirty_update_counter({Tab, 1}, 3)), + ?match([{Tab, 1, 5}], mnesia:dirty_read({Tab, 1})), + + ?match({atomic, 8}, mnesia:transaction(fun() -> + mnesia:dirty_update_counter({Tab, 1}, 3) end)), + + ?match(1, mnesia:dirty_update_counter({Tab, foo}, 1)), + ?match([{Tab, foo,1}], mnesia:dirty_read({Tab,foo})), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Delete record dirty + +dirty_delete(suite) -> + [ + dirty_delete_ram, + dirty_delete_disc, + dirty_delete_disc_only + ]. + +dirty_delete_ram(suite) -> []; +dirty_delete_ram(Config) when is_list(Config) -> + dirty_delete(Config, ram_copies). + +dirty_delete_disc(suite) -> []; +dirty_delete_disc(Config) when is_list(Config) -> + dirty_delete(Config, disc_copies). + +dirty_delete_disc_only(suite) -> []; +dirty_delete_disc_only(Config) when is_list(Config) -> + dirty_delete(Config, disc_only_copies). + +dirty_delete(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_delete, + Def = [{type, bag}, {attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match({'EXIT', _}, mnesia:dirty_delete([])), + ?match({'EXIT', _}, mnesia:dirty_delete({Tab})), + ?match({'EXIT', _}, mnesia:dirty_delete({Tab, 1, 2})), + ?match(ok, mnesia:dirty_delete({Tab, 1})), + ?match(ok, mnesia:dirty_write({Tab, 1, 2})), + ?match(ok, mnesia:dirty_delete({Tab, 1})), + ?match(ok, mnesia:dirty_write({Tab, 1, 2})), + ?match(ok, mnesia:dirty_write({Tab, 1, 2})), + ?match(ok, mnesia:dirty_delete({Tab, 1})), + + ?match(ok, mnesia:dirty_write({Tab, 1, 2})), + ?match({atomic, ok}, mnesia:transaction(fun() -> + mnesia:dirty_delete({Tab, 1}) end)), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Delete matching record dirty + +dirty_delete_object(suite) -> + [ + dirty_delete_object_ram, + dirty_delete_object_disc, + dirty_delete_object_disc_only + ]. + +dirty_delete_object_ram(suite) -> []; +dirty_delete_object_ram(Config) when is_list(Config) -> + dirty_delete_object(Config, ram_copies). + +dirty_delete_object_disc(suite) -> []; +dirty_delete_object_disc(Config) when is_list(Config) -> + dirty_delete_object(Config, disc_copies). + +dirty_delete_object_disc_only(suite) -> []; +dirty_delete_object_disc_only(Config) when is_list(Config) -> + dirty_delete_object(Config, disc_only_copies). + +dirty_delete_object(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_delete_object, + Def = [{type, bag}, {attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + OneRec = {Tab, 1, 2}, + ?match({'EXIT', _}, mnesia:dirty_delete_object([])), + ?match({'EXIT', _}, mnesia:dirty_delete_object({Tab})), + ?match({'EXIT', _}, mnesia:dirty_delete_object({Tab, 1})), + ?match(ok, mnesia:dirty_delete_object(OneRec)), + ?match(ok, mnesia:dirty_write(OneRec)), + ?match(ok, mnesia:dirty_delete_object(OneRec)), + ?match(ok, mnesia:dirty_write(OneRec)), + ?match(ok, mnesia:dirty_write(OneRec)), + ?match(ok, mnesia:dirty_delete_object(OneRec)), + + ?match(ok, mnesia:dirty_write(OneRec)), + ?match({atomic, ok}, mnesia:transaction(fun() -> + mnesia:dirty_delete_object(OneRec) end)), + + ?match({'EXIT', {aborted, {bad_type, Tab, _}}}, mnesia:dirty_delete_object(Tab, {Tab, {['_']}, 21})), + ?match({'EXIT', {aborted, {bad_type, Tab, _}}}, mnesia:dirty_delete_object(Tab, {Tab, {['$5']}, 21})), + + ?verify_mnesia(Nodes, []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Read matching records dirty + +dirty_match_object(suite) -> + [ + dirty_match_object_ram, + dirty_match_object_disc, + dirty_match_object_disc_only + ]. + +dirty_match_object_ram(suite) -> []; +dirty_match_object_ram(Config) when is_list(Config) -> + dirty_match_object(Config, ram_copies). + +dirty_match_object_disc(suite) -> []; +dirty_match_object_disc(Config) when is_list(Config) -> + dirty_match_object(Config, disc_copies). + +dirty_match_object_disc_only(suite) -> []; +dirty_match_object_disc_only(Config) when is_list(Config) -> + dirty_match_object(Config, disc_only_copies). + +dirty_match_object(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_match, + Def = [{attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + OneRec = {Tab, 1, 2}, + OnePat = {Tab, '$1', 2}, + ?match([], mnesia:dirty_match_object(OnePat)), + ?match(ok, mnesia:dirty_write(OneRec)), + ?match([OneRec], mnesia:dirty_match_object(OnePat)), + ?match({atomic, [OneRec]}, mnesia:transaction(fun() -> + mnesia:dirty_match_object(OnePat) end)), + + ?match({'EXIT', _}, mnesia:dirty_match_object({foo, '$1', 2})), + ?match({'EXIT', _}, mnesia:dirty_match_object({[], '$1', 2})), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +dirty_index(suite) -> + [ + dirty_index_match_object, + dirty_index_read, + dirty_index_update + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Dirty read matching records by using an index + +dirty_index_match_object(suite) -> + [ + dirty_index_match_object_ram, + dirty_index_match_object_disc, + dirty_index_match_object_disc_only + ]. + +dirty_index_match_object_ram(suite) -> []; +dirty_index_match_object_ram(Config) when is_list(Config) -> + dirty_index_match_object(Config, ram_copies). + +dirty_index_match_object_disc(suite) -> []; +dirty_index_match_object_disc(Config) when is_list(Config) -> + dirty_index_match_object(Config, disc_copies). + +dirty_index_match_object_disc_only(suite) -> []; +dirty_index_match_object_disc_only(Config) when is_list(Config) -> + dirty_index_match_object(Config, disc_only_copies). + +dirty_index_match_object(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_index_match_object, + ValPos = 3, + BadValPos = ValPos + 1, + Def = [{attributes, [k, v]}, {Storage, [Node1]}, {index, [ValPos]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match([], mnesia:dirty_index_match_object({Tab, '$1', 2}, ValPos)), + OneRec = {Tab, 1, 2}, + ?match(ok, mnesia:dirty_write(OneRec)), + + ?match([OneRec], mnesia:dirty_index_match_object({Tab, '$1', 2}, ValPos)), + ?match({'EXIT', _}, mnesia:dirty_index_match_object({Tab, '$1', 2}, BadValPos)), + ?match({'EXIT', _}, mnesia:dirty_index_match_object({foo, '$1', 2}, ValPos)), + ?match({'EXIT', _}, mnesia:dirty_index_match_object({[], '$1', 2}, ValPos)), + ?match({atomic, [OneRec]}, mnesia:transaction(fun() -> + mnesia:dirty_index_match_object({Tab, '$1', 2}, ValPos) end)), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Read records by using an index + +dirty_index_read(suite) -> + [ + dirty_index_read_ram, + dirty_index_read_disc, + dirty_index_read_disc_only + ]. + +dirty_index_read_ram(suite) -> []; +dirty_index_read_ram(Config) when is_list(Config) -> + dirty_index_read(Config, ram_copies). + +dirty_index_read_disc(suite) -> []; +dirty_index_read_disc(Config) when is_list(Config) -> + dirty_index_read(Config, disc_copies). + +dirty_index_read_disc_only(suite) -> []; +dirty_index_read_disc_only(Config) when is_list(Config) -> + dirty_index_read(Config, disc_only_copies). + +dirty_index_read(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_index_read, + ValPos = 3, + BadValPos = ValPos + 1, + Def = [{type, set}, + {attributes, [k, v]}, + {Storage, [Node1]}, + {index, [ValPos]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + OneRec = {Tab, 1, 2}, + ?match([], mnesia:dirty_index_read(Tab, 2, ValPos)), + ?match(ok, mnesia:dirty_write(OneRec)), + ?match([OneRec], mnesia:dirty_index_read(Tab, 2, ValPos)), + ?match({atomic, [OneRec]}, + mnesia:transaction(fun() -> mnesia:dirty_index_read(Tab, 2, ValPos) end)), + ?match(42, mnesia:dirty_update_counter({Tab, 1}, 40)), + ?match([{Tab,1,42}], mnesia:dirty_read({Tab, 1})), + ?match([], mnesia:dirty_index_read(Tab, 2, ValPos)), + ?match([{Tab, 1, 42}], mnesia:dirty_index_read(Tab, 42, ValPos)), + + ?match({'EXIT', _}, mnesia:dirty_index_read(Tab, 2, BadValPos)), + ?match({'EXIT', _}, mnesia:dirty_index_read(foo, 2, ValPos)), + ?match({'EXIT', _}, mnesia:dirty_index_read([], 2, ValPos)), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +dirty_index_update(suite) -> + [ + dirty_index_update_set_ram, + dirty_index_update_set_disc, + dirty_index_update_set_disc_only, + dirty_index_update_bag_ram, + dirty_index_update_bag_disc, + dirty_index_update_bag_disc_only + ]; +dirty_index_update(doc) -> + ["See Ticket OTP-2083, verifies that a table with a index is " + "update in the correct way i.e. the index finds the correct " + "records after a update"]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +dirty_index_update_set_ram(suite) -> []; +dirty_index_update_set_ram(Config) when is_list(Config) -> + dirty_index_update_set(Config, ram_copies). + +dirty_index_update_set_disc(suite) -> []; +dirty_index_update_set_disc(Config) when is_list(Config) -> + dirty_index_update_set(Config, disc_copies). + +dirty_index_update_set_disc_only(suite) -> []; +dirty_index_update_set_disc_only(Config) when is_list(Config) -> + dirty_index_update_set(Config, disc_only_copies). + +dirty_index_update_set(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = index_test, + ValPos = v1, + ValPos2 = v3, + Def = [{attributes, [k, v1, v2, v3]}, + {Storage, [Node1]}, + {index, [ValPos]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + Pat1 = {Tab, '$1', 2, '$2', '$3'}, + Pat2 = {Tab, '$1', '$2', '$3', '$4'}, + + Rec1 = {Tab, 1, 2, 3, 4}, + Rec2 = {Tab, 2, 2, 13, 14}, + Rec3 = {Tab, 1, 12, 13, 14}, + Rec4 = {Tab, 4, 2, 13, 14}, + + ?match([], mnesia:dirty_index_read(Tab, 2, ValPos)), + ?match(ok, mnesia:dirty_write(Rec1)), + ?match([Rec1], mnesia:dirty_index_read(Tab, 2, ValPos)), + + ?match(ok, mnesia:dirty_write(Rec2)), + R1 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec2], lists:sort(R1)), + + ?match(ok, mnesia:dirty_write(Rec3)), + R2 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec2], lists:sort(R2)), + ?match([Rec2], mnesia:dirty_index_match_object(Pat1, ValPos)), + + {atomic, R3} = mnesia:transaction(fun() -> mnesia:match_object(Pat2) end), + ?match([Rec3, Rec2], lists:sort(R3)), + + ?match(ok, mnesia:dirty_write(Rec4)), + R4 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec2, Rec4], lists:sort(R4)), + + ?match(ok, mnesia:dirty_delete({Tab, 4})), + ?match([Rec2], mnesia:dirty_index_read(Tab, 2, ValPos)), + + ?match({atomic, ok}, mnesia:del_table_index(Tab, ValPos)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec4) end)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos2)), + + R5 = mnesia:dirty_match_object(Pat2), + ?match([Rec3, Rec2, Rec4], lists:sort(R5)), + + R6 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec2, Rec4], lists:sort(R6)), + ?match([], mnesia:dirty_index_read(Tab, 4, ValPos2)), + R7 = mnesia:dirty_index_read(Tab, 14, ValPos2), + ?match([Rec3, Rec2, Rec4], lists:sort(R7)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec1) end)), + R8 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec2, Rec4], lists:sort(R8)), + ?match([Rec1], mnesia:dirty_index_read(Tab, 4, ValPos2)), + R9 = mnesia:dirty_index_read(Tab, 14, ValPos2), + ?match([Rec2, Rec4], lists:sort(R9)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec2) end)), + R10 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec4], lists:sort(R10)), + ?match([Rec1], mnesia:dirty_index_read(Tab, 4, ValPos2)), + ?match([Rec4], mnesia:dirty_index_read(Tab, 14, ValPos2)), + + ?match(ok, mnesia:dirty_delete({Tab, 4})), + R11 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1], lists:sort(R11)), + ?match([Rec1], mnesia:dirty_index_read(Tab, 4, ValPos2)), + ?match([], mnesia:dirty_index_read(Tab, 14, ValPos2)), + + ?verify_mnesia(Nodes, []). + +dirty_index_update_bag_ram(suite) -> []; +dirty_index_update_bag_ram(Config)when is_list(Config) -> + dirty_index_update_bag(Config, ram_copies). + +dirty_index_update_bag_disc(suite) -> []; +dirty_index_update_bag_disc(Config)when is_list(Config) -> + dirty_index_update_bag(Config, disc_copies). + +dirty_index_update_bag_disc_only(suite) -> []; +dirty_index_update_bag_disc_only(Config)when is_list(Config) -> + dirty_index_update_bag(Config, disc_only_copies). + +dirty_index_update_bag(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = index_test, + ValPos = v1, + ValPos2 = v3, + Def = [{type, bag}, + {attributes, [k, v1, v2, v3]}, + {Storage, [Node1]}, + {index, [ValPos]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + Pat1 = {Tab, '$1', 2, '$2', '$3'}, + Pat2 = {Tab, '$1', '$2', '$3', '$4'}, + + Rec1 = {Tab, 1, 2, 3, 4}, + Rec2 = {Tab, 2, 2, 13, 14}, + Rec3 = {Tab, 1, 12, 13, 14}, + Rec4 = {Tab, 4, 2, 13, 4}, + Rec5 = {Tab, 1, 2, 234, 14}, + + %% Simple Index + ?match([], mnesia:dirty_index_read(Tab, 2, ValPos)), + ?match(ok, mnesia:dirty_write(Rec1)), + ?match([Rec1], mnesia:dirty_index_read(Tab, 2, ValPos)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec2) end)), + R1 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec2], lists:sort(R1)), + + ?match(ok, mnesia:dirty_write(Rec3)), + R2 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec2], lists:sort(R2)), + + R3 = mnesia:dirty_index_match_object(Pat1, ValPos), + ?match([Rec1, Rec2], lists:sort(R3)), + + R4 = mnesia:dirty_match_object(Pat2), + ?match([Rec1, Rec3, Rec2], lists:sort(R4)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec4) end)), + R5 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec2, Rec4], lists:sort(R5)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete({Tab, 4}) end)), + R6 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec2], lists:sort(R6)), + + ?match(ok, mnesia:dirty_delete_object(Rec1)), + ?match([Rec2], mnesia:dirty_index_read(Tab, 2, ValPos)), + R7 = mnesia:dirty_match_object(Pat2), + ?match([Rec3, Rec2], lists:sort(R7)), + + %% Two indexies + ?match({atomic, ok}, mnesia:del_table_index(Tab, ValPos)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec1) end)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec4) end)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos2)), + + R8 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec2, Rec4], lists:sort(R8)), + + R9 = mnesia:dirty_index_read(Tab, 4, ValPos2), + ?match([Rec1, Rec4], lists:sort(R9)), + R10 = mnesia:dirty_index_read(Tab, 14, ValPos2), + ?match([Rec3, Rec2], lists:sort(R10)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec5) end)), + R11 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec1, Rec5, Rec2, Rec4], lists:sort(R11)), + R12 = mnesia:dirty_index_read(Tab, 4, ValPos2), + ?match([Rec1, Rec4], lists:sort(R12)), + R13 = mnesia:dirty_index_read(Tab, 14, ValPos2), + ?match([Rec5, Rec3, Rec2], lists:sort(R13)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec1) end)), + R14 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec5, Rec2, Rec4], lists:sort(R14)), + ?match([Rec4], mnesia:dirty_index_read(Tab, 4, ValPos2)), + R15 = mnesia:dirty_index_read(Tab, 14, ValPos2), + ?match([Rec5, Rec3, Rec2], lists:sort(R15)), + + ?match(ok, mnesia:dirty_delete_object(Rec5)), + R16 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec2, Rec4], lists:sort(R16)), + ?match([Rec4], mnesia:dirty_index_read(Tab, 4, ValPos2)), + R17 = mnesia:dirty_index_read(Tab, 14, ValPos2), + ?match([Rec3, Rec2], lists:sort(R17)), + + ?match(ok, mnesia:dirty_write(Rec1)), + ?match(ok, mnesia:dirty_delete({Tab, 1})), + R18 = mnesia:dirty_index_read(Tab, 2, ValPos), + ?match([Rec2, Rec4], lists:sort(R18)), + ?match([Rec4], mnesia:dirty_index_read(Tab, 4, ValPos2)), + R19 = mnesia:dirty_index_read(Tab, 14, ValPos2), + ?match([Rec2], lists:sort(R19)), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Dirty iteration +%% dirty_slot, dirty_first, dirty_next + +dirty_iter(suite) -> + [ + dirty_iter_ram, + dirty_iter_disc, + dirty_iter_disc_only + ]. + +dirty_iter_ram(suite) -> []; +dirty_iter_ram(Config) when is_list(Config) -> + dirty_iter(Config, ram_copies). + +dirty_iter_disc(suite) -> []; +dirty_iter_disc(Config) when is_list(Config) -> + dirty_iter(Config, disc_copies). + +dirty_iter_disc_only(suite) -> []; +dirty_iter_disc_only(Config) when is_list(Config) -> + dirty_iter(Config, disc_only_copies). + +dirty_iter(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = dirty_iter, + Def = [{type, bag}, {attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match([], all_slots(Tab)), + ?match([], all_nexts(Tab)), + + Keys = lists:seq(1, 5), + Records = [{Tab, A, B} || A <- Keys, B <- lists:seq(1, 2)], + lists:foreach(fun(Rec) -> ?match(ok, mnesia:dirty_write(Rec)) end, Records), + + SortedRecords = lists:sort(Records), + ?match(SortedRecords, lists:sort(all_slots(Tab))), + ?match(Keys, lists:sort(all_nexts(Tab))), + + ?match({'EXIT', _}, mnesia:dirty_first(foo)), + ?match({'EXIT', _}, mnesia:dirty_next(foo, foo)), + ?match({'EXIT', _}, mnesia:dirty_slot(foo, 0)), + ?match({'EXIT', _}, mnesia:dirty_slot(foo, [])), + ?match({atomic, Keys}, + mnesia:transaction(fun() -> lists:sort(all_nexts(Tab)) end)), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Returns a list of all keys in table +all_slots(Tab) -> + all_slots(Tab, [], 0). + +all_slots(_Tab, '$end_of_table', _) -> + []; +all_slots(Tab, PrevRecords, PrevSlot) -> + Records = mnesia:dirty_slot(Tab, PrevSlot), + PrevRecords ++ all_slots(Tab, Records, PrevSlot + 1). + +%% Returns a list of all keys in table + +all_nexts(Tab) -> + FirstKey = mnesia:dirty_first(Tab), + all_nexts(Tab, FirstKey). + +all_nexts(_Tab, '$end_of_table') -> + []; +all_nexts(Tab, PrevKey) -> + Key = mnesia:dirty_next(Tab, PrevKey), + [PrevKey] ++ all_nexts(Tab, Key). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +admin_tests(doc) -> + ["Verifies that dirty operations work during schema operations"]; + +admin_tests(suite) -> + [del_table_copy_1, + del_table_copy_2, + del_table_copy_3, + add_table_copy_1, + add_table_copy_2, + add_table_copy_3, + add_table_copy_4, + move_table_copy_1, + move_table_copy_2, + move_table_copy_3, + move_table_copy_4]. + +update_trans(Tab, Key, Acc) -> + Update = + fun() -> + Res = (catch mnesia:read({Tab, Key})), + case Res of + [{Tab, Key, Extra, Acc}] -> + mnesia:write({Tab,Key,Extra, Acc+1}); + Val -> + {read, Val, {acc, Acc}} + end + end, + receive + {Pid, quit} -> Pid ! {self(), Acc} + after + 3 -> + case catch mnesia:sync_dirty(Update) of + ok -> + update_trans(Tab, Key, Acc+1); + Else -> + ?error("Dirty Operation failed on ~p (update no ~p) with ~p~n" + "Info w2read ~p w2write ~p w2commit ~p storage ~p ~n", + [node(), + Acc, + Else, + mnesia:table_info(Tab, where_to_read), + mnesia:table_info(Tab, where_to_write), + mnesia:table_info(Tab, where_to_commit), + mnesia:table_info(Tab, storage_type)]) + end + end. + +del_table_copy_1(suite) -> []; +del_table_copy_1(Config) when is_list(Config) -> + [_Node1, Node2, _Node3] = Nodes = ?acquire_nodes(3, Config), + del_table(Node2, Node2, Nodes). %Called on same Node as deleted +del_table_copy_2(suite) -> []; +del_table_copy_2(Config) when is_list(Config) -> + [Node1, Node2, _Node3] = Nodes = ?acquire_nodes(3, Config), + del_table(Node1, Node2, Nodes). %Called from other Node +del_table_copy_3(suite) -> []; +del_table_copy_3(Config) when is_list(Config) -> + [_Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + del_table(Node3, Node2, Nodes). %Called from Node w.o. table + +del_table(CallFrom, DelNode, [Node1, Node2, Node3]) -> + Tab = schema_ops, + Def = [{disc_only_copies, [Node1]}, {ram_copies, [Node2]}, + {attributes, [key, attr1, attr2]}], + ?log("Test case removing table from ~w, with ~w~n", [DelNode, Def]), + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 1000), + + Pid1 = spawn_link(Node1, ?MODULE, update_trans, [Tab, 1, 0]), + Pid2 = spawn_link(Node2, ?MODULE, update_trans, [Tab, 2, 0]), + Pid3 = spawn_link(Node3, ?MODULE, update_trans, [Tab, 3, 0]), + + + dbg:tracer(process, {fun(Msg,_) -> tracer(Msg) end, void}), + %% dbg:n(Node2), + %% dbg:n(Node3), + %% dbg:tp('_', []), + %% dbg:tpl(dets, [timestamp]), + dbg:p(Pid1, [m,c,timestamp]), + + ?match({atomic, ok}, + rpc:call(CallFrom, mnesia, del_table_copy, [Tab, DelNode])), + + Pid1 ! {self(), quit}, R1 = + receive {Pid1, Res1} -> Res1 + after + 5000 -> io:format("~p~n",[process_info(Pid1)]),error + end, + Pid2 ! {self(), quit}, R2 = + receive {Pid2, Res2} -> Res2 + after + 5000 -> error + end, + Pid3 ! {self(), quit}, R3 = + receive {Pid3, Res3} -> Res3 + after + 5000 -> error + end, + verify_oids(Tab, Node1, Node2, Node3, R1, R2, R3), + ?verify_mnesia([Node1, Node2, Node3], []). + +tracer({trace_ts, _, send, Msg, Pid, {_,S,Ms}}) -> + io:format("~p:~p ~p >> ~w ~n",[S,Ms,Pid,Msg]); +tracer({trace_ts, _, 'receive', Msg, {_,S,Ms}}) -> + io:format("~p:~p << ~w ~n",[S,Ms,Msg]); + + +tracer(Msg) -> + io:format("UMsg ~p ~n",[Msg]), + ok. + + + +add_table_copy_1(suite) -> []; +add_table_copy_1(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{ram_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + add_table(Node1, Node3, Nodes, Def). +%% Not so much diff from 1 but I got a feeling of a bug +%% should behave exactly the same but just checking the internal ordering +add_table_copy_2(suite) -> []; +add_table_copy_2(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{ram_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + add_table(Node2, Node3, Nodes, Def). +add_table_copy_3(suite) -> []; +add_table_copy_3(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{ram_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + add_table(Node3, Node3, Nodes, Def). +add_table_copy_4(suite) -> []; +add_table_copy_4(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_only_copies, [Node1]}, + {attributes, [key, attr1, attr2]}], + add_table(Node2, Node3, Nodes, Def). + +add_table(CallFrom, AddNode, [Node1, Node2, Node3], Def) -> + ?log("Test case adding table at ~w, with ~w~n", [AddNode, Def]), + Tab = schema_ops, + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 1002), + + Pid1 = spawn_link(Node1, ?MODULE, update_trans, [Tab, 1, 0]), + Pid2 = spawn_link(Node2, ?MODULE, update_trans, [Tab, 2, 0]), + Pid3 = spawn_link(Node3, ?MODULE, update_trans, [Tab, 3, 0]), + + ?match({atomic, ok}, rpc:call(CallFrom, mnesia, add_table_copy, + [Tab, AddNode, ram_copies])), + Pid1 ! {self(), quit}, R1 = receive {Pid1, Res1} -> Res1 after 5000 -> error end, + Pid2 ! {self(), quit}, R2 = receive {Pid2, Res2} -> Res2 after 5000 -> error end, + Pid3 ! {self(), quit}, R3 = receive {Pid3, Res3} -> Res3 after 5000 -> error end, + verify_oids(Tab, Node1, Node2, Node3, R1, R2, R3), + ?verify_mnesia([Node1, Node2, Node3], []). + +move_table_copy_1(suite) -> []; +move_table_copy_1(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{ram_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + move_table(Node1, Node1, Node3, Nodes, Def). +move_table_copy_2(suite) -> []; +move_table_copy_2(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{ram_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + move_table(Node2, Node1, Node3, Nodes, Def). +move_table_copy_3(suite) -> []; +move_table_copy_3(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{ram_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + move_table(Node3, Node1, Node3, Nodes, Def). +move_table_copy_4(suite) -> []; +move_table_copy_4(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{ram_copies, [Node1]}, + {attributes, [key, attr1, attr2]}], + move_table(Node2, Node1, Node3, Nodes, Def). + +move_table(CallFrom, FromNode, ToNode, [Node1, Node2, Node3], Def) -> + ?log("Test case move table from ~w to ~w, with ~w~n", [FromNode, ToNode, Def]), + Tab = schema_ops, + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 1002), + + Pid1 = spawn_link(Node1, ?MODULE, update_trans, [Tab, 1, 0]), + Pid2 = spawn_link(Node2, ?MODULE, update_trans, [Tab, 2, 0]), + Pid3 = spawn_link(Node3, ?MODULE, update_trans, [Tab, 3, 0]), + + ?match({atomic, ok}, rpc:call(CallFrom, mnesia, move_table_copy, + [Tab, FromNode, ToNode])), + Pid1 ! {self(), quit}, + R1 = receive {Pid1, Res1} -> Res1 after 5000 -> ?error("timeout pid1~n", []) end, + Pid2 ! {self(), quit}, + R2 = receive {Pid2, Res2} -> Res2 after 5000 -> ?error("timeout pid2~n", []) end, + Pid3 ! {self(), quit}, + R3 = receive {Pid3, Res3} -> Res3 after 5000 -> ?error("timeout pid3~n", []) end, + verify_oids(Tab, Node1, Node2, Node3, R1, R2, R3), + ?verify_mnesia([Node1, Node2, Node3], []). + +% Verify consistency between different nodes +% Due to limitations in the current dirty_ops this can wrong from time to time! +verify_oids(Tab, N1, N2, N3, R1, R2, R3) -> + io:format("DEBUG 1=>~p 2=>~p 3=>~p~n", [R1,R2,R3]), + ?match([{_, _, _, R1}], rpc:call(N1, mnesia, dirty_read, [{Tab, 1}])), + ?match([{_, _, _, R1}], rpc:call(N2, mnesia, dirty_read, [{Tab, 1}])), + ?match([{_, _, _, R1}], rpc:call(N3, mnesia, dirty_read, [{Tab, 1}])), + ?match([{_, _, _, R2}], rpc:call(N1, mnesia, dirty_read, [{Tab, 2}])), + ?match([{_, _, _, R2}], rpc:call(N2, mnesia, dirty_read, [{Tab, 2}])), + ?match([{_, _, _, R2}], rpc:call(N3, mnesia, dirty_read, [{Tab, 2}])), + ?match([{_, _, _, R3}], rpc:call(N1, mnesia, dirty_read, [{Tab, 3}])), + ?match([{_, _, _, R3}], rpc:call(N2, mnesia, dirty_read, [{Tab, 3}])), + ?match([{_, _, _, R3}], rpc:call(N3, mnesia, dirty_read, [{Tab, 3}])). + +insert(_Tab, 0) -> ok; +insert(Tab, N) when N > 0 -> + ok = mnesia:sync_dirty(fun() -> false = mnesia:is_transaction(), mnesia:write({Tab, N, N, 0}) end), + insert(Tab, N-1). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/mnesia/test/mnesia_durability_test.erl b/lib/mnesia/test/mnesia_durability_test.erl new file mode 100644 index 0000000000..b917b0ca40 --- /dev/null +++ b/lib/mnesia/test/mnesia_durability_test.erl @@ -0,0 +1,1470 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_durability_test). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +-record(test_rec,{key,val}). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +all(doc) -> + ["Verify durability", + "Verify that the effects of committed transactions are durable.", + "The content of the tables tables must be restored at startup."]; +all(suite) -> + [ + load_tables, + durability_of_dump_tables, + durability_of_disc_copies, + durability_of_disc_only_copies + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +load_tables(doc) -> + ["Try to provoke all kinds of table load scenarios."]; +load_tables(suite) -> + [ + load_latest_data, + load_local_contents_directly, + load_directly_when_all_are_ram_copiesA, + load_directly_when_all_are_ram_copiesB, + late_load_when_all_are_ram_copies_on_ram_nodes, + load_when_last_replica_becomes_available, + load_when_we_have_down_from_all_other_replica_nodes, + late_load_transforms_into_disc_load, + late_load_leads_to_hanging, + force_load_when_nobody_intents_to_load, + force_load_when_someone_has_decided_to_load, + force_load_when_someone_else_already_has_loaded, + force_load_when_we_has_loaded, + force_load_on_a_non_local_table, + force_load_when_the_table_does_not_exist, + load_tables_with_master_tables + ]. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +load_latest_data(doc) -> + ["Base functionality, verify that the latest data is loaded"]; +load_latest_data(suite) -> []; +load_latest_data(Config) when is_list(Config) -> + [N1,N2,N3] = Nodes = ?acquire_nodes(3, Config), + %%Create a replicated local table + ?match({atomic,ok}, mnesia:create_table(t0, [{disc_copies,[N1,N2]}])), + ?match({atomic,ok}, mnesia:create_table(t1, [{disc_copies,[N1,N2]}])), + ?match({atomic,ok}, mnesia:create_table(t2, [{disc_copies,[N1,N2]}])), + ?match({atomic,ok}, mnesia:create_table(t3, [{disc_copies,[N1,N2]}])), + ?match({atomic,ok}, mnesia:create_table(t4, [{disc_copies,[N1,N2]}])), + ?match({atomic,ok}, mnesia:create_table(t5, [{disc_copies,[N1,N2]}])), + Rec1 = {t1, test, ok}, + Rec2 = {t1, test, 2}, + + ?match([], mnesia_test_lib:kill_mnesia([N1])), + ?match(ok, rpc:call(N2, mnesia, dirty_write, [Rec2])), + ?match([], mnesia_test_lib:kill_mnesia([N2])), + ?match([], mnesia_test_lib:kill_mnesia([N3])), + + ?match([], mnesia_test_lib:start_mnesia([N1], [])), + %% Should wait for N2 + ?match({timeout, [t1]}, rpc:call(N1, mnesia, wait_for_tables, [[t1], 3000])), + ?match([], mnesia_test_lib:start_mnesia([N3], [])), + ?match({timeout, [t1]}, rpc:call(N1, mnesia, wait_for_tables, [[t1], 3000])), + + + ?match([], mnesia_test_lib:start_mnesia([N2], [])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[t1], 3000])), + ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[t1], 3000])), + %% We should find the record + ?match([Rec2], rpc:call(N1, mnesia, dirty_read, [t1, test])), + ?match([Rec2], rpc:call(N2, mnesia, dirty_read, [t1, test])), + + %% ok, lets switch order + ?match(ok, mnesia:dirty_delete_object(Rec1)), + ?match(ok, mnesia:dirty_delete_object(Rec2)), + %% redo + + ?match([], mnesia_test_lib:kill_mnesia([N2])), + ?match(ok, mnesia:dirty_write(Rec1)), + ?match([], mnesia_test_lib:kill_mnesia([N1])), + ?match([], mnesia_test_lib:kill_mnesia([N3])), + + ?match([], mnesia_test_lib:start_mnesia([N2], [])), + %% Should wait for N1 + ?match({timeout, [t1]}, rpc:call(N2, mnesia, wait_for_tables, [[t1], 2000])), + ?match([], mnesia_test_lib:start_mnesia([N3], [])), + ?match({timeout, [t1]}, rpc:call(N2, mnesia, wait_for_tables, [[t1], 2000])), + ?match([], mnesia_test_lib:start_mnesia([N1], [])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[t1], 1000])), + ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[t1], 1000])), + %% We should find the record + ?match([Rec1], rpc:call(N1, mnesia, dirty_read, [t1, test])), + ?match([Rec1], rpc:call(N2, mnesia, dirty_read, [t1, test])), + + ?verify_mnesia(Nodes, []). + + +load_local_contents_directly(doc) -> + ["Local contents shall always be loaded. Check this by having a local ", + "table on two nodes N1, N2, stopping N1 before N2, an then verifying ", + "that N1 can start without N2 being started."]; +load_local_contents_directly(suite) -> []; +load_local_contents_directly(Config) when is_list(Config) -> + [N1, N2] = Nodes = ?acquire_nodes(2, Config), + %%Create a replicated local table + ?match({atomic,ok}, + mnesia:create_table(test_rec, + [{local_content,true}, + {disc_copies,Nodes}, + {attributes,record_info(fields,test_rec)}] + ) ), + %%Verify that it has local contents. + ?match( true, mnesia:table_info(test_rec,local_content) ), + %%Helper Funs + Write_one = fun(Value) -> mnesia:write(#test_rec{key=1,val=Value}) end, + Read_one = fun(Key) -> mnesia:read( {test_rec, Key}) end, + %%Write a value one N1 that we may test against later + ?match({atomic,ok}, + rpc:call( N1, mnesia, transaction, [Write_one,[11]] ) ), + %%Stop Mnesia on N1 + %?match([], mnesia_test_lib:stop_mnesia([N1])), + ?match([], mnesia_test_lib:kill_mnesia([N1])), + + %%Write a value on N2, same key but a different value + ?match({atomic,ok}, + rpc:call( N2, mnesia, transaction, [Write_one,[22]] ) ), + %%Stop Mnesia on N2 + %?match([], mnesia_test_lib:stop_mnesia([N2])), + ?match([], mnesia_test_lib:kill_mnesia([N2])), + + %%Restart Mnesia on N1 verify that we can read from it without + %%starting Mnesia on N2. + ?match(ok, rpc:call(N1, mnesia, start, [])), + ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[test_rec], 30000])), + %%Read back the value + ?match( {atomic,[#test_rec{key=1,val=11}]}, + rpc:call(N1, mnesia, transaction, [Read_one,[1]] ) ), + %%Restart Mnesia on N2 and verify the contents there. + ?match(ok, rpc:call(N2, mnesia, start, [])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[test_rec], 30000])), + ?match( {atomic,[#test_rec{key=1,val=22}]}, + rpc:call(N2, mnesia, transaction, [Read_one,[1]] ) ), + %%Check that the start of Mnesai on N2 did not affect the contents on N1 + ?match( {atomic,[#test_rec{key=1,val=11}]}, + rpc:call(N1, mnesia, transaction, [Read_one,[1]] ) ), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +load_directly_when_all_are_ram_copiesA(doc) -> + ["Tables that are RAM copies only shall also be loaded directly. ", + "1. N1 and N2 has RAM copies of a table, stop N1 before N2. ", + "2. When N1 starts he shall have access to the table ", + " without having to start N2" ]; +load_directly_when_all_are_ram_copiesA(suite) -> []; +load_directly_when_all_are_ram_copiesA(Config) when is_list(Config) -> + [N1, N2] = Nodes = ?acquire_nodes(2, Config), + + ?match({atomic,ok}, + mnesia:create_table(test_rec, + [{ram_copies,Nodes}, + {attributes,record_info(fields,test_rec)}] + ) ), + ?match( Nodes, mnesia:table_info(test_rec,ram_copies) ), + ?match( [], mnesia:table_info(test_rec,disc_copies) ), + ?match( [], mnesia:table_info(test_rec,disc_only_copies) ), + Write_one = fun(Value) -> mnesia:write(#test_rec{key=2,val=Value}) end, + Read_one = fun() -> mnesia:read({test_rec,2}) end, + %%Write a value one N1 that we may test against later + ?match({atomic,ok}, + rpc:call( N1, mnesia, transaction, [Write_one,[11]] ) ), + %%Stop Mnesia on N1 + ?match([], mnesia_test_lib:kill_mnesia([N1])), + %%Write a value and check result (on N2; not possible on N1 + %%since Mnesia is stopped there). + ?match({atomic,ok}, rpc:call(N2,mnesia,transaction,[Write_one,[22]]) ), + ?match({atomic,[#test_rec{key=2,val=22}]}, + rpc:call(N2,mnesia,transaction,[Read_one]) ), + %%Stop Mnesia on N2 + ?match([], mnesia_test_lib:kill_mnesia([N2])), + %%Restart Mnesia on N1 verify that we can access test_rec from + %%N1 without starting Mnesia on N2. + ?match(ok, rpc:call(N1, mnesia, start, [])), + ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[test_rec], 30000])), + ?match({atomic,[]}, rpc:call(N1,mnesia,transaction,[Read_one])), + ?match({atomic,ok}, rpc:call(N1,mnesia,transaction,[Write_one,[33]])), + ?match({atomic,[#test_rec{key=2,val=33}]}, + rpc:call(N1,mnesia,transaction,[Read_one])), + %%Restart Mnesia on N2 and verify the contents there. + ?match([], mnesia_test_lib:start_mnesia([N2], [test_rec])), + ?match( {atomic,[#test_rec{key=2,val=33}]}, + rpc:call(N2, mnesia, transaction, [Read_one] ) ), + %%Check that the start of Mnesai on N2 did not affect the contents on N1 + ?match( {atomic,[#test_rec{key=2,val=33}]}, + rpc:call(N1, mnesia, transaction, [Read_one] ) ), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +load_directly_when_all_are_ram_copiesB(doc) -> + ["Tables that are RAM copies only shall be loaded from a replicat ", + "when possible. ", + "1. N1 and N2 has RAM copies of a table, stop N1 before N2.", + "2. Now start N2 first and then N1, N1 shall then load the table ", + " from N2."]; +load_directly_when_all_are_ram_copiesB(suite) -> []; +load_directly_when_all_are_ram_copiesB(Config) when is_list(Config) -> + [N1, N2] = Nodes = ?acquire_nodes(2, Config), + ?match({atomic,ok}, + mnesia:create_table(test_rec, + [{ram_copies,Nodes}, + {attributes,record_info(fields,test_rec)}] + ) ), + ?match( Nodes, mnesia:table_info(test_rec,ram_copies) ), + ?match( [], mnesia:table_info(test_rec,disc_copies) ), + ?match( [], mnesia:table_info(test_rec,disc_only_copies) ), + Write_one = fun(Value) -> mnesia:write(#test_rec{key=3,val=Value}) end, + Read_one = fun() -> mnesia:read( {test_rec, 3}) end, + %%Write a value one N1 that we may test against later + ?match({atomic,ok}, + rpc:call( N1, mnesia, transaction, [Write_one,[11]] ) ), + ?match({atomic,[#test_rec{key=3,val=11}]}, + rpc:call(N2,mnesia,transaction,[Read_one]) ), + %%Stop Mnesia on N1 + ?match([], mnesia_test_lib:kill_mnesia([N1])), + %%Write a value and check result (on N2; not possible on N1 + %%since Mnesia is stopped there). + ?match({atomic,ok}, rpc:call(N2,mnesia,transaction,[Write_one,[22]]) ), + ?match({atomic,[#test_rec{key=3,val=22}]}, + rpc:call(N2,mnesia,transaction,[Read_one]) ), + %%Stop Mnesia on N2 + ?match([], mnesia_test_lib:kill_mnesia([N2])), + %%Restart Mnesia on N2 verify that we can access test_rec from + %%N2 without starting Mnesia on N1. + ?match(ok, rpc:call(N2, mnesia, start, [])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[test_rec], 30000])), + ?match({atomic,[]}, rpc:call(N2,mnesia,transaction,[Read_one])), + ?match({atomic,ok}, rpc:call(N2,mnesia,transaction,[Write_one,[33]])), + ?match({atomic,[#test_rec{key=3,val=33}]}, + rpc:call(N2,mnesia,transaction,[Read_one])), + %%Restart Mnesia on N1 and verify the contents there. + ?match([], mnesia_test_lib:start_mnesia([N1], [test_rec])), + ?match( {atomic,[#test_rec{key=3,val=33}]}, + rpc:call(N1,mnesia,transaction,[Read_one])), + %%Check that the start of Mnesai on N1 did not affect the contents on N2 + ?match( {atomic,[#test_rec{key=3,val=33}]}, + rpc:call(N2,mnesia,transaction,[Read_one])), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +late_load_when_all_are_ram_copies_on_ram_nodes(doc) -> + ["Load of ram_copies tables when all replicas resides on disc less nodes"]; +late_load_when_all_are_ram_copies_on_ram_nodes(suite) -> + [ + late_load_when_all_are_ram_copies_on_ram_nodes1, + late_load_when_all_are_ram_copies_on_ram_nodes2 + ]. + +late_load_when_all_are_ram_copies_on_ram_nodes1(suite) -> []; +late_load_when_all_are_ram_copies_on_ram_nodes1(Config) when is_list(Config) -> + [N1, N2] = mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}, + delete_schema, + {reload_appls, [mnesia]}], + 2, Config, ?FILE, ?LINE), + Res = late_load_when_all_are_ram_copies_on_ram_nodes(N1, [N2], Config), + mnesia_test_lib:prepare_test_case([{reload_appls, [mnesia]}], + 2, Config, ?FILE, ?LINE), + Res. + +late_load_when_all_are_ram_copies_on_ram_nodes2(suite) -> []; +late_load_when_all_are_ram_copies_on_ram_nodes2(Config) when is_list(Config) -> + [N1, N2, N3] = mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}, + delete_schema, + {reload_appls, [mnesia]}], + 3, Config, ?FILE, ?LINE), + Res = late_load_when_all_are_ram_copies_on_ram_nodes(N1, [N2, N3], Config), + mnesia_test_lib:prepare_test_case([{reload_appls, [mnesia]}], + 3, Config, ?FILE, ?LINE), + Res. + +late_load_when_all_are_ram_copies_on_ram_nodes(DiscNode, RamNs, _Config) + when DiscNode == node() -> + ?match(ok, mnesia:create_schema([DiscNode])), + ?match(ok, mnesia:start()), + Nodes = [DiscNode | RamNs], + Extra = [{extra_db_nodes, Nodes}], + Ok = [ok || _ <- RamNs], + ?match({Ok, []}, rpc:multicall(RamNs, mnesia, start, [Extra])), + ?match([], wait_until_running(Nodes)), + + LastRam = lists:last(RamNs), + %% ?match({atomic, ok}, + %% mnesia:add_table_copy(schema, LastRam, ram_copies)), + Def = [{ram_copies, RamNs}, {attributes, record_info(fields, test_rec)}], + ?match({atomic,ok}, mnesia:create_table(test_rec, Def)), + ?verify_mnesia(Nodes, []), + ?match([], mnesia_test_lib:stop_mnesia(RamNs)), + ?match(stopped, mnesia:stop()), + ?match(ok, mnesia:start()), + + Rec1 = #test_rec{key=3, val=33}, + Rec2 = #test_rec{key=4, val=44}, + + FirstRam = hd(RamNs), + ?match(ok, rpc:call(FirstRam, mnesia, start, [Extra])), + ?match(ok, rpc:call(FirstRam, mnesia, wait_for_tables, + [[test_rec], 30000])), + ?match(ok, rpc:call(FirstRam, mnesia, dirty_write,[Rec1])), + ?match(ok, mnesia:wait_for_tables([test_rec], 30000)), + mnesia:dirty_write(Rec2), + + if + FirstRam /= LastRam -> + ?match(ok, rpc:call(LastRam, mnesia, start, [Extra])), + ?match(ok, rpc:call(LastRam, mnesia, wait_for_tables, + [[test_rec], 30000])); + true -> + ignore + end, + ?match([Rec1], rpc:call(LastRam, mnesia, dirty_read, [{test_rec, 3}])), + ?match([Rec2], rpc:call(LastRam, mnesia, dirty_read, [{test_rec, 4}])), + ?verify_mnesia(Nodes, []). + +wait_until_running(Nodes) -> + wait_until_running(Nodes, 30). + +wait_until_running(Nodes, Times) when Times > 0-> + Alive = mnesia:system_info(running_db_nodes), + case Nodes -- Alive of + [] -> + []; + Remaining -> + timer:sleep(timer:seconds(1)), + wait_until_running(Remaining, Times - 1) + end; +wait_until_running(Nodes, _) -> + Nodes. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +load_when_last_replica_becomes_available(doc) -> + ["Check that when all Mnesia nodes die at the same instant, then the ", + "replicated table shall be accessible when the last node is started ", + "again.", + "Checked by cheating. Start Mnesia on N1, N2, N3. Have a table ", + "replicated on disc on all three nodes, fill in some transactions, ", + "install a fallback. Restart mnesia on all nodes" + "This is the cheat and it simulates that all nodes died at the same ", + "time. Check that the table is only accessible after the last node ", + "has come up."]; +load_when_last_replica_becomes_available(suite) -> []; +load_when_last_replica_becomes_available(Config) when is_list(Config) -> + [N1, N2, N3] = Nodes = ?acquire_nodes(3, Config), + ?match({atomic,ok}, + mnesia:create_table(test_rec, + [{disc_copies,Nodes}, + {attributes,record_info(fields,test_rec)}] + ) ), + ?match( [], mnesia:table_info(test_rec,ram_copies) ), + ?match( Nodes, mnesia:table_info(test_rec,disc_copies) ), + ?match( [], mnesia:table_info(test_rec,disc_only_copies) ), + Write_one = fun(Key,Val)->mnesia:write(#test_rec{key=Key,val=Val}) end, + Read_one = fun(Key) ->mnesia:read( {test_rec, Key}) end, + %%Write one value from each node. + ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[1,11]])), + ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[2,22]])), + ?match({atomic,ok},rpc:call(N3,mnesia,transaction,[Write_one,[3,33]])), + %%Check the values + ?match({atomic,[#test_rec{key=1,val=11}]}, + rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ), + ?match({atomic,[#test_rec{key=2,val=22}]}, + rpc:call(N3,mnesia,transaction,[Read_one,[2]]) ), + ?match({atomic,[#test_rec{key=3,val=33}]}, + rpc:call(N1,mnesia,transaction,[Read_one,[3]]) ), + + ?match(ok, mnesia:backup("test_last_replica")), + ?match(ok, mnesia:install_fallback("test_last_replica")), + file:delete("test_last_replica"), + %%Stop Mnesia on all three nodes + ?match([], mnesia_test_lib:kill_mnesia(Nodes)), + + %%Start Mnesia on one node, make sure that test_rec is not available + ?match(ok, rpc:call(N2, mnesia, start, [])), + ?match({timeout,[test_rec]}, + rpc:call(N2, mnesia, wait_for_tables, [[test_rec], 10000])), + ?match(ok, rpc:call(N1, mnesia, start, [])), + ?match({timeout,[test_rec]}, + rpc:call(N1, mnesia, wait_for_tables, [[test_rec], 10000])), + %%Start the third node + ?match(ok, rpc:call(N3, mnesia, start, [])), + %%Make sure that the table is loaded everywhere + ?match(ok, rpc:call(N3, mnesia, wait_for_tables, [[test_rec], 30000])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[test_rec], 30000])), + ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[test_rec], 30000])), + + %%Check the values + ?match({atomic,[#test_rec{key=1,val=11}]}, + rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ), + ?match({atomic,[#test_rec{key=2,val=22}]}, + rpc:call(N3,mnesia,transaction,[Read_one,[2]]) ), + ?match({atomic,[#test_rec{key=3,val=33}]}, + rpc:call(N1,mnesia,transaction,[Read_one,[3]]) ), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +load_when_we_have_down_from_all_other_replica_nodes(doc) -> + ["The table can be loaded if this node was the last one surviving. ", + "Check this by having N1, N2, N3 and a table replicated on all those ", + "nodes. Then kill them in the N1, N2, N3 order. Then start N3 and ", + "verify that the table is available with correct contents."]; +load_when_we_have_down_from_all_other_replica_nodes(suite) -> []; +load_when_we_have_down_from_all_other_replica_nodes(Config) when is_list(Config) -> + [N1, N2, N3] = Nodes = ?acquire_nodes(3, Config), + ?match({atomic,ok}, + mnesia:create_table(test_rec, + [{disc_copies,Nodes}, + {attributes,record_info(fields,test_rec)}] + ) ), + ?match( [], mnesia:table_info(test_rec,ram_copies) ), + ?match( Nodes, mnesia:table_info(test_rec,disc_copies) ), + ?match( [], mnesia:table_info(test_rec,disc_only_copies) ), + Write_one = fun(Key,Val)->mnesia:write(#test_rec{key=Key,val=Val}) end, + Read_one = fun(Key) ->mnesia:read( {test_rec, Key}) end, + %%Write one value from each node. + ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[1,111]])), + ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[2,222]])), + ?match({atomic,ok},rpc:call(N3,mnesia,transaction,[Write_one,[3,333]])), + %%Check the values + ?match({atomic,[#test_rec{key=1,val=111}]}, + rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ), + ?match({atomic,[#test_rec{key=2,val=222}]}, + rpc:call(N3,mnesia,transaction,[Read_one,[2]]) ), + ?match({atomic,[#test_rec{key=3,val=333}]}, + rpc:call(N1,mnesia,transaction,[Read_one,[3]]) ), + %%Stop Mnesia on all three nodes + ?match([], mnesia_test_lib:kill_mnesia([N1])), + ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[22,22]])), + ?match([], mnesia_test_lib:kill_mnesia([N2])), + ?match({atomic,ok},rpc:call(N3,mnesia,transaction,[Write_one,[33,33]])), + ?match([], mnesia_test_lib:kill_mnesia([N3])), + ?verbose("Mnesia stoped on all three nodes.~n",[]), + + %%Start Mnesia on N3; wait for 'test_rec' table to load + ?match(ok, rpc:call(N3, mnesia, start, [])), + ?match(ok, rpc:call(N3, mnesia, wait_for_tables, [[test_rec], 30000])), + + %%Check the values + ?match({atomic,[#test_rec{key=1,val=111}]}, + rpc:call(N3,mnesia,transaction,[Read_one,[1]]) ), + ?match({atomic,[#test_rec{key=2,val=222}]}, + rpc:call(N3,mnesia,transaction,[Read_one,[2]]) ), + ?match({atomic,[#test_rec{key=3,val=333}]}, + rpc:call(N3,mnesia,transaction,[Read_one,[3]]) ), + ?match({atomic,[#test_rec{key=22,val=22}]}, + rpc:call(N3,mnesia,transaction,[Read_one,[22]]) ), + ?match({atomic,[#test_rec{key=33,val=33}]}, + rpc:call(N3,mnesia,transaction,[Read_one,[33]]) ), + ?verify_mnesia([N3], [N1, N2]). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +late_load_transforms_into_disc_load(doc) -> + ["Difficult case that needs instrumentation of Mnesia.", + "A table is force loaded, and Mnesia decides to load it from another ", + "Mnesia node because it is avaliable there. The other Mnesia node then ", + "dies in mid copy which shall make the first Mnesia node to really ", + "force load from disc.", + "Check this by starting N1 and N2 and replicating a table between ", + "them. Then kill N1 before N2. The idea is to start N2 first, then ", + "N1 and then do a force load on N1. This force load will load from ", + "N2 BUT N2 must be killed after the decision to load from it has ", + "been made. tricky."]; + +late_load_transforms_into_disc_load(suite) -> []; +late_load_transforms_into_disc_load(Config) when is_list(Config) -> + ?is_debug_compiled, + + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + + {success, [A, B]} = ?start_activities(Nodes), + + ?match(Node1, node(A)), + ?match(Node2, node(B)), + + Tab = late_load_table, + Def = [{attributes, [key, value]}, + {disc_copies, Nodes}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 111, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 222, 42})), + + TestPid = self(), + DebugId = {mnesia_loader, do_get_network_copy}, + DebugFun = fun(PrevContext, EvalContext) -> + ?verbose("interrupt late load, pid ~p #~p ~n context ~p ~n", + [self(),PrevContext,EvalContext]), + + mnesia_test_lib:kill_mnesia([Node2]), + TestPid ! {self(),debug_fun_was_called}, + + ?verbose("interrupt late_log - continues ~n",[]), + ?deactivate_debug_fun(DebugId), + PrevContext+1 + end, + ?remote_activate_debug_fun(Node1,DebugId, DebugFun, 1), + + %% kill mnesia on node1 + mnesia_test_lib:kill_mnesia([Node1]), + %% wait a while, so that mnesia is really down + timer:sleep(timer:seconds(1)), + + ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab, 222, 815}])), + + %% start Mnesia on node1 + ?match(ok,mnesia:start()), + ?match(yes, mnesia:force_load_table(Tab)), + ?match(ok, mnesia:wait_for_tables([Tab],timer:seconds(30))), + + receive_messages([debug_fun_was_called]), + + check_tables([A],[{Tab,111},{Tab,222}],[[{Tab,111,4711}],[{Tab,222,42}]]), + ?verify_mnesia([Node1], [Node2]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +late_load_leads_to_hanging(doc) -> + ["Difficult case that needs instrumentation of Mnesia.", + "A table is loaded, and Mnesia decides to load it from another ", + "Mnesia node because it has the latest copy there. ", + "The other Mnesia node then ", + "dies in mid copy which shall make the first Mnesia node not to ", + "force load from disc but to wait for the other node to come up again", + "Check this by starting N1 and N2 and replicating a table between ", + "them. Then kill N1 before N2. The idea is to start N2 first, then ", + "N1. This load will load from ", + "N2 BUT N2 must be killed after the decision to load from it has ", + "been made. tricky."]; + +late_load_leads_to_hanging(suite) -> []; +late_load_leads_to_hanging(Config) when is_list(Config) -> + ?is_debug_compiled, + + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + + Tab = late_load_table, + Def = [{attributes, [key, value]}, + {disc_copies, Nodes}], + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 111, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 222, 42})), + + DebugId = {mnesia_loader, do_get_network_copy}, + DebugFun = fun(PrevContext, EvalContext) -> + ?verbose("interrupt late load, pid ~p #~p ~n context ~p ~n", + [self(), PrevContext, EvalContext]), + mnesia_test_lib:kill_mnesia([Node2]), + ?verbose("interrupt late load - continues ~n",[]), + ?deactivate_debug_fun(DebugId), + PrevContext+1 + end, + + ?remote_activate_debug_fun(Node1,DebugId, DebugFun, 1), + mnesia_test_lib:kill_mnesia([Node1]), + %% wait a while, so that mnesia is really down + timer:sleep(timer:seconds(1)), + + ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab, 333, 666}])), + + %% start Mnesia on node1 + ?match(ok, mnesia:start()), + + ?match({timeout, [Tab]}, mnesia:wait_for_tables([Tab], timer:seconds(2))), + + ?match({'EXIT', {aborted, _}}, mnesia:dirty_read({Tab, 222})), + %% mnesia on node1 is waiting for node2 coming up + + ?match(ok, rpc:call(Node2, mnesia, start, [])), + ?match(ok, mnesia:wait_for_tables([Tab], timer:seconds(30))), + ?match([{Tab, 333, 666}], mnesia:dirty_read({Tab, 333})), + ?verify_mnesia([Node2, Node1], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +force_load_when_nobody_intents_to_load(doc) -> + ["Normal force load. Start N1 N2, kill in N1, N2 order. Start N1 do ", + "force load. Did it work?"]; +force_load_when_nobody_intents_to_load(suite) -> []; +force_load_when_nobody_intents_to_load(Config) when is_list(Config) -> + [N1, N2] = Nodes = ?acquire_nodes(2, Config), + Table = test_rec, + Trec1a = #test_rec{key=1,val=111}, + Trec1b = #test_rec{key=1,val=333}, + Trec2a = #test_rec{key=2,val=222}, + Trec3a = #test_rec{key=3,val=333}, + Trec3b = #test_rec{key=3,val=666}, + + ?match({atomic,ok}, rpc:call(N1, mnesia,create_table, + [Table, + [{disc_copies,Nodes}, + {attributes,record_info(fields,test_rec)} + ] ] ) ), + ?match( [], mnesia:table_info(Table,ram_copies) ), + ?match( Nodes, mnesia:table_info(Table,disc_copies) ), + ?match( [], mnesia:table_info(Table,disc_only_copies) ), + Write_one = fun(Rec) -> mnesia:write(Rec) end, + Read_one = fun(Key) -> mnesia:read({Table, Key}) end, + %%Write one value + ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[Trec1a]])), + %%Check it + ?match({atomic,[Trec1a]},rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ), + %%Shut down mnesia on N1 + ?match([], mnesia_test_lib:stop_mnesia([N1])), + %%Write and check value while N1 is down + ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[Trec1b]])), + ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[Trec2a]])), + ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[Trec3a]])), + ?match({aborted,{node_not_running,N1}}, + rpc:call(N1,mnesia,transaction,[Read_one,[2]]) ), + ?match({atomic,[Trec1b]},rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ), + ?match({atomic,[Trec2a]},rpc:call(N2,mnesia,transaction,[Read_one,[2]]) ), + ?match({atomic,[Trec3a]},rpc:call(N2,mnesia,transaction,[Read_one,[3]]) ), + %%Shut down Mnesia on N2 + ?match([], mnesia_test_lib:stop_mnesia([N2])), + + %%Restart Mnesia on N1 + ?match(ok, rpc:call(N1, mnesia, start, [])), + %%Check that table is not available (waiting for N2) + ?match({timeout,[Table]}, + rpc:call(N1, mnesia, wait_for_tables, [[Table], 3000])), + + %%Force load on N1 + ?match(yes,rpc:call(N1,mnesia,force_load_table,[Table])), + %%Check values + ?match({atomic,[Trec1a]},rpc:call(N1,mnesia,transaction,[Read_one,[1]]) ), + ?match({atomic,[]}, rpc:call(N1,mnesia,transaction,[Read_one,[2]]) ), + ?match({atomic,[]}, rpc:call(N1,mnesia,transaction,[Read_one,[3]]) ), + %%Write a value for key=3 + ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[Trec3b]])), + + %%Restart N2 and check values + ?match(ok, rpc:call(N2, mnesia, start, [])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[Table], 30000])), + + ?match({atomic,[Trec1a]},rpc:call(N1,mnesia,transaction,[Read_one,[1]]) ), + ?match({atomic,[Trec1a]},rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ), + + ?match({atomic,[]},rpc:call(N1,mnesia,transaction,[Read_one,[2]]) ), + ?match({atomic,[]},rpc:call(N2,mnesia,transaction,[Read_one,[2]]) ), + + ?match({atomic,[Trec3b]},rpc:call(N1,mnesia,transaction,[Read_one,[3]]) ), + ?match({atomic,[Trec3b]},rpc:call(N2,mnesia,transaction,[Read_one,[3]]) ), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +force_load_when_someone_has_decided_to_load(doc) -> + ["Difficult case that needs instrumentation of Mnesia.", + "Start N1 and N2, replicate table, kill in N1, N2 order. Start N2 ", + "and start N1 before N2 has really loaded the table but after N2 has ", + "decided to load it."]; + +force_load_when_someone_has_decided_to_load(suite) -> []; +force_load_when_someone_has_decided_to_load(Config) when is_list(Config) -> + ?is_debug_compiled, + + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + {success, [A, B]} = ?start_activities(Nodes), + ?match(Node1, node(A)), %% Just to check :) + ?match(Node2, node(B)), + + Tab = late_load_table, + Def = [{attributes, [key, value]}, {disc_copies, Nodes}], + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 111, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 222, 42})), + + Self = self(), + DebugId = {mnesia_controller, late_disc_load}, + DebugFun = fun(PrevContext, EvalContext) -> + ?verbose("interrupt late disc load, + pid ~p #~p ~n context ~p ~n", + [self(),PrevContext,EvalContext]), + Self ! {self(), fun_in_postion}, + wait_for_signal(), + ?verbose("interrupt late disc load - continues ~n",[]), + ?deactivate_debug_fun(DebugId), + PrevContext+1 + end, + + %% kill mnesia on node1 + mnesia_test_lib:kill_mnesia([Node1]), + %% wait a while, so that mnesia is really down + timer:sleep(timer:seconds(1)), + + ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab, 222, 815}])), + %% kill mnesia on node2 + mnesia_test_lib:kill_mnesia([Node2]), + %% wait a while, so that mnesia is really down + timer:sleep(timer:seconds(1)), + + ?remote_activate_debug_fun(Node2,DebugId, DebugFun, 1), + + B ! fun() -> mnesia:start() end, + [{Mnesia_Pid, fun_in_postion}] = receive_messages([fun_in_postion]), + + %% start Mnesia on node1 + A ! fun() -> mnesia:start() end, + ?match_receive(timeout), +% Got some problem with this testcase when we modified mnesia init +% These test cases are very implementation dependent! +% A ! fun() -> mnesia:wait_for_tables([Tab], 3000) end, +% ?match_receive({A, {timeout, [Tab]}}), + A ! fun() -> mnesia:force_load_table(Tab) end, + ?match_receive(timeout), + + Mnesia_Pid ! continue, + ?match_receive({B, ok}), + ?match_receive({A, ok}), + ?match_receive({A, yes}), + + B ! fun() -> mnesia:wait_for_tables([Tab], 10000) end, + ?match_receive({B, ok}), + ?match(ok, mnesia:wait_for_tables([Tab], timer:seconds(30))), + ?match([{Tab, 222, 815}], mnesia:dirty_read({Tab, 222})), + ?verify_mnesia(Nodes, []). + +wait_for_signal() -> + receive + continue -> ok + %% Don't eat any other mnesia internal msg's + after + timer:minutes(2) -> ?error("Timedout in wait_for_signal~n", []) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +force_load_when_someone_else_already_has_loaded(doc) -> + ["Normal case. Do a force load when somebody else has loaded the table. ", + "Start N1, N2, kill in N1, N2 order. Start N2 load the table, start N1 ", + "force load. Did it work? (i.e: did N1 load the table from N2 as that", + "one is the latest version and it is available on N2)"]; + +force_load_when_someone_else_already_has_loaded(suite) -> []; +force_load_when_someone_else_already_has_loaded(Config) when is_list(Config) -> + [N1, N2] = Nodes = ?acquire_nodes(2, Config), + Table = test_rec, + Trec1 = #test_rec{key=1,val=111}, + Trec2 = #test_rec{key=1,val=222}, + + ?match({atomic,ok}, rpc:call(N1, mnesia,create_table, + [Table, + [{disc_copies,Nodes}, + {attributes,record_info(fields,test_rec)} + ] ] ) ), + ?match( [], mnesia:table_info(Table,ram_copies) ), + ?match( Nodes, mnesia:table_info(Table,disc_copies) ), + ?match( [], mnesia:table_info(Table,disc_only_copies) ), + Write_one = fun(Rec) -> mnesia:write(Rec) end, + Read_one = fun(Key) -> mnesia:read({Table, Key}) end, + %%Write one value + ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[Trec1]])), + %%Check it + ?match({atomic,[Trec1]},rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ), + %%Shut down mnesia + ?match([], mnesia_test_lib:stop_mnesia([N1])), + timer:sleep(500), + ?match([], mnesia_test_lib:stop_mnesia([N2])), + %%Restart Mnesia on N2;wait for tables to load + ?match(ok, rpc:call(N2, mnesia, start, [])), + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[test_rec], 30000])), + %%Write one value + ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[Trec2]])), + %%Start on N1; force load + ?match(ok, rpc:call(N1, mnesia, start, [])), + %%Force load from file + ?match(yes, rpc:call(N1,mnesia,force_load_table,[Table])), + %%Check the value + ?match({atomic,[Trec2]},rpc:call(N1,mnesia,transaction,[Read_one,[1]]) ), + %% === there must be a Trec2 here !!!! + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +force_load_when_we_has_loaded(doc) -> + ["Force load a table we already have loaded"]; +force_load_when_we_has_loaded(suite) -> []; +force_load_when_we_has_loaded(Config) when is_list(Config) -> + [N1] = Nodes = ?acquire_nodes(1, Config), + Table = test_rec, + Trec1 = #test_rec{key=1,val=111}, + Trec2 = #test_rec{key=1,val=222}, + + ?match({atomic,ok}, rpc:call(N1, mnesia,create_table, + [Table, + [{disc_copies,Nodes}, + {attributes,record_info(fields,test_rec)} + ] ] ) ), + ?match( [], mnesia:table_info(Table,ram_copies) ), + ?match( Nodes, mnesia:table_info(Table,disc_copies) ), + ?match( [], mnesia:table_info(Table,disc_only_copies) ), + Write_one = fun(Rec) -> mnesia:write(Rec) end, + Read_one = fun(Key) -> mnesia:read({Table, Key}) end, + %%Write one value + ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[Trec1]])), + %%Check it + ?match({atomic,[Trec1]},rpc:call(N1,mnesia,transaction,[Read_one,[1]]) ), + %%Shut down mnesia + ?match([], mnesia_test_lib:stop_mnesia(Nodes)), + %%Restart Mnesia;wait for tables to load + ?match([], mnesia_test_lib:start_mnesia(Nodes, [Table])), + %%Write one value + ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[Trec2]])), + %%Force load from file + ?match(yes, rpc:call(N1,mnesia,force_load_table,[Table])), + %%Check the value + ?match({atomic,[Trec2]},rpc:call(N1,mnesia,transaction,[Read_one,[1]]) ), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +force_load_on_a_non_local_table(doc) -> + ["This is NOT allowed, the test case is a negative test", + "Force load on a table that isn't replicated on this node."]; +force_load_on_a_non_local_table(suite) -> []; +force_load_on_a_non_local_table(Config) when is_list(Config) -> + [N1, N2, N3] = Nodes = ?acquire_nodes( 3, Config), + TableNodes = lists:sublist(Nodes,2), + Table = test_rec, + Trec1 = #test_rec{key=1,val=11}, + + ?match({atomic,ok}, rpc:call(N1, mnesia,create_table, + [Table, + [{disc_copies,TableNodes}, + {attributes,record_info(fields,test_rec)} + ] ] ) ), + ?match( [], mnesia:table_info(Table,ram_copies) ), + ?match( TableNodes, mnesia:table_info(Table,disc_copies) ), + ?match( [], mnesia:table_info(Table,disc_only_copies) ), + Write_one = fun(Rec) -> mnesia:write(Rec) end, + Read_one = fun(Key) -> mnesia:read({Table, Key}) end, + %%Write one value + ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[Trec1]])), + %%Check it from the other nodes + ?match({atomic,[Trec1]},rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ), + ?match({atomic,[Trec1]},rpc:call(N3,mnesia,transaction,[Read_one,[1]]) ), + + %%Make sure that Table is non-local + ?match_inverse(N3, rpc:call(N3,mnesia,table_info,[Table,where_to_read])), + %%Try to force load it + ?match(yes, rpc:call(N3,mnesia,force_load_table,[Table])), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +force_load_when_the_table_does_not_exist(doc) -> + ["This is NOT allowed, the test case is a negative test", + "Force load on a table that doesn't exist."]; +force_load_when_the_table_does_not_exist(suite) -> []; +force_load_when_the_table_does_not_exist(Config) when is_list(Config) -> + Nodes = ?acquire_nodes( 2, Config), + + %%Dummy table + ?match({atomic,ok}, + mnesia:create_table(test_rec, + [{disc_copies,Nodes}, + {attributes,record_info(fields,test_rec)}] + ) ), + ?match( [], mnesia:table_info(test_rec,ram_copies) ), + ?match( Nodes, mnesia:table_info(test_rec,disc_copies) ), + ?match( [], mnesia:table_info(test_rec,disc_only_copies) ), + Tab = dummy, + %%Make sure that Tab is an unknown table + ?match( false, lists:member(Tab,mnesia:system_info(tables)) ), + ?match( {error, {no_exists, Tab}}, mnesia:force_load_table(Tab) ), + ?verify_mnesia(Nodes, []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +load_tables_with_master_tables(doc) -> + ["Verifies the semantics of different master nodes settings", + "The semantics should be:", + "1. Mnesia downs, Normally decides from where mnesia should load tables", + "2. Master tables (overrides mnesia downs) ", + "3. Force load (overrides Master tables) ", + "--- 1st from active master nodes", + "--- 2nd from active nodes", + "--- 3rd get local copy (if ram create new one)" + ]; + +load_tables_with_master_tables(suite) -> + [master_nodes, + starting_master_nodes, + master_on_non_local_tables, + remote_force_load_with_local_master_node]. + + +-define(SDwrite(Tup), fun() -> mnesia:write(Tup) end). + +master_nodes(suite) -> []; +master_nodes(Config) when is_list(Config) -> + [A, B, C] = Nodes = ?acquire_nodes(3, Config), + Tab = test_table_master_nodes, + ?match({atomic,ok}, mnesia:create_table(Tab, [{disc_copies, Nodes}])), + + %% Test one: Master A and the table should be loaded from A + + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [A]])), + ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + + mnesia_test_lib:stop_mnesia([A]), + ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])), + + ?match([{Tab, 1, init}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])), + + %% Test 2: Master [A,B] and B is Up the table should be loaded from B + + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [A, B]])), + ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + + mnesia_test_lib:stop_mnesia([A]), + ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])), + + ?match([{Tab, 1, updated}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])), + + %% Test 3: Master [A,B] and B is down the table should be loaded from A + + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [A, B]])), + ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + + mnesia_test_lib:stop_mnesia([A]), + ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + mnesia_test_lib:stop_mnesia([B]), + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])), + + ?match(ok, rpc:call(B, mnesia, start, [])), + ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 3000])), + + ?match([{Tab, 1, init}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, _Unknown}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])), + + %% Test 4: Master [B] and B is Up the table should be loaded from B + + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [B]])), + ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + + mnesia_test_lib:stop_mnesia([A]), + ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])), + + ?match([{Tab, 1, updated}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])), + + %% Test 5: Master [B] and B is down the table should not be loaded + + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [B]])), + ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + + mnesia_test_lib:stop_mnesia([A]), + ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + mnesia_test_lib:stop_mnesia([B]), + ?match({atomic, ok}, rpc:call(C, mnesia, sync_transaction, [?SDwrite({Tab, 1, update_2})])), + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match({timeout, [Tab]}, rpc:call(A, mnesia, wait_for_tables, [[Tab], 2000])), + + %% Test 6: Force load on table that couldn't be loaded due to master + %% table setttings, loads other active replicas i.e. from C + + ?match(yes, rpc:call(A, mnesia, force_load_table, [Tab])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])), + + ?match(ok, rpc:call(B, mnesia, start, [])), + ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 3000])), + + ?match([{Tab, 1, update_2}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, update_2}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, update_2}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])), + + %% Test 7: Master [B] and B is down the table should not be loaded, + %% force_load when there are no active replicas availible + %% should generate a load of a local table + + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [B]])), + ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + + mnesia_test_lib:stop_mnesia([A]), + ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + mnesia_test_lib:stop_mnesia([B, C]), + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match({timeout, [Tab]}, rpc:call(A, mnesia, wait_for_tables, [[Tab], 2000])), + + ?match(yes, rpc:call(A, mnesia, force_load_table, [Tab])), + ?match([{Tab, 1, init}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + + ?verify_mnesia([A], [B,C]). + +starting_master_nodes(suite) -> []; +starting_master_nodes(doc) -> + ["Complementory to TEST 5 and 6 above, if the master node (B) starts" + " and loads the table it should be loaded on the waiting node (A) "]; +starting_master_nodes(Config) when is_list(Config) -> + [A, B, C] = Nodes = ?acquire_nodes(3, Config), + Tab = starting_master_nodes, + ?match({atomic,ok}, mnesia:create_table(Tab, [{disc_copies, Nodes}])), + %% Start by checking TEST 5 above. + + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [B]])), + ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + mnesia_test_lib:stop_mnesia([A]), + ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + mnesia_test_lib:stop_mnesia([B]), + ?match({atomic, ok}, rpc:call(C, mnesia, sync_transaction, [?SDwrite({Tab, 1, update_2})])), + + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match({timeout, [Tab]}, rpc:call(A, mnesia, wait_for_tables, [[Tab], 2000])), + %% Start the B node and the table should be loaded on A! + ?match(ok, rpc:call(B, mnesia, start, [])), + ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 3000])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])), + + ?verify_mnesia([A,B,C], []). + + +master_on_non_local_tables(suite) -> []; +master_on_non_local_tables(Config) when is_list(Config) -> + [A, B, C] = Nodes = ?acquire_nodes(3, Config), + Tab = test_table_non_local, + ?match({atomic,ok}, mnesia:create_table(Tab, [{disc_copies, [B, C]}])), + + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [B]])), + ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + + %% Test 1: Test that table info are updated when master node comes up + + mnesia_test_lib:stop_mnesia([A, B]), + ?match({atomic, ok}, rpc:call(C, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + ?match(ok, rpc:call(A, mnesia, start, [])), + + ?match({timeout, [Tab]}, rpc:call(A, mnesia, wait_for_tables, [[Tab], 2000])), + ErrorRead = {badrpc,{'EXIT', {aborted,{no_exists,[test_table_non_local,1]}}}}, + ErrorWrite = {badrpc,{'EXIT', {aborted,{no_exists,test_table_non_local}}}}, + ?match(ErrorRead, rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match(ErrorWrite, rpc:call(A, mnesia, dirty_write, [{Tab, 1, updated_twice}])), + + ?match(ok, rpc:call(B, mnesia, start, [])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 2000])), + + ?match([{Tab, 1, updated}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match(B, rpc:call(A, mnesia, table_info, [Tab, where_to_read])), + ?match({atomic, ok}, rpc:call(A, mnesia, sync_transaction, [?SDwrite({Tab, 1, init})])), + + %% Test 2: Test that table info are updated after force_load + + mnesia_test_lib:stop_mnesia([A, B]), + ?match({atomic, ok}, rpc:call(C, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])), + ?match(ok, rpc:call(A, mnesia, start, [])), + + ?match({timeout, [Tab]}, rpc:call(A, mnesia, wait_for_tables, [[Tab], 2000])), + ?match(yes, rpc:call(A, mnesia, force_load_table, [Tab])), + ?match(C, rpc:call(A, mnesia, table_info, [Tab, where_to_read])), + + ?match([{Tab, 1, updated}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match({atomic, ok}, rpc:call(A, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated_twice})])), + + ?match(ok, rpc:call(B, mnesia, start, [])), + ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 10000])), + + ?match([{Tab, 1, updated_twice}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated_twice}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, updated_twice}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])), + + ?verify_mnesia(Nodes, []). + +remote_force_load_with_local_master_node(doc) -> + ["Force load a table on a remote node while the ", + "local node is down. Start the local node and ", + "verfify that the tables is loaded from disc locally " + "if the local node has itself as master node and ", + "the remote node has both the local and remote node ", + "as master nodes"]; +remote_force_load_with_local_master_node(suite) -> []; +remote_force_load_with_local_master_node(Config) when is_list(Config) -> + [A, B] = Nodes = ?acquire_nodes(2, Config), + + Tab = remote_force_load_with_local_master_node, + ?match({atomic,ok}, mnesia:create_table(Tab, [{disc_copies, Nodes}])), + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [A, B]])), + ?match(ok, rpc:call(B, mnesia, set_master_nodes, [Tab, [B]])), + + W = fun(Who) -> mnesia:write({Tab, who, Who}) end, + ?match({atomic, ok}, rpc:call(A,mnesia, sync_transaction, [W, [a]])), + ?match(stopped, rpc:call(A, mnesia, stop, [])), + ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [W, [b]])), + ?match(stopped, rpc:call(B, mnesia, stop, [])), + + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])), + ?match([{Tab, who, a}], rpc:call(A, mnesia, dirty_read, [{Tab, who}])), + + ?match(ok, rpc:call(B, mnesia, start, [])), + ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 3000])), + ?match([{Tab, who, b}], rpc:call(B, mnesia, dirty_read, [{Tab, who}])), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +durability_of_dump_tables(doc) -> + [ "Verify that all tables contain the correct data when Mnesia", + "is restarted and tables are loaded from disc to recover", + " their previous contents. " ]; +durability_of_dump_tables(suite) -> [dump_ram_copies, + dump_disc_copies, + dump_disc_only]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +dump_ram_copies(doc) -> + ["Check that ram_copies tables are loaded with the" + "contents that had been dumped before Mnesia", + "was restarted. " ]; +dump_ram_copies(suite) -> []; +dump_ram_copies(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(3, Config), + {success, [P1,P2,P3]} = ?start_activities(Nodes), + + NP1 = node(P1), + NP2 = node(P2), + + {A,B,C} = case node() of + NP1 -> + %?verbose("first case ~n"), + {P3,P2,P1}; + NP2 -> + %?verbose("second case ~n"), + {P3,P1,P2}; + _ -> + {P1,P2,P3} + end, + + Node1 = node(A), + Node2 = node(B), + Node3 = node(C), + + ?verbose(" A pid:~p node:~p ~n",[A,Node1]), + ?verbose(" B pid:~p node:~p ~n",[B,Node2]), + ?verbose(" C pid:~p node:~p ~n",[C,Node3]), + + + %% ram copies table on 2 nodes + + Tab = dump_table, + Def = [{attributes, [key, value]}, + {ram_copies, [Node1,Node2]}], + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match(ok, mnesia:dirty_write({Tab, 1, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 2, 42})), + ?match(ok, mnesia:dirty_write({Tab, 3, 256})), + + %% dump the table + + ?match( {atomic,ok}, mnesia:dump_tables([Tab])), + + %% perform updates (they shall be lost after kill Mnesia ) + + ?match(ok, mnesia:dirty_write({Tab, 1, 815})), + ?match(ok, mnesia:dirty_write({Tab, 2, 915})), + + %% add another replica on node3 + mnesia:add_table_copy(Tab,Node3,ram_copies), + + %% all 3 replicas shall have the new contents + cross_check_tables([A,B,C],Tab, + {[{Tab,1,815}],[{Tab,2,915}],[{Tab,3,256}]}), + + %% kill mnesia on node 3 + mnesia_test_lib:kill_mnesia([Node3]), + + %% wait a while, so that mnesia is really down + timer:sleep(timer:seconds(2)), + + mnesia_test_lib:kill_mnesia([Node1,Node2]), %% kill them as well + timer:sleep(timer:seconds(2)), + + %% start Mnesia only on node 3 + ?verbose("starting mnesia on Node3~n",[]), + + %% test_lib:mnesia_start doesnt work, because it waits + %% for the schema on all nodes ... ??? + ?match(ok,rpc:call(Node3,mnesia,start,[]) ), + ?match(ok,rpc:call(Node3,mnesia,wait_for_tables, + [[Tab],timer:seconds(30)] ) ), + + %% node3 shall have the conents of the dump + cross_check_tables([C],Tab,{[{Tab,1,4711}],[{Tab,2,42}],[{Tab,3,256}]}), + + %% start Mnesia on the other 2 nodes, too + mnesia_test_lib:start_mnesia([Node1,Node2],[Tab]), + + cross_check_tables([A,B,C],Tab, + {[{Tab,1,4711}],[{Tab,2,42}],[{Tab,3,256}]}), + ?verify_mnesia(Nodes, []). + +%% check the contents of the table + +cross_check_tables([],_tab,_elements) -> ok; +cross_check_tables([Pid|Rest],Tab,{Val1,Val2,Val3}) -> + Pid ! fun () -> + R1 = mnesia:dirty_read({Tab,1}), + R2 = mnesia:dirty_read({Tab,2}), + R3 = mnesia:dirty_read({Tab,3}), + {R1,R2,R3} + end, + ?match_receive({ Pid, {Val1, Val2, Val3 } }), + cross_check_tables(Rest,Tab,{Val1,Val2,Val3} ). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Should be in evil test suite !!! + +dump_disc_copies(doc) -> + ["Check that it is not possible to dump disc_copies tables"]; +dump_disc_copies(suite) -> []; +dump_disc_copies(Config) when is_list(Config) -> + do_dump_copies(Config, disc_copies). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Should be in evil test suite !!! +dump_disc_only(doc) -> + ["Check that it is not possible to dump disc_only_copies tables"]; +dump_disc_only(suite) -> []; +dump_disc_only(Config) when is_list(Config) -> + do_dump_copies(Config,disc_only_copies). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +do_dump_copies(Config,Copies) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + + Tab = dump_copies, + Def = [{attributes, [key, value]}, + {Copies, [Node1]}], + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + ?match(ok, mnesia:dirty_write({Tab, 1, 4711})), + ?match(ok, mnesia:dirty_write({Tab, 2, 42})), + ?match(ok, mnesia:dirty_write({Tab, 3, 256})), + + %% dump the table + ?match( {aborted, {"Only allowed on ram_copies",Tab,[Node1]}}, + mnesia:dump_tables([Tab])), + + ?match(ok, mnesia:dirty_write({Tab, 1, 815})), + ?match(ok, mnesia:dirty_write({Tab, 2, 915})), + + %% kill mnesia on node1 + mnesia_test_lib:kill_mnesia([Node1]), + + %% wait a while, so that mnesia is really down + timer:sleep(timer:seconds(1)), + + mnesia_test_lib:start_mnesia([Node1],[Tab]), + + ?match([{Tab, 1, 815}], mnesia:dirty_read({Tab,1}) ), + ?match([{Tab, 2, 915}], mnesia:dirty_read({Tab,2}) ), + ?match([{Tab, 3, 256}], mnesia:dirty_read({Tab,3}) ), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +durability_of_disc_copies(doc) -> + ["Perform all possible kinds of updates on tables and check" + "whether no data is lost after a restart of Mnesia.", + "This test is done for disc_copies"]; + +durability_of_disc_copies(suite) -> []; +durability_of_disc_copies(Config) when is_list(Config) -> + do_disc_durability(Config,disc_copies). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +durability_of_disc_only_copies(doc) -> + ["Perform all possible kinds of updates on tables and check" + "whether no data is lost after a restart of Mnesia.", + "This test is done for disc_only_copies"]; +durability_of_disc_only_copies(suite) -> []; +durability_of_disc_only_copies(Config) when is_list(Config) -> + do_disc_durability(Config,disc_only_copies). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +do_disc_durability(Config,CopyType) -> + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(1)}]), + {success, [A,B,C]} = ?start_activities(Nodes), + + Tab_set = disc_durability_set, + Def_set = [{attributes, [key, value]}, + {CopyType, Nodes}], + + Tab_bag = disc_durability_bag, + Def_bag = [{attributes, [key, value]}, + {type, bag}, + {CopyType, Nodes}], + + ?match({atomic, ok}, mnesia:create_table(Tab_set, Def_set)), + ?match({atomic, ok}, mnesia:create_table(Tab_bag, Def_bag)), + + %% do updates + ?match({atomic, ok}, + mnesia:transaction(fun()-> + mnesia:write({Tab_set, 11, 1111}), + mnesia:write({Tab_set, 22, 2222}), + mnesia:write({Tab_set, 33, 3333}), + mnesia:write({Tab_set, 55, 5555}) + end)), + mnesia:dirty_write({Tab_set, 44, 4444}), + + ?match({atomic, ok}, + mnesia:transaction(fun()-> + mnesia:write({Tab_bag, 11, a_1111}), + mnesia:write({Tab_bag, 11, b_1111}), + mnesia:write({Tab_bag, 22, a_2222}), + mnesia:write({Tab_bag, 22, b_2222}), + mnesia:write({Tab_bag, 33, a_3333}), + mnesia:write({Tab_bag, 33, b_3333}) + end)), + ?match({atomic, ok}, + mnesia:transaction(fun()-> mnesia:delete({Tab_set, 22}) end)), + ?match(ok, mnesia:dirty_delete({Tab_set, 33})), + ?match(5558, mnesia:dirty_update_counter({Tab_set, 55}, 3)), + ?match({atomic, ok}, + mnesia:transaction(fun()-> + mnesia:delete_object({Tab_bag, 22, b_2222}) + end)), + ?match(ok, mnesia:dirty_delete_object({Tab_bag, 33, b_3333})), + ?match(10, mnesia:dirty_update_counter({Tab_set, counter}, 10)), + ?match({atomic, ok}, % Also syncs update_counter + mnesia:sync_transaction(fun() -> mnesia:write({Tab_set,66,6666}) end)), + + Updated = {[[{Tab_set,counter,10}], + [{Tab_set,counter,10}], + [{Tab_set,counter,10}]],[]}, + ?match(Updated, rpc:multicall(Nodes, mnesia, dirty_read, [Tab_set,counter])), + + %% kill mnesia on all nodes, start it again and check the data + mnesia_test_lib:kill_mnesia(Nodes), + mnesia_test_lib:start_mnesia(Nodes,[Tab_set,Tab_bag]), + + ?log("Flushed ~p ~n", [mnesia_test_lib:flush()]), %% Debugging strange msgs.. + ?log("Processes ~p ~p ~p~n", [A,B,C]), + check_tables([A,B,C], + [{Tab_set,11}, {Tab_set,22},{Tab_set,33}, + {Tab_set,44},{Tab_set,55}, {Tab_set,66}, + {Tab_bag,11}, {Tab_bag,22},{Tab_bag,33}, + {Tab_set, counter}], + [[{Tab_set, 11, 1111}], [], [], [{Tab_set, 44, 4444}], + [{Tab_set, 55, 5558}], [{Tab_set, 66, 6666}], + lists:sort([{Tab_bag, 11, a_1111},{Tab_bag, 11, b_1111}]), + [{Tab_bag, 22, a_2222}], [{Tab_bag, 33, a_3333}], + [{Tab_set, counter, 10}]]), + + timer:sleep(1000), %% Debugging strange msgs.. + ?log("Flushed ~p ~n", [mnesia_test_lib:flush()]), + ?verify_mnesia(Nodes, []). + +%% check the contents of the table +%% +%% all the processes in the PidList shall find all +%% table entries in ValList + +check_tables([],_vallist,_resultList) -> ok; +check_tables([Pid|Rest],ValList,ResultList) -> + Pid ! fun () -> + check_values(ValList) + end, + ?match_receive({ Pid, ResultList }), + check_tables(Rest,ValList,ResultList). + +check_values([]) -> []; +check_values([{Tab,Key}|Rest]) -> + Ret = lists:sort(mnesia:dirty_read({Tab,Key})), + [Ret|check_values(Rest)]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% stolen from mnesia_recovery_test.erl: + +receive_messages([]) -> []; +receive_messages(ListOfMsgs) -> + receive + timeout -> + case lists:member(timeout, ListOfMsgs) of + false -> + ?warning("I (~p) have received unexpected msg~n ~p ~n", + [self(),timeout]), + receive_messages(ListOfMsgs); + true -> + ?verbose("I (~p) got msg ~p ~n", [self(),timeout]), + [ timeout | receive_messages(ListOfMsgs -- [timeout])] + end; + + {Pid, Msg} -> + case lists:member(Msg, ListOfMsgs) of + false -> + ?warning("I (~p) have received unexpected msg~n ~p ~n", + [self(),{Pid, Msg}]), + receive_messages(ListOfMsgs); + true -> + ?verbose("I (~p) got msg ~p from ~p ~n", [self(),Msg, Pid]), + [{Pid, Msg} | receive_messages(ListOfMsgs -- [Msg])] + end; + + Else -> ?warning("Recevied unexpected Msg~n ~p ~n", [Else]) + after timer:seconds(40) -> + ?error("Timeout in receive msgs while waiting for ~p~n", + [ListOfMsgs]) + end. + diff --git a/lib/mnesia/test/mnesia_evil_backup.erl b/lib/mnesia/test/mnesia_evil_backup.erl new file mode 100644 index 0000000000..bbbebeb02c --- /dev/null +++ b/lib/mnesia/test/mnesia_evil_backup.erl @@ -0,0 +1,750 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1998-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%%---------------------------------------------------------------------- +%%% File : mnesia_evil_backup.erl +%%% Author : Dan Gudmundsson <dgud@legolas> +%%% Purpose : Evil backup tests +%%% Created : 3 Jun 1998 by Dan Gudmundsson <[email protected]> +%%%---------------------------------------------------------------------- + +-module(mnesia_evil_backup). +-author('[email protected]'). +-compile(export_all). +-include("mnesia_test_lib.hrl"). + +%%-export([Function/Arity, ...]). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +all(doc) -> + ["Checking all the functionality regarding ", + "to the backup and different ", + "kinds of restore and fallback interface"]; + +all(suite) -> + [ + backup, + bad_backup, + global_backup_checkpoint, + restore_tables, + traverse_backup, + selective_backup_checkpoint, + incremental_backup_checkpoint, +%% local_backup_checkpoint, + install_fallback, + uninstall_fallback, + local_fallback, + sops_with_checkpoint + ]. + +backup(doc) -> ["Checking the interface to the function backup", + "We don't check that the backups can be used here", + "That is checked in install_fallback and in restore"]; +backup(suite) -> []; +backup(Config) when is_list(Config) -> + [Node1, Node2] = _Nodes = ?acquire_nodes(2, Config), + Tab = backup_tab, + Def = [{disc_copies, [Node1]}, {ram_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, test_ok})), + File = "backup_test.BUP", + ?match(ok, mnesia:backup(File)), + + File2 = "backup_test2.BUP", + Tab2 = backup_tab2, + Def2 = [{disc_only_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match(ok, mnesia:backup(File2, mnesia_backup)), + + File3 = "backup_test3.BUP", + mnesia_test_lib:kill_mnesia([Node2]), + ?match({error, _}, mnesia:backup(File3, mnesia_backup)), + + ?match(ok, file:delete(File)), + ?match(ok, file:delete(File2)), + ?match({error, _}, file:delete(File3)), + ?verify_mnesia([Node1], [Node2]). + + +bad_backup(suite) -> []; +bad_backup(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = backup_tab, + Def = [{disc_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, test_ok})), + File = "backup_test.BUP", + ?match(ok, mnesia:backup(File)), + file:write_file(File, "trash", [append]), + ?match(ok, mnesia:dirty_write({Tab, 1, test_bad})), + ?match({atomic,[Tab]}, mnesia:restore(File, [{clear_tables, [Tab]}])), + ?match([{Tab,1,test_ok}], mnesia:dirty_read(Tab, 1)), + + ?match(ok, file:delete(File)), + ?verify_mnesia([Node1], []). + + + +global_backup_checkpoint(doc) -> + ["Checking the interface to the function backup_checkpoint", + "We don't check that the backups can be used here", + "That is checked in install_fallback and in restore"]; +global_backup_checkpoint(suite) -> []; +global_backup_checkpoint(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = backup_cp, + Def = [{disc_copies, [Node1]}, {ram_copies, [Node2]}], + File = "backup_checkpoint.BUP", + File2 = "backup_checkpoint2.BUP", + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, test_ok})), + ?match({error, _}, mnesia:backup_checkpoint(cp_name, File)), + Spec = [{name, cp_name}, {max, mnesia:system_info(tables)}], + ?match({ok, _Name, _Ns}, mnesia:activate_checkpoint(Spec)), + ?match(ok, mnesia:backup_checkpoint(cp_name, File)), + ?match({error, _}, mnesia:backup_checkpoint(cp_name_nonexist, File)), + ?match(ok, mnesia:backup_checkpoint(cp_name, File2, mnesia_backup)), + ?match({error, _}, file:delete(File)), + ?match(ok, file:delete(File2)), + ?verify_mnesia(Nodes, []). + +restore_tables(doc) -> + ["Tests the interface of restore"]; + +restore_tables(suite) -> + [ + restore_errors, + restore_clear, + restore_keep, + restore_recreate, + restore_clear_ram + ]. + +restore_errors(suite) -> []; +restore_errors(Config) when is_list(Config) -> + [_Node] = ?acquire_nodes(1, Config), + ?match({aborted, enoent}, mnesia:restore(notAfile, [])), + ?match({aborted, {badarg, _}}, mnesia:restore(notAfile, not_a_list)), + ?match({aborted, {badarg, _}}, mnesia:restore(notAfile, [test_badarg])), + ?match({aborted, {badarg, _}}, mnesia:restore(notAfile, [{test_badarg, xxx}])), + ?match({aborted, {badarg, _}}, mnesia:restore(notAfile, [{skip_tables, xxx}])), + ?match({aborted, {badarg, _}}, mnesia:restore(notAfile, [{recreate_tables, [schema]}])), + ?match({aborted, {badarg, _}}, mnesia:restore(notAfile, [{default_op, asdklasd}])), + ok. + +restore_clear(suite) -> []; +restore_clear(Config) when is_list(Config) -> + restore(Config, clear_tables). + +restore_keep(suite) -> []; +restore_keep(Config) when is_list(Config) -> + restore(Config, keep_tables). + +restore_recreate(suite) -> []; +restore_recreate(Config) when is_list(Config) -> + restore(Config, recreate_tables). + +check_tab(Records, Line) -> + Verify = fun({Table, Key, Val}) -> + case catch mnesia:dirty_read({Table, Key}) of + [{Table, Key, Val}] -> ok; + Else -> + mnesia_test_lib:error("Not matching on Node ~p ~n" + " Expected ~p~n Actual ~p~n", + [node(), {Table, Key, Val}, Else], + ?MODULE, Line), + exit(error) + end; + (Recs) -> + [{Tab, Key, _}, _] = Recs, + SRecs = lists:sort(Recs), + R_Recs = lists:sort(catch mnesia:dirty_read({Tab, Key})), + case R_Recs of + SRecs -> ok; + Else -> + mnesia_test_lib:error("Not matching on Node ~p ~n" + " Expected ~p~n Actual ~p~n", + [node(), SRecs, Else], + ?MODULE, Line), + exit(error) + end + end, + lists:foreach(Verify, Records). + +restore(Config, Op) -> + [Node1, Node2, _Node3] = Nodes = ?acquire_nodes(3, Config), + + Tab1 = ram_snmp, + Def1 = [{snmp, [{key, integer}]}, {ram_copies, [Node1]}], + Tab2 = disc_index, + Def2 = [{index, [val]}, {disc_copies, [Node1, Node2]}], + Tab3 = dionly_bag, + Def3 = [{type, bag}, {disc_only_copies, Nodes}], + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)), + + File1 = "restore1.BUP", + File2 = "restore2.BUP", + + Restore = fun(O, A) -> + case mnesia:restore(O, A) of + {atomic, Tabs} when is_list(Tabs) -> {atomic, lists:sort(Tabs)}; + Other -> Other + end + end, + Tabs = lists:sort([Tab1, Tab2, Tab3]), + + [mnesia:dirty_write({Tab1, N, N+42}) || N <- lists:seq(1, 10)], + [mnesia:dirty_write({Tab2, N, N+43}) || N <- lists:seq(1, 10)], + [mnesia:dirty_write({Tab3, N, N+44}) || N <- lists:seq(1, 10)], + + Res1 = [{Tab1, N, N+42} || N <- lists:seq(1, 10)], + Res2 = [{Tab2, N, N+43} || N <- lists:seq(1, 10)], + Res3 = [{Tab3, N, N+44} || N <- lists:seq(1, 10)], + + {ok, Name, _} = mnesia:activate_checkpoint([{min, Tabs}, {ram_overrides_dump, true}]), + file:delete(File1), + + %% Test standard Restore on one table on one node + ?match(ok, mnesia:backup_checkpoint(Name, File1)), + ?match(ok, mnesia:deactivate_checkpoint(Name)), + ?match(ok, mnesia:backup(File2)), + [mnesia:dirty_write({Tab1, N, N+1}) || N <- lists:seq(1, 11)], + [mnesia:dirty_write({Tab2, N, N+1}) || N <- lists:seq(1, 11)], + [mnesia:dirty_write({Tab3, N, N+1}) || N <- lists:seq(1, 11)], + _Res11 = [{Tab1, N, N+1} || N <- lists:seq(1, 11)], + Res21 = [{Tab2, N, N+1} || N <- lists:seq(1, 11)], + Res31 = [[{Tab3, N, N+1}, {Tab3, N, N+44}] || N <- lists:seq(1, 10)], + + ?match({atomic, [Tab1]}, Restore(File1, [{Op, [Tab1]}, + {skip_tables, Tabs -- [Tab1]}])), + case Op of + keep_tables -> + ?match([{Tab1, 11, 12}], mnesia:dirty_read({Tab1, 11})); + clear_tables -> + ?match([], mnesia:dirty_read({Tab1, 11})); + recreate_tables -> + ?match([], mnesia:dirty_read({Tab1, 11})) + end, + [rpc:call(Node, ?MODULE, check_tab, [Res1, ?LINE]) || Node <- Nodes], + [rpc:call(Node, ?MODULE, check_tab, [Res21, ?LINE]) || Node <- Nodes], + [rpc:call(Node, ?MODULE, check_tab, [Res31, ?LINE]) || Node <- Nodes], + + %% Restore all tables on it's nodes + mnesia_schema:clear_table(Tab1), + mnesia_schema:clear_table(Tab2), + mnesia_schema:clear_table(Tab3), + [mnesia:dirty_write({Tab1, N, N+1}) || N <- lists:seq(1, 11)], + [mnesia:dirty_write({Tab2, N, N+1}) || N <- lists:seq(1, 11)], + [mnesia:dirty_write({Tab3, N, N+1}) || N <- lists:seq(1, 11)], + + ?match({atomic, ok}, mnesia:del_table_copy(Tab2, Node1)), + + ?match({ok, Node1}, mnesia:subscribe({table, Tab1})), + + ?match({atomic, Tabs}, Restore(File1, [{default_op, Op}, + {module, mnesia_backup}])), + case Op of + clear_tables -> + ?match_receive({mnesia_table_event, {delete, {schema, Tab1}, _}}), + ?match_receive({mnesia_table_event, {write, {schema, Tab1, _}, _}}), + check_subscr(Tab1), + [rpc:call(Node, ?MODULE, check_tab, [Res1, ?LINE]) || Node <- Nodes], + [rpc:call(Node, ?MODULE, check_tab, [Res2, ?LINE]) || Node <- Nodes], + [rpc:call(Node, ?MODULE, check_tab, [Res3, ?LINE]) || Node <- Nodes], + ?match([], mnesia:dirty_read({Tab1, 11})), + ?match([], mnesia:dirty_read({Tab2, 11})), + ?match([], mnesia:dirty_read({Tab3, 11})), + %% Check Index + ?match([{Tab2, 10, 53}], mnesia:dirty_index_read(Tab2, 53, val)), + ?match([], mnesia:dirty_index_read(Tab2, 11, val)), + %% Check Snmp + ?match({ok, [1]}, mnesia:snmp_get_next_index(Tab1,[])), + ?match({ok, {Tab1, 1, 43}}, mnesia:snmp_get_row(Tab1, [1])), + ?match(undefined, mnesia:snmp_get_row(Tab1, [11])), + %% Check schema info + ?match([Node2], mnesia:table_info(Tab2, where_to_write)); + keep_tables -> + check_subscr(Tab1), + [rpc:call(Node, ?MODULE, check_tab, [Res1, ?LINE]) || Node <- Nodes], + [rpc:call(Node, ?MODULE, check_tab, [Res2, ?LINE]) || Node <- Nodes], + [rpc:call(Node, ?MODULE, check_tab, [Res31, ?LINE]) || Node <- Nodes], + ?match([{Tab1, 11, 12}], mnesia:dirty_read({Tab1, 11})), + ?match([{Tab2, 11, 12}], mnesia:dirty_read({Tab2, 11})), + ?match([{Tab3, 11, 12}], mnesia:dirty_read({Tab3, 11})), + ?match([{Tab2, 10, 53}], mnesia:dirty_index_read(Tab2, 53, val)), + %% Check Index + ?match([], mnesia:dirty_index_read(Tab2, 11, val)), + ?match({ok, [1]}, mnesia:snmp_get_next_index(Tab1,[])), + %% Check Snmp + ?match({ok, {Tab1, 1, 43}}, mnesia:snmp_get_row(Tab1, [1])), + ?match({ok, {Tab1, 11, 12}}, mnesia:snmp_get_row(Tab1, [11])), + %% Check schema info + ?match([Node2], mnesia:table_info(Tab2, where_to_write)); + recreate_tables -> + check_subscr(Tab1, 0), + [rpc:call(Node, ?MODULE, check_tab, [Res1, ?LINE]) || Node <- Nodes], + [rpc:call(Node, ?MODULE, check_tab, [Res2, ?LINE]) || Node <- Nodes], + [rpc:call(Node, ?MODULE, check_tab, [Res3, ?LINE]) || Node <- Nodes], + ?match([], mnesia:dirty_read({Tab1, 11})), + ?match([], mnesia:dirty_read({Tab2, 11})), + ?match([], mnesia:dirty_read({Tab3, 11})), + %% Check Index + ?match([{Tab2, 10, 53}], mnesia:dirty_index_read(Tab2, 53, val)), + ?match([], mnesia:dirty_index_read(Tab2, 11, val)), + %% Check Snmp + ?match({ok, [1]}, mnesia:snmp_get_next_index(Tab1,[])), + ?match({ok, {Tab1, 1, 43}}, mnesia:snmp_get_row(Tab1, [1])), + ?match(undefined, mnesia:snmp_get_row(Tab1, [11])), + %% Check schema info + Ns = lists:sort([Node1, Node2]), + ?match(Ns, lists:sort(mnesia:table_info(Tab2, where_to_write))) + end, + ?match(ok, file:delete(File1)), + ?match(ok, file:delete(File2)), + ?verify_mnesia(Nodes, []). + + +check_subscr(Tab) -> + check_subscr(Tab, 10). + +check_subscr(_Tab, 0) -> + receive + Msg -> + ?error("Too many msgs ~p~n", [Msg]) + after 500 -> + ok + end; +check_subscr(Tab, N) -> + V = N +42, + receive + {mnesia_table_event, {write, {Tab, N, V}, _}} -> + check_subscr(Tab, N-1) + after 500 -> + ?error("Missing ~p~n", [{Tab, N, V}]) + end. + +restore_clear_ram(suite) -> []; +restore_clear_ram(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(3, [{diskless, true}|Config]), + + ?match({atomic, ok}, mnesia:create_table(a, [{ram_copies, Nodes}])), + + Write = fun(What) -> + mnesia:write({a,1,What}), + mnesia:write({a,2,What}), + mnesia:write({a,3,What}) + end, + Bup = "restore_clear_ram.BUP", + + ?match({atomic, ok}, mnesia:transaction(Write, [initial])), + ?match({ok, _, _}, mnesia:activate_checkpoint([{name,test}, + {min, [schema, a]}, + {ram_overrides_dump, true}])), + ?match(ok, mnesia:backup_checkpoint(test, Bup)), + + ?match({atomic, ok}, mnesia:transaction(Write, [data])), + ?match({atomic, [a]}, mnesia:restore(Bup, [{clear_tables,[a]},{default_op,skip_tables}])), + + restore_clear_ram_loop(100, Nodes, Bup), + + ok. + +restore_clear_ram_loop(N, Nodes = [N1,N2,N3], Bup) when N > 0 -> + ?match([], mnesia_test_lib:stop_mnesia(Nodes)), + ?match({_, []}, rpc:multicall([N1,N2], mnesia, start, [[{extra_db_nodes, Nodes}]])), + Key = rpc:async_call(N3, mnesia, start, [[{extra_db_nodes, Nodes}]]), + ?match({atomic, ok}, mnesia:create_table(a, [{ram_copies, Nodes}])), + ?match({atomic, [a]}, mnesia:restore(Bup, [{clear_tables,[a]},{default_op,skip_tables}])), + ?match(ok, rpc:yield(Key)), + ?match(ok, rpc:call(N3, mnesia, wait_for_tables, [[a], 3000])), + case rpc:multicall(Nodes, mnesia, table_info, [a,size]) of + {[3,3,3], []} -> + restore_clear_ram_loop(N-1, Nodes, Bup); + Error -> + ?match(3, Error) + end; +restore_clear_ram_loop(_,_,_) -> + ok. + +traverse_backup(doc) -> + ["Testing the traverse_backup interface, the resulting file is not tested though", + "See install_fallback for result using the output file from traverse_backup", + "A side effect is that the backup file contents are tested"]; +traverse_backup(suite) -> []; +traverse_backup(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = backup_tab, + Def = [{disc_copies, [Node1]}, {ram_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, test_nok})), + ?match(ok, mnesia:dirty_write({Tab, 2, test_nok})), + ?match(ok, mnesia:dirty_write({Tab, 3, test_nok})), + ?match(ok, mnesia:dirty_write({Tab, 4, test_nok})), + ?match(ok, mnesia:dirty_write({Tab, 5, test_nok})), + File = "_treverse_backup.BUP", + File2 = "traverse_backup2.BUP", + File3 = "traverse_backup3.BUP", + ?match(ok, mnesia:backup(File)), + + Fun = fun({backup_tab, N, _}, Acc) -> {[{backup_tab, N, test_ok}], Acc+1}; + (Other, Acc) -> {[Other], Acc} + end, + + ?match({ok, 5}, mnesia:traverse_backup(File, read_only, Fun, 0)), + ?match(ok, file:delete(read_only)), + + ?match({ok, 5}, mnesia:traverse_backup(File, mnesia_backup, + dummy, read_only, Fun, 0)), + + ?match({ok, 5}, mnesia:traverse_backup(File, File2, Fun, 0)), + ?match({ok, 5}, mnesia:traverse_backup(File2, mnesia_backup, + File3, mnesia_backup, Fun, 0)), + + BadFun = fun({bad_tab, _N, asd}, Acc) -> {{error, error}, Acc} end, + ?match({error, _}, mnesia:traverse_backup(File, read_only, BadFun, 0)), + ?match({error, _}, file:delete(read_only)), + ?match(ok, file:delete(File)), + ?match(ok, file:delete(File2)), + ?match(ok, file:delete(File3)), + ?verify_mnesia(Nodes, []). + + +install_fallback(doc) -> + ["This tests the install_fallback intf.", + "It also verifies that the output from backup_checkpoint and traverse_backup", + "is valid"]; +install_fallback(suite) -> []; +install_fallback(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = fallbacks_test, + Def = [{disc_copies, [Node1]}, {ram_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, test_nok})), + ?match(ok, mnesia:dirty_write({Tab, 2, test_nok})), + ?match(ok, mnesia:dirty_write({Tab, 3, test_nok})), + ?match(ok, mnesia:dirty_write({Tab, 4, test_nok})), + ?match(ok, mnesia:dirty_write({Tab, 5, test_nok})), + + Tab2 = fallbacks_test2, + Def2 = [{disc_copies, [node()]}], + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + Tab3 = fallbacks_test3, + ?match({atomic, ok}, mnesia:create_table(Tab3, Def2)), + Fun2 = fun(Key) -> + Rec = {Tab2, Key, test_ok}, + mnesia:dirty_write(Rec), + [Rec] + end, + TabSize3 = 1000, + OldRecs2 = [Fun2(K) || K <- lists:seq(1, TabSize3)], + + Spec =[{name, cp_name}, {max, mnesia:system_info(tables)}], + ?match({ok, _Name, Nodes}, mnesia:activate_checkpoint(Spec)), + ?match(ok, mnesia:dirty_write({Tab, 6, test_nok})), + [mnesia:dirty_write({Tab2, K, test_nok}) || K <- lists:seq(1, TabSize3 + 10)], + File = "install_fallback.BUP", + File2 = "install_fallback2.BUP", + File3 = "install_fallback3.BUP", + ?match(ok, mnesia:backup_checkpoint(cp_name, File)), + + Fun = fun({T, N, _}, Acc) when T == Tab -> + case N rem 2 of + 0 -> + io:format("write ~p -> ~p~n", [N, T]), + {[{T, N, test_ok}], Acc + 1}; + 1 -> + io:format("write ~p -> ~p~n", [N, Tab3]), + {[{Tab3, N, test_ok}], Acc + 1} + end; + ({T, N}, Acc) when T == Tab -> + case N rem 2 of + 0 -> + io:format("delete ~p -> ~p~n", [N, T]), + {[{T, N}], Acc + 1}; + 1 -> + io:format("delete ~p -> ~p~n", [N, Tab3]), + {[{Tab3, N}], Acc + 1} + end; + (Other, Acc) -> + {[Other], Acc} + end, + ?match({ok, _}, mnesia:traverse_backup(File, File2, Fun, 0)), + ?match(ok, mnesia:install_fallback(File2)), + + mnesia_test_lib:kill_mnesia([Node1, Node2]), + timer:sleep(timer:seconds(1)), % Let it die! + + ?match([], mnesia_test_lib:start_mnesia([Node1, Node2], [Tab, Tab2, Tab3])), + + % Verify + ?match([], mnesia:dirty_read({Tab, 1})), + ?match([{Tab3, 1, test_ok}], mnesia:dirty_read({Tab3, 1})), + ?match([{Tab, 2, test_ok}], mnesia:dirty_read({Tab, 2})), + ?match([], mnesia:dirty_read({Tab3, 2})), + ?match([], mnesia:dirty_read({Tab, 3})), + ?match([{Tab3, 3, test_ok}], mnesia:dirty_read({Tab3, 3})), + ?match([{Tab, 4, test_ok}], mnesia:dirty_read({Tab, 4})), + ?match([], mnesia:dirty_read({Tab3, 4})), + ?match([], mnesia:dirty_read({Tab, 5})), + ?match([{Tab3, 5, test_ok}], mnesia:dirty_read({Tab3, 5})), + ?match([], mnesia:dirty_read({Tab, 6})), + ?match([], mnesia:dirty_read({Tab3, 6})), + ?match([], [mnesia:dirty_read({Tab2, K}) || K <- lists:seq(1, TabSize3)] -- OldRecs2), + ?match(TabSize3, mnesia:table_info(Tab2, size)), + + % Check the interface + file:delete(File3), + ?match({error, _}, mnesia:install_fallback(File3)), + ?match({error, _}, mnesia:install_fallback(File2, mnesia_badmod)), + ?match(ok, mnesia:install_fallback(File2, mnesia_backup)), + ?match(ok, file:delete(File)), + ?match(ok, file:delete(File2)), + ?match({error, _}, file:delete(File3)), + ?verify_mnesia(Nodes, []). + +uninstall_fallback(suite) -> []; +uninstall_fallback(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = uinst_fallbacks_test, + File = "uinst_fallback.BUP", + File2 = "uinst_fallback2.BUP", + Def = [{disc_copies, [Node1]}, {ram_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, test_ok})), + ?match(ok, mnesia:backup(File)), + Fun = fun({T, N, _}, Acc) when T == Tab -> + {[{T, N, test_nok}], Acc+1}; + (Other, Acc) -> {[Other], Acc} + end, + ?match({ok, _}, mnesia:traverse_backup(File, File2, Fun, 0)), + ?match({error, enoent}, mnesia:uninstall_fallback()), + ?match(ok, mnesia:install_fallback(File2)), + ?match(ok, file:delete(File)), + ?match(ok, file:delete(File2)), + ?match(ok, mnesia:uninstall_fallback()), + + mnesia_test_lib:kill_mnesia([Node1, Node2]), + timer:sleep(timer:seconds(1)), % Let it die! + ?match([], mnesia_test_lib:start_mnesia([Node1, Node2], [Tab])), + ?match([{Tab, 1, test_ok}], mnesia:dirty_read({Tab, 1})), + ?verify_mnesia(Nodes, []). + +local_fallback(suite) -> []; +local_fallback(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = local_fallback, + File = "local_fallback.BUP", + Def = [{disc_copies, Nodes}], + Key = foo, + Pre = {Tab, Key, pre}, + Post = {Tab, Key, post}, + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write(Pre)), + ?match(ok, mnesia:backup(File)), + ?match(ok, mnesia:dirty_write(Post)), + Local = [{scope, local}], + ?match({error, enoent}, mnesia:uninstall_fallback(Local)), + ?match(ok, mnesia:install_fallback(File, Local)), + ?match(true, mnesia:system_info(fallback_activated)), + ?match(ok, mnesia:uninstall_fallback(Local)), + ?match(false, mnesia:system_info(fallback_activated)), + ?match(ok, mnesia:install_fallback(File, Local)), + ?match(true, mnesia:system_info(fallback_activated)), + + ?match(false, rpc:call(Node2, mnesia, system_info , [fallback_activated])), + ?match(ok, rpc:call(Node2, mnesia, install_fallback , [File, Local])), + ?match([Post], mnesia:dirty_read({Tab, Key})), + ?match([Post], rpc:call(Node2, mnesia, dirty_read, [{Tab, Key}])), + + ?match([], mnesia_test_lib:kill_mnesia(Nodes)), + ?match([], mnesia_test_lib:start_mnesia(Nodes, [Tab])), + ?match([Pre], mnesia:dirty_read({Tab, Key})), + ?match([Pre], rpc:call(Node2, mnesia, dirty_read, [{Tab, Key}])), + Dir = rpc:call(Node2, mnesia, system_info , [directory]), + + ?match(ok, mnesia:dirty_write(Post)), + ?match([Post], mnesia:dirty_read({Tab, Key})), + ?match([], mnesia_test_lib:kill_mnesia([Node2])), + ?match(ok, mnesia:install_fallback(File, Local ++ [{mnesia_dir, Dir}])), + ?match([], mnesia_test_lib:kill_mnesia([Node1])), + + ?match([], mnesia_test_lib:start_mnesia([Node2], [])), + ?match(yes, rpc:call(Node2, mnesia, force_load_table, [Tab])), + ?match([], mnesia_test_lib:start_mnesia(Nodes, [Tab])), + ?match([Pre], mnesia:dirty_read({Tab, Key})), + + ?match(ok, file:delete(File)), + ?verify_mnesia(Nodes, []). + +selective_backup_checkpoint(doc) -> + ["Perform a selective backup of a checkpoint"]; +selective_backup_checkpoint(suite) -> []; +selective_backup_checkpoint(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = sel_backup, + OmitTab = sel_backup_omit, + CpName = sel_cp, + Def = [{disc_copies, [Node1, Node2]}], + File = "selective_backup_checkpoint.BUP", + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match({atomic, ok}, mnesia:create_table(OmitTab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 1, test_ok})), + ?match(ok, mnesia:dirty_write({OmitTab, 1, test_ok})), + CpSpec = [{name, CpName}, {max, mnesia:system_info(tables)}], + ?match({ok, CpName, _Ns}, mnesia:activate_checkpoint(CpSpec)), + + BupSpec = [{tables, [Tab]}], + ?match(ok, mnesia:backup_checkpoint(CpName, File, BupSpec)), + + ?match([schema, sel_backup], bup_tables(File, mnesia_backup)), + ?match(ok, file:delete(File)), + + BupSpec2 = [{tables, [Tab, OmitTab]}], + ?match(ok, mnesia:backup_checkpoint(CpName, File, BupSpec2)), + + ?match([schema, sel_backup, sel_backup_omit], + bup_tables(File, mnesia_backup)), + ?match(ok, file:delete(File)), + ?verify_mnesia(Nodes, []). + +bup_tables(File, Mod) -> + Fun = fun(Rec, Tabs) -> + Tab = element(1, Rec), + Tabs2 = [Tab | lists:delete(Tab, Tabs)], + {[Rec], Tabs2} + end, + case mnesia:traverse_backup(File, Mod, dummy, read_only, Fun, []) of + {ok, Tabs} -> + lists:sort(Tabs); + {error, Reason} -> + exit(Reason) + end. + +incremental_backup_checkpoint(doc) -> + ["Perform a incremental backup of a checkpoint"]; +incremental_backup_checkpoint(suite) -> []; +incremental_backup_checkpoint(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = incr_backup, + Def = [{disc_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + OldRecs = [{Tab, K, -K} || K <- lists:seq(1, 5)], + ?match([ok|_], [mnesia:dirty_write(R) || R <- OldRecs]), + OldCpName = old_cp, + OldCpSpec = [{name, OldCpName}, {min, [Tab]}], + ?match({ok, OldCpName, _Ns}, mnesia:activate_checkpoint(OldCpSpec)), + + BupSpec = [{tables, [Tab]}], + OldFile = "old_full_backup.BUP", + ?match(ok, mnesia:backup_checkpoint(OldCpName, OldFile, BupSpec)), + ?match(OldRecs, bup_records(OldFile, mnesia_backup)), + ?match(ok, mnesia:dirty_delete({Tab, 1})), + ?match(ok, mnesia:dirty_write({Tab, 2, 2})), + ?match(ok, mnesia:dirty_write({Tab, 3, -3})), + + NewCpName = new_cp, + NewCpSpec = [{name, NewCpName}, {min, [Tab]}], + ?match({ok, NewCpName, _Ns}, mnesia:activate_checkpoint(NewCpSpec)), + ?match(ok, mnesia:dirty_write({Tab, 4, 4})), + + NewFile = "new_full_backup.BUP", + ?match(ok, mnesia:backup_checkpoint(NewCpName, NewFile, BupSpec)), + NewRecs = [{Tab, 2, 2}, {Tab, 3, -3}, + {Tab, 4, 4}, {Tab, 4}, {Tab, 4, -4}, {Tab, 5, -5}], + ?match(NewRecs, bup_records(NewFile, mnesia_backup)), + + DiffFile = "diff_backup.BUP", + DiffBupSpec = [{tables, [Tab]}, {incremental, OldCpName}], + ?match(ok, mnesia:backup_checkpoint(NewCpName, DiffFile, DiffBupSpec)), + DiffRecs = [{Tab, 1}, {Tab, 2}, {Tab, 2, 2}, {Tab, 3}, {Tab, 3, -3}, + {Tab, 4}, {Tab, 4, 4}, {Tab, 4}, {Tab, 4, -4}], + ?match(DiffRecs, bup_records(DiffFile, mnesia_backup)), + + ?match(ok, mnesia:deactivate_checkpoint(OldCpName)), + ?match(ok, mnesia:deactivate_checkpoint(NewCpName)), + ?match(ok, file:delete(OldFile)), + ?match(ok, file:delete(NewFile)), + ?match(ok, file:delete(DiffFile)), + + ?verify_mnesia(Nodes, []). + +bup_records(File, Mod) -> + Fun = fun(Rec, Recs) when element(1, Rec) == schema -> + {[Rec], Recs}; + (Rec, Recs) -> + {[Rec], [Rec | Recs]} + end, + case mnesia:traverse_backup(File, Mod, dummy, read_only, Fun, []) of + {ok, Recs} -> + lists:keysort(1, lists:keysort(2, lists:reverse(Recs))); + {error, Reason} -> + exit(Reason) + end. + +sops_with_checkpoint(doc) -> + ["Test schema operations during a checkpoint"]; +sops_with_checkpoint(suite) -> []; +sops_with_checkpoint(Config) when is_list(Config) -> + Ns = ?acquire_nodes(2, Config), + + ?match({ok, cp1, Ns}, mnesia:activate_checkpoint([{name, cp1},{max,mnesia:system_info(tables)}])), + Tab = tab, + ?match({atomic, ok}, mnesia:create_table(Tab, [{disc_copies,Ns}])), + OldRecs = [{Tab, K, -K} || K <- lists:seq(1, 5)], + [mnesia:dirty_write(R) || R <- OldRecs], + + ?match({ok, cp2, Ns}, mnesia:activate_checkpoint([{name, cp2},{max,mnesia:system_info(tables)}])), + File1 = "cp1_delete_me.BUP", + ?match(ok, mnesia:dirty_write({Tab,6,-6})), + ?match(ok, mnesia:backup_checkpoint(cp1, File1)), + ?match(ok, mnesia:dirty_write({Tab,7,-7})), + File2 = "cp2_delete_me.BUP", + ?match(ok, mnesia:backup_checkpoint(cp2, File2)), + + ?match(ok, mnesia:deactivate_checkpoint(cp1)), + ?match(ok, mnesia:backup_checkpoint(cp2, File1)), + ?match(ok, mnesia:dirty_write({Tab,8,-8})), + + ?match({atomic,ok}, mnesia:delete_table(Tab)), + ?match({error,_}, mnesia:backup_checkpoint(cp2, File2)), + ?match({'EXIT',_}, mnesia:dirty_write({Tab,9,-9})), + + ?match({atomic,_}, mnesia:restore(File1, [{default_op, recreate_tables}])), + Test = fun(N) when N > 5 -> ?error("To many records in backup ~p ~n", [N]); + (N) -> case mnesia:dirty_read(Tab,N) of + [{Tab,N,B}] when -B =:= N -> ok; + Other -> ?error("Not matching ~p ~p~n", [N,Other]) + end + end, + [Test(N) || N <- mnesia:dirty_all_keys(Tab)], + ?match({aborted,enoent}, mnesia:restore(File2, [{default_op, recreate_tables}])), + + file:delete(File1), file:delete(File2), + + ?verify_mnesia(Ns, []). diff --git a/lib/mnesia/test/mnesia_evil_coverage_test.erl b/lib/mnesia/test/mnesia_evil_coverage_test.erl new file mode 100644 index 0000000000..4fbf1b4003 --- /dev/null +++ b/lib/mnesia/test/mnesia_evil_coverage_test.erl @@ -0,0 +1,2401 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_evil_coverage_test). +-author('[email protected]'). +-include("mnesia_test_lib.hrl"). + +-compile([export_all]). + +-define(cleanup(N, Config), + mnesia_test_lib:prepare_test_case([{reload_appls, [mnesia]}], + N, Config, ?FILE, ?LINE)). +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Evil usage of the API.", + "Invoke all functions in the API and try to cover all legal uses", + "cases as well the illegal dito. This is a complement to the", + "other more explicit test cases."]; +all(suite) -> + [ + system_info, + table_info, + error_description, + db_node_lifecycle, + evil_delete_db_node, + start_and_stop, + checkpoint, + table_lifecycle, + add_copy_conflict, + add_copy_when_going_down, + replica_management, + schema_availability, + local_content, + table_access_modifications, + replica_location, + table_sync, + user_properties, + unsupp_user_props, + record_name, + snmp_access, + subscriptions, + iteration, + debug_support, + sorted_ets, + {mnesia_dirty_access_test, all}, + {mnesia_trans_access_test, all}, + {mnesia_evil_backup, all} + ]. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Get meta info about Mnesia + +system_info(suite) -> []; +system_info(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(all, Config), + Ns = ?sort(Nodes), + ?match(yes, mnesia:system_info(is_running)), + ?match(Ns, ?sort(mnesia:system_info(db_nodes))), + ?match(Ns, ?sort(mnesia:system_info(running_db_nodes))), + ?match(A when is_atom(A), mnesia:system_info(debug)), + ?match(L when is_list(L), mnesia:system_info(directory)), + ?match(L when is_list(L), mnesia:system_info(log_version)), + ?match({_, _}, mnesia:system_info(schema_version)), + ?match(L when is_list(L), mnesia:system_info(tables)), + ?match(L when is_list(L), mnesia:system_info(local_tables)), + ?match(L when is_list(L), mnesia:system_info(held_locks)), + ?match(L when is_list(L), mnesia:system_info(lock_queue)), + ?match(L when is_list(L), mnesia:system_info(transactions)), + ?match(I when is_integer(I), mnesia:system_info(transaction_failures)), + ?match(I when is_integer(I), mnesia:system_info(transaction_commits)), + ?match(I when is_integer(I), mnesia:system_info(transaction_restarts)), + ?match(L when is_list(L), mnesia:system_info(checkpoints)), + ?match(A when is_atom(A), mnesia:system_info(backup_module)), + ?match(true, mnesia:system_info(auto_repair)), + ?match({_, _}, mnesia:system_info(dump_log_interval)), + ?match(A when is_atom(A), mnesia:system_info(dump_log_update_in_place)), + ?match(I when is_integer(I), mnesia:system_info(transaction_log_writes)), + ?match(I when is_integer(I), mnesia:system_info(send_compressed)), + ?match(L when is_list(L), mnesia:system_info(all)), + ?match({'EXIT', {aborted, Reason }} when element(1, Reason) == badarg + , mnesia:system_info(ali_baba)), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Get meta info about table + +table_info(suite) -> []; +table_info(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + + Tab = table_info, + Type = bag, + ValPos = 3, + Attrs = [k, v], + Arity = length(Attrs) +1, + + Schema = + case mnesia_test_lib:diskless(Config) of + true -> [{type, Type}, {attributes, Attrs}, {index, [ValPos]}, + {ram_copies, Nodes}]; + false -> + [{type, Type}, {attributes, Attrs}, {index, [ValPos]}, + {disc_only_copies, [Node1]}, {ram_copies, [Node2]}, + {disc_copies, [Node3]}] + end, + ?match({atomic, ok}, mnesia:create_table(Tab, Schema)), + + Size = 10, + Keys = lists:seq(1, Size), + Records = [{Tab, A, 7} || A <- Keys], + lists:foreach(fun(Rec) -> ?match(ok, mnesia:dirty_write(Rec)) end, Records), + ?match(Mem when is_integer(Mem), mnesia:table_info(Tab, memory)), + ?match(Size, mnesia:table_info(Tab, size)), + ?match(Type, mnesia:table_info(Tab, type)), + + case mnesia_test_lib:diskless(Config) of + true -> + ?match(Nodes, mnesia:table_info(Tab, ram_copies)); + false -> + ?match([Node3], mnesia:table_info(Tab, mnesia_test_lib:storage_type(disc_copies, Config))), + ?match([Node2], mnesia:table_info(Tab, ram_copies)), + ?match([Node1], mnesia:table_info(Tab, mnesia_test_lib:storage_type(disc_only_copies, Config))) + end, + Read = [Node1, Node2, Node3], + ?match(true, lists:member(mnesia:table_info(Tab, where_to_read), Read)), + Write = ?sort([Node1, Node2, Node3]), + ?match(Write, ?sort(mnesia:table_info(Tab, where_to_write))), + ?match([ValPos], mnesia:table_info(Tab, index)), + ?match(Arity, mnesia:table_info(Tab, arity)), + ?match(Attrs, mnesia:table_info(Tab, attributes)), + ?match({Tab, '_', '_'}, mnesia:table_info(Tab, wild_pattern)), + ?match({atomic, Attrs}, mnesia:transaction(fun() -> + mnesia:table_info(Tab, attributes) end)), + + ?match(L when is_list(L), mnesia:table_info(Tab, all)), + + %% Table info when table not loaded + ?match({atomic, ok}, + mnesia:create_table(tab_info, Schema)), + ?match(stopped, mnesia:stop()), + ?match(stopped, rpc:call(Node2, mnesia, stop, [])), + ?match(ok, mnesia:start()), + ?match(ok, mnesia:wait_for_tables([tab_info], 5000)), + ?match(0, mnesia:table_info(tab_info, size)), + ?verify_mnesia([Node1, Node3], [Node2]). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Check the error descriptions + +error_description(suite) -> []; +error_description(Config) when is_list(Config) -> + ?acquire_nodes(1, Config), + Errors = [nested_transaction, badarg, no_transaction, combine_error, + bad_index, already_exists, index_exists, no_exists, system_limit, + mnesia_down, not_a_db_node, bad_type, node_not_running, + truncated_binary_file, active, illegal + ], + ?match(X when is_atom(X), mnesia:error_description({error, bad_error_msg})), + ?match(X when is_tuple(X), mnesia:error_description({'EXIT', pid, bad})), + %% This is real error msg + ?match(X when is_tuple(X), mnesia:error_description( + {error, + {"Cannot prepare checkpoint (bad reply)", + {{877,957351,758147},a@legolas}, + {error,{node_not_running,a1@legolas}}}})), + check_errors(error, Errors), + check_errors(aborted, Errors), + check_errors('EXIT', Errors). + +check_errors(_Err, []) -> ok; +check_errors(Err, [Desc|R]) -> + ?match(X when is_list(X), mnesia:error_description({Err, Desc})), + check_errors(Err, R). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Add and drop db nodes + +db_node_lifecycle(suite) -> []; +db_node_lifecycle(Config) when is_list(Config) -> + [Node1, Node2, Node3] = AllNodes = ?acquire_nodes(3, Config), + Tab = db_node_lifecycle, + + Who = fun(T) -> + L1 = mnesia:table_info(T, ram_copies), + L2 = mnesia:table_info(T, disc_copies), + L3 = mnesia:table_info(T, disc_only_copies), + L1 ++ L2 ++ L3 + end, + + SNs = ?sort(AllNodes), + + Schema = [{name, Tab}, {ram_copies, [Node1, Node2]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + ?match([], mnesia_test_lib:stop_mnesia(AllNodes)), + ?match(ok, mnesia:delete_schema(AllNodes)), + ?match({error, _}, mnesia:create_schema(foo)), + ?match({error, _}, mnesia:create_schema([foo])), + ?match({error, _}, mnesia:create_schema([foo@bar])), + ?match(ok, mnesia:start()), + ?match(false, mnesia:system_info(use_dir)), + ?match({atomic, ok}, mnesia:create_table(Tab, [])), + ?match({aborted, {has_no_disc, Node1}}, mnesia:dump_tables([Tab])), + ?match({aborted, {has_no_disc, Node1}}, mnesia:change_table_copy_type(Tab, node(), disc_copies)), + ?match({aborted, {has_no_disc, Node1}}, mnesia:change_table_copy_type(Tab, node(), disc_only_copies)), + + ?match(stopped, mnesia:stop()), + + ?match(ok, mnesia:create_schema(AllNodes)), + ?match([], mnesia_test_lib:start_mnesia(AllNodes)), + + ?match([SNs, SNs, SNs], + lists:map({lists, sort}, + element(1, rpc:multicall(AllNodes, mnesia, table_info, + [schema, disc_copies])))), + + ?match({aborted, {already_exists, schema, Node2, _}}, + mnesia:change_table_copy_type(schema, Node2, disc_copies)), + ?match({atomic, ok}, + mnesia:change_table_copy_type(schema, Node2, ram_copies)), + ?match({aborted, {already_exists, schema, Node2, _}}, + mnesia:change_table_copy_type(schema, Node2, ram_copies)), + + ?match({atomic, ok}, + mnesia:change_table_copy_type(schema, Node2, disc_copies)), + + ?match([SNs, SNs, SNs], + lists:map({lists, sort}, + element(1, rpc:multicall(AllNodes, mnesia, table_info, + [schema, disc_copies])))), + + %% Delete the DB + + Tab2 = disk_tab, + Tab3 = not_local, + Tab4 = local, + Tab5 = remote, + + Tabs = [Schema, + [{name, Tab2}, {disc_copies, AllNodes}], + [{name, Tab3}, {ram_copies, [Node2, Node3]}], + [{name, Tab4}, {disc_only_copies, [Node1]}], + [{name, Tab5}, {disc_only_copies, [Node2]}]], + + [?match({atomic, ok}, mnesia:create_table(T)) || T <- Tabs ], + + ?match({aborted, {active, _, Node2}}, + mnesia:del_table_copy(schema, Node2)), + + ?match([], mnesia_test_lib:stop_mnesia([Node1])), + ?match({aborted, {node_not_running, Node1}}, + mnesia:del_table_copy(schema, Node2)), + + ?match([], mnesia_test_lib:start_mnesia([Node1],[Tab2,Tab4])), + ?match([], mnesia_test_lib:stop_mnesia([Node2])), + ?match({atomic, ok}, + mnesia:del_table_copy(schema, Node2)), + + %% Check + RemNodes = AllNodes -- [Node2], + + ?match(RemNodes, mnesia:system_info(db_nodes)), + ?match([Node1], Who(Tab)), + ?match(RemNodes, Who(Tab2)), + ?match([Node3], Who(Tab3)), + ?match([Node1], Who(Tab4)), + ?match({'EXIT', {aborted, {no_exists, _, _}}}, Who(Tab5)), + + ?match({atomic, ok}, + mnesia:change_table_copy_type(Tab2, Node3, ram_copies)), + + ?match({atomic, ok}, + mnesia:change_table_copy_type(schema, Node3, ram_copies)), + + ?match([], mnesia_test_lib:stop_mnesia([Node3])), + ?match({atomic, ok}, + mnesia:del_table_copy(schema, Node3)), + ?match([Node1], mnesia:system_info(db_nodes)), + ?match([Node1], Who(Tab)), + ?match([Node1], Who(Tab2)), + ?match({'EXIT', {aborted, {no_exists, _, _}}}, Who(Tab3)), + ?match([Node1], Who(Tab4)), + ?match({'EXIT', {aborted, {no_exists, _, _}}}, Who(Tab5)), + + ?verify_mnesia([Node1], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Drop a db node when several disk resident nodes are down + +evil_delete_db_node(suite) -> []; +evil_delete_db_node(Config) when is_list(Config) -> + [Node1, Node2, Node3] = AllNodes = ?acquire_nodes(3, Config), + Tab = evil_delete_db_node, + + ?match({atomic, ok}, mnesia:create_table(Tab, [{disc_copies, AllNodes}])), + + ?match([], mnesia_test_lib:stop_mnesia([Node2, Node3])), + + ?match({atomic, ok}, mnesia:del_table_copy(schema, Node2)), + + RemNodes = AllNodes -- [Node2], + + ?match(RemNodes, mnesia:system_info(db_nodes)), + ?match(RemNodes, mnesia:table_info(Tab, disc_copies)), + + ?verify_mnesia([Node1], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Start and stop the system + +start_and_stop(suite) -> []; +start_and_stop(Config) when is_list(Config) -> + [Node1 | _] = Nodes = ?acquire_nodes(all, Config), + + ?match(stopped, rpc:call(Node1, mnesia, stop, [])), + ?match(stopped, rpc:call(Node1, mnesia, stop, [])), + ?match(ok, rpc:call(Node1, mnesia, start, [])), + ?match(ok, rpc:call(Node1, mnesia, start, [])), + ?match(stopped, rpc:call(Node1, mnesia, stop, [])), + ?verify_mnesia(Nodes -- [Node1], [Node1]), + ?match([], mnesia_test_lib:start_mnesia(Nodes)), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Checkpoints and backup management + +checkpoint(suite) -> []; +checkpoint(Config) when is_list(Config) -> + checkpoint(2, Config), + checkpoint(3, Config). + +checkpoint(NodeConfig, Config) -> + [Node1 | _] = TabNodes = ?acquire_nodes(NodeConfig, Config), + CreateTab = fun(Type, N, Ns) -> + Tab0 = lists:concat(["local_checkpoint_", Type, N]), + Tab = list_to_atom(Tab0), + catch mnesia:delete_table(Tab), + ?match({atomic, ok}, + mnesia:create_table(Tab, [{Type, Ns}])), + Tab + end, + CreateTabs = fun(Type, Acc) -> + [CreateTab(Type, 1, [hd(TabNodes)]), + CreateTab(Type, 2, TabNodes), + CreateTab(Type, 3, [lists:last(TabNodes)])] ++ + Acc + end, + Types = [ram_copies, disc_copies, disc_only_copies], + Tabs = lists:foldl(CreateTabs, [], Types), + Recs = ?sort([{T, N, N} || T <- Tabs, N <- lists:seq(1, 10)]), + lists:foreach(fun(R) -> ?match(ok, mnesia:dirty_write(R)) end, Recs), + + CpName = a_checkpoint_name, + MinArgs = [{name, CpName}, {min, Tabs}, {allow_remote, false}], + ?match({error, _}, rpc:call(Node1, mnesia, activate_checkpoint, [MinArgs])), + + MaxArgs = [{name, CpName}, {max, Tabs}, {allow_remote, true}], + ?match({ok, CpName, L} when is_list(L), + rpc:call(Node1, mnesia, activate_checkpoint, [MaxArgs])), + ?match(ok, rpc:call(Node1, mnesia, deactivate_checkpoint, [CpName])), + + Args = [{name, CpName}, {min, Tabs}, {allow_remote, true}], + ?match({ok, CpName, L} when is_list(L), + rpc:call(Node1, mnesia, activate_checkpoint, [Args])), + Recs2 = ?sort([{T, K, 0} || {T, K, _} <- Recs]), + lists:foreach(fun(R) -> ?match(ok, mnesia:dirty_write(R)) end, Recs2), + ?match(ok, rpc:call(Node1, mnesia, deactivate_checkpoint, [CpName])), + + ?match({error, Reason1 } when element(1, Reason1) == no_exists, + mnesia:deactivate_checkpoint(CpName)), + ?match({error, Reason2 } when element(1, Reason2) == badarg, + mnesia:activate_checkpoint(foo)), + ?match({error, Reason3 } when element(1, Reason3) == badarg, + mnesia:activate_checkpoint([{foo, foo}])), + ?match({error, Reason4 } when element(1, Reason4) == badarg, + mnesia:activate_checkpoint([{max, foo}])), + ?match({error, Reason5 } when element(1, Reason5) == badarg, + mnesia:activate_checkpoint([{min, foo}])), + ?match({error, _}, mnesia:activate_checkpoint([{min, [foo@bar]}])), + ?match({error, Reason6 } when element(1, Reason6) == badarg, + mnesia:activate_checkpoint([{allow_remote, foo}])), + + Fun = fun(Tab) -> ?match({atomic, ok}, mnesia:delete_table(Tab)) end, + lists:foreach(Fun, Tabs), + ?verify_mnesia(TabNodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Create and delete tables + +%% Get meta info about table + +-define(vrl, mnesia_test_lib:verify_replica_location). + +replica_location(suite) -> []; +replica_location(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Tab = replica_location, + + %% Create three replicas + Schema = [{name, Tab}, {disc_only_copies, [Node1]}, + {ram_copies, [Node2]}, {disc_copies, [Node3]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ?match([], ?vrl(Tab, [Node1], [Node2], [Node3], Nodes)), + + %% Delete one replica + ?match({atomic, ok}, mnesia:del_table_copy(Tab, Node2)), + ?match([], ?vrl(Tab, [Node1], [], [Node3], Nodes)), + + %% Move one replica + ?match({atomic, ok}, mnesia:move_table_copy(Tab, Node1, Node2)), + ?match([], ?vrl(Tab, [Node2], [], [Node3], Nodes)), + + %% Change replica type + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node2, ram_copies)), + ?match([], ?vrl(Tab, [], [Node2], [Node3], Nodes)), + + ?verify_mnesia(Nodes, []). + +table_lifecycle(suite) -> []; +table_lifecycle(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + + ?match({atomic, ok}, mnesia:create_table([{type, bag}, + {ram_copies, [Node1]}, + {attributes, [rajtan, tajtan]}, + {name, order_of_args}])), + ?match([], mnesia:dirty_read({order_of_args, 4711})), + ?match({atomic, ok}, mnesia:create_table([{name, already_exists}, + {ram_copies, [Node1]}])), + ?match({aborted, Reason23 } when element(1, Reason23) ==already_exists, + mnesia:create_table([{name, already_exists}, + {ram_copies, [Node1]}])), + ?match({aborted, Reason21 } when element(1, Reason21) == bad_type, + mnesia:create_table([{name, bad_node}, {ram_copies, ["foo"]}])), + ?match({aborted, Reason2} when element(1, Reason2) == bad_type, + mnesia:create_table([{name, zero_arity}, {attributes, []}])), + ?match({aborted, Reason3} when element(1, Reason3) == badarg, + mnesia:create_table([])), + ?match({aborted, Reason4} when element(1, Reason4) == badarg, + mnesia:create_table(atom)), + ?match({aborted, Reason5} when element(1, Reason5) == badarg, + mnesia:create_table({cstruct, table_name_as_atom})), + ?match({aborted, Reason6 } when element(1, Reason6) == bad_type, + mnesia:create_table([{name, no_host}, {ram_copies, foo}])), + ?match({aborted, Reason7 } when element(1, Reason7) == bad_type, + mnesia:create_table([{name, no_host}, {disc_only_copies, foo}])), + ?match({aborted, Reason8} when element(1, Reason8) == bad_type, + mnesia:create_table([{name, no_host}, {disc_copies, foo}])), + + CreateFun = + fun() -> ?match({aborted, nested_transaction}, + mnesia:create_table([{name, nested_trans}])), ok + end, + ?match({atomic, ok}, mnesia:transaction(CreateFun)), + ?match({atomic, ok}, mnesia:create_table([{name, remote_tab}, + {ram_copies, [Node2]}])), + + ?match({atomic, ok}, mnesia:create_table([{name, a_brand_new_tab}, + {ram_copies, [Node1]}])), + ?match([], mnesia:dirty_read({a_brand_new_tab, 4711})), + ?match({atomic, ok}, mnesia:delete_table(a_brand_new_tab)), + ?match({'EXIT', {aborted, Reason31}} when element(1, Reason31) == no_exists, + mnesia:dirty_read({a_brand_new_tab, 4711})), + ?match({aborted, Reason41} when element(1, Reason41) == no_exists, + mnesia:delete_table(a_brand_new_tab)), + ?match({aborted, Reason9} when element(1, Reason9) == badarg, + mnesia:create_table([])), + + ?match({atomic, ok}, mnesia:create_table([{name, nested_del_trans}, + {ram_copies, [Node1]}])), + + DeleteFun = fun() -> ?match({aborted, nested_transaction}, + mnesia:delete_table(nested_del_trans)), ok end, + ?match({atomic, ok}, mnesia:transaction(DeleteFun)), + + ?match({aborted, Reason10} when element(1, Reason10) == bad_type, + mnesia:create_table([{name, create_with_index}, {index, 2}])), + ?match({aborted, Reason32} when element(1, Reason32) == bad_type, + mnesia:create_table([{name, create_with_index}, {index, [-1]}])), + ?match({aborted, Reason33} when element(1, Reason33) == bad_type, + mnesia:create_table([{name, create_with_index}, {index, [0]}])), + ?match({aborted, Reason34} when element(1, Reason34) == bad_type, + mnesia:create_table([{name, create_with_index}, {index, [1]}])), + ?match({aborted, Reason35} when element(1, Reason35) == bad_type, + mnesia:create_table([{name, create_with_index}, {index, [2]}])), + ?match({atomic, ok}, + mnesia:create_table([{name, create_with_index}, {index, [3]}, + {ram_copies, [Node1]}])), + ets:new(ets_table, [named_table]), + + ?match({aborted, _}, mnesia:create_table(ets_table, [{ram_copies, Nodes}])), + + ?verify_mnesia(Nodes, []). + +add_copy_conflict(suite) -> []; +add_copy_conflict(doc) -> + ["Verify that OTP-5065 doesn't happen again, whitebox testing"]; +add_copy_conflict(Config) when is_list(Config) -> + Nodes = [Node1, Node2] = + ?acquire_nodes(2, Config ++ [{tc_timeout, timer:minutes(2)}]), + + ?match({atomic, ok}, mnesia:create_table(a, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(b, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(test, [{ram_copies, [Node2]}])), + mnesia:stop(), + ?match(ok,mnesia:start([{no_table_loaders, 1}])), + + verify_ll_queue(10), + + Self = self(), + Test = fun() -> + Res = mnesia:add_table_copy(test, Node1, ram_copies), + Self ! {test, Res} + end, + %% Create conflict with loader queue. + spawn_link(Test), + ?match_receive(timeout), + %% Conflict ok + mnesia_controller:unblock_controller(), + + ?match_receive({test, {atomic,ok}}), + + ?verify_mnesia(Nodes, []), + ?cleanup(1, Config). + +verify_ll_queue(0) -> + ?error("Couldn't find anything in queue~n",[]); +verify_ll_queue(N) -> + ?match(granted,mnesia_controller:block_controller()), + case mnesia_controller:get_info(1000) of + {info,{state,_,true,[],_Loader,[],[],[],_,_,_,_,_,_}} -> + %% Very slow SMP machines havn't loaded it yet.. + mnesia_controller:unblock_controller(), + timer:sleep(10), + verify_ll_queue(N-1); + {info,{state,_,true,[],Loader,LL,[],[],_,_,_,_,_,_}} -> + io:format("~p~n", [{Loader,LL}]), + ?match([_], LL); %% Verify that something is in the loader queue + Else -> + ?error("No match ~p maybe the internal format has changed~n",[Else]) + end. + +add_copy_when_going_down(suite) -> []; +add_copy_when_going_down(doc) -> + ["Tests abort when node we load from goes down"]; +add_copy_when_going_down(Config) -> + [Node1, Node2] = + ?acquire_nodes(2, Config ++ [{tc_timeout, timer:minutes(2)}]), + ?match({atomic, ok}, mnesia:create_table(a, [{ram_copies, [Node1]}])), + %% Grab a write lock + WriteAndWait = fun() -> + mnesia:write({a,1,1}), + receive continue -> ok + end + end, + _Lock = spawn(fun() -> mnesia:transaction(WriteAndWait) end), + Tester = self(), + spawn_link(fun() -> Res = rpc:call(Node2,mnesia, add_table_copy, + [a, Node2, ram_copies]), + Tester ! {test, Res} + end), + %% We have a lock here we should get a timeout + ?match_receive(timeout), + mnesia_test_lib:kill_mnesia([Node1]), + ?match_receive({test,{aborted,_}}), + ?verify_mnesia([Node2], []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Add, drop and move replicas, change storage types +%% Change table layout (only arity change supported) + +-record(replica_management, {k, v}). +-record(new_replica_management, {k, v, extra}). + +-define(SS(R), lists:sort(element(1,R))). + +replica_management(doc) -> + "Add, drop and move replicas, change storage types."; +replica_management(suite) -> + []; +replica_management(Config) when is_list(Config) -> + %% add_table_copy/3, del_table_copy/2, move_table_copy/3, + %% change_table_copy_type/3, transform_table/3 + + Nodes = [Node1, Node2, Node3] = ?acquire_nodes(3, Config), + + Tab = replica_management, + Attrs = record_info(fields, replica_management), + + %% + %% Add, delete and change replicas + %% + ?match({atomic, ok}, + mnesia:create_table([{name, Tab}, {attributes, Attrs}, + {ram_copies, [Node1, Node3]}])), + [?match(ok, mnesia:dirty_write({Tab, K, K + 2})) || K <-lists:seq(1, 10)], + ?match([], ?vrl(Tab, [], [Node1, Node3], [], Nodes)), + %% R - - + ?match({atomic, ok}, mnesia:dump_tables([Tab])), + ?match({aborted, Reason50 } when element(1, Reason50) == combine_error, + mnesia:add_table_copy(Tab, Node2, disc_copies)), + ?match({aborted, Reason51 } when element(1, Reason51) == combine_error, + mnesia:change_table_copy_type(Tab, Node1, disc_copies)), + ?match({atomic, ok}, mnesia:clear_table(Tab)), + SyncedCheck = fun() -> + mnesia:lock({record,Tab,0}, write), + ?match([0,0,0], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))) + end, + mnesia:transaction(SyncedCheck), + + ?match({[0,0,0], []}, rpc:multicall(Nodes, mnesia, table_info, [Tab, size])), + ?match({atomic, ok}, mnesia:del_table_copy(Tab, Node1)), + ?match({atomic, ok}, mnesia:del_table_copy(Tab, Node3)), + ?match([], ?vrl(Tab, [], [], [], Nodes)), + %% - - - + ?match({aborted,Reason52} when element(1, Reason52) == no_exists, + mnesia:add_table_copy(Tab, Node3, ram_copies)), + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {attributes, Attrs}, + {disc_copies, [Node1]}])), + ?match([], ?vrl(Tab, [], [], [Node1], Nodes)), + %% D - - + [?match(ok, mnesia:dirty_write({Tab, K, K + 2})) || K <-lists:seq(1, 10)], + + ?match({aborted, Reason53} when element(1, Reason53) == badarg, + mnesia:add_table_copy(Tab, Node2, bad_storage_type)), + ?match({atomic, ok}, mnesia:add_table_copy(Tab, Node2, disc_only_copies)), + ?match([], ?vrl(Tab, [Node2], [], [Node1], Nodes)), + ?match([0,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + %% D DO - + ?match({atomic, ok}, mnesia:add_table_copy(Tab, Node3, ram_copies)), + ?match([], ?vrl(Tab, [Node2], [Node3], [Node1], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + %% D DO R + ?match({atomic, ok}, + mnesia:change_table_copy_type(Tab, Node1, disc_only_copies)), + ?match([], ?vrl(Tab, [Node1, Node2], [Node3], [], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + %% DO DO R + ?match({aborted, Reason54} when element(1, Reason54) == already_exists, + mnesia:add_table_copy(Tab, Node3, ram_copies)), + ?match({atomic, ok}, mnesia:del_table_copy(Tab, Node1)), + ?match([], ?vrl(Tab, [Node2], [Node3], [], Nodes)), + %% - DO R + ?match({aborted, _}, mnesia:del_table_copy(Tab, Node1)), + ?match(Tab, ets:new(Tab, [named_table])), + ?match({aborted, _}, mnesia:add_table_copy(Tab, Node1, disc_copies)), + ?match(true, ets:delete(Tab)), + ?match({atomic, ok}, mnesia:add_table_copy(Tab, Node1, disc_copies)), + ?match([], ?vrl(Tab, [Node2], [Node3], [Node1], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + %% D DO R + ?match({atomic, ok},mnesia:change_table_copy_type(Tab, Node3, disc_only_copies)), + ?match([], ?vrl(Tab, [Node2, Node3], [], [Node1], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + + %% D DO D0 + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node3, ram_copies)), + ?match([], ?vrl(Tab, [Node2], [Node3], [Node1], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + %% D DO R + ?match({atomic, ok}, + mnesia:change_table_copy_type(Tab, Node2, disc_copies)), + ?match([], ?vrl(Tab, [], [Node3], [Node1,Node2], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + + %% D D R + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node1, disc_only_copies)), + ?match([], ?vrl(Tab, [Node1], [Node3], [Node2], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + + %% DO D R + ?match(Tab, ets:new(Tab, [named_table])), + ?match({aborted, _}, mnesia:change_table_copy_type(Tab, Node1, ram_copies)), + ?match(true, ets:delete(Tab)), + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node1, ram_copies)), + ?match([], ?vrl(Tab, [], [Node3,Node1], [Node2], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + %% R D R + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node1, disc_copies)), + ?match([], ?vrl(Tab, [], [Node3], [Node2,Node1], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + + %% D D R + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node2, disc_only_copies)), + ?match([], ?vrl(Tab, [Node2], [Node3], [Node1], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + + %% D DO R + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node3, disc_only_copies)), + ?match([], ?vrl(Tab, [Node2, Node3], [], [Node1], Nodes)), + ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + %% D DO DO + %% test clear + ?match({atomic, ok}, mnesia:clear_table(Tab)), + mnesia:transaction(SyncedCheck), + + %% rewrite for rest of testcase + [?match(ok, mnesia:dirty_write({Tab, K, K + 2})) || K <-lists:seq(1, 10)], + + %% D DO DO + ?match({atomic, ok}, mnesia:del_table_copy(Tab, Node2)), + ?match([], ?vrl(Tab, [Node3], [], [Node1], Nodes)), + %% D - DO + ?match({aborted, Reason55} when element(1, Reason55) == already_exists, + mnesia:change_table_copy_type(Tab, Node1, disc_copies)), + + %% + %% Move replica + %% + ?match({atomic, ok}, mnesia:move_table_copy(Tab, Node1, Node2)), + ?match([], ?vrl(Tab, [Node3], [], [Node2], Nodes)), + ?match([0,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))), + %% - D DO + ?match({aborted, _}, mnesia:move_table_copy(Tab, Node1, Node2)), + ?match([], mnesia_test_lib:stop_mnesia([Node3])), + ?match({atomic,ok}, mnesia:transaction(fun() -> mnesia:write({Tab, 43, sync_me}) end)), + ?match([], ?vrl(Tab, [Node3], [], [Node2],Nodes -- [Node3])), + %% - D DO + ?match({aborted,Reason56} when element(1, Reason56) == not_active, + mnesia:move_table_copy(Tab, Node3, Node1)), + ?match([], ?vrl(Tab, [Node3], [], [Node2],Nodes -- [Node3])), + %% DO D - + ?match([], mnesia_test_lib:start_mnesia([Node3])), + ?match([], ?vrl(Tab, [Node3], [], [Node2], Nodes)), + %% DO D - + + %% + %% Transformer + %% + + NewAttrs = record_info(fields, new_replica_management), + Transformer = + fun(Rec) when is_record(Rec, replica_management) -> + #new_replica_management{k = Rec#replica_management.k, + v = Rec#replica_management.v, + extra = Rec#replica_management.k * 2} + end, + ?match({atomic, ok}, mnesia:transform_table(Tab, fun(R) -> R end, Attrs)), + ?match({atomic, ok}, mnesia:transform_table(Tab, Transformer, NewAttrs, new_replica_management)), + + ERlist = [#new_replica_management{k = K, v = K+2, extra = K*2} || K <- lists:seq(1, 10)], + ARlist = [hd(mnesia:dirty_read(Tab, K)) || K <- lists:seq(1, 10)], + + ?match(ERlist, ARlist), + + ?match({aborted, Reason56} when element(1, Reason56) == bad_type, + mnesia:transform_table(Tab, Transformer, 0)), + ?match({aborted, Reason57} when element(1, Reason57) == bad_type, + mnesia:transform_table(Tab, Transformer, -1)), + ?match({aborted, Reason58} when element(1, Reason58) == bad_type, + mnesia:transform_table(Tab, Transformer, [])), + ?match({aborted, Reason59} when element(1, Reason59) == bad_type, + mnesia:transform_table(Tab, no_fun, NewAttrs)), + ?match({aborted, Reason59} when element(1, Reason59) == bad_type, + mnesia:transform_table(Tab, fun(X) -> X end, NewAttrs, {tuple})), + + %% OTP-3878 + ?match({atomic, ok}, mnesia:transform_table(Tab, ignore, + NewAttrs ++ [dummy])), + ?match({atomic, ok}, mnesia:transform_table(Tab, ignore, + NewAttrs ++ [dummy], last_rec)), + + ARlist = [hd(mnesia:dirty_read(Tab, K)) || K <- lists:seq(1, 10)], + ?match({'EXIT',{aborted,{bad_type,_}}}, + mnesia:dirty_write(Tab, #new_replica_management{})), + ?match(ok, mnesia:dirty_write(Tab, {last_rec, k, v, e, dummy})), + + ?verify_mnesia(Nodes, []). + +schema_availability(doc) -> + ["Test that schema succeeds (or fails) as intended when some db nodes are down."]; +schema_availability(suite) -> + []; +schema_availability(Config) when is_list(Config) -> + [N1, _N2, N3] = Nodes = ?acquire_nodes(3, Config), + Tab = schema_availability, + Storage = mnesia_test_lib:storage_type(ram_copies, Config), + Def1 = [{Storage, [N1, N3]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def1)), + + N = 10, + ?match(ok, mnesia:sync_dirty(fun() -> [mnesia:write({Tab, K, K + 2}) || K <- lists:seq(1, N)], ok end)), + ?match({[N,0,N], []}, rpc:multicall(Nodes, mnesia, table_info, [Tab, size])), + ?match([], mnesia_test_lib:kill_mnesia([N3])), + ?match({[N,0,0], []}, rpc:multicall(Nodes, mnesia, table_info, [Tab, size])), + + ?match([], mnesia_test_lib:start_mnesia([N3], [Tab])), + ?match({[N,0,N], []}, rpc:multicall(Nodes, mnesia, table_info, [Tab, size])), + ?match([], mnesia_test_lib:kill_mnesia([N3])), + + ?match({atomic, ok}, mnesia:clear_table(Tab)), + ?match({[0,0,0], []}, rpc:multicall(Nodes, mnesia, table_info, [Tab, size])), + + ?match([], mnesia_test_lib:start_mnesia([N3], [Tab])), + ?match({[0,0,0], []}, rpc:multicall(Nodes, mnesia, table_info, [Tab, size])), + + ?verify_mnesia(Nodes, []). + +-define(badrpc(Tab), {badrpc, {'EXIT', {aborted,{no_exists,Tab}}}}). + +local_content(doc) -> + ["Test local_content functionality, we want to see that correct" + " properties gets propageted correctly between nodes"]; +local_content(suite) -> []; +local_content(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Tab1 = local1, + Def1 = [{local_content, true}, {ram_copies, Nodes}], + Tab2 = local2, + Def2 = [{local_content, true}, {disc_copies, [Node1]}], + Tab3 = local3, + Def3 = [{local_content, true}, {disc_only_copies, [Node1]}], + Tab4 = local4, + Def4 = [{local_content, true}, {ram_copies, [Node1]}], + + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)), + ?match({atomic, ok}, mnesia:create_table(Tab4, Def4)), + + ?match(ok, rpc:call(Node1, mnesia, dirty_write, [{Tab1, 1, Node1}])), + ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab1, 1, Node2}])), + ?match(ok, rpc:call(Node3, mnesia, dirty_write, [{Tab1, 1, Node3}])), + ?match(ok, rpc:call(Node1, mnesia, dirty_write, [{Tab2, 1, Node1}])), + ?match(ok, rpc:call(Node1, mnesia, dirty_write, [{Tab3, 1, Node1}])), + ?match(ok, rpc:call(Node1, mnesia, dirty_write, [{Tab4, 1, Node1}])), + + ?match(?badrpc(Tab2), rpc:call(Node2, mnesia, dirty_write, [{Tab2, 1, Node2}])), + ?match(?badrpc(Tab3), rpc:call(Node2, mnesia, dirty_write, [{Tab3, 1, Node2}])), + ?match(?badrpc(Tab4), rpc:call(Node2, mnesia, dirty_write, [{Tab4, 1, Node2}])), + + ?match({atomic, ok}, rpc:call(Node1, mnesia, add_table_copy, [Tab2, Node2, ram_copies])), + ?match({atomic, ok}, rpc:call(Node2, mnesia, add_table_copy, [Tab3, Node2, disc_copies])), + ?match({atomic, ok}, rpc:call(Node3, mnesia, add_table_copy, [Tab4, Node2, disc_only_copies])), + ?match([], rpc:call(Node2, mnesia, dirty_read, [{Tab2, 1}])), + ?match([], rpc:call(Node2, mnesia, dirty_read, [{Tab3, 1}])), + ?match([], rpc:call(Node2, mnesia, dirty_read, [{Tab4, 1}])), + + ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab2, 1, Node2}])), + ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab3, 1, Node2}])), + ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab4, 1, Node2}])), + + ?match([{Tab1, 1, Node1}], rpc:call(Node1, mnesia, dirty_read, [{Tab1, 1}])), + ?match([{Tab2, 1, Node1}], rpc:call(Node1, mnesia, dirty_read, [{Tab2, 1}])), + ?match([{Tab3, 1, Node1}], rpc:call(Node1, mnesia, dirty_read, [{Tab3, 1}])), + ?match([{Tab4, 1, Node1}], rpc:call(Node1, mnesia, dirty_read, [{Tab4, 1}])), + + ?match([{Tab1, 1, Node2}], rpc:call(Node2, mnesia, dirty_read, [{Tab1, 1}])), + ?match([{Tab2, 1, Node2}], rpc:call(Node2, mnesia, dirty_read, [{Tab2, 1}])), + ?match([{Tab3, 1, Node2}], rpc:call(Node2, mnesia, dirty_read, [{Tab3, 1}])), + ?match([{Tab4, 1, Node2}], rpc:call(Node2, mnesia, dirty_read, [{Tab4, 1}])), + + ?match([{Tab1, 1, Node3}], rpc:call(Node3, mnesia, dirty_read, [{Tab1, 1}])), + ?match(?badrpc([_Tab2, 1]), rpc:call(Node3, mnesia, dirty_read, [{Tab2, 1}])), + ?match(?badrpc([_Tab3, 1]), rpc:call(Node3, mnesia, dirty_read, [{Tab3, 1}])), + ?match(?badrpc([_Tab4, 1]), rpc:call(Node3, mnesia, dirty_read, [{Tab4, 1}])), + + ?match({atomic, ok}, + mnesia:change_table_copy_type(schema, Node3, ram_copies)), + ?match([], mnesia_test_lib:stop_mnesia([Node3])), + + %% Added for OTP-44306 + ?match(ok, rpc:call(Node3, mnesia, start, [])), + ?match({ok, _}, mnesia:change_config(extra_db_nodes, [Node3])), + + mnesia_test_lib:sync_tables([Node3], [Tab1]), + + ?match([], rpc:call(Node3, mnesia, dirty_read, [{Tab1, 1}])), + + ?match({atomic, ok}, rpc:call(Node1, mnesia, clear_table, [Tab1])), + + SyncedCheck = fun(Tab) -> + mnesia:lock({record,Tab,0}, write), + {OK, []} = rpc:multicall(Nodes, mnesia, table_info, [Tab, size]), + OK + end, + ?match({atomic, [0,1,0]}, mnesia:transaction(SyncedCheck, [Tab1])), + ?match({atomic, ok}, rpc:call(Node2, mnesia, clear_table, [Tab2])), + ?match({atomic, [1,0,0]}, mnesia:transaction(SyncedCheck, [Tab2])), + ?match({atomic, ok}, rpc:call(Node2, mnesia, clear_table, [Tab3])), + ?match({atomic, [1,0,0]}, mnesia:transaction(SyncedCheck, [Tab3])), + + ?verify_mnesia(Nodes, []). + +table_access_modifications(suite) -> + [ + change_table_access_mode, + change_table_load_order, + set_master_nodes, + offline_set_master_nodes + ]. + +change_table_access_mode(suite) -> []; +change_table_access_mode(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Tab = test_access_mode_tab, + + Def = case mnesia_test_lib:diskless(Config) of + true -> [{name, Tab}, {ram_copies, Nodes}]; + false -> [{name, Tab}, {ram_copies, [Node1]}, + {disc_copies, [Node2]}, + {disc_only_copies, [Node3]}] + end, + ?match({atomic, ok}, mnesia:create_table(Def)), + + Write = fun(What) -> mnesia:write({Tab, 1, What}) end, + Read = fun() -> mnesia:read({Tab, 1}) end, + + ?match({atomic, ok}, mnesia:transaction(Write, [test_ok])), + %% test read_only + ?match({atomic, ok}, mnesia:change_table_access_mode(Tab, read_only)), + ?match({aborted, _}, mnesia:transaction(Write, [nok])), + ?match({'EXIT', {aborted, _}}, mnesia:dirty_write({Tab, 1, [nok]})), + ?match({aborted, _}, rpc:call(Node2, mnesia, transaction, [Write, [nok]])), + ?match({aborted, _}, rpc:call(Node3, mnesia, transaction, [Write, [nok]])), + ?match({atomic, [{Tab, 1, test_ok}]}, mnesia:transaction(Read)), + %% test read_write + ?match({atomic, ok}, mnesia:change_table_access_mode(Tab, read_write)), + ?match({atomic, ok}, mnesia:transaction(Write, [test_ok1])), + ?match({atomic, [{Tab, 1, test_ok1}]}, mnesia:transaction(Read)), + ?match({atomic, ok}, rpc:call(Node2, mnesia, transaction, [Write, [test_ok2]])), + ?match({atomic, [{Tab, 1, test_ok2}]}, mnesia:transaction(Read)), + ?match({atomic, ok}, rpc:call(Node3, mnesia, transaction, [Write, [test_ok3]])), + ?match({atomic, [{Tab, 1, test_ok3}]}, mnesia:transaction(Read)), + + ?match({atomic, ok}, mnesia:delete_table(Tab)), + + Def4 = [{name, Tab}, {access_mode, read_only_bad}], + ?match({aborted, {bad_type, _, _}}, mnesia:create_table(Def4)), + + Def2 = [{name, Tab}, {access_mode, read_only}], + ?match({atomic, ok}, mnesia:create_table(Def2)), + ?match({aborted, _}, mnesia:transaction(Write, [nok])), + + ?match({atomic, ok}, mnesia:change_table_access_mode(Tab, read_write)), + ?match({atomic, ok}, mnesia:delete_table(Tab)), + + Def3 = [{name, Tab}, {mnesia_test_lib:storage_type(disc_copies, Config), + [Node1, Node2]}, + {access_mode, read_write}], + ?match({atomic, ok}, mnesia:create_table(Def3)), + ?match({atomic, ok}, mnesia:transaction(Write, [ok])), + ?match({atomic, ok}, mnesia:change_table_access_mode(Tab, read_only)), + ?match({aborted, _}, mnesia:del_table_copy(Tab, Node2)), + ?match({aborted, _}, mnesia:del_table_copy(Tab, Node1)), + ?match({aborted, _}, mnesia:delete_table(Tab)), + ?match({atomic, ok}, mnesia:change_table_access_mode(Tab, read_write)), + + ?match({aborted, {bad_type, _, _}}, + mnesia:change_table_access_mode(Tab, strange_atom)), + ?match({atomic, ok}, mnesia:delete_table(Tab)), + + ?match({aborted, {no_exists, _}}, + mnesia:change_table_access_mode(err_tab, read_only)), + ?match({aborted, {no_exists, _}}, + mnesia:change_table_access_mode([Tab], read_only)), + ?verify_mnesia(Nodes, []). + +change_table_load_order(suite) -> []; +change_table_load_order(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Tab1 = load_order_tab1, + Tab2 = load_order_tab2, + Tab3 = load_order_tab3, + + Def = case mnesia_test_lib:diskless(Config) of + true -> [{ram_copies, Nodes}]; + false -> + [{ram_copies, [Node1]}, + {disc_copies, [Node2]}, + {disc_only_copies, [Node3]}] + end, + ?match({atomic, ok}, mnesia:create_table(Tab1, Def)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def)), + + ?match({aborted, _}, mnesia:change_table_load_order(Tab1, should_be_integer)), + ?match({aborted, _}, mnesia:change_table_load_order(err_tab, 5)), + ?match({aborted, _}, mnesia:change_table_load_order([err_tab], 5)), + ?match({atomic, ok}, mnesia:change_table_load_order(Tab1, 5)), + ?match({atomic, ok}, mnesia:change_table_load_order(Tab2, 4)), + ?match({atomic, ok}, mnesia:change_table_load_order(Tab3, 73)), + + ?match({aborted, _}, mnesia:change_table_load_order(schema, -32)), + + ?verify_mnesia(Nodes, []). + +set_master_nodes(suite) -> []; +set_master_nodes(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Tab1 = master_node_tab1, + Tab2 = master_node_tab2, + Tab3 = master_node_tab3, + Def1 = [{ram_copies, [Node1, Node2]}], + Def2 = [{disc_copies, [Node2, Node3]}], + Def3 = [{disc_only_copies, [Node3, Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)), + + ?match({error, _}, mnesia:set_master_nodes(schema, ['[email protected]'])), + ?match({error, _}, mnesia:set_master_nodes(badtab, [Node2, Node3])), + ?match({error, _}, mnesia:set_master_nodes(Tab1, [Node3])), + ?match([], mnesia:table_info(Tab1, master_nodes)), + + ?match(ok, mnesia:set_master_nodes(schema, [Node3, Node1])), + ?match([Node3, Node1], mnesia:table_info(schema, master_nodes)), + ?match(ok, mnesia:set_master_nodes(Tab1, [Node2])), + ?match([Node2], mnesia:table_info(Tab1, master_nodes)), + ?match(ok, mnesia:set_master_nodes(Tab1, [Node2, Node1])), + ?match([Node2, Node1], mnesia:table_info(Tab1, master_nodes)), + ?match(ok, mnesia:set_master_nodes(Tab2, [Node2])), % Should set where_to_read to Node2! + ?match([Node2], mnesia:table_info(Tab2, master_nodes)), + ?match(ok, mnesia:set_master_nodes(Tab3, [Node3])), + ?match([Node3], mnesia:table_info(Tab3, master_nodes)), + ?match(ok, mnesia:set_master_nodes(Tab3, [])), + ?match([], mnesia:table_info(Tab3, master_nodes)), + + ?match(ok, mnesia:set_master_nodes([Node1])), + ?match([Node1], mnesia:table_info(schema, master_nodes)), + ?match([Node1], mnesia:table_info(Tab1, master_nodes)), + ?match([], mnesia:table_info(Tab2, master_nodes)), + ?match([Node1], mnesia:table_info(Tab3, master_nodes)), + + ?match(ok, mnesia:set_master_nodes([Node1, Node2])), + ?match([Node1, Node2], mnesia:table_info(schema, master_nodes)), + ?match([Node1, Node2], mnesia:table_info(Tab1, master_nodes)), + ?match([Node2], mnesia:table_info(Tab2, master_nodes)), + ?match([Node1], mnesia:table_info(Tab3, master_nodes)), + + ?verify_mnesia(Nodes, []). + +offline_set_master_nodes(suite) -> []; +offline_set_master_nodes(Config) when is_list(Config) -> + [Node] = Nodes = ?acquire_nodes(1, Config), + Tab1 = offline_master_node_tab1, + Tab2 = offline_master_node_tab2, + Tab3 = offline_master_node_tab3, + Tabs = ?sort([Tab1, Tab2, Tab3]), + Def1 = [{ram_copies, [Node]}], + Def2 = [{disc_copies, [Node]}], + Def3 = [{disc_only_copies, [Node]}], + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)), + ?match([], mnesia:system_info(master_node_tables)), + ?match([], mnesia_test_lib:stop_mnesia([Node])), + + ?match(ok, mnesia:set_master_nodes(Tab1, [Node])), + ?match(ok, mnesia:set_master_nodes(Tab2, [Node])), + ?match(ok, mnesia:set_master_nodes(Tab3, [Node])), + ?match([], mnesia_test_lib:start_mnesia([Node])), + ?match(Tabs, ?sort(mnesia:system_info(master_node_tables))), + + ?match([], mnesia_test_lib:stop_mnesia([Node])), + ?match(ok, mnesia:set_master_nodes(Tab1, [])), + ?match(ok, mnesia:set_master_nodes(Tab2, [])), + ?match(ok, mnesia:set_master_nodes(Tab3, [])), + ?match([], mnesia_test_lib:start_mnesia([Node])), + ?match([], mnesia:system_info(master_node_tables)), + + ?match([], mnesia_test_lib:stop_mnesia([Node])), + ?match(ok, mnesia:set_master_nodes([Node])), + ?match([], mnesia_test_lib:start_mnesia([Node])), + AllTabs = ?sort([schema | Tabs]), + ?match(AllTabs, ?sort(mnesia:system_info(master_node_tables))), + + ?match([], mnesia_test_lib:stop_mnesia([Node])), + ?match(ok, mnesia:set_master_nodes([])), + ?match([], mnesia_test_lib:start_mnesia([Node])), + ?match([], mnesia:system_info(master_node_tables)), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Syncronize table with log or disc +%% +table_sync(suite) -> + [ + dump_tables, + dump_log, + wait_for_tables, + force_load_table + ]. + +%% Dump ram tables on disc +dump_tables(suite) -> []; +dump_tables(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = dump_tables, + Schema = [{name, Tab}, {attributes, [k, v]}, {ram_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + %% Dump 10 records + Size = 10, + Keys = lists:seq(1, Size), + Records = [{Tab, A, 7} || A <- Keys], + lists:foreach(fun(Rec) -> ?match(ok, mnesia:dirty_write(Rec)) end, Records), + + AllKeys = fun() -> ?sort(mnesia:all_keys(Tab)) end, + + ?match({atomic, Keys}, mnesia:transaction(AllKeys)), + ?match({atomic, ok}, mnesia:dump_tables([Tab])), + + %% Delete one record + ?match(ok, mnesia:dirty_delete({Tab, 5})), + Keys2 = lists:delete(5, Keys), + + ?match({atomic, Keys2}, mnesia:transaction(AllKeys)), + + %% Check that all 10 is restored after a stop + ?match([], mnesia_test_lib:stop_mnesia([Node1, Node2])), + ?match([], mnesia_test_lib:start_mnesia([Node1, Node2])), + ?match(ok, mnesia:wait_for_tables([Tab], infinity)), + + ?match({atomic, Keys}, mnesia:transaction(AllKeys)), + + ?match({aborted,Reason} when element(1, Reason) == no_exists, + mnesia:dump_tables([foo])), + ?verify_mnesia(Nodes, []). + +dump_log(suite) -> []; +dump_log(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = dump_log, + Schema = [{name, Tab}, {attributes, [k, v]}, {ram_copies, [Node1, Node2]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + Tab1 = dump_log1, + Schema1 = [{name, Tab1}, {attributes, [k, v]}, {disc_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema1)), + Tab2 = dump_log2, + Schema2 = [{name, Tab2}, {attributes, [k, v]}, {disc_only_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema2)), + + ?match(ok, mnesia:dirty_write({Tab, 1, ok})), + ?match(ok, mnesia:dirty_write({Tab1, 1, ok})), + ?match(ok, mnesia:dirty_write({Tab2, 1, ok})), + + ?match(dumped, mnesia:dump_log()), + ?match(dumped, rpc:call(Node2, mnesia, dump_log, [])), + + ?match({atomic, ok}, mnesia:change_table_copy_type(schema, Node2, ram_copies)), + ?match(dumped, rpc:call(Node2, mnesia, dump_log, [])), + + Self = self(), + spawn(fun() -> dump_log(100, Self) end), + spawn(fun() -> dump_log(100, Self) end), + + ?match(ok, receive finished -> ok after 3000 -> timeout end), + ?match(ok, receive finished -> ok after 3000 -> timeout end), + + ?verify_mnesia(Nodes, []). + +dump_log(N, Tester) when N > 0 -> + mnesia:dump_log(), + dump_log(N-1, Tester); +dump_log(_, Tester) -> + Tester ! finished. + + +wait_for_tables(doc) -> + ["Intf. test of wait_for_tables, see also force_load_table"]; +wait_for_tables(suite) -> []; +wait_for_tables(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = wf_tab, + Schema = [{name, Tab}, {ram_copies, [Node1, Node2]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ?match(ok, mnesia:wait_for_tables([wf_tab], infinity)), + ?match(ok, mnesia:wait_for_tables([], timer:seconds(5))), + ?match({timeout, [bad_tab]}, mnesia:wait_for_tables([bad_tab], timer:seconds(5))), + ?match(ok, mnesia:wait_for_tables([wf_tab], 0)), + ?match({error,_}, mnesia:wait_for_tables([wf_tab], -1)), + ?verify_mnesia(Nodes, []). + +force_load_table(suite) -> []; +force_load_table(Config) when is_list(Config) -> + [Node1, Node2] = ?acquire_nodes(2, Config), + Tab = wf_tab, + + Schema = [{name, Tab}, {disc_copies, [Node1, Node2]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ?match(ok, mnesia:dirty_write({Tab, 1, test_ok})), + mnesia_test_lib:kill_mnesia([Node1]), + ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab, 1, test_nok}])), + mnesia_test_lib:kill_mnesia([Node2]), + %% timer:sleep(timer:seconds(5)), + ?match(ok, mnesia:start()), + ?match({timeout, [Tab]}, mnesia:wait_for_tables([Tab], 5)), + ?match({'EXIT', _}, mnesia:dirty_read({Tab, 1})), + ?match(yes, mnesia:force_load_table(Tab)), + ?match([{Tab, 1, test_ok}], mnesia:dirty_read({Tab, 1})), + + ?match({error, _}, mnesia:force_load_table(error_tab)), + ?verify_mnesia([Node1], [Node2]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +user_properties(doc) -> + ["Test of reading, writing and deletion of user properties", + "Plus initialization of user properties when a table is created", + "Do also test mnesia:table_info(Tab, user_properties)"]; +user_properties(suite) -> []; +user_properties(Config) when is_list(Config) -> + [Node] = Nodes = ?acquire_nodes(1, Config), + Tab1 = user_properties_1, + Tab2 = user_properties_2, + Tab3 = user_properties_3, + Def1 = [{ram_copies, [Node]}, {user_properties, []}], + Def2 = [{mnesia_test_lib:storage_type(disc_copies, Config), [Node]}], + Def3 = [{mnesia_test_lib:storage_type(disc_only_copies, Config), [Node]}, + {user_properties, []}], + + PropKey = my_prop, + Prop = {PropKey, some, elements}, + Prop2 = {PropKey, some, other, elements}, + YourProp= {your_prop}, + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)), + + ?match([], mnesia:table_info(Tab1, user_properties)), + ?match([], mnesia:table_info(Tab2, user_properties)), + ?match([], mnesia:table_info(Tab3, user_properties)), + + ?match({'EXIT', {no_exists, {Tab1, user_property, PropKey}}}, + mnesia:read_table_property(Tab1, PropKey)), + ?match({'EXIT', {no_exists, {Tab2, user_property, PropKey}}}, + mnesia:read_table_property(Tab2, PropKey)), + ?match({'EXIT', {no_exists, {Tab3, user_property, PropKey}}}, + mnesia:read_table_property(Tab3, PropKey)), + + ?match({atomic, ok}, mnesia:write_table_property(Tab1, Prop)), + ?match({atomic, ok}, mnesia:write_table_property(Tab2, Prop)), + ?match({atomic, ok}, mnesia:write_table_property(Tab3, Prop)), + ?match({atomic, ok}, mnesia:write_table_property(Tab1, YourProp)), + ?match({atomic, ok}, mnesia:write_table_property(Tab2, YourProp)), + ?match({atomic, ok}, mnesia:write_table_property(Tab3, YourProp)), + + ?match(Prop, mnesia:read_table_property(Tab1, PropKey)), + ?match(Prop, mnesia:read_table_property(Tab2, PropKey)), + ?match(Prop, mnesia:read_table_property(Tab3, PropKey)), + + ?match({atomic, ok}, mnesia:write_table_property(Tab1, Prop2)), + ?match({atomic, ok}, mnesia:write_table_property(Tab2, Prop2)), + ?match({atomic, ok}, mnesia:write_table_property(Tab3, Prop2)), + ?match(Prop2, mnesia:read_table_property(Tab1, PropKey)), + ?match(Prop2, mnesia:read_table_property(Tab2, PropKey)), + ?match(Prop2, mnesia:read_table_property(Tab3, PropKey)), + + ?match({atomic, ok}, mnesia:delete_table_property(Tab1, PropKey)), + ?match({atomic, ok}, mnesia:delete_table_property(Tab2, PropKey)), + ?match({atomic, ok}, mnesia:delete_table_property(Tab3, PropKey)), + + ?match([YourProp], mnesia:table_info(Tab1, user_properties)), + ?match([YourProp], mnesia:table_info(Tab2, user_properties)), + ?match([YourProp], mnesia:table_info(Tab3, user_properties)), + + Tab4 = user_properties_4, + ?match({atomic, ok}, + mnesia:create_table(Tab4, [{user_properties, [Prop]}])), + + ?match([Prop], mnesia:table_info(Tab4, user_properties)), + + %% Some error cases + + ?match({aborted, {bad_type, Tab1, {}}}, + mnesia:write_table_property(Tab1, {})), + ?match({aborted, {bad_type, Tab1, ali}}, + mnesia:write_table_property(Tab1, ali)), + + Tab5 = user_properties_5, + ?match({aborted, {bad_type, Tab5, {user_properties, {}}}}, + mnesia:create_table(Tab5, [{user_properties, {}}])), + ?match({aborted, {bad_type, Tab5, {user_properties, ali}}}, + mnesia:create_table(Tab5, [{user_properties, ali}])), + ?match({aborted, {bad_type, Tab5, {user_properties, [{}]}}}, + mnesia:create_table(Tab5, [{user_properties, [{}]}])), + ?match({aborted, {bad_type, Tab5, {user_properties, [ali]}}}, + mnesia:create_table(Tab5, [{user_properties, [ali]}])), + + ?verify_mnesia(Nodes, []). + + +unsupp_user_props(doc) -> + ["Simple test of adding user props in a schema_transaction"]; +unsupp_user_props(suite) -> []; +unsupp_user_props(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab1 = silly1, + Tab2 = silly2, + Storage = mnesia_test_lib:storage_type(ram_copies, Config), + + ?match({atomic, ok}, rpc:call(Node1, mnesia, + create_table, [Tab1, [{Storage, [Node1]}]])), + ?match({atomic, ok}, rpc:call(Node1, mnesia, + create_table, [Tab2, [{Storage, [Node1]}]])), + + F1 = fun() -> + mnesia_schema:do_write_table_property( + silly1, {prop, propval1}), + mnesia_schema:do_write_table_property( + silly2, {prop, propval2}), % same key as above + mnesia_schema:do_write_table_property( + schema, {prop, propval3}) % same key as above + end, + ?match({atomic, ok}, rpc:call(Node1, mnesia_schema, + schema_transaction, [F1])), + + ?match([{prop,propval1}], rpc:call(Node1, mnesia, + table_info, [silly1, user_properties])), + ?match([{prop,propval2}], rpc:call(Node1, mnesia, + table_info, [silly2, user_properties])), + ?match([{prop,propval3}], rpc:call(Node1, mnesia, + table_info, [schema, user_properties])), + + F2 = fun() -> + mnesia_schema:do_write_table_property( + silly1, {prop, propval1a}), + mnesia_schema:do_write_table_property( + silly1, {prop, propval1b}) % same key as above + end, + ?match({atomic, ok}, rpc:call(Node1, mnesia_schema, + schema_transaction, [F2])), + + ?match([{prop,propval1b}], rpc:call(Node1, mnesia, + table_info, + [silly1, user_properties])), + ?verify_mnesia([Node1], []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +snmp_access(doc) -> + ["Make Mnesia table accessible via SNMP"]; + +snmp_access(suite) -> + [ + snmp_open_table, + snmp_close_table, + snmp_get_next_index, + snmp_get_row, + snmp_get_mnesia_key, + snmp_update_counter, + snmp_order + ]. + +snmp_open_table(suite) -> []; +snmp_open_table(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab1 = local_snmp_table, + + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + Def1 = + case mnesia_test_lib:diskless(Config) of + true -> [{ram_copies, Nodes}]; + false -> + [{disc_copies, [Node1]}, {ram_copies, [Node2]}] + end, + + Tab2 = ext_snmp_table, + Def2 = [{Storage, [Node2]}], + ErrTab = non_existing_tab, + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab1, [{key, integer}])), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab2, [{key, integer}])), + ?match({aborted, _}, mnesia:snmp_open_table(ErrTab, [{key, integer}])), + ?verify_mnesia(Nodes, []). + +snmp_close_table(suite) -> []; +snmp_close_table(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab1 = local_snmp_table, + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + Def1 = + case mnesia_test_lib:diskless(Config) of + true -> [{ram_copies, Nodes}]; + false -> + [{disc_copies, [Node1]}, {ram_copies, [Node2]}] + end, + + Tab2 = ext_snmp_table, + Def2 = [{snmp, [{key, integer}]}, {Storage, [Node2]}], + ErrTab = non_existing_tab, + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(no_snmp_tab, [])), + add_some_records(Tab1, Tab2, 3), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab1, [{key, integer}])), + add_some_records(Tab1, Tab2, 5), + ?match({atomic, ok}, mnesia:snmp_close_table(Tab1)), + + Transform = fun(Tab, Key) -> + [{T,K,V}] = mnesia:read(Tab, Key, write), + mnesia:delete(T,K, write), + mnesia:write({T, {K,K}, V, 43+V}) + end, + + ?match({atomic, ok}, mnesia:transform_table(Tab1, ignore, [key,val,new])), + ?match({atomic, ok}, + mnesia:transaction(fun() -> + mnesia:write_lock_table(Tab1), + Keys = mnesia:select(Tab1, [{{'_','$1','_'}, [], + ['$1']}]), + [Transform(Tab1, Key) || Key <- Keys], + ok + end)), + ?match([{Tab1, {1, 1}, 1, 44}], mnesia:dirty_read(Tab1, {1, 1})), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab1, [{key,{integer,integer}}])), + + ?match({atomic, ok}, mnesia:snmp_close_table(Tab2)), + ?match({atomic, ok}, mnesia:transform_table(Tab2, ignore, [key,val,new])), + ?match({atomic, ok}, + mnesia:transaction(fun() -> + mnesia:write_lock_table(Tab2), + Keys = mnesia:select(Tab2, [{{'_','$1','_'}, [], + ['$1']}]), + [Transform(Tab2, Key) || Key <- Keys], + ok + end)), + + ?match({atomic, ok}, mnesia:snmp_open_table(Tab2, [{key,{integer,integer}}])), + + %% Should be aborted ???? + ?match({atomic, ok}, mnesia:snmp_close_table(no_snmp_tab)), + ?match({aborted, _}, mnesia:snmp_close_table(ErrTab)), + ?verify_mnesia(Nodes, []). + +snmp_get_next_index(suite) -> []; +snmp_get_next_index(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab1 = local_snmp_table, + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + Def1 = + case mnesia_test_lib:diskless(Config) of + true -> [{ram_copies, Nodes}]; + false -> + [{disc_copies, [Node1]}, {ram_copies, [Node2]}] + end, + + Tab2 = ext_snmp_table, + Def2 = [{Storage, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab1, [{key, integer}])), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab2, [{key, integer}])), + add_some_records(Tab1, Tab2, 1), + Test = + fun() -> + %% Test local tables + {success, Res11} = ?match({ok, _}, mnesia:snmp_get_next_index(Tab1,[])), + {ok, Index11} = Res11, + {success, _Res12} = + ?match(endOfTable, mnesia:snmp_get_next_index(Tab1, Index11)), + ?match({'EXIT',_}, mnesia:snmp_get_next_index(Tab1, endOfTable)), + + %% Test external table + {success, Res21} = + ?match({ok, _}, mnesia:snmp_get_next_index(Tab2, [])), + {ok, Index21} = Res21, + {success, _Res22} = + ?match(endOfTable, mnesia:snmp_get_next_index(Tab2, Index21)), + + {ok, Row} = mnesia:snmp_get_row(Tab1, Index11), + ?match(ok, mnesia:dirty_delete(Tab1, hd(Index11))), + + ?match(endOfTable, mnesia:snmp_get_next_index(Tab1,[])), + + ok = mnesia:dirty_write(Row), %% Reset to coming tests + + %% Test of non existing table + %%?match(endOfTable, mnesia:snmp_get_next_index(ErrTab, [])), + ok + end, + ?match(ok, Test()), + ?match({atomic,ok}, mnesia:transaction(Test)), + ?match(ok, mnesia:sync_dirty(Test)), + ?match(ok, mnesia:activity(transaction,Test,mnesia)), + + %%io:format("**********Before ~p~n", [mnesia_lib:val({Tab1,snmp})]), + %%io:format(" ~p ~n", [ets:tab2list(mnesia_lib:val({local_snmp_table,{index,snmp}}))]), + ?match([], mnesia_test_lib:stop_mnesia(Nodes)), + ?match([], mnesia_test_lib:start_mnesia(Nodes, [Tab1, Tab2])), + %%io:format("**********After ~p~n", [mnesia_lib:val({Tab1,snmp})]), + %%io:format(" ~p ~n", [ets:tab2list(mnesia_lib:val({local_snmp_table,{index,snmp}}))]), + + ?match(ok, Test()), + ?match({atomic,ok}, mnesia:transaction(Test)), + ?match(ok, mnesia:sync_dirty(Test)), + ?match(ok, mnesia:activity(transaction,Test,mnesia)), + + ?verify_mnesia(Nodes, []). + +add_some_records(Tab1, Tab2, N) -> + Recs1 = [{Tab1, I, I} || I <- lists:reverse(lists:seq(1, N))], + Recs2 = [{Tab2, I, I} || I <- lists:reverse(lists:seq(20, 20+N-1))], + lists:foreach(fun(R) -> mnesia:dirty_write(R) end, Recs1), + Fun = fun(R) -> mnesia:write(R) end, + Trans = fun() -> lists:foreach(Fun, Recs2) end, + {atomic, ok} = mnesia:transaction(Trans), + %% Sync things, so everything gets everywhere! + {atomic, ok} = mnesia:sync_transaction(fun() -> mnesia:write(lists:last(Recs1)) end), + {atomic, ok} = mnesia:sync_transaction(fun() -> mnesia:write(lists:last(Recs2)) end), + ?sort(Recs1 ++ Recs2). + +snmp_get_row(suite) -> []; +snmp_get_row(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab1 = local_snmp_table, + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + Def1 = + case mnesia_test_lib:diskless(Config) of + true -> [{ram_copies, Nodes}]; + false -> + [{disc_copies, [Node1]}, {ram_copies, [Node2]}] + end, + Tab2 = ext_snmp_table, + Def2 = [{Storage, [Node2]}], + Tab3 = snmp_table, + Def3 = [{Storage, [Node1]}, + {attributes, [key, data1, data2]}], + + Setup = fun() -> + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab1, [{key, integer}])), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab2, [{key, integer}])), + ?match({atomic, ok}, mnesia:snmp_open_table( + Tab3, [{key, {fix_string,integer}}])), + add_some_records(Tab1, Tab2, 1) + end, + Clear = fun() -> + ?match({atomic, ok}, mnesia:delete_table(Tab1)), + ?match({atomic, ok}, mnesia:delete_table(Tab2)), + ?match({atomic, ok}, mnesia:delete_table(Tab3)) + end, + Test = + fun() -> + %% Test local tables + {success, Res11} = + ?match({ok, [1]}, mnesia:snmp_get_next_index(Tab1,[])), + {ok, Index11} = Res11, + ?match({ok, {Tab1,1,1}}, mnesia:snmp_get_row(Tab1, Index11)), + ?match(endOfTable, mnesia:snmp_get_next_index(Tab1, Index11)), + ?match({'EXIT',_}, mnesia:snmp_get_row(Tab1, endOfTable)), + ?match(undefined, mnesia:snmp_get_row(Tab1, [73])), + + Add = fun() -> mnesia:write({Tab3, {"f_string", 3}, data1, data2}) end, + ?match({atomic, ok}, mnesia:transaction(Add)), + {success, {ok, Index31}} = ?match({ok, RowIndex31} when is_list(RowIndex31), + mnesia:snmp_get_next_index(Tab3,[])), + ?match({ok, Row31} when is_tuple(Row31), + mnesia:snmp_get_row(Tab3, Index31)), + ?match(endOfTable, mnesia:snmp_get_next_index(Tab3, Index31)), + Del = fun() -> mnesia:delete({Tab3,{"f_string",3}}) end, + ?match({atomic, ok}, mnesia:transaction(Del)), + ?match(undefined, mnesia:snmp_get_row(Tab3, "f_string" ++ [3])), + ?match(undefined, mnesia:snmp_get_row(Tab3, "f_string" ++ [73])), + + %% Test external table + {success, Res21} = ?match({ok,[20]}, mnesia:snmp_get_next_index(Tab2, [])), + {ok, Index21} = Res21, + ?match({ok, Row2} when is_tuple(Row2), mnesia:snmp_get_row(Tab2, Index21)), + ?match(endOfTable, mnesia:snmp_get_next_index(Tab2, Index21)), + %% Test of non existing table + %% ?match(endOfTable, mnesia:snmp_get_next_index(ErrTab, [])), + ok + end, + Setup(), + ?match(ok, Test()), + Clear(), Setup(), + ?match({atomic,ok}, mnesia:transaction(Test)), + Clear(), Setup(), + ?match(ok, mnesia:sync_dirty(Test)), + Clear(), Setup(), + ?match(ok, mnesia:activity(transaction,Test,mnesia)), + + Clear(), Setup(), + ?match([], mnesia_test_lib:stop_mnesia(Nodes)), + ?match([], mnesia_test_lib:start_mnesia(Nodes, [Tab1, Tab2])), + ?match(ok, Test()), + Clear(), Setup(), + ?match([], mnesia_test_lib:stop_mnesia(Nodes)), + ?match([], mnesia_test_lib:start_mnesia(Nodes, [Tab1, Tab2])), + ?match({atomic,ok}, mnesia:transaction(Test)), + + ?verify_mnesia(Nodes, []). + +snmp_get_mnesia_key(suite) -> []; +snmp_get_mnesia_key(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab1 = local_snmp_table, + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + Def1 = + case mnesia_test_lib:diskless(Config) of + true -> [{ram_copies, Nodes}]; + false -> + [{disc_copies, [Node1]}, {ram_copies, [Node2]}] + end, + + Tab2 = ext_snmp_table, + Def2 = [{Storage, [Node2]}], + + Tab3 = fix_string, + Setup = fun() -> + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def1)), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab1, [{key, integer}])), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab2, [{key, integer}])), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab3, [{key, {fix_string,integer}}])), + + add_some_records(Tab1, Tab2, 1) + end, + Clear = fun() -> + ?match({atomic, ok}, mnesia:delete_table(Tab1)), + ?match({atomic, ok}, mnesia:delete_table(Tab2)), + ?match({atomic, ok}, mnesia:delete_table(Tab3)) + end, + Test = + fun() -> + %% Test local tables + {success, Res11} = + ?match({ok, [1]}, mnesia:snmp_get_next_index(Tab1,[])), + {ok, Index11} = Res11, + ?match({ok, 1}, mnesia:snmp_get_mnesia_key(Tab1, Index11)), + %% Test external tables + {success, Res21} = + ?match({ok, [20]}, mnesia:snmp_get_next_index(Tab2, [])), + {ok, Index21} = Res21, + ?match({ok, 20}, mnesia:snmp_get_mnesia_key(Tab2, Index21)), + ?match(undefined, mnesia:snmp_get_mnesia_key(Tab2, [97])), + ?match({'EXIT', _}, mnesia:snmp_get_mnesia_key(Tab2, 97)), + + ?match({atomic,ok}, mnesia:transaction(fun() -> mnesia:delete({Tab1,1}) end)), + ?match(undefined, mnesia:snmp_get_mnesia_key(Tab1, Index11)), + + ?match({atomic,ok},mnesia:transaction(fun() -> mnesia:write({Tab1,73,7}) end)), + ?match({ok, 73}, mnesia:snmp_get_mnesia_key(Tab1, [73])), + ?match({atomic,ok}, mnesia:transaction(fun() -> mnesia:delete({Tab1,73}) end)), + ?match(undefined, mnesia:snmp_get_mnesia_key(Tab1, [73])), + + ?match({atomic,ok},mnesia:transaction(fun() -> mnesia:write({Tab3,{"S",5},7}) end)), + ?match({ok,{"S",5}}, mnesia:snmp_get_mnesia_key(Tab3, [$S,5])), + ?match({atomic,ok},mnesia:transaction(fun() -> mnesia:delete({Tab3,{"S",5}}) end)), + ?match(undefined, mnesia:snmp_get_mnesia_key(Tab3, [$S,5])), + + ok + end, + Setup(), + ?match(ok, Test()), + Clear(), Setup(), + ?match({atomic,ok}, mnesia:transaction(Test)), + Clear(), Setup(), + ?match(ok, mnesia:sync_dirty(Test)), + Clear(), Setup(), + ?match(ok, mnesia:activity(transaction,Test,mnesia)), + ?verify_mnesia(Nodes, []). + +snmp_update_counter(doc) -> + ["Verify that counters may be updated for tables with SNMP property"]; +snmp_update_counter(suite) -> []; +snmp_update_counter(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = snmp_update_counter, + Def = [{attributes, [key, value]}, + {snmp, [{key, integer}]}, + {ram_copies, [Node1]} + ], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + Oid = {Tab, 1}, + ?match([], mnesia:dirty_read(Oid)), + ?match(ok, mnesia:dirty_write({Tab, 1, 1})), + ?match([{Tab, _Key, 1}], mnesia:dirty_read(Oid)), + ?match(3, mnesia:dirty_update_counter(Oid, 2)), + ?match([{Tab, _Key, 3}], mnesia:dirty_read(Oid)), + ?verify_mnesia(Nodes, []). + +snmp_order(doc) -> + ["Verify that sort order is correct in transactions and dirty variants"]; +snmp_order(suite) -> []; +snmp_order(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = snmp_order, + Def = [{attributes, [key, value]}, + {snmp, [{key, {integer, integer, integer}}]}, + {ram_copies, [Node1]} + ], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + Oid = {Tab, 1}, + ?match([], mnesia:dirty_read(Oid)), + ?match({'EXIT', {aborted, _}}, mnesia:dirty_write({Tab, 1, 1})), + [mnesia:dirty_write({Tab, {A,B,2}, default}) || + A <- lists:seq(1, 9, 2), + B <- lists:seq(2, 8, 2)], + + Test1 = fun() -> + Keys0 = get_keys(Tab, []), + ?match(Keys0, lists:sort(Keys0)), + ?match([[1,2,2]|_], Keys0), + Keys1 = get_keys(Tab, [5]), + ?match(Keys1, lists:sort(Keys1)), + ?match([[5,2,2]|_], Keys1), + Keys2 = get_keys(Tab, [7, 4]), + ?match(Keys2, lists:sort(Keys2)), + ?match([[7,4,2]|_], Keys2), + ok + end, + ?match(ok, Test1()), + ?match({atomic, ok},mnesia:transaction(Test1)), + ?match(ok,mnesia:sync_dirty(Test1)), + + Test2 = fun() -> + mnesia:write(Tab, {Tab,{0,0,2},updated}, write), + mnesia:write(Tab, {Tab,{5,3,2},updated}, write), + mnesia:write(Tab, {Tab,{10,10,2},updated}, write), + Keys0 = get_keys(Tab, []), + ?match([[0,0,2],[1,2,2]|_], Keys0), + ?match(Keys0, lists:sort(Keys0)), + + Keys1 = get_keys(Tab, [5]), + ?match([[5,2,2],[5,3,2]|_], Keys1), + ?match(Keys1, lists:sort(Keys1)), + + Keys2 = get_keys(Tab, [7,4]), + ?match([[7,4,2]|_], Keys2), + ?match(Keys2, lists:sort(Keys2)), + ?match([10,10,2], lists:last(Keys0)), + ?match([10,10,2], lists:last(Keys1)), + ?match([10,10,2], lists:last(Keys2)), + + ?match([[10,10,2]], get_keys(Tab, [10])), + ok + end, + + ?match({atomic, ok},mnesia:transaction(Test2)), + + ?verify_mnesia(Nodes, []). + +get_keys(Tab, Key) -> + case mnesia:snmp_get_next_index(Tab, Key) of + endOfTable -> []; + {ok, Next} -> + [Next|get_keys(Tab, Next)] + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-record(tab, {i, e1, e2}). % Simple test table + +subscriptions(doc) -> + ["Test the event subscription mechanism"]; +subscriptions(suite) -> + [subscribe_standard, + subscribe_extended]. + +subscribe_extended(doc) -> + ["Test the extended set of events, test with and without checkpoints. "]; +subscribe_extended(suite) -> + []; +subscribe_extended(Config) when is_list(Config) -> + [N1, N2]=Nodes=?acquire_nodes(2, Config), + Tab1 = etab, + Storage = mnesia_test_lib:storage_type(ram_copies, Config), + Def1 = [{Storage, [N1, N2]}, {attributes, record_info(fields, tab)}], + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + + Tab2 = bag, + Def2 = [{Storage, [N1, N2]}, + {type, bag}, + {record_name, Tab1}, + {attributes, record_info(fields, tab)}], + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + + ?match({ok, N1}, mnesia:subscribe({table, Tab1, detailed})), + ?match({ok, N1}, mnesia:subscribe({table, Tab2, detailed})), + + ?match({error, {already_exists, _}}, mnesia:subscribe({table, Tab1, simple})), + ?match({error, {badarg, {table, Tab1, bad}}}, mnesia:subscribe({table, Tab1, bad})), + + ?match({ok, N1}, mnesia:subscribe(activity)), + test_ext_sub(Tab1, Tab2), + + ?match({ok, N1}, mnesia:unsubscribe(activity)), + ?match({ok, N1}, mnesia:subscribe({table, Tab1, detailed})), + ?match({atomic, ok}, mnesia:clear_table(Tab1)), + ?match({mnesia_table_event, {delete, schema, {schema, Tab1}, [{schema, Tab1, _}],_}}, recv_event()), + ?match({mnesia_table_event, {write, schema, {schema, Tab1, _}, [], _}}, recv_event()), + + ?match({atomic, ok}, mnesia_schema:clear_table(Tab2)), + ?match({mnesia_table_event, {delete, schema, {schema, Tab2}, [{schema, Tab2, _}],_}}, + recv_event()), + ?match({mnesia_table_event, {write, schema, {schema, Tab2, _}, [], _}}, recv_event()), + + ?match({ok, N1}, mnesia:unsubscribe({table, Tab2, detailed})), + {ok, _, _} = mnesia:activate_checkpoint([{name, testing}, + {ram_overrides_dump, true}, + {max, [Tab1, Tab2]}]), + ?match({ok, N1}, mnesia:subscribe({table, Tab2, detailed})), + ?match({ok, N1}, mnesia:subscribe(activity)), + test_ext_sub(Tab1, Tab2), + + ?verify_mnesia(Nodes, []). + +test_ext_sub(Tab1, Tab2) -> + %% The basics + Rec1 = {Tab1, 1, 0, 0}, + Rec2 = {Tab1, 1, 1, 0}, + Rec3 = {Tab1, 2, 1, 0}, + Rec4 = {Tab1, 2, 2, 0}, + + Write = fun(Tab, Rec) -> + mnesia:transaction(fun() -> mnesia:write(Tab, Rec, write) + end) + end, + Delete = fun(Tab, Rec) -> + mnesia:transaction(fun() -> mnesia:delete(Tab, Rec, write) + end) + end, + DelObj = fun(Tab, Rec) -> + mnesia:transaction(fun() -> mnesia:delete_object(Tab, Rec, write) + end) + end, + + S = self(), + D = {dirty, self()}, + %% SET + ?match(ok, mnesia:dirty_write(Rec1)), + ?match({mnesia_table_event, {write, Tab1, Rec1, [], D}}, recv_event()), + ?match(ok, mnesia:dirty_write(Rec3)), + ?match({mnesia_table_event, {write, Tab1, Rec3, [], D}}, recv_event()), + ?match(ok, mnesia:dirty_write(Rec1)), + ?match({mnesia_table_event, {write, Tab1, Rec1, [Rec1], D}}, recv_event()), + ?match({atomic, ok}, Write(Tab1, Rec2)), + ?match({mnesia_table_event, {write, Tab1, Rec2, [Rec1], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ?match(ok, mnesia:dirty_delete({Tab1, 2})), + ?match({mnesia_table_event, {delete, Tab1, {Tab1, 2}, [Rec3], D}}, recv_event()), + ?match({atomic, ok}, DelObj(Tab1, Rec2)), + ?match({mnesia_table_event, {delete, Tab1, Rec2, [Rec2], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + + ?match({atomic, ok}, Delete(Tab1, 1)), + ?match({mnesia_table_event, {delete, Tab1, {Tab1, 1}, [], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + + ?match({ok, _N1}, mnesia:unsubscribe({table, Tab1, detailed})), + + %% BAG + + ?match({atomic, ok}, Write(Tab2, Rec1)), + ?match({mnesia_table_event, {write, Tab2, Rec1, [], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ?match({atomic, ok}, Write(Tab2, Rec4)), + ?match({mnesia_table_event, {write, Tab2, Rec4, [], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ?match({atomic, ok}, Write(Tab2, Rec3)), + ?match({mnesia_table_event, {write, Tab2, Rec3, [Rec4], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ?match({atomic, ok}, Write(Tab2, Rec2)), + ?match({mnesia_table_event, {write, Tab2, Rec2, [Rec1], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ?match({atomic, ok}, Write(Tab2, Rec1)), + ?match({mnesia_table_event, {write, Tab2, Rec1, [Rec1, Rec2], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ?match({atomic, ok}, DelObj(Tab2, Rec2)), + ?match({mnesia_table_event, {delete, Tab2, Rec2, [Rec2], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ?match({atomic, ok}, Delete(Tab2, 1)), + ?match({mnesia_table_event, {delete, Tab2, {Tab2, 1}, [Rec1], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ?match({atomic, ok}, Delete(Tab2, 2)), + ?match({mnesia_table_event, {delete, Tab2, {Tab2, 2}, [Rec4, Rec3], {tid,_,S}}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()), + ok. + + +subscribe_standard(doc) -> + ["Tests system events and the orignal table events"]; +subscribe_standard(suite) -> []; +subscribe_standard(Config) when is_list(Config)-> + [N1, N2]=?acquire_nodes(2, Config), + Tab = tab, + + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + Def = [{Storage, [N1, N2]}, {attributes, record_info(fields, tab)}], + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + + %% Check system events + ?match({ok, N1}, mnesia:subscribe(system)), + ?match({ok, N1}, mnesia:subscribe(activity)), + + ?match([], mnesia_test_lib:kill_mnesia([N2])), + ?match({mnesia_system_event, {mnesia_down, N2}}, recv_event()), + ?match(timeout, recv_event()), + + ?match([], mnesia_test_lib:start_mnesia([N2], [Tab])), + ?match({mnesia_activity_event, _}, recv_event()), + ?match({mnesia_system_event,{mnesia_up, N2}}, recv_event()), + + ?match(true, lists:member(self(), mnesia:system_info(subscribers))), + ?match([], mnesia_test_lib:kill_mnesia([N1])), + timer:sleep(500), + mnesia_test_lib:flush(), + ?match([], mnesia_test_lib:start_mnesia([N1], [Tab])), + ?match(timeout, recv_event()), + + ?match({ok, N1}, mnesia:subscribe(system)), + ?match({error, {already_exists, system}}, mnesia:subscribe(system)), + ?match(stopped, mnesia:stop()), + ?match({mnesia_system_event, {mnesia_down, N1}}, recv_event()), + ?match({error, {node_not_running, N1}}, mnesia:subscribe(system)), + ?match([], mnesia_test_lib:start_mnesia([N1, N2], [Tab])), + + %% Check table events + ?match({ok, N1}, mnesia:subscribe(activity)), + Old_Level = mnesia:set_debug_level(trace), + ?match({ok, N1}, mnesia:subscribe({table,Tab})), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(#tab{i=155}) end)), + Self = self(), + ?match({mnesia_table_event, {write, _, _}}, recv_event()), + ?match({mnesia_activity_event, {complete, {tid, _, Self}}}, recv_event()), + + ?match({ok, N1}, mnesia:unsubscribe({table,Tab})), + ?match({ok, N1}, mnesia:unsubscribe(activity)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(#tab{i=255}) end)), + + ?match(timeout, recv_event()), + mnesia:set_debug_level(Old_Level), + + %% Check deletion of replica + + ?match({ok, N1}, mnesia:subscribe({table,Tab})), + ?match({ok, N1}, mnesia:subscribe(activity)), + ?match(ok, mnesia:dirty_write(#tab{i=355})), + ?match({mnesia_table_event, {write, _, _}}, recv_event()), + ?match({atomic, ok}, mnesia:del_table_copy(Tab, N1)), + ?match({mnesia_activity_event, _}, recv_event()), + ?match(ok, mnesia:dirty_write(#tab{i=455})), + ?match(timeout, recv_event()), + + ?match({atomic, ok}, mnesia:move_table_copy(Tab, N2, N1)), + ?match({mnesia_activity_event, _}, recv_event()), + ?match({ok, N1}, mnesia:subscribe({table,Tab})), + ?match(ok, mnesia:dirty_write(#tab{i=555})), + ?match({mnesia_table_event, {write, _, _}}, recv_event()), + ?match({atomic, ok}, mnesia:move_table_copy(Tab, N1, N2)), + ?match({mnesia_activity_event, _}, recv_event()), + ?match(ok, mnesia:dirty_write(#tab{i=655})), + ?match(timeout, recv_event()), + + ?match({atomic, ok}, mnesia:add_table_copy(Tab, N1, ram_copies)), + ?match({mnesia_activity_event, _}, recv_event()), + ?match({ok, N1}, mnesia:subscribe({table,Tab})), + ?match({error, {already_exists, {table,Tab, simple}}}, + mnesia:subscribe({table,Tab})), + ?match(ok, mnesia:dirty_write(#tab{i=755})), + ?match({mnesia_table_event, {write, _, _}}, recv_event()), + + ?match({atomic, ok}, mnesia:delete_table(Tab)), + ?match({mnesia_activity_event, _}, recv_event()), + ?match(timeout, recv_event()), + + mnesia_test_lib:kill_mnesia([N1]), + + ?verify_mnesia([N2], [N1]). + +recv_event() -> + receive + Event -> Event + after 1000 -> + timeout + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +iteration(doc) -> + ["Verify that the iteration functions works as expected"]; +iteration(suite) -> + [foldl]. + + +foldl(suite) -> + []; +foldl(doc) -> + [""]; +foldl(Config) when is_list(Config) -> + Nodes = [_N1, N2] = ?acquire_nodes(2, Config), + Tab1 = fold_local, + Tab2 = fold_remote, + Tab3 = fold_ordered, + ?match({atomic, ok}, mnesia:create_table(Tab1, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(Tab2, [{ram_copies, [N2]}, {type, bag}])), + ?match({atomic, ok}, mnesia:create_table(Tab3, [{ram_copies, Nodes}, + {type, ordered_set}])), + + Tab1Els = [{Tab1, N, N} || N <- lists:seq(1, 10)], + Tab2Els = ?sort([{Tab2, 1, 2} | [{Tab2, N, N} || N <- lists:seq(1, 10)]]), + Tab3Els = [{Tab3, N, N} || N <- lists:seq(1, 10)], + + [mnesia:sync_transaction(fun() -> mnesia:write(E) end) || E <- Tab1Els], + [mnesia:sync_transaction(fun() -> mnesia:write(E) end) || E <- Tab2Els], + [mnesia:sync_transaction(fun() -> mnesia:write(E) end) || E <- Tab3Els], + + Fold = fun(Tab) -> + lists:reverse(mnesia:foldl(fun(E, A) -> [E | A] end, [], Tab)) + end, + Fold2 = fun(Tab, Lock) -> + lists:reverse(mnesia:foldl(fun(E, A) -> [E | A] end, [], Tab, Lock)) + end, + Exit = fun(Tab) -> + lists:reverse(mnesia:foldl(fun(_E, _A) -> exit(testing) end, [], Tab)) + end, + %% Errors + ?match({aborted, _}, mnesia:transaction(Fold, [error])), + ?match({aborted, _}, mnesia:transaction(fun(Tab) -> mnesia:foldl(badfun,[],Tab) end, + [Tab1])), + ?match({aborted, testing}, mnesia:transaction(Exit, [Tab1])), + ?match({aborted, _}, mnesia:transaction(Fold2, [Tab1, read_lock])), + + %% Success + ?match({atomic, Tab1Els}, sort_res(mnesia:transaction(Fold, [Tab1]))), + ?match({atomic, Tab2Els}, sort_res(mnesia:transaction(Fold, [Tab2]))), + ?match({atomic, Tab3Els}, mnesia:transaction(Fold, [Tab3])), + + ?match({atomic, Tab1Els}, sort_res(mnesia:transaction(Fold2, [Tab1, read]))), + ?match({atomic, Tab1Els}, sort_res(mnesia:transaction(Fold2, [Tab1, write]))), + + ?match(Tab1Els, sort_res(mnesia:sync_dirty(Fold, [Tab1]))), + ?match(Tab2Els, sort_res(mnesia:async_dirty(Fold, [Tab2]))), + + ?verify_mnesia(Nodes, []). + +sort_res({atomic, List}) -> + {atomic, ?sort(List)}; +sort_res(Else) when is_list(Else) -> + ?sort(Else); +sort_res(Else) -> + Else. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +debug_support(doc) -> + ["Check that the debug support has not decayed."]; +debug_support(suite) -> + [ + info, + schema_0, + schema_1, + view_0, + view_1, + view_2, + lkill, + kill + ]. + +info(suite) -> []; +info(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + ?match(ok, mnesia:info()), + ?verify_mnesia(Nodes, []). + +schema_0(suite) -> []; +schema_0(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + ?match(ok, mnesia:schema()), + ?verify_mnesia(Nodes, []). + +schema_1(suite) -> []; +schema_1(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + Tab = schema_1, + ?match({atomic, ok}, mnesia:create_table(Tab, [])), + ?match(ok, mnesia:schema(Tab)), + ?verify_mnesia(Nodes, []). + +view_0(suite) -> []; +view_0(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + ?match(ok, mnesia_lib:view()), + ?verify_mnesia(Nodes, []). + +view_1(suite) -> []; +view_1(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + BinCore = mnesia_lib:mkcore({crashinfo, "Just testing..."}), + CoreFile = lists:concat(["MnesiaCore.", node(), ".view_1.", ?MODULE]), + ?match(ok, file:write_file(CoreFile, BinCore)), + ?match(ok, mnesia_lib:view(CoreFile)), + ?match(ok, file:delete(CoreFile)), + + ?match(stopped, mnesia:stop()), + Dir = mnesia:system_info(directory), + ?match(eof, mnesia_lib:view(filename:join(Dir, "LATEST.LOG"))), + ?match(ok, mnesia_lib:view(filename:join(Dir, "schema.DAT"))), + ?verify_mnesia([], Nodes). + +view_2(suite) -> []; +view_2(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + BinCore = mnesia_lib:mkcore({crashinfo, "More testing..."}), + File = lists:concat([?MODULE, "view_2.", node()]), + ?match(ok, file:write_file(File, BinCore)), + ?match(ok, mnesia_lib:view(File, core)), + ?match(ok, file:delete(File)), + + ?match(stopped, mnesia:stop()), + Dir = mnesia:system_info(directory), + ?match(ok, file:rename(filename:join(Dir, "LATEST.LOG"), File)), + ?match(eof, mnesia_lib:view(File, log)), + ?match(ok, file:delete(File)), + + ?match(ok, file:rename(filename:join(Dir, "schema.DAT"), File)), + ?match(ok, mnesia_lib:view(File, dat)), + ?match(ok, file:delete(File)), + ?verify_mnesia([], Nodes). + +lkill(suite) -> []; +lkill(Config) when is_list(Config) -> + [Node1, Node2] = ?acquire_nodes(2, Config), + + ?match(yes, rpc:call(Node1, mnesia, system_info, [is_running])), + ?match(yes, rpc:call(Node2, mnesia, system_info, [is_running])), + ?match(ok, rpc:call(Node2, mnesia, lkill, [])), + ?match(yes, rpc:call(Node1, mnesia, system_info, [is_running])), + ?match(no, rpc:call(Node2, mnesia, system_info, [is_running])), + ?verify_mnesia([Node1], [Node2]). + +kill(suite) -> []; +kill(Config) when is_list(Config) -> + [Node1, Node2] = ?acquire_nodes(2, Config), + + ?match(yes, rpc:call(Node1, mnesia, system_info, [is_running])), + ?match(yes, rpc:call(Node2, mnesia, system_info, [is_running])), + ?match({_, []}, rpc:call(Node2, mnesia, kill, [])), + ?match(no, rpc:call(Node1, mnesia, system_info, [is_running])), + ?match(no, rpc:call(Node2, mnesia, system_info, [is_running])), + ?verify_mnesia([], [Node1, Node2]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +record_name(doc) -> + ["Verify that record names may be differ from the name of ", + "the hosting table. Check at least access, restore, " + "registry, subscriptions and traveres_backup"]; +record_name(suite) -> + [ + record_name_dirty_access + ]. + +record_name_dirty_access(suite) -> + [ + record_name_dirty_access_ram, + record_name_dirty_access_disc, + record_name_dirty_access_disc_only + ]. + +record_name_dirty_access_ram(suite) -> + []; +record_name_dirty_access_ram(Config) when is_list(Config) -> + record_name_dirty_access(ram_copies, Config). + +record_name_dirty_access_disc(suite) -> + []; +record_name_dirty_access_disc(Config) when is_list(Config) -> + record_name_dirty_access(disc_copies, Config). + +record_name_dirty_access_disc_only(suite) -> + []; +record_name_dirty_access_disc_only(Config) when is_list(Config) -> + record_name_dirty_access(disc_only_copies, Config). + +record_name_dirty_access(Storage, Config) -> + [Node1, _Node2] = Nodes = ?acquire_nodes(2, Config), + + List = lists:concat([record_name_dirty_access_, Storage]), + Tab = list_to_atom(List), + RecName = some_record, + Attr = val, + TabDef = [{type, bag}, + {record_name, RecName}, + {index, [Attr]}, + {Storage, Nodes}], + ?match({atomic, ok}, mnesia:create_table(Tab, TabDef)), + + ?match(RecName, mnesia:table_info(Tab, record_name)), + + ?match(ok, mnesia:dirty_write(Tab, {RecName, 2, 20})), + ?match(ok, mnesia:dirty_write(Tab, {RecName, 2, 21})), + ?match(ok, mnesia:dirty_write(Tab, {RecName, 2, 22})), + + %% Backup test + BupFile = List ++ ".BUP", + CpName = cpname, + CpArgs = [{name, CpName}, {min, [Tab]}, {ram_overrides_dump, true}], + ?match({ok, CpName, _}, mnesia:activate_checkpoint(CpArgs)), + ?match(ok, mnesia:backup_checkpoint(CpName, BupFile)), + ?match(ok, mnesia:deactivate_checkpoint(CpName)), + + ?match(ok, mnesia:dirty_write(Tab, {RecName, 1, 10})), + ?match({ok, Node1}, mnesia:subscribe({table, Tab})), + ?match(ok, mnesia:dirty_write(Tab, {RecName, 3, 10})), + + Twos =?sort( [{RecName, 2, 20}, {RecName, 2, 21}, {RecName, 2, 22}]), + ?match(Twos, ?sort(mnesia:dirty_read(Tab, 2))), + + ?match(ok, mnesia:dirty_delete_object(Tab, {RecName, 2, 21})), + + Tens = ?sort([{RecName, 1, 10}, {RecName, 3, 10}]), + TenPat = {RecName, '_', 10}, + ?match(Tens, ?sort(mnesia:dirty_match_object(Tab, TenPat))), + ?match(Tens, ?sort(mnesia:dirty_select(Tab, [{TenPat, [], ['$_']}]))), + + %% Subscription test + E = mnesia_table_event, + ?match_receive({E, {write, {Tab, 3, 10}, _}}), + ?match_receive({E, {delete_object, {Tab, 2, 21}, _}}), + ?match({ok, Node1}, mnesia:unsubscribe({table, Tab})), + + ?match([], mnesia_test_lib:stop_mnesia([Node1])), + ?match([], mnesia_test_lib:start_mnesia(Nodes, [Tab])), + + ?match(Tens, ?sort(mnesia:dirty_index_match_object(Tab, TenPat, Attr) )), + ?match(Tens, ?sort(mnesia:dirty_index_read(Tab, 10, Attr))), + + ?match([1, 2, 3], ?sort(mnesia:dirty_all_keys(Tab))), + + ?match({ok, Node1}, mnesia:subscribe({table, Tab})), + ?match(ok, mnesia:dirty_delete(Tab, 2)), + ?match([], mnesia:dirty_read(Tab, 2)), + + ?match_receive({E, {delete, {Tab, 2}, _}}), + ?match([], mnesia_test_lib:flush()), + ?match({ok, Node1}, mnesia:unsubscribe({table, Tab})), + + %% Restore test + ?match({atomic, [Tab]}, mnesia:restore(BupFile, [{recreate_tables, [Tab]}])), + ?match(RecName, mnesia:table_info(Tab, record_name)), + + ?match(Twos, ?sort(mnesia:dirty_match_object(Tab, mnesia:table_info(Tab, wild_pattern)))), + ?match(Twos, ?sort(mnesia:dirty_select(Tab, + [{mnesia:table_info(Tab, wild_pattern), + [],['$_']}]))), + + %% Traverse backup test + + Fun = fun(Rec, {Good, Bad}) -> + ?verbose("BUP: ~p~n", [Rec]), + case Rec of + {T, K, V} when T == Tab -> + Good2 = Good ++ [{RecName, K, V}], + {[Rec], {?sort(Good2), Bad}}; + {T, K} when T == Tab -> + Good2 = [G || G <- Good, element(2, G) /= K], + {[Rec], {?sort(Good2), Bad}}; + _ when element(1, Rec) == schema -> + {[Rec], {Good, Bad}}; + _ -> + Bad2 = Bad ++ [Rec], + {[Rec], {Good, ?sort(Bad2)}} + end + end, + + ?match({ok, {Twos, []}}, mnesia:traverse_backup(BupFile, mnesia_backup, + dummy, read_only, + Fun, {[], []})), + ?match(ok, file:delete(BupFile)), + + %% Update counter test + + CounterTab = list_to_atom(lists:concat([Tab, "_counter"])), + CounterTabDef = [{record_name, some_counter}], + C = my_counter, + ?match({atomic, ok}, mnesia:create_table(CounterTab, CounterTabDef)), + ?match(some_counter, mnesia:table_info(CounterTab, record_name)), + ?match(0, mnesia:dirty_update_counter(CounterTab, gurka, -10)), + ?match(10, mnesia:dirty_update_counter(CounterTab, C, 10)), + ?match(11, mnesia:dirty_update_counter(CounterTab, C, 1)), + ?match(4711, mnesia:dirty_update_counter(CounterTab, C, 4700)), + ?match([{some_counter, C, 4711}], mnesia:dirty_read(CounterTab, C)), + ?match(0, mnesia:dirty_update_counter(CounterTab, C, -4747)), + + %% Registry tests + + RegTab = list_to_atom(lists:concat([Tab, "_registry"])), + RegTabDef = [{record_name, some_reg}], + ?match(ok, mnesia_registry:create_table(RegTab, RegTabDef)), + ?match(some_reg, mnesia:table_info(RegTab, record_name)), + {success, RegRecs} = + ?match([_ | _], mnesia_registry_test:dump_registry(node(), RegTab)), + + R = ?sort(RegRecs), + ?match(R, ?sort(mnesia_registry_test:restore_registry(node(), RegTab))), + + ?verify_mnesia(Nodes, []). + +sorted_ets(suite) -> + []; +sorted_ets(Config) when is_list(Config) -> + [N1, N2, N3] = All = ?acquire_nodes(3, Config), + + Tab = sorted_tab, + Def = case mnesia_test_lib:diskless(Config) of + true -> [{name, Tab}, {type, ordered_set}, {ram_copies, All}]; + false -> [{name, Tab}, {type, ordered_set}, + {ram_copies, [N1]}, + {disc_copies,[N2, N3]}] + end, + + ?match({atomic, ok}, mnesia:create_table(Def)), + ?match({aborted, _}, mnesia:create_table(fel, [{disc_only_copies, N1}])), + + ?match([ok | _], + [mnesia:dirty_write({Tab, {dirty, N}, N}) || N <- lists:seq(1, 10)]), + ?match({atomic, _}, + mnesia:sync_transaction(fun() -> + [mnesia:write({Tab, {trans, N}, N}) || + N <- lists:seq(1, 10)] + end)), + + List = mnesia:dirty_match_object({Tab, '_', '_'}), + ?match(List, ?sort(List)), + ?match(List, rpc:call(N2, mnesia, dirty_match_object, [{Tab, '_', '_'}])), + ?match(List, rpc:call(N3, mnesia, dirty_match_object, [{Tab, '_', '_'}])), + + mnesia_test_lib:stop_mnesia(All), + mnesia_test_lib:start_mnesia(All, [sorted_tab]), + + List = mnesia:dirty_match_object({Tab, '_', '_'}), + ?match(List, ?sort(List)), + ?match(List, rpc:call(N2, mnesia, dirty_match_object, [{Tab, '_', '_'}])), + ?match(List, rpc:call(N3, mnesia, dirty_match_object, [{Tab, '_', '_'}])), + + ?match(List, rpc:call(N3, mnesia, dirty_select, [Tab, [{{Tab, '_', '_'},[],['$_']}]])), + + TransMatch = fun() -> + mnesia:write({Tab, {trans, 0}, 0}), + mnesia:write({Tab, {trans, 11}, 11}), + mnesia:match_object({Tab, '_', '_'}) + end, + TransSelect = fun() -> + mnesia:write({Tab, {trans, 0}, 0}), + mnesia:write({Tab, {trans, 11}, 11}), + mnesia:select(Tab, [{{Tab, '_', '_'},[],['$_']}]) + end, + + TList = mnesia:transaction(TransMatch), + STList = ?sort(TList), + ?match(STList, TList), + ?match(STList, rpc:call(N2, mnesia, transaction, [TransMatch])), + ?match(STList, rpc:call(N3, mnesia, transaction, [TransMatch])), + + TSel = mnesia:transaction(TransSelect), + ?match(STList, TSel), + ?match(STList, rpc:call(N2, mnesia, transaction, [TransSelect])), + ?match(STList, rpc:call(N3, mnesia, transaction, [TransSelect])), + + ?match({atomic, ok}, mnesia:create_table(rec, [{type, ordered_set}])), + [ok = mnesia:dirty_write(R) || R <- [{rec,1,1}, {rec,2,1}]], + ?match({atomic, ok}, mnesia:add_table_index(rec, 3)), + TestIt = fun() -> + ok = mnesia:write({rec,1,1}), + mnesia:index_read(rec, 1, 3) + end, + ?match({atomic, [{rec,1,1}, {rec,2,1}]}, mnesia:transaction(TestIt)). + + diff --git a/lib/mnesia/test/mnesia_examples_test.erl b/lib/mnesia/test/mnesia_examples_test.erl new file mode 100644 index 0000000000..d1b1409c9d --- /dev/null +++ b/lib/mnesia/test/mnesia_examples_test.erl @@ -0,0 +1,160 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_examples_test). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +-define(init(N, Config), + mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}, + delete_schema], + N, Config, ?FILE, ?LINE)). + +opt_net_load(ExampleMod) -> + opt_net_load([node() | nodes()], ExampleMod, ok). + +opt_net_load([Node | Nodes], ExampleMod, Res) -> + case rpc:call(Node, ?MODULE, opt_load, [ExampleMod]) of + {module, ExampleMod} -> + opt_net_load(Nodes, ExampleMod, Res); + {error, Reason} -> + Error = {opt_net_load, ExampleMod, Node, Reason}, + opt_net_load(Nodes, ExampleMod, {error, Error}); + {badrpc, Reason} -> + Error = {opt_net_load, ExampleMod, Node, Reason}, + opt_net_load(Nodes, ExampleMod, {error, Error}) + end; +opt_net_load([], _ExampleMod, Res) -> + Res. + +opt_load(Mod) -> + case code:is_loaded(Mod) of + {file, _} -> + {module, Mod}; + false -> + Abs = filename:join([code:lib_dir(mnesia), examples, Mod]), + code:load_abs(Abs) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Run all examples mentioned in the documentation", + "Are really all examples covered?"]; +all(suite) -> + [ + bup, + company, + meter, + tpcb + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +bup(doc) -> ["Run the backup examples in bup.erl"]; +bup(suite) -> []; +bup(Config) when is_list(Config) -> + Nodes = ?init(3, Config), + opt_net_load(bup), + ?match(ok, bup:test(Nodes)). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +company(doc) -> + ["Run the company examples in company.erl and company_o.erl"]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +tpcb(doc) -> + ["Run the sample configurations of the stress tests in mnesia_tpcb.erl"]; +tpcb(suite) -> + [ + replica_test, + sticky_replica_test, + dist_test, + conflict_test, + frag_test, + frag2_test, + remote_test, + remote_frag2_test + ]. + +replica_test(suite) -> []; +replica_test(Config) when is_list(Config) -> + ?init(3, Config), + opt_net_load(mnesia_tpcb), + ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(replica_test, ram_copies))). + +sticky_replica_test(suite) -> []; +sticky_replica_test(Config) when is_list(Config) -> + ?init(3, Config), + opt_net_load(mnesia_tpcb), + ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(sticky_replica_test, ram_copies))). + +dist_test(suite) -> []; +dist_test(Config) when is_list(Config) -> + ?init(3, [{tc_timeout, timer:minutes(10)} | Config]), + opt_net_load(mnesia_tpcb), + ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(dist_test, ram_copies))). + +conflict_test(suite) -> []; +conflict_test(Config) when is_list(Config) -> + ?init(3, Config), + opt_net_load(mnesia_tpcb), + ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(conflict_test, ram_copies))). + +frag_test(suite) -> []; +frag_test(Config) when is_list(Config) -> + ?init(3, Config), + opt_net_load(mnesia_tpcb), + ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(frag_test, ram_copies))). + +frag2_test(suite) -> []; +frag2_test(Config) when is_list(Config) -> + ?init(3, Config), + opt_net_load(mnesia_tpcb), + ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(frag2_test, ram_copies))). + +remote_test(suite) -> []; +remote_test(Config) when is_list(Config) -> + ?init(3, Config), + opt_net_load(mnesia_tpcb), + ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(remote_test, ram_copies))). + +remote_frag2_test(suite) -> []; +remote_frag2_test(Config) when is_list(Config) -> + ?init(3, Config), + opt_net_load(mnesia_tpcb), + ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(remote_frag2_test, ram_copies))). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +meter(doc) -> + ["Run the meter example in mnesia_meter.erl"]; +meter(suite) -> + []; +meter(Config) when is_list(Config) -> + [N | _] = ?init(3, Config), + opt_net_load(mnesia_meter), + ?match(ok, mnesia_meter:go(ram_copies, [N])). + + diff --git a/lib/mnesia/test/mnesia_frag_test.erl b/lib/mnesia/test/mnesia_frag_test.erl new file mode 100644 index 0000000000..4add340254 --- /dev/null +++ b/lib/mnesia/test/mnesia_frag_test.erl @@ -0,0 +1,875 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1999-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_frag_test). +-author('[email protected]'). +-include("mnesia_test_lib.hrl"). + +-compile([export_all]). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +-define(match_dist(ExpectedRes, Expr), + case ?match(ExpectedRes, Expr) of + + mnesia_test_lib:error(Format, Args,?FILE,?LINE)). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +all(doc) -> + ["Verify the functionality of fragmented tables"]; +all(suite) -> + [ + light, + medium + ]. + +light(suite) -> + [ + nice, + evil + ]. + +medium(suite) -> + [ + consistency + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +nice(suite) -> + [ + nice_single, + nice_multi, + nice_access, + iter_access + ]. + +nice_single(suite) -> []; +nice_single(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + + %% Create a table with 2 fragments and 12 records + Tab = nice_frag, + Props = [{n_fragments, 2}, {node_pool, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, [{frag_properties, Props}])), + Records = [{Tab, N, -N} || N <- lists:seq(1, 12)], + [frag_write(Tab, R) || R <- Records], + ?match([{Node1, 2}], frag_dist(Tab)), + ?match([8, 4], frag_rec_dist(Tab)), + + %% Adding a new node to pool should not affect distribution + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_node, Node2})), + Dist = frag_dist(Tab), + ?match([{Node2, 0}, {Node1, 2}], Dist), + ?match([8, 4], frag_rec_dist(Tab)), + + %% Add new fragment hopefully on the new node + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist})), + Dist2 = frag_dist(Tab), + ?match([{Node2, 1}, {Node1, 2}], Dist2), + ?match([3, 4, 5], frag_rec_dist(Tab)), + + %% Add new fragment hopefully on the new node + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist2})), + Dist3 = frag_dist(Tab), + ?match([{Node1, 2}, {Node2, 2}], Dist3), + ?match([3, 2, 5, 2], frag_rec_dist(Tab)), + + %% Add new fragment hopefully on the new node + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist3})), + Dist4 = frag_dist(Tab), + ?match([{Node2, 2}, {Node1, 3}], Dist4), + ?match([_, _, _, _, _], frag_rec_dist(Tab)), + + %% Dropping a node in pool should not affect distribution + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {del_node, Node1})), + ?match([{Node2, 2}, {Node1, 3}], frag_dist(Tab)), + ?match([_, _, _, _, _], frag_rec_dist(Tab)), + + %% Dropping a fragment + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + Dist5 = frag_dist(Tab), + ?match([{Node2, 2}, {Node1, 2}], Dist5), + ?match([3, 2, 5, 2], frag_rec_dist(Tab)), + + %% Add new fragment hopefully on the new node + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist5})), + Dist6 = frag_dist(Tab), + ?match([{Node2, 3}, {Node1, 2}], Dist6), + ?match([_, _, _, _, _], frag_rec_dist(Tab)), + + %% Dropping all fragments but one + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + ?match([3, 2, 5, 2], frag_rec_dist(Tab)), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + ?match([3, 4, 5], frag_rec_dist(Tab)), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + ?match([8, 4], frag_rec_dist(Tab)), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + ?match([{Node2, 0}, {Node1, 1}], frag_dist(Tab)), + ?match([12], frag_rec_dist(Tab)), + + %% Defragmenting the table clears frag_properties + ?match(Len when Len > 0, + length(mnesia:table_info(Tab, frag_properties))), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, deactivate)), + ?match(0, length(mnesia:table_info(Tab, frag_properties))), + + %% Making the table fragmented again + Props2 = [{n_fragments, 1}], + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {activate, Props2})), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, frag_dist(Tab)})), + Dist7 = frag_dist(Tab), + ?match([{Node1, 1}, {Node2, 1}], Dist7), + ?match([8, 4], frag_rec_dist(Tab)), + + %% Deleting the fragmented table + ?match({atomic, ok}, mnesia:delete_table(Tab)), + ?match(false, lists:member(Tab, mnesia:system_info(tables))), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +nice_multi(doc) -> + ["Extending the nice case with one more node, ", + "one more replica and a foreign key"]; +nice_multi(suite) -> []; +nice_multi(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + + %% Create a table with 2 fragments and 8 records + Tab = frag_master, + Name = frag_rec, + Type = case mnesia_test_lib:diskless(Config) of + true -> n_ram_copies; + false -> n_disc_copies + end, + Props = [{n_fragments, 2}, + {Type, 2}, + {node_pool, [Node2, Node1]}], + Def = [{frag_properties, Props}, + {attributes, [id, data]}, + {record_name, Name}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + [frag_write(Tab, {Name, Id, -Id}) || Id <- lists:seq(1, 8)], + ?match([6, 2], frag_rec_dist(Tab)), + ?match([{Node2, 2}, {Node1, 2}], frag_dist(Tab)), + + %% And connect another table to it, via a foreign key + TabF = frag_slave, + PropsF = [{foreign_key, {Tab, foreign_id}}], + DefF = [{frag_properties, PropsF}, + {attributes, [id, foreign_id]}], + + ?match({atomic, ok}, mnesia:create_table(TabF, DefF)), + [frag_write(TabF, {TabF, {Id}, Id}) || Id <- lists:seq(1, 16)], + ?match([10, 6], frag_rec_dist(TabF)), + ?match([{Node2, 2}, {Node1, 2}], frag_dist(TabF)), + + %% Adding a new node to pool should not affect distribution + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_node, Node3})), + Dist = frag_dist(Tab), + ?match([{Node3, 0}, {Node2, 2}, {Node1, 2}], Dist), + ?match([6, 2], frag_rec_dist(Tab)), + DistF = frag_dist(TabF), + ?match([{Node3, 0}, {Node2, 2}, {Node1, 2}], DistF), + ?match([10, 6], frag_rec_dist(TabF)), + + %% Add new fragment hopefully on the new node + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist})), + Dist2 = frag_dist(Tab), + ?match([{Node3, 1},{Node1, 2},{Node2,3}], Dist2), + ?match([_, _, _], frag_rec_dist(Tab)), + DistF2 = frag_dist(TabF), + ?match([{Node3, 1},{Node1, 2},{Node2,3}], DistF2), + ?match([_, _, _], frag_rec_dist(TabF)), + + %% Add new fragment hopefully on the new node + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist2})), + Dist3 = frag_dist(Tab), + ?match([{Node3, 2},{Node2,3},{Node1, 3}], Dist3), + ?match([3, 0, 3, 2], frag_rec_dist(Tab)), + DistF3 = frag_dist(TabF), + ?match([{Node3, 2},{Node2,3},{Node1, 3}], DistF3), + ?match([3, 3, 7, 3], frag_rec_dist(TabF)), + + %% Add new fragment hopefully on the new node + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist3})), + Dist4 = frag_dist(Tab), + ?match([{Node1, 3}, {Node3, 3},{Node2, 4}], Dist4), + ?match([_, _, _, _, _], frag_rec_dist(Tab)), + DistF4 = frag_dist(TabF), + ?match([{Node1, 3}, {Node3, 3},{Node2, 4}], DistF4), + ?match([_, _, _, _, _], frag_rec_dist(TabF)), + + %% Dropping a node in pool should not affect distribution + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {del_node, Node1})), + ?match([{Node3, 3},{Node2, 4}, {Node1, 3}], frag_dist(Tab)), + ?match([_, _, _, _, _], frag_rec_dist(Tab)), + ?match([{Node3, 3},{Node2, 4}, {Node1, 3}], frag_dist(TabF)), + ?match([_, _, _, _, _], frag_rec_dist(TabF)), + + %% Dropping a fragment + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + Dist5 = frag_dist(Tab), + ?match([{Node3, 2},{Node2,3},{Node1, 3}], Dist5), + ?match([3, 0, 3, 2], frag_rec_dist(Tab)), + DistF5 = frag_dist(Tab), + ?match([{Node3, 2},{Node2,3},{Node1, 3}], DistF5), + ?match([3, 3, 7, 3], frag_rec_dist(TabF)), + + %% Add new fragment hopefully on the new node + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist5})), + Dist6 = frag_dist(Tab), + ?match([{Node3, 3},{Node2, 4},{Node1, 3}], Dist6), + ?match([_, _, _, _, _], frag_rec_dist(Tab)), + DistF6 = frag_dist(TabF), + ?match([{Node3, 3},{Node2, 4},{Node1, 3}], DistF6), + ?match([_, _, _, _, _], frag_rec_dist(TabF)), + + %% Dropping all fragments but one + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + ?match([3, 0, 3, 2], frag_rec_dist(Tab)), + ?match([3, 3, 7, 3], frag_rec_dist(TabF)), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + ?match([_, _, _], frag_rec_dist(Tab)), + ?match([_, _, _], frag_rec_dist(TabF)), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + ?match([6, 2], frag_rec_dist(Tab)), + ?match([10, 6], frag_rec_dist(TabF)), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)), + ?match([{Node3, 0}, {Node2, 1}, {Node1, 1}], frag_dist(Tab)), + ?match([8], frag_rec_dist(Tab)), + ?match([{Node3, 0}, {Node2, 1}, {Node1, 1}], frag_dist(TabF)), + ?match([16], frag_rec_dist(TabF)), + + %% Defragmenting the tables clears frag_properties + ?match(Len when Len > 0, + length(mnesia:table_info(TabF, frag_properties))), + ?match({atomic, ok}, mnesia:change_table_frag(TabF, deactivate)), + ?match(0, length(mnesia:table_info(TabF, frag_properties))), + ?match(Len when Len > 0, + length(mnesia:table_info(Tab, frag_properties))), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, deactivate)), + ?match(0, length(mnesia:table_info(Tab, frag_properties))), + + %% Making the tables fragmented again + Props2 = [{n_fragments, 1}, {node_pool, [Node1, Node2]}], + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {activate, Props2})), + ?match({atomic, ok}, mnesia:delete_table(TabF)), + ?match({atomic, ok}, mnesia:create_table(TabF, DefF)), + [frag_write(TabF, {TabF, {Id}, Id}) || Id <- lists:seq(1, 16)], + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, frag_dist(Tab)})), + ?match([{Node1, 2}, {Node2, 2}], frag_dist(Tab)), + ?match([6, 2], frag_rec_dist(Tab)), + ?match([{Node1, 2}, {Node2, 2}], frag_dist(TabF)), + ?match([10, 6], frag_rec_dist(TabF)), + + %% Deleting the fragmented tables + ?match({atomic, ok}, mnesia:delete_table(TabF)), + ?match(false, lists:member(TabF, mnesia:system_info(tables))), + ?match({atomic, ok}, mnesia:delete_table(Tab)), + ?match(false, lists:member(Tab, mnesia:system_info(tables))), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +nice_access(doc) -> + ["Cover entire callback interface"]; +nice_access(suite) -> []; +nice_access(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(3, Config), + + Tab = frag_access, + Pool = lists:sort(Nodes), + Props = [{n_fragments, 20}, + {n_ram_copies, 2}, + {node_pool, Pool}], + Def = [{frag_properties, Props}, + {type, ordered_set}, + {index, [val]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + [frag_write(Tab, {Tab, Id, Id}) || Id <- lists:seq(1, 400)], + + %% And connect another table to it, via a foreign key + TabF = frag_access_slave, + PropsF = [{foreign_key, {Tab, val}}], + DefF = [{frag_properties, PropsF}, + {index, [val]}], + ?match({atomic, ok}, mnesia:create_table(TabF, DefF)), + [frag_write(TabF, {TabF, Id, Id}) || Id <- lists:seq(1, 400)], + + ?match(done, mnesia:activity(transaction, fun do_access/3, [Tab, Tab, Pool], mnesia_frag)), + ?match(done, mnesia:activity(transaction, fun do_access/3, [TabF, Tab, Pool], mnesia_frag)), + + ?verify_mnesia(Nodes, []). + +do_access(Tab, Master, Pool) -> + ?match(20, mnesia:table_info(Tab, n_fragments)), + ?match(Pool, mnesia:table_info(Tab, node_pool)), + ?match(2, mnesia:table_info(Tab, n_ram_copies)), + ?match(0, mnesia:table_info(Tab, n_disc_copies)), + ?match(0, mnesia:table_info(Tab, n_disc_only_copies)), + ?match(20, length(mnesia:table_info(Tab, frag_names))), + ?match(20, length(mnesia:table_info(Tab, frag_size))), + ?match(20, length(mnesia:table_info(Tab, frag_memory))), + PoolSize = length(Pool), + ?match(PoolSize, length(mnesia:table_info(Tab, frag_dist))), + ?match(400, mnesia:table_info(Tab, size)), + ?match(I when is_integer(I), mnesia:table_info(Tab, memory)), + ?match(Tab, mnesia:table_info(Tab, base_table)), + + Foreign = + if + Master == Tab -> + ?match(undefined, mnesia:table_info(Tab, foreign_key)), + ?match([_], mnesia:table_info(Tab, foreigners)), + ?match({'EXIT', {aborted, {combine_error, Tab, frag_properties, {foreign_key, undefined}}}}, + mnesia:read({Tab, 5}, 5, read)), + fun({T, _K}) -> T end; + true -> + ?match({Master, 3}, mnesia:table_info(Tab, foreign_key)), + ?match([], mnesia:table_info(Tab, foreigners)), + fun({T, K}) -> {T, K} end + end, + + Attr = val, + ?match(400, mnesia:table_info(Tab, size)), + Count = fun(_, N) -> N + 1 end, + ?match(400, mnesia:foldl(Count, 0, Tab)), + ?match(400, mnesia:foldr(Count, 0, Tab)), + ?match(ok, mnesia:write({Tab, [-1], 1})), + ?match(401, length(mnesia:match_object(Tab, {Tab, '_', '_'}, read))), + ?match(401, length(mnesia:select(Tab, [{{Tab, '_', '$1'}, [], ['$1']}], read))), + + First = mnesia:select(Tab, [{{Tab, '_', '$1'}, [], ['$1']}], 10, read), + TestCont = fun('$end_of_table', Total, _This) -> + Total; + ({Res,Cont1}, Total, This) -> + Cont = mnesia:select(Cont1), + This(Cont, length(Res) + Total, This) + end, + ?match(401, TestCont(First, 0, TestCont)), + + %% OTP + [_, Frag2|_] = frag_names(Tab), + Frag2key = mnesia:dirty_first(Frag2), + ?match({[Frag2key],_},mnesia:select(Tab,[{{Tab,Frag2key,'$1'},[],['$1']}],100,read)), + + ?match([{Tab, [-1], 1}], mnesia:read(Foreign({Tab, 1}), [-1], read)), + ?match(401, mnesia:foldl(Count, 0, Tab)), + ?match(401, mnesia:foldr(Count, 0, Tab)), + ?match(ok, mnesia:delete(Foreign({Tab, 2}), 2, write)), + ?match([], mnesia:read(Foreign({Tab, 2}), 2, read)), + ?match([{Tab, 3, 3}], mnesia:read(Foreign({Tab, 3}), 3, read)), + ?match(400, mnesia:foldl(Count, 0, Tab)), + ?match(400, mnesia:foldr(Count, 0, Tab)), + ?match(ok, mnesia:delete_object({Tab, 3, 3})), + ?match([], mnesia:read(Foreign({Tab, 3}), 3, read)), + One = lists:sort([{Tab, 1, 1}, {Tab, [-1], 1}]), + Pat = {Tab, '$1', 1}, + ?match(One, lists:sort(mnesia:match_object(Tab, Pat, read))), + ?match([1,[-1]], lists:sort(mnesia:select(Tab, [{Pat, [], ['$1']}], read))), + ?match([[[-1]]], lists:sort(mnesia:select(Tab, [{Pat, [{is_list, '$1'}], [['$1']]}], read))), + ?match([[1, 100]], lists:sort(mnesia:select(Tab, [{Pat, [{is_integer, '$1'}], [['$1',100]]}], read))), + ?match([1,[-1]], lists:sort(mnesia:select(Tab, [{Pat, [{is_list, '$1'}], ['$1']},{Pat, [{is_integer, '$1'}], ['$1']}], read))), + ?match(One, lists:sort(mnesia:index_match_object(Tab, Pat, Attr, read) )), + ?match(One, lists:sort(mnesia:index_read(Tab, 1, Attr))), + Keys = mnesia:all_keys(Tab), + ?match([-1], lists:max(Keys)), %% OTP-3779 + ?match(399, length(Keys)), + ?match(399, mnesia:foldl(Count, 0, Tab)), + ?match(399, mnesia:foldr(Count, 0, Tab)), + + ?match(Pool, lists:sort(mnesia:lock({table, Tab}, write))), + + done. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +iter_access(doc) -> + ["Cover table iteration via callback interface"]; +iter_access(suite) -> []; +iter_access(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(3, Config), + + Tab = frag_access, + Pool = lists:sort(Nodes), + Props = [{n_fragments, 20}, + {n_ram_copies, 2}, + {node_pool, Pool}], + Def = [{frag_properties, Props}, + {type, ordered_set}, + {index, [val]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + [frag_write(Tab, {Tab, Id, Id}) || Id <- lists:seq(1, 400)], + + FragNames = frag_names(Tab), + RawRead = + fun(Frag) -> + Node = mnesia:table_info(Frag, where_to_read), + {Frag, rpc:call(Node, ets, tab2list, [Frag])} + end, + + ?match(done, mnesia:activity(transaction, fun nice_iter_access/3, [Tab, FragNames, RawRead], mnesia_frag)), + + FragNames = frag_names(Tab), + [First, Second | _] = FragNames, + [Last, LastButOne | _] = lists:reverse(FragNames), + + ?match({atomic, ok}, mnesia:clear_table(First)), + ?match({atomic, ok}, mnesia:clear_table(Second)), + ?match({atomic, ok}, mnesia:clear_table(lists:nth(8, FragNames))), + ?match({atomic, ok}, mnesia:clear_table(lists:nth(9, FragNames))), + ?match({atomic, ok}, mnesia:clear_table(lists:nth(10, FragNames))), + ?match({atomic, ok}, mnesia:clear_table(lists:nth(11, FragNames))), + ?match({atomic, ok}, mnesia:clear_table(LastButOne)), + ?match({atomic, ok}, mnesia:clear_table(Last)), + + ?match(done, mnesia:activity(transaction, fun evil_iter_access/3, [Tab, FragNames, RawRead], mnesia_frag)), + Size = fun(Table) -> mnesia:table_info(Table, size) end, + ?match(true, 0 < mnesia:activity(transaction, Size, [Tab], mnesia_frag)), + ?match({atomic, ok}, mnesia:activity(ets, fun() -> mnesia:clear_table(Tab) end, mnesia_frag)), + ?match(0, mnesia:activity(transaction, Size, [Tab], mnesia_frag)), + + ?verify_mnesia(Nodes, []). + +nice_iter_access(Tab, FragNames, RawRead) -> + RawData = ?ignore(lists:map(RawRead, FragNames)), + Keys = [K || {_, Recs} <- RawData, {_, K, _} <- Recs], + ExpectedFirst = hd(Keys), + ?match(ExpectedFirst, mnesia:first(Tab)), + ExpectedLast = lists:last(Keys), + ?match(ExpectedLast, mnesia:last(Tab)), + + ExpectedAllPrev = ['$end_of_table' | lists:reverse(tl(lists:reverse(Keys)))], + ?match(ExpectedAllPrev, lists:map(fun(K) -> mnesia:prev(Tab, K) end, Keys)), + + ExpectedAllNext = tl(Keys) ++ ['$end_of_table'], + ?match(ExpectedAllNext, lists:map(fun(K) -> mnesia:next(Tab, K) end, Keys)), + + done. + +evil_iter_access(Tab, FragNames, RawRead) -> + RawData = ?ignore(lists:map(RawRead, FragNames)), + Keys = [K || {_, Recs} <- RawData, {_, K, _} <- Recs], + ExpectedFirst = hd(Keys), + ?match(ExpectedFirst, mnesia:first(Tab)), + ExpectedLast = lists:last(Keys), + ?match(ExpectedLast, mnesia:last(Tab)), + + ExpectedAllPrev = ['$end_of_table' | lists:reverse(tl(lists:reverse(Keys)))], + ?match(ExpectedAllPrev, lists:map(fun(K) -> mnesia:prev(Tab, K) end, Keys)), + + ExpectedAllNext = tl(Keys) ++ ['$end_of_table'], + ?match(ExpectedAllNext, lists:map(fun(K) -> mnesia:next(Tab, K) end, Keys)), + + done. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +consistency(doc) -> + ["Add and delete fragments during TPC-B"]; +consistency(suite) -> []; +consistency(Config) when is_list(Config) -> + ?skip("Not yet implemented (NYI).~n", []), + Nodes = ?acquire_nodes(2, Config), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +evil(doc) -> + ["Evil coverage of fragmentation API."]; +evil(suite) -> + [ + evil_create, + evil_delete, + evil_change, + evil_combine, + evil_loop, + evil_delete_db_node + ]. + +evil_create(suite) -> []; +evil_create(Config) when is_list(Config) -> + [Node1, _Node2] = Nodes = ?acquire_nodes(2, Config), + + Create = fun(T, D, P) -> mnesia:create_table(T, [{frag_properties, P}| D]) end, + + Tab = evil_create, + %% Props in general + ?match({aborted, {badarg, Tab, {frag_properties, no_list}}}, + Create(Tab, [], no_list)), + ?match({aborted, {badarg,Tab , [no_tuple]}}, + Create(Tab, [], [no_tuple])), + ?match({aborted,{badarg, Tab, bad_key}}, + Create(Tab, [], [{bad_key, 7}])), + + %% n_fragments + ?match({aborted,{badarg, Tab, [{n_fragments}]}}, + Create(Tab, [], [{n_fragments}])), + ?match({aborted,{badarg, Tab, [{n_fragments, 1, 1}]}}, + Create(Tab, [], [{n_fragments, 1, 1}])), + ?match({aborted, {bad_type,Tab, {n_fragments, a}}}, + Create(Tab, [], [{n_fragments, a}])), + ?match({aborted, {bad_type, Tab, {n_fragments, 0}}}, + Create(Tab, [], [{n_fragments, 0}])), + + %% *_copies + ?match({aborted, {bad_type, Tab, {n_ram_copies, -1}}}, + Create(Tab, [], [{n_ram_copies, -1}, {n_fragments, 1}])), + ?match({aborted, {bad_type, Tab, {n_disc_copies, -1}}}, + Create(Tab, [], [{n_disc_copies, -1}, {n_fragments, 1}])), + ?match({aborted, {bad_type, Tab, {n_disc_only_copies, -1}}}, + Create(Tab, [], [{n_disc_only_copies, -1}, {n_fragments, 1}])), + + %% node_pool + ?match({aborted, {bad_type, Tab, {node_pool, 0}}}, + Create(Tab, [], [{node_pool, 0}])), + ?match({aborted, {combine_error, Tab, "Too few nodes in node_pool"}}, + Create(Tab, [], [{n_ram_copies, 2}, {node_pool, [Node1]}])), + + %% foreign_key + ?match({aborted, {bad_type, Tab, {foreign_key, bad_key}}}, + Create(Tab, [], [{foreign_key, bad_key}])), + ?match({aborted,{bad_type, Tab, {foreign_key, {bad_key}}}}, + Create(Tab, [], [{foreign_key, {bad_key}}])), + ?match({aborted, {no_exists, {bad_tab, frag_properties}}}, + Create(Tab, [], [{foreign_key, {bad_tab, val}}])), + ?match({aborted, {combine_error, Tab, {Tab, val}}}, + Create(Tab, [], [{foreign_key, {Tab, val}}])), + ?match({atomic, ok}, + Create(Tab, [], [{n_fragments, 1}])), + + ?match({aborted, {already_exists, Tab}}, + Create(Tab, [], [{n_fragments, 1}])), + + Tab2 = evil_create2, + ?match({aborted, {bad_type, no_attr}}, + Create(Tab2, [], [{foreign_key, {Tab, no_attr}}])), + ?match({aborted, {combine_error, Tab2, _, _, _}}, + Create(Tab2, [], [{foreign_key, {Tab, val}}, + {node_pool, [Node1]}])), + ?match({aborted, {combine_error, Tab2, _, _, _}}, + Create(Tab2, [], [{foreign_key, {Tab, val}}, + {n_fragments, 2}])), + ?match({atomic, ok}, + Create(Tab2, [{attributes, [a, b, c]}], [{foreign_key, {Tab, c}}])), + Tab3 = evil_create3, + ?match({aborted, {combine_error, Tab3, _, _, _}}, + Create(Tab3, [{attributes, [a, b]}], [{foreign_key, {Tab2, b}}])), + ?match({atomic, ok}, + Create(Tab3, [{attributes, [a, b]}], [{foreign_key, {Tab, b}}])), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +evil_delete(suite) -> []; +evil_delete(Config) when is_list(Config) -> + ?skip("Not yet implemented (NYI).~n", []), + Nodes = ?acquire_nodes(2, Config), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +evil_change(suite) -> []; +evil_change(Config) when is_list(Config) -> + [N1,N2,_N3] = Nodes = ?acquire_nodes(3, Config), + Create = fun(T, D, P) -> mnesia:create_table(T, [{frag_properties, P}| D]) end, + Props1 = [{n_fragments, 2}, {node_pool, [N1]}], + Tab1 = evil_change_ram, + ?match({atomic, ok}, Create(Tab1, [], Props1)), + + ?match({atomic,ok}, mnesia:change_table_frag(Tab1, {add_frag, Nodes})), + Dist10 = frag_dist(Tab1), + ?match([{N1,3}], Dist10), + ?match({atomic, ok}, mnesia:change_table_frag(Tab1, {add_node, N2})), + Dist11 = frag_dist(Tab1), + ?match([{N2,0},{N1,3}], Dist11), + mnesia_test_lib:kill_mnesia([N2]), + ?match({aborted,_}, mnesia:change_table_frag(Tab1, {add_frag, [N2,N1]})), + ?verbose("~p~n",[frag_dist(Tab1)]), + mnesia_test_lib:start_mnesia([N2]), + + Tab2 = evil_change_disc, + ?match({atomic,ok}, Create(Tab2,[],[{n_disc_copies,1},{n_fragments,1},{node_pool,[N1,N2]}])), + ?verbose("~p~n", [frag_dist(Tab2)]), + ?match({atomic,ok}, mnesia:change_table_frag(Tab2, {add_frag, [N1,N2]})), + _Dist20 = frag_dist(Tab2), + mnesia_test_lib:kill_mnesia([N2]), + ?match({atomic,ok}, mnesia:change_table_frag(Tab2, {add_frag, [N1,N2]})), + ?match({aborted,_}, mnesia:change_table_frag(Tab2, {add_frag, [N2,N1]})), + + mnesia_test_lib:start_mnesia([N2]), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +evil_combine(doc) -> ["Bug in mnesia_4.1.5. and earlier"]; +evil_combine(suite) -> []; +evil_combine(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + ?match({atomic, ok},mnesia:create_table(tab1, [{disc_copies, [Node1]}, + {frag_properties, [{n_fragments, 2}, + {node_pool, [Node1]}, + {n_disc_copies, 1}]}])), + ?match({atomic, ok},mnesia:create_table(tab2, [{disc_copies, [Node1]}])), + mnesia:wait_for_tables([tab1, tab2], infinity), + + Add2 = fun() -> + mnesia:transaction(fun() -> + mnesia:write({tab2,1,1}) + end) + end, + Fun = fun() -> + Add2(), + mnesia:write({tab1,9,10}) + end, + ?match(ok, mnesia:activity({transaction, 1}, Fun, [], mnesia_frag)), + + Read = fun(T, K) -> + mnesia:read(T, K, read) + end, + + ?match([{tab1,9,10}],mnesia:activity(async_dirty, Read, [tab1, 9], mnesia_frag)), + ?match([{tab2,1,1}],mnesia:activity(async_dirty, Read, [tab2, 1], mnesia_frag)), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +evil_loop(doc) -> ["Test select/[14]"]; +evil_loop(suite) -> []; +evil_loop(Config) when is_list(Config) -> + [Node1,_Node2] = ?acquire_nodes(2, Config), + Tab1 = ss_oset, + Tab2 = ss_set, + Tab3 = ss_bag, + Tabs = [Tab1, Tab2, Tab3], + RecName = ss, + ?match({atomic, ok}, mnesia:create_table([{name, Tab1}, + {ram_copies, [Node1]}, + {record_name, RecName}, + {type, ordered_set}])), + ?match({atomic, ok}, mnesia:create_table([{name, Tab2}, + {record_name, RecName}, + {ram_copies, [Node1]}, + {type, set}])), + ?match({atomic, ok}, mnesia:create_table([{name, Tab3}, + {record_name, RecName}, + {ram_copies, [Node1]}, + {type, bag}])), + Keys = [-3, -2] ++ lists:seq(1, 5, 2) ++ lists:seq(6, 10), + Recs = [{RecName, K, K} || K <- Keys], + [mnesia:dirty_write(Tab1, R) || R <- Recs], + [mnesia:dirty_write(Tab2, R) || R <- Recs], + [mnesia:dirty_write(Tab3, R) || R <- Recs], + + Activate = + fun(Tab) -> + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {activate, []})), + Dist = frag_dist(Tab), + ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist})) + end, + + Activate(Tab1), + Activate(Tab2), + Activate(Tab3), + + Match = fun(Tab) -> mnesia:match_object(Tab, {'_', '_', '_'}, write) end, + Select = fun(Tab) -> mnesia:select(Tab, [{'_', [], ['$_']}]) end, + Trans = fun(Fun, Args) -> mnesia:activity(transaction, Fun, Args, mnesia_frag) end, + LoopHelp = fun('$end_of_table',_) -> + []; + ({Res,Cont},Fun) -> + Sel = mnesia:select(Cont), + Res ++ Fun(Sel, Fun) + end, + SelLoop = fun(Table) -> + Sel = mnesia:select(Table, [{'_', [], ['$_']}], 1, read), + LoopHelp(Sel, LoopHelp) + end, + + R1 = {RecName, 2, 2}, + R2 = {RecName, 4, 4}, + R3 = {RecName, 2, 3}, + R4 = {RecName, 3, 1}, + R5 = {RecName, 104, 104}, + W1 = fun(Tab,Search) -> + mnesia:write(Tab,R1,write), + mnesia:write(Tab,R2,write), + Search(Tab) + end, + S1 = lists:sort([R1, R2| Recs]), + ?match(S1, sort_res(Trans(W1, [Tab1, Select]))), + ?match(S1, sort_res(Trans(W1, [Tab1, Match]))), + ?match(S1, sort_res(Trans(W1, [Tab1, SelLoop]))), + ?match(S1, sort_res(Trans(W1, [Tab2, Select]))), + ?match(S1, sort_res(Trans(W1, [Tab2, SelLoop]))), + ?match(S1, sort_res(Trans(W1, [Tab2, Match]))), + ?match(S1, sort_res(Trans(W1, [Tab3, Select]))), + ?match(S1, sort_res(Trans(W1, [Tab3, SelLoop]))), + ?match(S1, sort_res(Trans(W1, [Tab3, Match]))), + [mnesia:dirty_delete_object(Frag, R) || R <- [R1, R2], + Tab <- Tabs, + Frag <- frag_names(Tab)], + + W2 = fun(Tab, Search) -> + mnesia:write(Tab, R3, write), + mnesia:write(Tab, R1, write), + Search(Tab) + end, + S2 = lists:sort([R1 | Recs]), + S2Bag = lists:sort([R1, R3 | Recs]), + io:format("S2 = ~p\n", [S2]), + ?match(S2, sort_res(Trans(W2, [Tab1, Select]))), + ?match(S2, sort_res(Trans(W2, [Tab1, SelLoop]))), + ?match(S2, sort_res(Trans(W2, [Tab1, Match]))), + ?match(S2, sort_res(Trans(W2, [Tab2, Select]))), + ?match(S2, sort_res(Trans(W2, [Tab2, SelLoop]))), + ?match(S2, sort_res(Trans(W2, [Tab2, Match]))), + io:format("S2Bag = ~p\n", [S2Bag]), + ?match(S2Bag, sort_res(Trans(W2, [Tab3, Select]))), + ?match(S2Bag, sort_res(Trans(W2, [Tab3, SelLoop]))), + ?match(S2Bag, sort_res(Trans(W2, [Tab3, Match]))), + + W3 = fun(Tab,Search) -> + mnesia:write(Tab, R4, write), + mnesia:delete(Tab, element(2, R1), write), + Search(Tab) + end, + S3Bag = lists:sort([R4 | lists:delete(R1, Recs)]), + S3 = lists:delete({RecName, 3, 3}, S3Bag), + ?match(S3, sort_res(Trans(W3, [Tab1, Select]))), + ?match(S3, sort_res(Trans(W3, [Tab1, SelLoop]))), + ?match(S3, sort_res(Trans(W3, [Tab1, Match]))), + ?match(S3, sort_res(Trans(W3, [Tab2, SelLoop]))), + ?match(S3, sort_res(Trans(W3, [Tab2, Select]))), + ?match(S3, sort_res(Trans(W3, [Tab2, Match]))), + ?match(S3Bag, sort_res(Trans(W3, [Tab3, Select]))), + ?match(S3Bag, sort_res(Trans(W3, [Tab3, SelLoop]))), + ?match(S3Bag, sort_res(Trans(W3, [Tab3, Match]))), + + W4 = fun(Tab,Search) -> + mnesia:delete(Tab, -1, write), + mnesia:delete(Tab, 4 , write), + mnesia:delete(Tab, 17, write), + mnesia:delete_object(Tab, {RecName, -1, x}, write), + mnesia:delete_object(Tab, {RecName, 4, x}, write), + mnesia:delete_object(Tab, {RecName, 42, x}, write), + mnesia:delete_object(Tab, R2, write), + mnesia:write(Tab, R5, write), + Search(Tab) + end, + S4Bag = lists:sort([R5 | S3Bag]), + S4 = lists:sort([R5 | S3]), + ?match(S4, sort_res(Trans(W4, [Tab1, Select]))), + ?match(S4, sort_res(Trans(W4, [Tab1, SelLoop]))), + ?match(S4, sort_res(Trans(W4, [Tab1, Match]))), + ?match(S4, sort_res(Trans(W4, [Tab2, Select]))), + ?match(S4, sort_res(Trans(W4, [Tab2, SelLoop]))), + ?match(S4, sort_res(Trans(W4, [Tab2, Match]))), + ?match(S4Bag, sort_res(Trans(W4, [Tab3, Select]))), + ?match(S4Bag, sort_res(Trans(W4, [Tab3, SelLoop]))), + ?match(S4Bag, sort_res(Trans(W4, [Tab3, Match]))), + [mnesia:dirty_delete_object(Tab, R) || R <- [{RecName, 3, 3}, R5], Tab <- Tabs], + + %% hmmm anything more?? + + ?verify_mnesia([Node1], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +evil_delete_db_node(doc) -> + ["Delete db_node with a repicated table with foreign key"]; +evil_delete_db_node(suite) -> []; +evil_delete_db_node(Config) when is_list(Config) -> + Nodes = lists:sort(?acquire_nodes(2, Config)), + Local = node(), + Remote = hd(Nodes -- [Local]), + + Type = case mnesia_test_lib:diskless(Config) of + true -> n_ram_copies; + false -> n_disc_copies + end, + Tab = frag_master, + ?match({atomic, ok}, mnesia:create_table(Tab, [{frag_properties, [{Type, 2}, {node_pool, Nodes}]}])), + ExtraTab = frag_foreigner, + ?match({atomic, ok}, mnesia:create_table(ExtraTab, [{frag_properties, [{foreign_key, {Tab, key}}, {node_pool, Nodes}]}])), + + GetPool = fun(T) -> + case lists:keysearch(node_pool, 1, mnesia:table_info (T, frag_properties)) of + {value, {node_pool, N}} -> lists:sort(N); + false -> [] + end + end, + ?match(Nodes, GetPool(Tab)), + ?match(Nodes, GetPool(ExtraTab)), + + + ?match(stopped, rpc:call(Remote, mnesia, stop, [])), + ?match({atomic, ok}, mnesia:del_table_copy(schema, Remote)), + + ?match([Local], GetPool(Tab)), + ?match([Local], GetPool(ExtraTab)), + + ?verify_mnesia([Local], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Misc convenient helpers + +frag_write(Tab, Rec) -> + Fun = fun() -> mnesia:write(Tab, Rec, write) end, + mnesia:activity(sync_dirty, Fun, mnesia_frag). + +frag_dist(Tab) -> + Fun = fun() -> mnesia:table_info(Tab, frag_dist) end, + mnesia:activity(sync_dirty, Fun, mnesia_frag). + +frag_names(Tab) -> + Fun = fun() -> mnesia:table_info(Tab, frag_names) end, + mnesia:activity(sync_dirty, Fun, mnesia_frag). + +frag_rec_dist(Tab) -> + Fun = fun() -> mnesia:table_info(Tab, frag_size) end, + [Size || {_, Size} <- mnesia:activity(sync_dirty, Fun, mnesia_frag)]. + +table_size(Tab) -> + Node = mnesia:table_info(Tab, where_to_read), + rpc:call(Node, mnesia, table_info, [Tab, size]). + +sort_res(List) when is_list(List) -> + lists:sort(List); +sort_res(Else) -> + Else. + +rev_res(List) when is_list(List) -> + lists:reverse(List); +rev_res(Else) -> + Else. diff --git a/lib/mnesia/test/mnesia_inconsistent_database_test.erl b/lib/mnesia/test/mnesia_inconsistent_database_test.erl new file mode 100644 index 0000000000..b19cd8e01b --- /dev/null +++ b/lib/mnesia/test/mnesia_inconsistent_database_test.erl @@ -0,0 +1,74 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1998-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_inconsistent_database_test). +-author('[email protected]'). + +-behaviour(gen_event). + +%%-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +%% gen_event callback interface +-export([init/1, handle_event/2, handle_call/2, handle_info/2, + terminate/2, code_change/3]). + + +init(_Args) -> + ?verbose("~p installed as event_module~n", [?MODULE]), + {ok, []}. + +handle_event(Msg, State) -> + handle_any_event(Msg, State). + +handle_info(Msg, State) -> + handle_any_event(Msg, State). + + +handle_call(Msg, State) -> + handle_any_event(Msg, State). + + +%% The main... + +handle_any_event({mnesia_system_event, Event}, State) + when element(1, Event) == inconsistent_database -> + ?error("Got event: ~p~n", [Event]), + {ok, State}; +handle_any_event(Msg, State) -> + ?verbose("Got event: ~p~n", [Msg]), + {ok, State}. + +%%----------------------------------------------------------------- +%% terminate(Reason, State) -> +%% AnyVal +%%----------------------------------------------------------------- + +terminate(_Reason, _State) -> + ok. + +%%---------------------------------------------------------------------- +%% Func: code_change/3 +%% Purpose: Upgrade process when its code is to be changed +%% Returns: {ok, NewState} +%%---------------------------------------------------------------------- +code_change(_OldVsn, _State, _Extra) -> + exit(not_supported). + diff --git a/lib/mnesia/test/mnesia_install_test.erl b/lib/mnesia/test/mnesia_install_test.erl new file mode 100644 index 0000000000..42a2a19f37 --- /dev/null +++ b/lib/mnesia/test/mnesia_install_test.erl @@ -0,0 +1,342 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_install_test). +-author('[email protected]'). + +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Run some small but demanding test cases in order to verify", + "that the basic functionality in Mnesia still works.", + "", + "Try some very simple things to begin with and increase the", + "difficulty stepwise. This test suite should be run before", + "all the others if you expect to find bugs.", + "", + "The function mnesia_install_test:silly() does not use the whole", + "infra structure of the test suite. Invoke it on a single node to", + "begin with. If that works, proceed with pong = net_adm:ping(SomeOtherNode)", + "and rerun silly() in order to perform some distributed tests."]; +all(suite) -> + [ + silly_durability, + silly_move, + silly_upgrade + %,stress + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Stepwise of more and more advanced features +silly() -> + Nodes = [node()] ++ nodes(), + mnesia_test_lib:kill_mnesia(Nodes), + Config = [{nodes, Nodes}], + mnesia_test_lib:eval_test_case(?MODULE, silly2, Config). + +silly2(Config) when is_list(Config) -> + [Node1 | _] = Nodes = ?acquire_nodes(3, Config), + mnesia_test_lib:kill_mnesia(Nodes), + ?ignore([mnesia:delete_schema([N]) || N <- Nodes]), + ?match(ok, mnesia:create_schema([Node1])), + ?match(ok, rpc:call(Node1, mnesia, start, [])), + ?match(ok, rpc:call(Node1, mnesia, wait_for_tables, + [[schema], infinity])), + Res = silly_durability(Config), + StressFun = fun(F) -> apply(?MODULE, F, [Config]) end, + R = + case length(Nodes) of + L when L > 1 -> + Node2 = lists:nth(2, Nodes), + AddDb = [schema, Node2, ram_copies], + ?match({atomic, ok}, + rpc:call(Node1, mnesia, add_table_copy, AddDb)), + Args = [[{extra_db_nodes, [Node1]}]], + ?match(ok, rpc:call(Node2, mnesia, start, Args)), + ChangeDb = [schema, Node2, disc_copies], + ?match({atomic, ok}, + rpc:call(Node1, mnesia, change_table_copy_type, + ChangeDb)), + ?match([], mnesia_test_lib:sync_tables([Node1, Node2], + [schema])), + MoveRes = silly_move(Config), + UpgradeRes = silly_upgrade(Config), + StressRes = [StressFun(F) || F <- stress(suite)], + ?verify_mnesia([Node2], []), + [Res, MoveRes, UpgradeRes] ++ StressRes; + _ -> + StressRes = [StressFun(F) || F <- stress(suite)], + ?warning("Too few nodes. Perform net_adm:ping(OtherNode) " + "and rerun!!!~n", []), + [Res | StressRes] + end, + ?verify_mnesia([Node1], []), + R. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +silly_durability(doc) -> + ["Simple test of durability"]; +silly_durability(suite) -> []; +silly_durability(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = silly, + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + + ?match({atomic, ok}, rpc:call(Node1, mnesia, + create_table, [Tab, [{Storage, [Node1]}]])), + + Read = fun() -> mnesia:read({Tab, a}) end, + Write = fun() -> mnesia:write({Tab, a, b}) end, + + ?match({atomic, []}, + rpc:call(Node1, mnesia, transaction, [Read])), + ?match({atomic, ok}, + rpc:call(Node1, mnesia, transaction, [Write])), + ?match({atomic, [{Tab, a, b}]}, + rpc:call(Node1, mnesia, transaction, [Read])), + + ?match(stopped, rpc:call(Node1, mnesia, stop, [])), + ?match(ok, rpc:call(Node1, mnesia, start, [])), + case mnesia_test_lib:diskless(Config) of + true -> + skip; + false -> + ?match(ok, rpc:call(Node1, mnesia, wait_for_tables, [[Tab], infinity])), + ?match({atomic, [{Tab, a, b}]}, + rpc:call(Node1, mnesia, transaction, [Read])) + end, + ?verify_mnesia([Node1], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +silly_move(doc) -> + ["Simple test of movement of a replica from one node to another"]; +silly_move(suite) -> []; +silly_move(Config) when is_list(Config) -> + [Node1, Node2] = ?acquire_nodes(2, Config), + Tab = silly_move, + ?match({atomic, ok}, + rpc:call(Node1, mnesia, + create_table, [Tab, [{ram_copies, [Node2]}]])), + ?match([], mnesia_test_lib:sync_tables([Node1, Node2], [Tab])), + + Read = fun() -> mnesia:read({Tab, a}) end, + Write = fun() -> mnesia:write({Tab, a, b}) end, + + ?match({atomic, []}, + rpc:call(Node1, mnesia, transaction, [Read])), + ?match({atomic, ok}, + rpc:call(Node1, mnesia, transaction, [Write])), + ?match({atomic, [{Tab, a, b}]}, + rpc:call(Node1, mnesia, transaction, [Read])), + + case mnesia_test_lib:diskless(Config) of + true -> skip; + false -> + ?match({atomic, ok}, + rpc:call(Node1, mnesia, + change_table_copy_type, [Tab, Node2, disc_only_copies])), + ?match([], mnesia_test_lib:sync_tables([Node1, Node2], [Tab])) + end, + ?match({atomic, [{Tab, a, b}]}, rpc:call(Node1, mnesia, transaction, [Read])), + + ?match({atomic, ok}, + rpc:call(Node1, mnesia, + move_table_copy, [Tab, Node2, Node1])), + ?match([], mnesia_test_lib:sync_tables([Node1, Node2], [Tab])), + ?match({atomic, [{Tab, a, b}]}, + rpc:call(Node1, mnesia, transaction, [Read])), + ?verify_mnesia([Node1], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +silly_upgrade(doc) -> + ["Simple test of a schema upgrade and restore from backup"]; +silly_upgrade(suite) -> []; +silly_upgrade(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Name = silly_upgrade, + Tab1 = silly_upgrade1, + Tab2 = silly_upgrade2, + Bup = "silly_upgrade.BUP", + Bup2 = "silly_upgrade_part.BUP", + ?match({atomic, ok}, mnesia:create_table(Tab1, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(Tab2, [{disc_only_copies, Nodes}])), + + CpState = add_some_records(Tab1, Tab2, []), + ?match(match, verify_state(Tab1, Tab2, CpState)), + file:delete(Bup), + ?match(ok, mnesia:backup(Bup)), + Args = [{name, Name}, {ram_overrides_dump, true}, + {min, [Tab1, schema]}, {max, [Tab2]}], + ?match({ok, Name, _}, mnesia:activate_checkpoint(Args)), + + IgnoreState = add_more_records(Tab1, Tab2, CpState), + ?match(match, verify_state(Tab1, Tab2, IgnoreState)), + ?match({mismatch, _, _}, verify_state(Tab1, Tab2, CpState)), + ?match({atomic, ok}, mnesia:del_table_copy(Tab2, Node1)), + file:delete(Bup2), + ?match(ok, mnesia:backup_checkpoint(Name, Bup2)), + + UpgradeState = transform_some_records(Tab1, Tab2, IgnoreState), + ?match({mismatch, _, _}, verify_state(Tab1, Tab2, CpState)), + ?match({mismatch, _, _}, verify_state(Tab1, Tab2, IgnoreState)), + ?match(match, verify_state(Tab1, Tab2, UpgradeState)), + + ?match(ok, mnesia:deactivate_checkpoint(Name)), + ?match(match, verify_state(Tab1, Tab2, UpgradeState)), + + ?match(ok, mnesia:install_fallback(Bup2)), + file:delete(Bup2), + %% Will generate intentional crash, fatal error + ?match([], mnesia_test_lib:stop_mnesia([Node2])), + wait_till_dead([Node1, Node2]), + ?match([], mnesia_test_lib:start_mnesia([Node1, Node2], [Tab1, Tab2])), + ?match(match, verify_state(Tab1, Tab2, CpState)), + + ?match(ok, mnesia:install_fallback(Bup)), + file:delete(Bup), + %% Will generate intentional crash, fatal error + ?match([], mnesia_test_lib:stop_mnesia([Node1, Node2])), + wait_till_dead([Node1, Node2]), + ?match([], mnesia_test_lib:start_mnesia([Node1, Node2], [Tab1, Tab2])), + CpState2 = [X || X <- CpState, element(1, X) /= Tab1], + ?match(match, verify_state(Tab1, Tab2, CpState2)), + ?verify_mnesia(Nodes, []). + +wait_till_dead([]) -> ok; +wait_till_dead([N|Ns]) -> + Apps = rpc:call(N, application, which_applications, []), + case lists:keymember(mnesia, 1, Apps) of + true -> + timer:sleep(10), + wait_till_dead([N|Ns]); + false -> + wait_till_dead(Ns) + end. + +add_some_records(Tab1, Tab2, Old) -> + Recs1 = [{Tab1, I, I} || I <- lists:seq(1, 30)], + Recs2 = [{Tab2, I, I} || I <- lists:seq(20, 40)], + lists:foreach(fun(R) -> mnesia:dirty_write(R) end, Recs1), + Fun = fun(R) -> mnesia:write(R) end, + Trans = fun() -> lists:foreach(Fun, Recs2) end, + ?match({atomic, _}, mnesia:transaction(Trans)), + lists:sort(Old ++ Recs1 ++ Recs2). + +add_more_records(Tab1, Tab2, Old) -> + Change1 = [{T, K, V+100} || {T, K, V} <- Old, K==23], + Change2 = [{T, K, V+100} || {T, K, V} <- Old, K==24], + Del = [{T, K} || {T, K, _V} <- Old, K>=25], + New = [{Tab1, 50, 50}, {Tab2, 50, 50}], + lists:foreach(fun(R) -> mnesia:dirty_write(R) end, Change1), + lists:foreach(fun(R) -> mnesia:dirty_delete(R) end, Del), + Fun = fun(R) -> mnesia:write(R) end, + Trans = fun() -> lists:foreach(Fun, Change2 ++ New) end, + ?match({atomic, ok}, mnesia:transaction(Trans)), + Recs = [{T, K, V} || {T, K, V} <- Old, K<23] ++ Change1 ++ Change2 ++ New, + lists:sort(Recs). + + +verify_state(Tab1, Tab2, Exp) -> + Fun = fun() -> + Act1 = [mnesia:read({Tab1, K}) || K <- mnesia:all_keys(Tab1)], + Act2 = [mnesia:read({Tab2, K}) || K <- mnesia:all_keys(Tab2)], + Act = lists:append(Act1) ++ lists:append(Act2), + {ok, Act -- Exp, Exp -- Act} + end, + case mnesia:transaction(Fun) of + {atomic, {ok, [], []}} -> match; + {atomic, {ok, More, Less}} -> {mismatch, More, Less}; + {aborted, Reason} -> {error, Reason} + end. + +transform_some_records(Tab1, _Tab2, Old) -> + Fun = fun(Rec) -> + list_to_tuple(tuple_to_list(Rec) ++ [4711]) + end, + ?match({atomic, ok}, + mnesia:transform_table(Tab1, Fun, [key, val, extra])), + Filter = fun(Rec) when element(1, Rec) == Tab1 -> {true, Fun(Rec)}; + (_) -> true + end, + lists:sort(lists:zf(Filter, Old)). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +stress(doc) -> + ["Stress the system a little"]; +stress(suite) -> + [ + conflict, + dist + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +dist(doc) -> + ["Avoid lock conflicts in order to maximize thruput", + "Ten drivers per node, tables replicated to all nodes, lots of branches"]; +dist(suite) -> []; +dist(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, 10 * 60000}]), + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + ?match({ok, _}, mnesia_tpcb:start(dist_args(Nodes, Storage))). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +conflict(doc) -> + ["Provoke a lot of lock conflicts.", + "Ten drivers per node, tables replicated to all nodes, single branch"]; +conflict(suite) -> []; +conflict(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, 10 * 60000}]), + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + ?match({ok, _}, mnesia_tpcb:start(conflict_args(Nodes, Storage))). + +conflict_args(Nodes, ReplicaType) -> + [{db_nodes, Nodes}, + {driver_nodes, Nodes}, + {replica_nodes, Nodes}, + {n_drivers_per_node, 10}, + {n_branches, 1}, + {n_accounts_per_branch, 10}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(5)}, + {report_interval, timer:seconds(10)}, + {use_running_mnesia, true}, + {reuse_history_id, true}]. + +dist_args(Nodes, ReplicaType) -> + [{db_nodes, Nodes}, + {driver_nodes, Nodes}, + {replica_nodes, Nodes}, + {n_drivers_per_node, 10}, + {n_branches, length(Nodes) * 100}, + {n_accounts_per_branch, 10}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(5)}, + {report_interval, timer:seconds(10)}, + {use_running_mnesia, true}, + {reuse_history_id, true}]. + diff --git a/lib/mnesia/test/mnesia_isolation_test.erl b/lib/mnesia/test/mnesia_isolation_test.erl new file mode 100644 index 0000000000..4fc6e8fe58 --- /dev/null +++ b/lib/mnesia/test/mnesia_isolation_test.erl @@ -0,0 +1,2419 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_isolation_test). +-author('[email protected]'). + +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Verify the isolation property.", + "Operations of concurrent transactions must yield results which", + "are indistinguishable from the results which would be obtained by", + "forcing each transaction to be serially executed to completion in", + "some order. This means that repeated reads of the same records", + "within any committed transaction must have returned identical", + "data when run concurrently with any mix of arbitary transactions.", + "Updates in one transaction must not be visible in any other", + "transaction before the transaction has been committed."]; +all(suite) -> + [ + locking, + visibility + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +locking(doc) -> + ["Verify locking semantics for various configurations", + " NoLock = lock_funs(no_lock, any_granularity)", + " SharedLock = lock_funs(shared_lock, any_granularity)", + " ExclusiveLock = lock_funs(exclusive_lock, any_granularity)", + " AnyLock = lock_funs(any_lock, any_granularity)"]; +locking(suite) -> + [no_conflict, + simple_queue_conflict, + advanced_queue_conflict, + simple_deadlock_conflict, + advanced_deadlock_conflict, + lock_burst, + sticky_locks, + unbound_locking, + admin_conflict, +%% removed_resources, + nasty + ]. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +no_conflict(suite) -> []; +no_conflict(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = no_conflict, + create_conflict_table(Tab, [Node1]), + Fun = fun(OtherOid, Lock1, Lock2) -> + %% Start two transactions + {success, [B, A]} = ?start_activities([Node1, Node1]), + ?start_transactions([B, A]), + + A ! fun() -> Lock1(one_oid(Tab)), ok end, + ?match_receive({A, ok}), + B ! fun() -> Lock2(OtherOid), ok end, + ?match_receive({B, ok}), + A ! fun() -> mnesia:abort(ok) end, + ?match_receive({A, {aborted, ok}}), + B ! fun() -> mnesia:abort(ok) end, + ?match_receive({B, {aborted, ok}}) + end, + NoLocks = lock_funs(no_lock, any_granularity), + SharedLocks = lock_funs(shared_lock, any_granularity), + AnyLocks = lock_funs(any_lock, any_granularity), + OneOneFun = fun(Lock1, Lock2) -> Fun(one_oid(Tab), Lock1, Lock2) end, + fun_loop(OneOneFun, NoLocks, AnyLocks), + fun_loop(OneOneFun, AnyLocks, NoLocks), + fun_loop(OneOneFun, SharedLocks, SharedLocks), + + %% Lock different objects + OneOtherFun = fun(Lock1, Lock2) -> Fun(other_oid(Tab), Lock1, Lock2) end, + OneSharedLocks = lock_funs(shared_lock, one), + OneExclusiveLocks = lock_funs(exclusive_lock, one), + fun_loop(OneOtherFun, OneSharedLocks, OneExclusiveLocks), + fun_loop(OneOtherFun, OneExclusiveLocks, OneSharedLocks), + fun_loop(OneOtherFun, OneExclusiveLocks, OneExclusiveLocks), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +simple_queue_conflict(suite) -> []; +simple_queue_conflict(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = simple_queue_conflict, + create_conflict_table(Tab, [Node1]), + Fun = fun(OneLock, OtherLock) -> + %% Start two transactions + {success, [B, A]} = ?start_activities([Node1, Node1]), + ?start_transactions([B, A]), + + A ! fun() -> OneLock(one_oid(Tab)), ok end, + ?match_receive({A, ok}), + B ! fun() -> OtherLock(one_oid(Tab)), ok end, + wait_for_lock(B, [Node1], 20), % Max 10 sec + A ! end_trans, + ?match_multi_receive([{A, {atomic, end_trans}}, {B, ok}]), + B ! fun() -> mnesia:abort(ok) end, + ?match_receive({B, {aborted, ok}}) + end, + OneSharedLocks = lock_funs(shared_lock, one), + AllSharedLocks = lock_funs(shared_lock, all), + OneExclusiveLocks = lock_funs(exclusive_lock, one), + AllExclusiveLocks = lock_funs(exclusive_lock, all), + fun_loop(Fun, OneExclusiveLocks, OneExclusiveLocks), + fun_loop(Fun, AllExclusiveLocks, AllExclusiveLocks), + fun_loop(Fun, OneExclusiveLocks, AllExclusiveLocks), + fun_loop(Fun, AllExclusiveLocks, OneExclusiveLocks), + fun_loop(Fun, OneSharedLocks, AllExclusiveLocks), + fun_loop(Fun, AllSharedLocks, OneExclusiveLocks), + ok. + +wait_for_lock(Pid, _Nodes, 0) -> + Queue = mnesia:system_info(lock_queue), + ?error("Timeout while waiting for lock on Pid ~p in queue ~p~n", [Pid, Queue]); +wait_for_lock(Pid, Nodes, N) -> + rpc:multicall(Nodes, sys, get_status, [mnesia_locker]), + List = [rpc:call(Node, mnesia, system_info, [lock_queue]) || Node <- Nodes], + Q = lists:append(List), + check_q(Pid, Q, Nodes, N). + +check_q(Pid, [{_Oid, _Op, Pid, _Tid, _WFT} | _Tail], _N, _Count) -> ok; +check_q(Pid, [_ | Tail], N, Count) -> check_q(Pid, Tail, N, Count); +check_q(Pid, [], N, Count) -> + timer:sleep(500), + wait_for_lock(Pid, N, Count - 1). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +advanced_queue_conflict(suite) -> []; +advanced_queue_conflict(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = advanced_queue_conflict, + create_conflict_table(Tab, [Node1]), + OneRec = {Tab, 3, 3}, + OneOid = {Tab, 3}, + OtherRec = {Tab, 4, 4}, + OtherOid = {Tab, 4}, + + %% Start four transactions + {success, [D, C, B, A]} = ?start_activities(lists:duplicate(4, Node1)), + ?start_transactions([D, C, B, A]), + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + + %% Acquire some locks + A ! fun() -> mnesia:write(OneRec) end, + ?match_receive({A, ok}), + A ! fun() -> mnesia:read(OneOid) end, + ?match_receive({A, [OneRec]}), + + B ! fun() -> mnesia:write(OtherRec) end, + ?match_receive({B, ok}), + B ! fun() -> mnesia:read(OneOid) end, + ?match_receive(timeout), + + C ! fun() -> mnesia:read(OtherOid) end, + ?match_receive(timeout), + D ! fun() -> mnesia:wread(OtherOid) end, + ?match_receive(timeout), + + %% and release them in a certain order + A ! end_trans, + ?match_multi_receive([{A, {atomic, end_trans}}, {B, [OneRec]}]), + B ! end_trans, + ?match_multi_receive([{B, {atomic, end_trans}}, {C, [OtherRec]}]), + C ! end_trans, + ?match_multi_receive([{C, {atomic, end_trans}}, {D, [OtherRec]}]), + D ! end_trans, + ?match_receive({D, {atomic, end_trans}}), + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +simple_deadlock_conflict(suite) -> []; +simple_deadlock_conflict(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = simple_deadlock_conflict, + create_conflict_table(Tab, [Node1]), + Rec = {Tab, 4, 4}, + Oid = {Tab, 4}, + + %% Start two transactions + {success, [B, A]} = ?start_activities(lists:duplicate(2, Node1)), + mnesia_test_lib:start_transactions([B, A], 0), % A is newest + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + + B ! fun() -> mnesia:write(Rec) end, + ?match_receive({B, ok}), + A ! fun() -> mnesia:read(Oid) end, + ?match_receive({A, {aborted, nomore}}), + B ! end_trans, + ?match_receive({B, {atomic, end_trans}}), + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +advanced_deadlock_conflict(suite) -> []; +advanced_deadlock_conflict(Config) when is_list(Config) -> + [Node1, Node2] = ?acquire_nodes(2, Config), + Tab = advanced_deadlock_conflict, + create_conflict_table(Tab, [Node2]), + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + Rec = {Tab, 4, 4}, + Oid = {Tab, 4}, + + %% Start two transactions + {success, [B, A]} = ?start_activities([Node1, Node2]), + mnesia_test_lib:start_sync_transactions([B, A], 0), % A is newest + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + + B ! fun() -> mnesia:write(Rec) end, + ?match_receive({B, ok}), + A ! fun() -> mnesia:read(Oid) end, + ?match_receive({A, {aborted, nomore}}), + B ! end_trans, + ?match_receive({B, {atomic, end_trans}}), + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +one_oid(Tab) -> {Tab, 1}. +other_oid(Tab) -> {Tab, 2}. + +create_conflict_table(Tab, Nodes) -> + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, Nodes}, + {attributes, [key, val]}, + {index, [val]} + ])), + ?match([], mnesia_test_lib:sync_tables(Nodes, [Tab])), + init_conflict_table(Tab). + +init_conflict_table(Tab) -> + Recs = mnesia:dirty_match_object({Tab, '_', '_'}), + lists:foreach(fun(R) -> mnesia:dirty_delete_object(R) end, Recs), + Keys = [one_oid(Tab), other_oid(Tab)], + [mnesia:dirty_write({T, K, K}) || {T, K} <- Keys]. + +%% Apply Fun for each X and Y +fun_loop(Fun, Xs, Ys) -> + lists:foreach(fun(X) -> lists:foreach(fun(Y) -> do_fun(Fun, X, Y) end, Ys) end, Xs). + +do_fun(Fun, X, Y) -> + Pid = spawn_link(?MODULE, do_fun, [self(), Fun, X, Y]), + receive + {done_fun, Pid} -> done_fun + end. + +do_fun(Monitor, Fun, X, Y) -> + ?log("{do_fun ~p~n", [[Fun, X, Y]]), + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + Fun(X, Y), + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + unlink(Monitor), + Monitor ! {done_fun, self()}, + exit(done_fun). + +%% Returns a list of fun's +lock_funs(no_lock, one) -> + [ + fun(Oid) -> mnesia:dirty_read(Oid) end, + fun({Tab, Key}) -> mnesia:dirty_write({Tab, Key, Key}) end, + fun({Tab, Key}) -> mnesia:dirty_write({Tab, Key, Key}), + mnesia:dirty_update_counter({Tab, Key}, 0) end, + fun(Oid) -> mnesia:dirty_delete(Oid) end, + fun({Tab, Key}) -> mnesia:dirty_delete_object({Tab, Key, Key}) end, + fun({Tab, Key}) -> mnesia:dirty_match_object({Tab, Key, Key}) end, + fun({Tab, Key}) -> mnesia:dirty_index_match_object({Tab, Key, Key}, val) end, + fun({Tab, Key}) -> mnesia:dirty_index_read(Tab, Key, val) end, + fun({Tab, Key}) -> mnesia:dirty_index_match_object({Tab, '_', Key}, val) end + ]; +lock_funs(no_lock, all) -> + [ + fun({Tab, _}) -> mnesia:dirty_match_object({Tab, '_', '_'}) end, + fun({Tab, _}) -> slot_iter(Tab) end, + fun({Tab, _}) -> key_iter(Tab) end + ]; +lock_funs(shared_lock, one) -> + + [ + fun(Oid) -> mnesia:read(Oid) end, + fun({Tab, Key}) -> + init_conflict_table(Tab), + mnesia:dirty_delete(other_oid(Tab)), + mnesia:match_object({Tab, Key, Key}) end + ]; +lock_funs(shared_lock, all) -> + [ + fun({Tab, _}) -> mnesia:read_lock_table(Tab) end, + fun({Tab, Key}) -> mnesia:match_object({Tab, '_', Key}) end, + fun({Tab, _}) -> mnesia:match_object({Tab, '_', '_'}) end, + fun({Tab, _}) -> mnesia:all_keys(Tab) end, + fun({Tab, Key}) -> mnesia:index_match_object({Tab, '_', Key}, val) end, + fun({Tab, Key}) -> mnesia:index_read(Tab, Key, val) end + ]; +lock_funs(exclusive_lock, one) -> + [ + fun(Oid) -> mnesia:wread(Oid) end, + fun({Tab, Key}) -> mnesia:write({Tab, Key, Key}) end, + fun(Oid) -> mnesia:delete(Oid) end, + fun({Tab, Key}) -> mnesia:delete_object({Tab, Key, Key}) end, + fun({Tab, Key}) -> mnesia:s_write({Tab, Key, Key}) end, + fun(Oid) -> mnesia:s_delete(Oid) end, + fun({Tab, Key}) -> mnesia:s_delete_object({Tab, Key, Key}) end + ]; +lock_funs(exclusive_lock, all) -> + [ + fun({Tab, _}) -> mnesia:write_lock_table(Tab) end + ]; +lock_funs(Compatibility, any_granularity) -> + lists:append([lock_funs(Compatibility, Granularity) || + Granularity <- [one, all]]); +lock_funs(any_lock, Granularity) -> + lists:append([lock_funs(Compatibility, Granularity) || + Compatibility <- [no_lock, shared_lock, exclusive_lock]]). + +slot_iter(Tab) -> + slot_iter(Tab, mnesia:dirty_slot(Tab, 0), 1). +slot_iter(_Tab, '$end_of_table', _) -> + []; +slot_iter(Tab, Recs, Slot) -> + Recs ++ slot_iter(Tab, mnesia:dirty_slot(Tab, Slot), Slot+1). + +key_iter(Tab) -> + key_iter(Tab, mnesia:dirty_first(Tab)). +key_iter(_Tab, '$end_of_table') -> + []; +key_iter(Tab, Key) -> + [Key | key_iter(Tab, mnesia:dirty_next(Tab, Key))]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +lock_burst(suite) -> []; +lock_burst(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = burst, + ?match({atomic, ok}, mnesia:create_table(Tab, + [{attributes, [a, b]}, + {ram_copies, [Node1]}])), + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ?match(ok, burst_em(Tab, 1000)), + ?match([{burst,1,1000}], mnesia:dirty_read(Tab,1)), + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +burst_em(Tab, N) -> + spawn_link(?MODULE, burst_counter, [self(), Tab, N]), + receive + burst_counter_done -> ok + end. + +burst_counter(Monitor, Tab, N) when N > 0 -> + ?match(ok, burst_gen(Tab, N, self())), + Monitor ! burst_receiver(N). + +burst_receiver(0) -> + burst_counter_done; +burst_receiver(N) -> + receive + burst_incr_done -> + burst_receiver(N-1) + end. + +burst_gen(_, 0, _) -> + ok; +burst_gen(Tab, N, Father) when is_integer(N), N > 0 -> + spawn_link(?MODULE, burst_incr, [Tab, Father]), + burst_gen(Tab, N-1, Father). + +burst_incr(Tab, Father) -> + Fun = fun() -> + Val = + case mnesia:read({Tab, 1}) of + [{Tab, 1, V}] -> V; + [] -> 0 + end, + mnesia:write({Tab, 1, Val+1}) + end, + ?match({atomic, ok}, mnesia:transaction(Fun)), + Father ! burst_incr_done. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +sticky_locks(doc) -> + ["Simple Tests of sticky locks"]; + +sticky_locks(suite) -> + [ + basic_sticky_functionality + %% Needs to be expandand a little bit further + ]. + +basic_sticky_functionality(suite) -> []; +basic_sticky_functionality(Config) when is_list(Config) -> + [N1, N2] = Nodes = ?acquire_nodes(2, Config), + Tab = basic_table, + Storage = mnesia_test_lib:storage_type(disc_copies, Config), + ?match({atomic, ok}, mnesia:create_table(Tab, [{Storage, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(sync, [{ram_copies, Nodes}])), + Trans1 = fun() -> + ?match(ok, mnesia:s_write({Tab, 1, 2})), + ?match([{Tab, 1, 2}], mnesia:read({Tab, 1})), + ?match(timeout, receive M -> M after 500 -> timeout end), + ?match(ok, mnesia:s_write({Tab, 2, 2})), + ?match(ok, mnesia:write({Tab, 42, 4711})) + end, + Trans2 = fun() -> + ?match([{Tab, 1, 2}], mnesia:read({Tab, 1})), + ?match(timeout, receive M -> M after 500 -> timeout end), + ?match(ok, mnesia:write({Tab, 1, 4711})), + ?match(ok, mnesia:s_write({Tab, 2, 4})), + ?match(ok, mnesia:delete({Tab, 42})) + end, + rpc:call(N1, mnesia, transaction, [Trans1]), + ?match([{Tab,N1}], rpc:call(N1, ?MODULE, get_sticky, [])), + ?match([{Tab,N1}], rpc:call(N2, ?MODULE, get_sticky, [])), + + rpc:call(N2, mnesia, transaction, [Trans2]), + ?match([], rpc:call(N1, ?MODULE, get_sticky, [])), + ?match([], rpc:call(N2, ?MODULE, get_sticky, [])), + + Slock = fun() -> mnesia:read({sync,sync}),get_sticky() end, + ?match({atomic, [{Tab,1, 4711}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)), + ?match({atomic, [{Tab,2, 4}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 2}) end)), + ?match({atomic, [{Tab,N1}]}, rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:s_write({Tab, 1, 3}),Slock() end])), + ?match([{Tab,N1}], rpc:call(N2, ?MODULE, get_sticky, [])), + + ?match({atomic,[]}, rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:s_write({Tab, 1, 4}),Slock() end])), + + ?match([], rpc:call(N1, ?MODULE, get_sticky, [])), + ?match([], rpc:call(N2, ?MODULE, get_sticky, [])), + + ?match({atomic,[{Tab,N2}]}, rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:s_write({Tab, 1, 4}),Slock() end])), + + ?match({atomic,[]}, rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:s_write({Tab, 1, 5}),Slock() end])), + ?match({atomic,[{Tab,N1}]}, rpc:call(N1, mnesia, transaction, + [fun() -> mnesia:s_write({Tab, 1, 5}),Slock() end])), + ?match({atomic,[]}, rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:s_write({Tab, 1, 6}),Slock() end])), + ?match({atomic,[{Tab,N2}]}, rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:s_write({Tab, 1, 7}),Slock() end])), + + ?match([{Tab,N2}], get_sticky()), + ?match({atomic, [{Tab,1, 7}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)), + ?match([{Tab,N2}], get_sticky()), + ?match({atomic, [{Tab,2, 4}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 2}) end)), + ?match([{Tab,N2}], get_sticky()), + ?match({atomic,[{Tab,N2}]}, rpc:call(N2, mnesia, transaction, + [fun() -> mnesia:s_write({Tab, 1, 6}),Slock() end])), + ?match([{Tab,N2}], get_sticky()), + ?match({atomic, [{Tab,1, 6}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)), + ?match([{Tab,N2}], get_sticky()), + ?match({atomic, [{Tab,2, 4}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 2}) end)), + ?match([{Tab,N2}], get_sticky()), + ?verify_mnesia(Nodes, []). + +get_sticky() -> + mnesia_locker ! {get_table, self(), mnesia_sticky_locks}, + receive {mnesia_sticky_locks, Locks} -> Locks end. + +get_held() -> + mnesia_locker ! {get_table, self(), mnesia_sticky_locks}, + receive {mnesia_sticky_locks, Locks} -> Locks end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +unbound_locking(suite) -> + [unbound1, unbound2]; + +unbound_locking(doc) -> + ["Check that mnesia handles unbound key variables, GPRS bug." + "Ticket id: OTP-3342"]. + +unbound1(suite) -> []; +unbound1(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + + ?match({atomic, ok}, mnesia:create_table(ul, [])), + + Tester = self(), + Write = fun() -> + mnesia:write({ul, {key, {17,42}}, val}), + ?log("~p Got write lock waiting...~n", [self()]), + Tester ! continue, + receive + continue -> + ok + end, + ?log("..continuing~n", []), + ok + end, + + {success, [A]} = ?start_activities([Node1]), + ?start_transactions([A]), + A ! Write, + + receive continue -> ok end, + + Match = fun() -> + case catch mnesia:match_object({ul, {key, {'_', '$0'}}, '_'}) of + {'EXIT', What} -> %% Cyclic first time + ?log("Cyclic Restarting~n", []), + A ! continue, + A ! end_trans, + exit(What); + Res -> + ?log("Got match log ~p...~n", [Res]), + Res + end + end, + ?match({atomic, [{ul,{key,{17,42}},val}]}, mnesia:transaction(Match)), + + ?match_receive({A, ok}), + ?match_receive({A, {atomic, end_trans}}), + ok. + +unbound2(suite) -> []; +unbound2(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + + ?match({atomic, ok}, mnesia:create_table(ul, [])), + + {success, [B, A]} = ?start_activities([Node1, Node1]), + + Me = self(), + + Write = fun() -> + mnesia:write({ul, {key, {17,42}}, val}), + ?log("~p Got write lock waiting... Tid ~p ~n", + [self(), get(mnesia_activity_state)]), + Me ! ok_lock, + receive + continue -> + ok + end, + ?log("..continuing~n", []), + ok + end, + + Match = fun() -> + receive + continueB -> + ?log("~p, moving on TID ~p~n", + [self(), get(mnesia_activity_state)]), + Me ! {self(), continuing} + end, + case catch mnesia:match_object({ul, {key, {'_', '$0'}}, + '_'}) of + {'EXIT', What} -> %% Cyclic first time + ?log("Cyclic Restarting ~p ~n", [What]), + {should_not_happen,What}; + Res -> + ?log("Got match log ~p...~n", [Res]), + Res + end + end, + + B ! fun() -> mnesia:transaction(Match) end, + timer:sleep(100), %% Let B be started first.. + A ! fun() -> mnesia:transaction(Write) end, + + receive ok_lock -> ok end, + + B ! continueB, + ?match_receive({B, continuing}), + + %% B should now be in lock queue. + A ! continue, + ?match_receive({A, {atomic, ok}}), + ?match_receive({B, {atomic, [{ul,{key,{17,42}},val}]}}), + ok. + +receiver() -> + receive + {_Pid, begin_trans} -> + receiver(); + Else -> + Else + after + 10000 -> + timeout + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +admin_conflict(doc) -> + ["Provoke lock conflicts with schema transactions and checkpoints."]; +admin_conflict(suite) -> + [ + create_table, + delete_table, + move_table_copy, + add_table_index, + del_table_index, + transform_table, + snmp_open_table, + snmp_close_table, + change_table_copy_type, + change_table_access, + add_table_copy, + del_table_copy, + dump_tables, + extra_admin_tests + ]. + +create_table(suite) -> []; +create_table(Config) when is_list(Config) -> + [ThisNode, Node2] = ?acquire_nodes(2, Config), + Tab = c_t_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end, + ?match_receive({A, ok}), %% A is executed + + DiskMaybe = mnesia_test_lib:storage_type(disc_copies, Config), + + Pid = spawn_link(?MODULE, op, [self(), mnesia, create_table, + [test_tab1, [{DiskMaybe, [ThisNode]}]]]), + ?match_multi_receive([{Pid, {atomic, ok}}, + {'EXIT', Pid, normal}]), %% No Locks! op should be exec. + + Pid2 = spawn_link(?MODULE, op, [self(), mnesia, create_table, + [test_tab2, [{ram_copies, [Node2]}]]]), + + ?match_multi_receive([{Pid2, {atomic, ok}}, + {'EXIT', Pid2, normal}]), %% No Locks! op should be exec. + + A ! end_trans, + ?match_receive({A,{atomic,end_trans}}), + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +delete_table(suite) -> []; +delete_table(Config) when is_list(Config) -> + [ThisNode, Node2] = ?acquire_nodes(2, Config), + Tab = d_t_tab, + Def = [{ram_copies, [ThisNode, Node2]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:read({Tab, 1}) end, + ?match_receive({A, [{Tab, 1, 1, 0}]}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, delete_table, + [Tab]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +move_table_copy(suite) -> []; +move_table_copy(Config) when is_list(Config) -> + [ThisNode, Node2] = ?acquire_nodes(2, Config), + Tab = m_t_c_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:write({Tab, 1, 2, 3}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, move_table_copy, + [Tab, ThisNode, Node2]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + timer:sleep(500), %% Don't know how to sync this !!! + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + sys:get_status(whereis(mnesia_tm)), % Explicit sync, release locks is async + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +add_table_index(suite) -> []; +add_table_index(Config) when is_list(Config) -> + [ThisNode, _Node2] = ?acquire_nodes(2, Config ++ [{tc_timeout, 60000}]), + Tab = a_t_i_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, + add_table_index, [Tab, attr1]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +del_table_index(suite) -> []; +del_table_index(Config) when is_list(Config) -> + [ThisNode, _Node2] = ?acquire_nodes(2, Config), + Tab = d_t_i_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + ?match({atomic, ok}, mnesia:add_table_index(Tab, attr1)), + + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:write({Tab, 51, 51, attr2}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, del_table_index, + [Tab, attr1]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + %% Locks released! op should be exec. + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +transform_table(suite) -> []; +transform_table(Config) when is_list(Config) -> + [ThisNode, Node2] = ?acquire_nodes(2, Config), + Tab = t_t_tab, + Def = [{ram_copies, [ThisNode, Node2]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:read({Tab, 1}) end, + ?match_receive({A, [{Tab, 1, 1, 0}]}), %% A is executed + + Transform = fun({Table, Key, Attr1, Attr2}) -> % Need todo a transform + {Table, Key, {Attr1, Attr2}} end, + Pid = spawn_link(?MODULE, op, [self(), mnesia, transform_table, + [Tab, Transform, [key, attr1]]]), + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +snmp_open_table(suite) -> []; +snmp_open_table(Config) when is_list(Config) -> + [ThisNode, _Node2] = ?acquire_nodes(2, Config), + Tab = s_o_t_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:write({Tab, 1, 1, 100}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, snmp_open_table, + [Tab, [{key, integer}]]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + %% Locks released! op should be exec. Can take a while (thats the timeout) + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +snmp_close_table(suite) -> []; +snmp_close_table(Config) when is_list(Config) -> + [ThisNode, _Node2] = ?acquire_nodes(2, Config), + Tab = s_c_t_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab, [{key, integer}])), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:write({Tab, 1, 1, 100}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, snmp_close_table, [Tab]]), + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + %% Locks released! op should be exec. Can take a while (thats the timeout) + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +change_table_copy_type(suite) -> []; +change_table_copy_type(Config) when is_list(Config) -> + [ThisNode, _Node2] = ?acquire_nodes(2, Config), + Tab = c_t_c_t_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, change_table_copy_type, + [Tab, ThisNode, disc_copies]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +change_table_access(suite) -> []; +change_table_access(Config) when is_list(Config) -> + [ThisNode, _Node2] = ?acquire_nodes(2, Config), + Tab = c_t_a_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, change_table_access_mode, + [Tab, read_only]]), + + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +add_table_copy(suite) -> []; +add_table_copy(Config) when is_list(Config) -> + [ThisNode, Node2] = ?acquire_nodes(2, Config), + Tab = a_t_c_tab, + Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + + A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, add_table_copy, + [Tab, Node2, ram_copies]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +del_table_copy(suite) -> []; +del_table_copy(Config) when is_list(Config) -> + [ThisNode, Node2] = ?acquire_nodes(2, Config), + Tab = d_t_c_tab, + Def = [{ram_copies, [ThisNode, Node2]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + A ! fun() -> mnesia:write({Tab, 1, 2, 5}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, del_table_copy, + [Tab, ThisNode]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A, {atomic,end_trans}}), + + ?match_receive({Pid, {atomic, ok}}), + ?match_receive({'EXIT', Pid, normal}), + + timer:sleep(500), %% Don't know how to sync this !!! + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + sys:get_status(whereis(mnesia_tm)), % Explicit sync, release locks is async + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +dump_tables(suite) -> []; +dump_tables(Config) when is_list(Config) -> + [ThisNode, Node2] = ?acquire_nodes(2, Config), + Tab = dump_t_tab, + Def = [{ram_copies, [ThisNode, Node2]}, {attributes, [key, attr1, attr2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 50), + {success, [A]} = ?start_activities([ThisNode]), + mnesia_test_lib:start_sync_transactions([A], 0), + A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end, + ?match_receive({A, ok}), %% A is executed + + Pid = spawn_link(?MODULE, op, [self(), mnesia, dump_tables, + [[Tab]]]), + + ?match_receive(timeout), %% op waits for locks occupied by A + + A ! end_trans, %% Kill A, locks should be released + ?match_receive({A,{atomic,end_trans}}), + + receive + Msg -> ?match({Pid, {atomic, ok}}, Msg) + after + timer:seconds(20) -> ?error("Operation timed out", []) + end, + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + ok. + +op(Father, Mod, Fun, Args) -> + Res = apply(Mod, Fun, Args), + Father ! {self(), Res}. + +insert(_Tab, 0) -> ok; +insert(Tab, N) when N > 0 -> + ok = mnesia:sync_dirty(fun() -> mnesia:write({Tab, N, N, 0}) end), + insert(Tab, N-1). + +extra_admin_tests(suite) -> + [del_table_copy_1, + del_table_copy_2, + del_table_copy_3, + add_table_copy_1, + add_table_copy_2, + add_table_copy_3, + add_table_copy_4, + move_table_copy_1, + move_table_copy_2, + move_table_copy_3, + move_table_copy_4]. + +update_own(Tab, Key, Acc) -> + Update = + fun() -> + Res = mnesia:read({Tab, Key}), + case Res of + [{Tab, Key, Extra, Acc}] -> + mnesia:write({Tab,Key,Extra, Acc+1}); + Val -> + {read, Val, {acc, Acc}} + end + end, + receive + {Pid, quit} -> Pid ! {self(), Acc} + after + 0 -> + case mnesia:transaction(Update) of + {atomic, ok} -> + update_own(Tab, Key, Acc+1); + Else -> + ?error("Trans failed on ~p with ~p~n" + "Info w2read ~p w2write ~p w2commit ~p storage ~p ~n", + [node(), + Else, + mnesia:table_info(Tab, where_to_read), + mnesia:table_info(Tab, where_to_write), + mnesia:table_info(Tab, where_to_commit), + mnesia:table_info(Tab, storage_type)]) + end + end. + +update_shared(Tab, Me, Acc) -> + Update = + fun() -> + W2R = mnesia:table_info(Tab, where_to_read), + Res = mnesia:read({Tab, 0}), + case Res of + [{Tab, Key, Extra, Val}] when element(Me, Extra) == Acc -> + Extra1 = setelement(Me, Extra, Acc+1), + Term = {Tab, Key, Extra1, Val+1}, + ok = mnesia:write(Term), +% ?log("At ~p: ~p w2r ~p w2w ~p ~n", +% [node(), Term, +% mnesia:table_info(Tab, where_to_read), + W2W = mnesia:table_info(Tab, where_to_write), + W2C = mnesia:table_info(Tab, where_to_commit), +%% mnesia:table_info(Tab, storage_type) +% ]), + {_Mod, Tid, Ts} = get(mnesia_activity_state), + io:format("~p ~p~n", [Tid, ets:tab2list(element(2,Ts))]), + {ok,Term,{W2R,W2W,W2C}}; + Val -> + Info = [{acc, Acc}, {me, Me}, + {tid, element(2, mnesia:get_activity_id())}, + {locks, mnesia:system_info(held_locks)}], + {read, Val, Info} + end + end, + receive + {Pid, quit} -> Pid ! {self(), Acc} + after + 0 -> + case mnesia:transaction(Update) of + {atomic, {ok,Term,W2}} -> + io:format("~p:~p:(~p,~p) ~w@~w~n", [erlang:now(),node(),Me,Acc,Term,W2]), + update_shared(Tab, Me, Acc+1); + Else -> + ?error("Trans failed on ~p with ~p~n" + "Info w2read ~p w2write ~p w2commit ~p storage ~p ~n", + [node(), + Else, + mnesia:table_info(Tab, where_to_read), + mnesia:table_info(Tab, where_to_write), + mnesia:table_info(Tab, where_to_commit), + mnesia:table_info(Tab, storage_type) + ]) + end + end. + +init_admin(Def, N1, N2, N3) -> + Tab = schema_ops, + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert(Tab, 1002), + + Pid1 = spawn_link(N1, ?MODULE, update_own, [Tab, 1, 0]), + Pid2 = spawn_link(N2, ?MODULE, update_own, [Tab, 2, 0]), + Pid3 = spawn_link(N3, ?MODULE, update_own, [Tab, 3, 0]), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, 0, {0,0,0}, 0}) end)), + + Pid4 = spawn_link(N1, ?MODULE, update_shared, [Tab, 1, 0]), + Pid5 = spawn_link(N2, ?MODULE, update_shared, [Tab, 2, 0]), + Pid6 = spawn_link(N3, ?MODULE, update_shared, [Tab, 3, 0]), + + {Pid1, Pid2, Pid3, Pid4, Pid5, Pid6}. + +verify_results({P1, P2, P3, P4, P5, P6}) -> + Tab = schema_ops, N1 = node(P1), N2 = node(P2), N3 = node(P3), + + try + P1 ! {self(), quit}, + R1 = receive {P1, Res1} -> Res1 after 9000 -> throw({timeout,P1}) end, + P2 ! {self(), quit}, + R2 = receive {P2, Res2} -> Res2 after 9000 -> throw({timeout,P2}) end, + P3 ! {self(), quit}, + R3 = receive {P3, Res3} -> Res3 after 9000 -> throw({timeout,P3}) end, + + P4 ! {self(), quit}, + R4 = receive {P4, Res4} -> Res4 after 9000 -> throw({timeout,P4}) end, + P5 ! {self(), quit}, + R5 = receive {P5, Res5} -> Res5 after 9000 -> throw({timeout,P5}) end, + P6 ! {self(), quit}, + R6 = receive {P6, Res6} -> Res6 after 9000 -> throw({timeout,P6}) end, + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write_lock_table(Tab) end)), + ?log("Should be ~p~n", [R1]), + ?match([{_, _, _, R1}], rpc:call(N1, mnesia, dirty_read, [{Tab, 1}])), + ?match([{_, _, _, R1}], rpc:call(N2, mnesia, dirty_read, [{Tab, 1}])), + ?match([{_, _, _, R1}], rpc:call(N3, mnesia, dirty_read, [{Tab, 1}])), + ?log("Should be ~p~n", [R2]), + ?match([{_, _, _, R2}], rpc:call(N1, mnesia, dirty_read, [{Tab, 2}])), + ?match([{_, _, _, R2}], rpc:call(N2, mnesia, dirty_read, [{Tab, 2}])), + ?match([{_, _, _, R2}], rpc:call(N3, mnesia, dirty_read, [{Tab, 2}])), + ?log("Should be ~p~n", [R3]), + ?match([{_, _, _, R3}], rpc:call(N1, mnesia, dirty_read, [{Tab, 3}])), + ?match([{_, _, _, R3}], rpc:call(N2, mnesia, dirty_read, [{Tab, 3}])), + ?match([{_, _, _, R3}], rpc:call(N3, mnesia, dirty_read, [{Tab, 3}])), + + Res = R4+R5+R6, + ?log("Should be {~p+~p+~p}= ~p~n", [R4, R5, R6, Res]), + ?match([{_, _, {R4,R5,R6}, Res}], rpc:call(N1, mnesia, dirty_read, [{Tab, 0}])), + ?match([{_, _, {R4,R5,R6}, Res}], rpc:call(N2, mnesia, dirty_read, [{Tab, 0}])), + ?match([{_, _, {R4,R5,R6}, Res}], rpc:call(N3, mnesia, dirty_read, [{Tab, 0}])) + catch throw:{timeout, Pid} -> + mnesia_lib:dist_coredump(), + ?error("Timeout ~p ~n", [Pid]) + end. + + +get_info(Tab) -> + Info = mnesia:table_info(Tab, all), + mnesia_lib:verbose("~p~n", [Info]). + +del_table_copy_1(suite) -> []; +del_table_copy_1(Config) when is_list(Config) -> + [_Node1, Node2, _Node3] = Nodes = ?acquire_nodes(3, Config), + del_table(Node2, Node2, Nodes). %Called on same Node as deleted +del_table_copy_2(suite) -> []; +del_table_copy_2(Config) when is_list(Config) -> + [Node1, Node2, _Node3] = Nodes = ?acquire_nodes(3, Config), + del_table(Node1, Node2, Nodes). %Called from other Node +del_table_copy_3(suite) -> []; +del_table_copy_3(Config) when is_list(Config) -> + [_Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + del_table(Node3, Node2, Nodes). %Called from Node w.o. table + +%%% The actual test +del_table(CallFrom, DelNode, [Node1, Node2, Node3]) -> + Def = [{ram_copies, [Node1]}, {disc_copies, [Node2]}, + {attributes, [key, attr1, attr2]}], + Tab = schema_ops, + Pids = init_admin(Def, Node1, Node2, Node3), + + ?log("Call from ~p delete table from ~p ~n", [CallFrom, DelNode]), + rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]), + + ?match({atomic, ok}, + rpc:call(CallFrom, mnesia, del_table_copy, [Tab, DelNode])), + + verify_results(Pids), + rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]), + ?verify_mnesia([Node1, Node2, Node3], []). + +add_table_copy_1(suite) -> []; +add_table_copy_1(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_only_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + add_table(Node1, Node3, Nodes, Def). +add_table_copy_2(suite) -> []; +add_table_copy_2(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_only_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + add_table(Node2, Node3, Nodes, Def). +add_table_copy_3(suite) -> []; +add_table_copy_3(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_only_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + add_table(Node3, Node3, Nodes, Def). +add_table_copy_4(suite) -> []; +add_table_copy_4(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_only_copies, [Node1]}, + {attributes, [key, attr1, attr2]}], + add_table(Node2, Node3, Nodes, Def). +%%% The actual test +add_table(CallFrom, AddNode, [Node1, Node2, Node3], Def) -> + Pids = init_admin(Def, Node1, Node2, Node3), + Tab = schema_ops, + ?log("Call from ~p add table to ~p ~n", [CallFrom, AddNode]), + rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]), + ?match({atomic, ok}, rpc:call(CallFrom, mnesia, add_table_copy, + [Tab, AddNode, ram_copies])), + verify_results(Pids), + rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]), + ?verify_mnesia([Node1, Node2, Node3], []). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +move_table_copy_1(suite) -> []; +move_table_copy_1(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + move_table(Node1, Node1, Node3, Nodes, Def). +move_table_copy_2(suite) -> []; +move_table_copy_2(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + move_table(Node2, Node1, Node3, Nodes, Def). +move_table_copy_3(suite) -> []; +move_table_copy_3(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_copies, [Node1, Node2]}, + {attributes, [key, attr1, attr2]}], + move_table(Node3, Node1, Node3, Nodes, Def). +move_table_copy_4(suite) -> []; +move_table_copy_4(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Def = [{disc_copies, [Node1]}, + {attributes, [key, attr1, attr2]}], + move_table(Node2, Node1, Node3, Nodes, Def). +%%% The actual test +move_table(CallFrom, FromNode, ToNode, [Node1, Node2, Node3], Def) -> + Pids = init_admin(Def, Node1, Node2, Node3), + Tab = schema_ops, + ?log("Call from ~p move table from ~p to ~p ~n", [CallFrom, FromNode, ToNode]), + rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]), + ?match({atomic, ok}, rpc:call(CallFrom, mnesia, move_table_copy, + [Tab, FromNode, ToNode])), + verify_results(Pids), + rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]), + ?verify_mnesia([Node1, Node2, Node3], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +visibility(doc) -> + ["Verify the visibility semantics for various configurations"]; +visibility(suite) -> + [ + dirty_updates_visible_direct, + dirty_reads_regardless_of_trans, + trans_update_invisibible_outside_trans, + trans_update_visible_inside_trans, + write_shadows, + delete_shadows, +%% delete_shadows2, + write_delete_shadows_bag, + write_delete_shadows_bag2, + iteration, + shadow_search, + snmp_shadows + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +dirty_updates_visible_direct(doc) -> + ["One process can immediately see dirty updates of another"]; +dirty_updates_visible_direct(suite) -> []; +dirty_updates_visible_direct(Config) when is_list(Config) -> + dirty_visibility(outside_trans, Config). + +dirty_reads_regardless_of_trans(doc) -> + ["Dirty reads are not affected by transaction context"]; +dirty_reads_regardless_of_trans(suite) -> []; +dirty_reads_regardless_of_trans(Config) when is_list(Config) -> + dirty_visibility(inside_trans, Config). + +dirty_visibility(Mode, Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = list_to_atom(lists:concat([dirty_visibility, '_', Mode])), + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, {ram_copies, [Node1]}])), + ValPos = 3, + + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + %% Start two processes + {success, [A]} = ?start_activities([Node1]), + + case Mode of + inside_trans -> + ?start_transactions([A]), + A ! fun() -> + mnesia:write({Tab, a, 11}), + mnesia:write({Tab, b, 22}), + mnesia:write({Tab, c, 1}), + mnesia:write({Tab, d, 2}), + mnesia:write({Tab, e, 3}), + lists:sort(mnesia:all_keys(Tab)) + end, + ?match_receive({A, [a, b, c, d, e]}); + outside_trans -> + ignore + end, + + RecA = {Tab, a, 1}, + PatA = {Tab, '$1', 1}, + RecB = {Tab, b, 3}, + PatB = {Tab, '$1', 3}, + RecB2 = {Tab, b, 2}, + PatB2 = {Tab, '$1', 2}, + ?match([], mnesia:dirty_read({Tab, a})), + ?match([], mnesia:dirty_read({Tab, b})), + ?match([], mnesia:dirty_match_object(PatA)), + ?match([], mnesia:dirty_match_object(PatB)), + ?match([], mnesia:dirty_match_object(PatB2)), + ?match([], mnesia:dirty_index_read(Tab, 1, ValPos)), + ?match([], mnesia:dirty_index_read(Tab, 3, ValPos)), + ?match([], mnesia:dirty_index_match_object(PatA, ValPos)), + ?match([], mnesia:dirty_index_match_object(PatB, ValPos)), + ?match([], mnesia:dirty_index_match_object(PatB2, ValPos)), + ?match('$end_of_table', mnesia:dirty_first(Tab)), + + %% dirty_write + A ! fun() -> mnesia:dirty_write(RecA) end, + ?match_receive({A, ok}), + ?match([RecA], mnesia:dirty_read({Tab, a})), + ?match([RecA], mnesia:dirty_match_object(PatA)), + ?match(a, mnesia:dirty_first(Tab)), + ?match([RecA], mnesia:dirty_index_read(Tab, 1, ValPos)), + ?match([RecA], mnesia:dirty_index_match_object(PatA, ValPos)), + ?match('$end_of_table', mnesia:dirty_next(Tab, a)), + + %% dirty_create + A ! fun() -> mnesia:dirty_write(RecB) end, + ?match_receive({A, ok}), + ?match([RecB], mnesia:dirty_read({Tab, b})), + ?match([RecB], mnesia:dirty_match_object(PatB)), + ?match([RecB], mnesia:dirty_index_read(Tab, 3, ValPos)), + ?match([RecB], mnesia:dirty_index_match_object(PatB, ValPos)), + ?match('$end_of_table', + mnesia:dirty_next(Tab, mnesia:dirty_next(Tab, mnesia:dirty_first(Tab)))), + + %% dirty_update_counter + A ! fun() -> mnesia:dirty_update_counter({Tab, b}, -1) end, + ?match_receive({A, _}), + ?match([RecB2], mnesia:dirty_read({Tab, b})), + ?match([], mnesia:dirty_match_object(PatB)), + ?match([RecB2], mnesia:dirty_match_object(PatB2)), + ?match([RecB2], mnesia:dirty_index_read(Tab, 2, ValPos)), + ?match([], mnesia:dirty_index_match_object(PatB, ValPos)), + ?match([RecB2], mnesia:dirty_index_match_object(PatB2, ValPos)), + ?match('$end_of_table', + mnesia:dirty_next(Tab, mnesia:dirty_next(Tab, mnesia:dirty_first(Tab)))), + + %% dirty_delete + A ! fun() -> mnesia:dirty_delete({Tab, b}) end, + ?match_receive({A, ok}), + ?match([], mnesia:dirty_read({Tab, b})), + ?match([], mnesia:dirty_match_object(PatB2)), + ?match([], mnesia:dirty_index_read(Tab, 3, ValPos)), + ?match([], mnesia:dirty_index_match_object(PatB2, ValPos)), + ?match(a, mnesia:dirty_first(Tab)), + ?match('$end_of_table', mnesia:dirty_next(Tab, a)), + + %% dirty_delete_object + ?match([RecA], mnesia:dirty_match_object(PatA)), + A ! fun() -> mnesia:dirty_delete_object(RecA) end, + ?match_receive({A, ok}), + ?match([], mnesia:dirty_read({Tab, a})), + ?match([], mnesia:dirty_match_object(PatA)), + ?match([], mnesia:dirty_index_read(Tab, 1, ValPos)), + ?match([], mnesia:dirty_index_match_object(PatA, ValPos)), + ?match('$end_of_table', mnesia:dirty_first(Tab)), + + case Mode of + inside_trans -> + A ! end_trans, + ?match_receive({A, {atomic, end_trans}}); + outside_trans -> + ignore + end, + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +trans_update_invisibible_outside_trans(doc) -> + ["Updates in a transaction are invisible outside the transaction"]; +trans_update_invisibible_outside_trans(suite) -> []; +trans_update_invisibible_outside_trans(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = trans_update_invisibible_outside_trans, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}])), + ValPos = 3, + RecA = {Tab, a, 1}, + PatA = {Tab, '$1', 1}, + RecB = {Tab, b, 3}, + PatB = {Tab, '$1', 3}, + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + Verify = + fun() -> + ?match([], mnesia:dirty_read({Tab, a})), + ?match([], mnesia:dirty_read({Tab, b})), + ?match([], mnesia:dirty_match_object(PatA)), + ?match([], mnesia:dirty_match_object(PatB)), + ?match([], mnesia:dirty_index_read(Tab, 1, ValPos)), + ?match([], mnesia:dirty_index_read(Tab, 3, ValPos)), + ?match([], mnesia:dirty_index_match_object(PatA, ValPos)), + ?match([], mnesia:dirty_index_match_object(PatB, ValPos)), + ?match('$end_of_table', mnesia:dirty_first(Tab)) + end, + + Fun = fun() -> + ?match(ok, mnesia:write(RecA)), + Verify(), + + ?match(ok, mnesia:write(RecB)), + Verify(), + + ?match(ok, mnesia:delete({Tab, b})), + Verify(), + + ?match([RecA], mnesia:match_object(PatA)), + Verify(), + + ?match(ok, mnesia:delete_object(RecA)), + Verify(), + ok + end, + ?match({atomic, ok}, mnesia:transaction(Fun)), + Verify(), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +trans_update_visible_inside_trans(doc) -> + ["Updates in a transaction are visible in the same transaction"]; +trans_update_visible_inside_trans(suite) -> []; +trans_update_visible_inside_trans(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = trans_update_visible_inside_trans, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}])), + ValPos = 3, + RecA = {Tab, a, 1}, + PatA = {Tab, '$1', 1}, + RecB = {Tab, b, 3}, + PatB = {Tab, '$1', 3}, + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + Fun = fun() -> + %% write + ?match(ok, mnesia:write(RecA)), + ?match([RecA], mnesia:read({Tab, a})), + ?match([RecA], mnesia:wread({Tab, a})), + ?match([RecA], mnesia:match_object(PatA)), + ?match([a], mnesia:all_keys(Tab)), + ?match([RecA], mnesia:index_match_object(PatA, ValPos)), + ?match([RecA], mnesia:index_read(Tab, 1, ValPos)), + + %% create + ?match(ok, mnesia:write(RecB)), + ?match([RecB], mnesia:read({Tab, b})), + ?match([RecB], mnesia:wread({Tab, b})), + ?match([RecB], mnesia:match_object(PatB)), + ?match([RecB], mnesia:index_match_object(PatB, ValPos)), + ?match([RecB], mnesia:index_read(Tab, 3, ValPos)), + + %% delete + ?match(ok, mnesia:delete({Tab, b})), + ?match([], mnesia:read({Tab, b})), + ?match([], mnesia:wread({Tab, b})), + ?match([], mnesia:match_object(PatB)), + ?match([a], mnesia:all_keys(Tab)), + ?match([], mnesia:index_match_object(PatB, ValPos)), + ?match([], mnesia:index_read(Tab, 2, ValPos)), + ?match([], mnesia:index_read(Tab, 3, ValPos)), + + %% delete_object + ?match(ok, mnesia:delete_object(RecA)), + ?match([], mnesia:read({Tab, a})), + ?match([], mnesia:wread({Tab, a})), + ?match([], mnesia:match_object(PatA)), + ?match([], mnesia:all_keys(Tab)), + ?match([], mnesia:index_match_object(PatA, ValPos)), + ?match([], mnesia:index_read(Tab, 2, ValPos)), + ?match([], mnesia:index_read(Tab, 3, ValPos)), + ok + end, + ?match({atomic, ok}, mnesia:transaction(Fun)), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +write_shadows(doc) -> + ["Tests whether the shadow shows the correct object when", + "writing to the table"]; +write_shadows(suite) -> []; +write_shadows(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = write_shadows, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}, + {type, set}])), + ValPos = 3, + RecA1 = {Tab, a, 1}, + PatA1 = {Tab, '$1', 1}, + RecA2 = {Tab, a, 2}, + PatA2 = {Tab, '$1', 2}, + + + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + Fun1 = fun() -> + ?match(ok, mnesia:write(RecA1)), + ok + end, + + ?match({atomic, ok}, mnesia:transaction(Fun1)), + + Fun2 = fun() -> + %% write shadow old write - is the confirmed value visable + %% in the shadow ? + ?match([RecA1], mnesia:read({Tab, a})), + ?match([RecA1], mnesia:wread({Tab, a})), + ?match([RecA1], mnesia:match_object(PatA1)), + ?match([a], mnesia:all_keys(Tab)), + ?match([RecA1], mnesia:index_match_object(PatA1, ValPos)), + ?match([RecA1], mnesia:index_read(Tab, 1, ValPos)), + + %% write shadow new write - is a new value visable instead + %% of the old value ? + ?match(ok, mnesia:write(RecA2)), + + ?match([RecA2], mnesia:read({Tab, a})), + ?match([RecA2], mnesia:wread({Tab, a})), + ?match([RecA2], mnesia:match_object(PatA2)), %% delete shadow old but not new write - is the new value visable + + ?match([a], mnesia:all_keys(Tab)), + ?match([RecA2], mnesia:index_match_object(PatA2, ValPos)), + ?match([RecA2], mnesia:index_read(Tab, 2, ValPos)), + ok + + end, + ?match({atomic, ok}, mnesia:transaction(Fun2)), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +delete_shadows(doc) -> + ["Test whether the shadow shows the correct object when deleting objects"]; +delete_shadows(suite) -> []; +delete_shadows(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = delete_shadows, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}, + {type, set}])), + ValPos = 3, + OidA = {Tab, a}, + RecA1 = {Tab, a, 1}, + PatA1 = {Tab, '$1', 1}, + RecA2 = {Tab, a, 2}, + PatA2 = {Tab, '$1', 2}, + + + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + Fun1 = fun() -> + ?match(ok, mnesia:write(RecA1)), + ok + end, + + ?match({atomic, ok}, mnesia:transaction(Fun1)), + + Fun2 = fun() -> + + + %% delete shadow old write - is the confirmed value invisible + %% when deleted in the transaction ? + ?match(ok, mnesia:delete(OidA)), + + ?match([], mnesia:read({Tab, a})), + ?match([], mnesia:wread({Tab, a})), + ?match([], mnesia:match_object(PatA1)), + ?match([], mnesia:all_keys(Tab)), + ?match([], mnesia:index_match_object(PatA1, ValPos)), + ?match([], mnesia:index_read(Tab, 1, ValPos)), + + %% delete shadow old but not new write - is the new value visable + %% when the old one was deleted ? + ?match(ok, mnesia:write(RecA2)), + + ?match([RecA2], mnesia:read({Tab, a})), + ?match([RecA2], mnesia:wread({Tab, a})), + ?match([RecA2], mnesia:match_object(PatA2)), + ?match([a], mnesia:all_keys(Tab)), + ?match([RecA2], mnesia:index_match_object(PatA2, ValPos)), + ?match([RecA2], mnesia:index_read(Tab, 2, ValPos)), + + %% delete shadow old and new write - is the new value invisable + %% when deleted ? + ?match(ok, mnesia:delete(OidA)), + + ?match([], mnesia:read({Tab, a})), + ?match([], mnesia:wread({Tab, a})), + ?match([], mnesia:match_object(PatA2)), + ?match([], mnesia:all_keys(Tab)), + ?match([], mnesia:index_match_object(PatA2, ValPos)), + ?match([], mnesia:index_read(Tab, 2, ValPos)), + ok + + end, + ?match({atomic, ok}, mnesia:transaction(Fun2)), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +write_delete_shadows_bag(doc) -> + ["Test the visibility of written and deleted objects in an bag type table"]; +write_delete_shadows_bag(suite) -> []; +write_delete_shadows_bag(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab = write_delete_shadows_bag, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}, + {type, bag}])), + ValPos = 3, + OidA = {Tab, a}, + + RecA1 = {Tab, a, 1}, + PatA1 = {Tab, '$1', 1}, + + RecA2 = {Tab, a, 2}, + PatA2 = {Tab, '$1', 2}, + + RecA3 = {Tab, a, 3}, + PatA3 = {Tab, '$1', 3}, + + PatA = {Tab, a, '_'}, + + + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + Fun1 = fun() -> + ?match(ok, mnesia:write(RecA1)), + ?match(ok, mnesia:write(RecA2)), + ok + end, + + ?match({atomic, ok}, mnesia:transaction(Fun1)), + + Fun2 = fun() -> + %% delete shadow old write - is the confirmed value invisible + %% when deleted in the transaction ? + ?match(ok, mnesia:delete_object(RecA1)), + + ?match([RecA2], mnesia:read({Tab, a})), + ?match([RecA2], mnesia:wread({Tab, a})), + ?match([RecA2], mnesia:match_object(PatA2)), + ?match([a], mnesia:all_keys(Tab)), + ?match([RecA2], mnesia:index_match_object(PatA2, ValPos)), + ?match([RecA2], mnesia:index_read(Tab, 2, ValPos)), + + ?match(ok, mnesia:delete(OidA)), + + ?match([], mnesia:read({Tab, a})), + ?match([], mnesia:wread({Tab, a})), + ?match([], mnesia:match_object(PatA1)), + ?match([], mnesia:all_keys(Tab)), + ?match([], mnesia:index_match_object(PatA1, ValPos)), + ?match([], mnesia:index_read(Tab, 1, ValPos)), + + %% delete shadow old but not new write - are both new value visable + %% when the old one was deleted ? + ?match(ok, mnesia:write(RecA2)), + ?match(ok, mnesia:write(RecA3)), + + + ?match([RecA2, RecA3], lists:sort(mnesia:read({Tab, a}))), + ?match([RecA2, RecA3], lists:sort(mnesia:wread({Tab, a}))), + ?match([RecA2], mnesia:match_object(PatA2)), + ?match([a], mnesia:all_keys(Tab)), + ?match([RecA2, RecA3], lists:sort(mnesia:match_object(PatA))), + ?match([RecA2], mnesia:index_match_object(PatA2, ValPos)), + ?match([RecA3], mnesia:index_match_object(PatA3, ValPos)), + ?match([RecA2], mnesia:index_read(Tab, 2, ValPos)), + + %% delete shadow old and new write - is the new value invisable + %% when deleted ? + ?match(ok, mnesia:delete(OidA)), + + ?match([], mnesia:read({Tab, a})), + ?match([], mnesia:wread({Tab, a})), + ?match([], mnesia:match_object(PatA2)), + ?match([], mnesia:all_keys(Tab)), + ?match([], mnesia:index_match_object(PatA2, ValPos)), + ?match([], mnesia:index_read(Tab, 2, ValPos)), + ok + end, + ?match({atomic, ok}, mnesia:transaction(Fun2)), + ok. + +write_delete_shadows_bag2(doc) -> + ["Test the visibility of written and deleted objects in an bag type table " + "and verifies the results"]; +write_delete_shadows_bag2(suite) -> []; +write_delete_shadows_bag2(Config) when is_list(Config) -> + + [Node1] = ?acquire_nodes(1, Config), + Tab = w_d_s_b, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}, + {type, bag}])), + Del = fun() -> + R1 = mnesia:read({Tab, 1}), + mnesia:delete({Tab, 1}), + R2 = mnesia:read({Tab, 1}), + mnesia:write({Tab, 1, 1}), + mnesia:write({Tab, 1, 2}), + R3 = mnesia:read({Tab, 1}), + {R1, R2, R3} + end, + DelObj = fun() -> + R1 = mnesia:read({Tab, 2}), + mnesia:delete_object({Tab, 2, 2}), + R2 = mnesia:read({Tab, 2}), + mnesia:write({Tab, 2, 1}), + mnesia:write({Tab, 2, 2}), + R3 = mnesia:read({Tab, 2}), + {R1, R2, R3} + end, + Both1 = [{Tab, 1, 1}, {Tab, 1, 2}], + Both2 = [{Tab, 2, 1}, {Tab, 2, 2}], + ?match({atomic, {[], [], Both1}}, mnesia:transaction(Del)), + ?match({atomic, {Both1, [], Both1}}, mnesia:transaction(Del)), + ?match({atomic, Both1}, mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)), + ?match({atomic, {[], [], Both2}}, mnesia:transaction(DelObj)), + ?match({atomic, {Both2, [{Tab, 2, 1}], Both2}}, mnesia:transaction(DelObj)), + ?match({atomic, Both2}, mnesia:transaction(fun() -> mnesia:read({Tab, 2}) end)), + ?verify_mnesia([Node1], []). + +shadow_search(doc) -> + ["Verifies that ordered_set tables are ordered, and the order is kept" + "even when table is shadowed by transaction updates"]; +shadow_search(suite) -> []; +shadow_search(Config) when is_list(Config) -> + [Node1] = ?acquire_nodes(1, Config), + Tab1 = ss_oset, + Tab2 = ss_set, + Tab3 = ss_bag, + Tabs = [Tab1,Tab2,Tab3], + RecName = ss, + ?match({atomic, ok}, mnesia:create_table([{name, Tab1}, + {ram_copies, [Node1]}, + {record_name, RecName}, + {type, ordered_set}])), + ?match({atomic, ok}, mnesia:create_table([{name, Tab2}, + {record_name, RecName}, + {ram_copies, [Node1]}, + {type, set}])), + ?match({atomic, ok}, mnesia:create_table([{name, Tab3}, + {record_name, RecName}, + {ram_copies, [Node1]}, + {type, bag}])), + Recs = [{RecName, K, K} || K <- [1,3,5]], + [mnesia:dirty_write(Tab1, R) || R <- Recs], + [mnesia:dirty_write(Tab2, R) || R <- Recs], + [mnesia:dirty_write(Tab3, R) || R <- Recs], + + Match = fun(Tab) -> mnesia:match_object(Tab, {'_','_','_'}, write) end, + Select = fun(Tab) -> mnesia:select(Tab, [{'_', [], ['$_']}]) end, +% Trans = fun(Fun,Args) -> mnesia:transaction(Fun,Args) end, + LoopHelp = fun('$end_of_table',_) -> []; + ({Res,Cont},Fun) -> + Sel = mnesia:select(Cont), + Res ++ Fun(Sel, Fun) + end, + SelLoop = fun(Table) -> + Sel = mnesia:select(Table, [{'_', [], ['$_']}], 1, read), + LoopHelp(Sel,LoopHelp) + end, + + R1 = {RecName, 2, 2}, R2 = {RecName, 4, 4}, + R3 = {RecName, 2, 3}, R4 = {RecName, 3, 1}, + R5 = {RecName, 104, 104}, + W1 = fun(Tab,Search) -> mnesia:write(Tab,R1,write), + mnesia:write(Tab,R2,write), + Search(Tab) + end, + S1 = lists:sort([R1,R2|Recs]), + ?match({atomic,S1}, mnesia:transaction(W1, [Tab1,Select])), + ?match({atomic,S1}, mnesia:transaction(W1, [Tab1,Match])), + ?match({atomic,S1}, mnesia:transaction(W1, [Tab1,SelLoop])), + ?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab2,Select]))), + ?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab2,SelLoop]))), + ?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab2,Match]))), + ?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab3,Select]))), + ?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab3,SelLoop]))), + ?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab3,Match]))), + [mnesia:dirty_delete_object(Tab,R) || R <- [R1,R2], Tab <- Tabs], + + W2 = fun(Tab,Search) -> + mnesia:write(Tab,R3,write), + mnesia:write(Tab,R1,write), + Search(Tab) + end, + S2 = lists:sort([R1|Recs]), + S2Bag = lists:sort([R1,R3|Recs]), + ?match({atomic,S2}, mnesia:transaction(W2, [Tab1,Select])), + ?match({atomic,S2}, mnesia:transaction(W2, [Tab1,SelLoop])), + ?match({atomic,S2}, mnesia:transaction(W2, [Tab1,Match])), + ?match({atomic,S2}, sort_res(mnesia:transaction(W2, [Tab2,Select]))), + ?match({atomic,S2}, sort_res(mnesia:transaction(W2, [Tab2,SelLoop]))), + ?match({atomic,S2}, sort_res(mnesia:transaction(W2, [Tab2,Match]))), + ?match({atomic,S2Bag}, sort_res(mnesia:transaction(W2, [Tab3,Select]))), + ?match({atomic,S2Bag}, sort_res(mnesia:transaction(W2, [Tab3,SelLoop]))), + ?match({atomic,S2Bag}, sort_res(mnesia:transaction(W2, [Tab3,Match]))), +%% [mnesia:dirty_delete_object(Tab,R) || R <- [R1,R3], Tab <- Tabs], + + W3 = fun(Tab,Search) -> + mnesia:write(Tab,R4,write), + mnesia:delete(Tab,element(2,R1),write), + Search(Tab) + end, + S3Bag = lists:sort([R4|lists:delete(R1,Recs)]), + S3 = lists:delete({RecName,3,3},S3Bag), + ?match({atomic,S3}, mnesia:transaction(W3, [Tab1,Select])), + ?match({atomic,S3}, mnesia:transaction(W3, [Tab1,SelLoop])), + ?match({atomic,S3}, mnesia:transaction(W3, [Tab1,Match])), + ?match({atomic,S3}, sort_res(mnesia:transaction(W3, [Tab2,SelLoop]))), + ?match({atomic,S3}, sort_res(mnesia:transaction(W3, [Tab2,Select]))), + ?match({atomic,S3}, sort_res(mnesia:transaction(W3, [Tab2,Match]))), + ?match({atomic,S3Bag}, sort_res(mnesia:transaction(W3, [Tab3,Select]))), + ?match({atomic,S3Bag}, sort_res(mnesia:transaction(W3, [Tab3,SelLoop]))), + ?match({atomic,S3Bag}, sort_res(mnesia:transaction(W3, [Tab3,Match]))), + + W4 = fun(Tab,Search) -> + mnesia:delete(Tab,-1,write), + mnesia:delete(Tab,4 ,write), + mnesia:delete(Tab,17,write), + mnesia:delete_object(Tab,{RecName, -1, x},write), + mnesia:delete_object(Tab,{RecName, 4, x},write), + mnesia:delete_object(Tab,{RecName, 42, x},write), + mnesia:delete_object(Tab,R2,write), + mnesia:write(Tab, R5, write), + Search(Tab) + end, + S4Bag = lists:sort([R5|S3Bag]), + S4 = lists:sort([R5|S3]), + ?match({atomic,S4}, mnesia:transaction(W4, [Tab1,Select])), + ?match({atomic,S4}, mnesia:transaction(W4, [Tab1,SelLoop])), + ?match({atomic,S4}, mnesia:transaction(W4, [Tab1,Match])), + ?match({atomic,S4}, sort_res(mnesia:transaction(W4, [Tab2,Select]))), + ?match({atomic,S4}, sort_res(mnesia:transaction(W4, [Tab2,SelLoop]))), + ?match({atomic,S4}, sort_res(mnesia:transaction(W4, [Tab2,Match]))), + ?match({atomic,S4Bag}, sort_res(mnesia:transaction(W4, [Tab3,Select]))), + ?match({atomic,S4Bag}, sort_res(mnesia:transaction(W4, [Tab3,SelLoop]))), + ?match({atomic,S4Bag}, sort_res(mnesia:transaction(W4, [Tab3,Match]))), + [mnesia:dirty_delete_object(Tab,R) || R <- [{RecName,3,3},R5], Tab <- Tabs], + + %% hmmm anything more?? + + ?verify_mnesia([Node1], []). + +removed_resources(suite) -> + [rr_kill_copy]; +removed_resources(doc) -> + ["Verify that the locking behave when resources are removed"]. + +rr_kill_copy(suite) -> []; +rr_kill_copy(Config) when is_list(Config) -> + Ns = ?acquire_nodes(3,Config ++ [{tc_timeout, 60000}]), + DeleteMe = fun(_Tab,Where2read) -> + ?match([], mnesia_test_lib:kill_mnesia([Where2read])) + end, + Del = removed_resources(Ns, DeleteMe), + ?verify_mnesia(Ns -- [Del], []). + +removed_resources([_N1,N2,N3], DeleteRes) -> + Tab = del_res, + ?match({atomic, ok}, mnesia:create_table(Tab,[{ram_copies, [N2,N3]}])), + + Init = fun() -> [mnesia:write({Tab,Key,Key}) || Key <- lists:seq(0,99)] end, + ?match([], [Bad || Bad <- mnesia:sync_dirty(Init), Bad /= ok]), + + Where2Read = mnesia:table_info(Tab, where_to_read), + [Keep] = [N2,N3] -- [Where2Read], + Tester = self(), + + Conflict = fun() -> + %% Read a value.. + [{Tab,1,Val}] = mnesia:read({Tab,1}), + case get(restart) of + undefined -> + Tester ! {pid_1, self()}, + %% Wait for sync, the read value have been + %% updated and this function should be restarted. + receive {Tester,sync} -> ok end, + put(restart, restarted); + restarted -> + ok + end, + mnesia:write({Tab,1,Val+10}) + end, + Lucky = fun() -> + [{Tab,1,Val}] = mnesia:read({Tab,1}), + mnesia:write({Tab,1,Val+100}) + end, + + CPid = spawn_link(fun() -> Tester ! {self(), mnesia:transaction(Conflict)} end), + + %% sync first transaction + receive {pid_1, CPid} -> synced end, + + DeleteRes(Tab, Where2Read), + + ?match(Keep, mnesia:table_info(Tab, where_to_read)), + + %% Run the other/Lucky transaction, this should work since + %% it won't grab a lock on the conflicting transactions Where2Read node. + + LPid = spawn_link(Keep, fun() -> Tester ! {self(),mnesia:transaction(Lucky)} end), + ?match_receive({LPid,{atomic,ok}}), + + %% Continue Transaction no 1 + CPid ! {self(), sync}, + + ?match(ok, receive {CPid,{atomic,ok}} -> ok after 2000 -> process_info(self()) end), + + ?match({atomic,[{del_res,1,111}]}, mnesia:transaction(fun() -> mnesia:read({Tab,1}) end)), + Where2Read. + +nasty(suite) -> []; + +nasty(doc) -> + ["Tries to fullfill a rather nasty locking scenario, where we have had " + "bugs, the testcase tries a combination of locks in locker queue"]; + +%% This testcase no longer works as it was intended to show errors when +%% tablelocks was allowed to be placed in the queue though locks existed +%% in the queue with less Tid's. This is no longer allowed and the testcase +%% has been update. + +nasty(Config) -> + ?acquire_nodes(1, Config), + Tab = nasty, + ?match({atomic, ok}, mnesia:create_table(Tab, [])), + Coord = self(), + Write = fun(Key) -> + mnesia:write({Tab, Key, write}), + Coord ! {write, Key, self(), mnesia:get_activity_id()}, + receive + continue -> + ok + end, + Coord ! {done, {write, Key}, self()} + end, + + Update = fun(Key) -> + Coord ! {update, Key, self(), mnesia:get_activity_id()}, + receive + continue -> + ok + end, + mnesia:read({Tab, Key}), + mnesia:write({Tab, Key, update}), + receive + continue -> + ok + end, + + Coord ! {done, {update, Key}, self()} + end, + + TabLock = fun() -> + Coord ! {tablock, Tab, self(), mnesia:get_activity_id()}, + receive + continue -> + ok + end, + mnesia:lock({table, Tab}, write), + Coord ! {done, {tablock, Tab}, self()} + end, + + Up = spawn_link(mnesia, transaction, [Update, [0]]), + ?match_receive({update, 0, Up, _Tid}), + TL = spawn_link(mnesia, transaction, [TabLock]), + ?match_receive({tablock, Tab, _Tl, _Tid}), + W0 = spawn_link(mnesia, transaction, [Write, [0]]), + ?match_receive({write, 0, W0, _Tid}), + W1 = spawn_link(mnesia, transaction, [Write, [1]]), + ?match_receive({write, 1, W1, _Tid}), + + %% Nothing should be in msg queue! + ?match(timeout, receive A -> A after 1000 -> timeout end), + Up ! continue, %% Should be queued + ?match(timeout, receive A -> A after 1000 -> timeout end), + TL ! continue, %% Should be restarted +% ?match({tablock, _, _, _}, receive A -> A after 1000 -> timeout end), + ?match(timeout, receive A -> A after 1000 -> timeout end), + + LQ1 = mnesia_locker:get_lock_queue(), + ?match({2, _}, {length(LQ1), LQ1}), + W0 ! continue, % Up should be in queue + ?match_receive({done, {write, 0}, W0}), + ?match_receive({'EXIT', W0, normal}), + + TL ! continue, % Should stay in queue W1 + ?match(timeout, receive A -> A after 1000 -> timeout end), + Up ! continue, % Should stay in queue (TL got higher tid) + ?match(timeout, receive A -> A after 1000 -> timeout end), + + LQ2 = mnesia_locker:get_lock_queue(), + ?match({2, _}, {length(LQ2), LQ2}), + + W1 ! continue, + ?match_receive({done, {write, 1}, W1}), + get_exit(W1), + get_exit(TL), + ?match_receive({done, {tablock,Tab}, TL}), + get_exit(Up), + ?match_receive({done, {update, 0}, Up}), + + ok. + +get_exit(Pid) -> + receive + {'EXIT', Pid, normal} -> + ok + after 10000 -> + ?error("Timeout EXIT ~p~n", [Pid]) + end. + +iteration(doc) -> + ["Verify that the updates before/during iteration are visable " + "and that the order is preserved for ordered_set tables"]; +iteration(suite) -> + [foldl,first_next]. + +foldl(doc) -> + [""]; +foldl(suite) -> + []; +foldl(Config) when is_list(Config) -> + Nodes = [_,N2] = ?acquire_nodes(2, Config), + Tab1 = foldl_local, + Tab2 = foldl_remote, + Tab3 = foldl_ordered, + Tab11 = foldr_local, + Tab21 = foldr_remote, + Tab31 = foldr_ordered, + ?match({atomic, ok}, mnesia:create_table(Tab1, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(Tab2, [{ram_copies, [N2]}, {type, bag}])), + ?match({atomic, ok}, mnesia:create_table(Tab3, [{ram_copies, Nodes}, + {type, ordered_set}])), + ?match({atomic, ok}, mnesia:create_table(Tab11, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(Tab21, [{ram_copies, [N2]}, {type, bag}])), + ?match({atomic, ok}, mnesia:create_table(Tab31, [{ram_copies, Nodes}, + {type, ordered_set}])), + + + Tab1Els = [{Tab1, N, N} || N <- lists:seq(1, 10)], + Tab2Els = [{Tab2, 1, 2} | [{Tab2, N, N} || N <- lists:seq(1, 10)]], + Tab3Els = [{Tab3, N, N} || N <- lists:seq(1, 10)], + Tab11Els = [{Tab11, N, N} || N <- lists:seq(1, 10)], + Tab21Els = [{Tab21, 1, 2} | [{Tab21, N, N} || N <- lists:seq(1, 10)]], + Tab31Els = [{Tab31, N, N} || N <- lists:seq(1, 10)], + + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab1Els], + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab2Els], + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab3Els], + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab11Els], + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab21Els], + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab31Els], + + Get = fun(E, A) -> [E | A] end, + + %% Before + AddB = fun(Tab, Func) -> + mnesia:write({Tab, 0, 0}), + mnesia:write({Tab, 1, 0}), + mnesia:write({Tab, 11, 0}), + mnesia:Func(Get, [], Tab) + end, + AddT1 = [{Tab1, 0, 0}, {Tab1, 1, 0}] ++ tl(Tab1Els) ++ [{Tab1, 11, 0}], + AddT2 = lists:sort([{Tab2, 0, 0}, {Tab2, 1, 0}] ++ Tab2Els ++ [{Tab2, 11, 0}]), + AddT3 = [{Tab3, 0, 0}, {Tab3, 1, 0}] ++ tl(Tab3Els) ++ [{Tab3, 11, 0}], + AddT11 = [{Tab11, 0, 0}, {Tab11, 1, 0}] ++ tl(Tab11Els) ++ [{Tab11, 11, 0}], + AddT21 = lists:sort([{Tab21, 0, 0}, {Tab21, 1, 0}] ++ Tab21Els ++ [{Tab21, 11, 0}]), + AddT31 = [{Tab31, 0, 0}, {Tab31, 1, 0}] ++ tl(Tab31Els) ++ [{Tab31, 11, 0}], + + ?match({atomic, AddT1}, sort_res(mnesia:transaction(AddB, [Tab1, foldl]))), + ?match({atomic, AddT2}, sort_res(mnesia:transaction(AddB, [Tab2, foldl]))), + ?match({atomic, AddT3}, rev_res(mnesia:transaction(AddB, [Tab3, foldl]))), + ?match({atomic, AddT11}, sort_res(mnesia:transaction(AddB, [Tab11, foldr]))), + ?match({atomic, AddT21}, sort_res(mnesia:transaction(AddB, [Tab21, foldr]))), + ?match({atomic, AddT31}, mnesia:transaction(AddB, [Tab31, foldr])), + + ?match({atomic, ok}, mnesia:create_table(copy, [{ram_copies, [N2]}, + {record_name, Tab1}])), + CopyRec = fun(NewRec, Acc) -> + %% OTP-5495 + W = fun() -> mnesia:write(copy, NewRec, write), [NewRec| Acc] end, + {atomic,Res} = sort_res(mnesia:transaction(W)), + Res + end, + Copy = fun() -> + AddT1 = mnesia:foldl(CopyRec, [], Tab1), + AddT1 = sort_res(mnesia:foldl(Get, [], copy)) + end, + ?match({atomic, AddT1}, sort_res(mnesia:transaction(Copy))), + + Del = fun(E, A) -> mnesia:delete_object(E), [E|A] end, + DelD = fun(Tab) -> + mnesia:write({Tab, 12, 12}), + mnesia:delete({Tab, 0}), + mnesia:foldr(Del, [], Tab), + mnesia:foldl(Get, [], Tab) + end, + ?match({atomic, []}, sort_res(mnesia:transaction(DelD, [Tab1]))), + ?match({atomic, []}, sort_res(mnesia:transaction(DelD, [Tab2]))), + ?match({atomic, []}, rev_res(mnesia:transaction(DelD, [Tab3]))), + + ListWrite = fun(Tab) -> %% OTP-3893 + mnesia:write({Tab, [12], 12}), + mnesia:foldr(Get, [], Tab) + end, + ?match({atomic, [{Tab1, [12], 12}]}, sort_res(mnesia:transaction(ListWrite, [Tab1]))), + ?match({atomic, [{Tab2, [12], 12}]}, sort_res(mnesia:transaction(ListWrite, [Tab2]))), + ?match({atomic, [{Tab3, [12], 12}]}, rev_res(mnesia:transaction(ListWrite, [Tab3]))), + + ?verify_mnesia(Nodes, []). + +sort_res({atomic, List}) when is_list(List) -> + {atomic, lists:sort(List)}; +sort_res(Else) when is_list(Else) -> + lists:sort(Else); +sort_res(Else) -> + Else. + +rev_res({atomic, List}) -> + {atomic, lists:reverse(List)}; +rev_res(Else) -> + Else. + + +first_next(doc) -> [""]; +first_next(suite) -> []; +first_next(Config) when is_list(Config) -> + Nodes = [_,N2] = ?acquire_nodes(2, Config), + Tab1 = local, + Tab2 = remote, + Tab3 = ordered, + Tab4 = bag, + Tabs = [Tab1,Tab2,Tab3,Tab4], + + ?match({atomic, ok}, mnesia:create_table(Tab1, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(Tab2, [{ram_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(Tab3, [{ram_copies, Nodes}, + {type, ordered_set}])), + ?match({atomic, ok}, mnesia:create_table(Tab4, [{ram_copies, Nodes}, + {type, bag}])), + + %% Some Helpers + Trans = fun(Fun) -> mnesia:transaction(Fun) end, + Continue = fun(first) -> next; + (last) -> prev + end, + LoopHelp = fun('$end_of_table',_,_,_Fun) -> []; + (Key,Tab,Op,Fun) -> + Next = mnesia:Op(Tab,Key), + [Next |Fun(Next,Tab,Op,Fun)] + end, + Loop = fun(Tab,Start) -> + First = mnesia:Start(Tab), + Res = [First|LoopHelp(First,Tab,Continue(Start),LoopHelp)], + case mnesia:table_info(Tab, type) of + ordered_set when Start == first -> Res; + ordered_set -> + {L1,L2} = lists:split(length(Res)-1,Res), + lists:reverse(L1) ++ L2; + _ -> lists:sort(Res) + end + end, + + %% Verify empty tables + [?match({atomic, ['$end_of_table']}, + Trans(fun() -> Loop(Tab,first) end)) + || Tab <- Tabs], + [?match({atomic, ['$end_of_table']}, + Trans(fun() -> Loop(Tab,last) end)) + || Tab <- Tabs], + %% Verify that trans write is visible inside trans + [?match({atomic, [0,10,'$end_of_table']}, + Trans(fun() -> + mnesia:write({Tab,0,0}), + mnesia:write({Tab,10,10}), + Loop(Tab,first) end)) + || Tab <- Tabs], + [?match({atomic, ['$end_of_table']}, + Trans(fun() -> + mnesia:delete({Tab,0}), + mnesia:delete({Tab,10}), + Loop(Tab,first) end)) + || Tab <- Tabs], + + [?match({atomic, [0,10,'$end_of_table']}, + Trans(fun() -> + mnesia:write({Tab,0,0}), + mnesia:write({Tab,10,10}), + Loop(Tab,last) end)) + || Tab <- Tabs], + [?match({atomic, ['$end_of_table']}, + Trans(fun() -> + mnesia:delete({Tab,0}), + mnesia:delete({Tab,10}), + Loop(Tab,last) end)) + || Tab <- Tabs], + + Tab1Els = [{Tab1, N, N} || N <- lists:seq(1, 5)], + Tab2Els = [{Tab2, N, N} || N <- lists:seq(1, 5)], + Tab3Els = [{Tab3, N, N} || N <- lists:seq(1, 5)], + Tab4Els = [{Tab4, 1, 2} | [{Tab4, N, N} || N <- lists:seq(1, 5)]], + + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab1Els], + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab2Els], + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab3Els], + [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab4Els], + Keys = lists:sort(mnesia:dirty_all_keys(Tab1)), + R1 = Keys++ ['$end_of_table'], + [?match({atomic, R1}, Trans(fun() -> Loop(Tab,first) end)) + || Tab <- Tabs], + + [?match({atomic, R1}, Trans(fun() -> Loop(Tab,last) end)) + || Tab <- Tabs], + R2 = R1 -- [3], + + [?match({atomic, R2}, Trans(fun() -> mnesia:delete({Tab,3}),Loop(Tab,first) end)) + || Tab <- Tabs], + [?match({atomic, R1}, Trans(fun() -> mnesia:write({Tab,3,3}),Loop(Tab,first) end)) + || Tab <- Tabs], + [?match({atomic, R2}, Trans(fun() -> mnesia:delete({Tab,3}),Loop(Tab,last) end)) + || Tab <- Tabs], + [?match({atomic, R1}, Trans(fun() -> mnesia:write({Tab,3,3}),Loop(Tab,last) end)) + || Tab <- Tabs], + [?match({atomic, R1}, Trans(fun() -> mnesia:write({Tab,4,19}),Loop(Tab,first) end)) + || Tab <- Tabs], + [?match({atomic, R1}, Trans(fun() -> mnesia:write({Tab,4,4}),Loop(Tab,last) end)) + || Tab <- Tabs], + + ?verify_mnesia(Nodes, []). + + +snmp_shadows(doc) -> [""]; +snmp_shadows(suite) -> []; +snmp_shadows(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + Tab = snmp_shadows, + io:format("With fixstring~n", []), + ?match({atomic, ok}, mnesia:create_table(Tab,[{snmp,[{key,{fix_string,integer}}]}])), + snmp_shadows_test(Tab), + ?match({atomic, ok}, mnesia:delete_table(Tab)), + io:format("Without fixstring~n", []), + ?match({atomic, ok}, mnesia:create_table(Tab,[{snmp,[{key,{string,integer}}]}])), + snmp_shadows_test(Tab), + ?verify_mnesia(Nodes, []). + +snmp_shadows_test(Tab) -> + [mnesia:dirty_write({Tab, {"string", N}, {N, init}}) || N <- lists:seq(2,8,2)], + + CheckOrder = fun(A={_,_,{_,_,State}}, Prev) -> + ?match({true, A, Prev}, {Prev < A, A, Prev}), + {State,A} + end, + R1 = mnesia:sync_dirty(fun() -> loop_snmp(Tab, []) end), + lists:mapfoldl(CheckOrder, {[],foo,foo}, R1), + R2 = mnesia:transaction(fun() -> loop_snmp(Tab, []) end), + ?match({atomic, R1}, R2), + + Shadow = fun() -> + ok = mnesia:write({Tab, {"string",1}, {1,update}}), + ok = mnesia:write({Tab, {"string",4}, {4,update}}), + ok = mnesia:write({Tab, {"string",6}, {6,update}}), + ok = mnesia:delete({Tab, {"string",6}}), + ok = mnesia:write({Tab, {"string",9}, {9,update}}), + ok = mnesia:write({Tab, {"string",3}, {3,update}}), + ok = mnesia:write({Tab, {"string",5}, {5,update}}), + [Row5] = mnesia:read({Tab, {"string",5}}), + ok = mnesia:delete_object(Row5), + loop_snmp(Tab, []) + end, + R3 = mnesia:sync_dirty(Shadow), + {L3,_} = lists:mapfoldl(CheckOrder, {[],foo,foo}, R3), + ?match([{1,update},{2,init},{3,update},{4,update},{8,init},{9,update}], L3), + ?match({atomic, ok}, mnesia:clear_table(Tab)), + + [mnesia:dirty_write({Tab, {"string", N}, {N, init}}) || N <- lists:seq(2,8,2)], + {atomic, R3} = mnesia:transaction(Shadow), + {L4,_} = lists:mapfoldl(CheckOrder, {[],foo,foo}, R3), + ?match([{1,update},{2,init},{3,update},{4,update},{8,init},{9,update}], L4), + ok. + +loop_snmp(Tab,Prev) -> + case mnesia:snmp_get_next_index(Tab,Prev) of + {ok, SKey} -> + {{ok,Row},_} = {mnesia:snmp_get_row(Tab, SKey),{?LINE,Prev,SKey}}, + {{ok,MKey},_} = {mnesia:snmp_get_mnesia_key(Tab,SKey),{?LINE,Prev,SKey}}, + ?match({[Row],Row,SKey,MKey}, {mnesia:read({Tab,MKey}),Row,SKey,MKey}), + [{SKey, MKey, Row} | loop_snmp(Tab, SKey)]; + endOfTable -> + [] + end. diff --git a/lib/mnesia/test/mnesia_measure_test.erl b/lib/mnesia/test/mnesia_measure_test.erl new file mode 100644 index 0000000000..fbf804dbec --- /dev/null +++ b/lib/mnesia/test/mnesia_measure_test.erl @@ -0,0 +1,203 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_measure_test). +-author('[email protected]'). +-compile([export_all]). + +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +-define(init(N, Config), + mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}, + delete_schema], + N, Config, ?FILE, ?LINE)). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +all(doc) -> + ["Measure various aspects of Mnesia", + "Verify that Mnesia has predictable response times,", + "that the transaction system has fair algoritms,", + "resource consumption, scalabilitym system limits etc.", + "Perform some benchmarks."]; +all(suite) -> + [ + prediction, + consumption, + scalability, + benchmarks + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +prediction(doc) -> + ["The system must have predictable response times.", + "The maintenance of the system should not impact on the", + "availability. Make sure that the response times does not vary too", + "much from the undisturbed normal usage.", + "Verify that deadlocks never occurs."]; +prediction(suite) -> + [ + reader_disturbed_by_node_down, + writer_disturbed_by_node_down, + reader_disturbed_by_node_up, + writer_disturbed_by_node_up, + reader_disturbed_by_schema_ops, + writer_disturbed_by_schema_ops, + reader_disturbed_by_checkpoint, + writer_disturbed_by_checkpoint, + reader_disturbed_by_dump_log, + writer_disturbed_by_dump_log, + reader_disturbed_by_backup, + writer_disturbed_by_backup, + reader_disturbed_by_restore, + writer_disturbed_by_restore, + fairness + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +fairness(doc) -> + ["Verify that the transaction system behaves fair, even under intense", + "stress. Combine different access patterns (transaction profiles)", + "in order to verify that concurrent applications gets a fair share", + "of the database resource. Verify that starvation never may occur."]; +fairness(suite) -> + [ + reader_competing_with_reader, + reader_competing_with_writer, + writer_competing_with_reader, + writer_competing_with_writer + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +consumption(doc) -> + ["Measure the resource consumption and publish the outcome. Make", + "sure that resources are released after failures."]; +consumption(suite) -> + [ + measure_resource_consumption, + determine_resource_leakage + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +scalability(doc) -> + ["Try out where the system limits are. We must at least meet the", + "documented system limits.", + "Redo the performance meters for various configurations and load,", + "especially near system limits."]; +scalability(suite) -> + [ + determine_system_limits, + performance_at_min_config, + performance_at_max_config, + performance_at_full_load, + resource_consumption_at_min_config, + resource_consumption_at_max_config, + resource_consumption_at_full_load + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +benchmarks(doc) -> + ["Measure typical database operations and publish them. Try to", + "verify that new releases of Mnesia always outperforms old", + "releases, or at least that the meters does not get worse."]; +benchmarks(suite) -> + [ + meter, + cost, + dbn_meters, + measure_all_api_functions, + tpcb, + mnemosyne_vs_mnesia_kernel + ]. + +dbn_meters(suite) -> []; +dbn_meters(Config) when is_list(Config) -> + _Nodes = ?init(3, Config), + ?match(ok, mnesia_dbn_meters:start()), + ok. + +tpcb(suite) -> + [ + ram_tpcb, + disc_tpcb, + disc_only_tpcb + ]. + +tpcb(ReplicaType, Config) -> + HarakiriDelay = {tc_timeout, timer:minutes(20)}, + Nodes = ?acquire_nodes(2, Config ++ [HarakiriDelay]), + Args = [{n_branches, 2}, + {n_drivers_per_node, 1}, + {replica_nodes, Nodes}, + {driver_nodes, [hd(Nodes)]}, + {use_running_mnesia, true}, + {use_sticky_locks, true}, + {replica_type, ReplicaType}], + ?match({ok, _}, mnesia_tpcb:start(Args)), + ?verify_mnesia(Nodes, []). + +ram_tpcb(suite) -> []; +ram_tpcb(Config) when is_list(Config) -> + tpcb(ram_copies, Config). + +disc_tpcb(suite) -> []; +disc_tpcb(Config) when is_list(Config) -> + tpcb(disc_copies, Config). + +disc_only_tpcb(suite) -> []; +disc_only_tpcb(Config) when is_list(Config) -> + tpcb(disc_only_copies, Config). + +meter(suite) -> + [ + ram_meter, + disc_meter, + disc_only_meter + ]. + +ram_meter(suite) -> []; +ram_meter(Config) when is_list(Config) -> + HarakiriDelay = [{tc_timeout, timer:minutes(20)}], + Nodes = ?init(3, Config ++ HarakiriDelay), + ?match(ok, mnesia_meter:go(ram_copies, Nodes)). + +disc_meter(suite) -> []; +disc_meter(Config) when is_list(Config) -> + HarakiriDelay = [{tc_timeout, timer:minutes(20)}], + Nodes = ?init(3, Config ++ HarakiriDelay), + ?match(ok, mnesia_meter:go(disc_copies, Nodes)). + +disc_only_meter(suite) -> []; +disc_only_meter(Config) when is_list(Config) -> + HarakiriDelay = [{tc_timeout, timer:minutes(20)}], + Nodes = ?init(3, Config ++ HarakiriDelay), + ?match(ok, mnesia_meter:go(disc_only_copies, Nodes)). + +cost(suite) -> []; +cost(Config) when is_list(Config) -> + Nodes = ?init(3, Config), + ?match(ok, mnesia_cost:go(Nodes)), + file:delete("MNESIA_COST"). diff --git a/lib/mnesia/test/mnesia_meter.erl b/lib/mnesia/test/mnesia_meter.erl new file mode 100644 index 0000000000..68094c4431 --- /dev/null +++ b/lib/mnesia/test/mnesia_meter.erl @@ -0,0 +1,465 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%% Getting started: +%% +%% 1 Start one or more distributed Erlang nodes +%% 2a Connect the nodes, e.g. with net_adm:ping/1 +%% 3a Run mnesia_meter:go() +%% 3b Run mnesia_meter:go(ReplicaType) +%% 3c Run mnesia_meter:go(ReplicaType, Nodes) + +-module(mnesia_meter). +-author('[email protected]'). +-export([ + go/0, + go/1, + go/2, + repeat_meter/2 + ]). + +-record(person, {name, %% atomic, unique key + data, %% compound structure + married_to, %% name of partner or undefined + children}). %% list of children + +-record(meter, {desc, init, meter, micros}). + +-record(result, {desc, list}). + +-define(TIMES, 1000). + +go() -> + go(ram_copies). + +go(ReplicaType) -> + go(ReplicaType, [node() | nodes()]). + +go(ReplicaType, Nodes) -> + {ok, FunOverhead} = tc(fun(_) -> {atomic, ok} end, ?TIMES), + Size = size(term_to_binary(#person{})), + io:format("A fun apply costs ~p micro seconds. Record size is ~p bytes.~n", + [FunOverhead, Size]), + Res = go(ReplicaType, Nodes, [], FunOverhead, []), + NewRes = rearrange(Res, []), + DescHeader = lists:flatten(io_lib:format("~w on ~w", [ReplicaType, Nodes])), + ItemHeader = lists:seq(1, length(Nodes)), + Header = #result{desc = DescHeader, list = ItemHeader}, + SepList = ['--------' || _ <- Nodes], + Separator = #result{desc = "", list = SepList}, + display([Separator, Header, Separator | NewRes] ++ [Separator]). + +go(_ReplicaType, [], _Config, _FunOverhead, Acc) -> + Acc; +go(ReplicaType, [H | T], OldNodes, FunOverhead, Acc) -> + Nodes = [H | OldNodes], + Config = [{ReplicaType, Nodes}], + Res = run(Nodes, Config, FunOverhead), + go(ReplicaType, T, Nodes, FunOverhead, [{ReplicaType, Nodes, Res} | Acc]). + +rearrange([{_ReplicaType, _Nodes, Meters} | Tail], Acc) -> + Acc2 = [add_meter(M, Acc) || M <- Meters], + rearrange(Tail, Acc2); +rearrange([], Acc) -> + Acc. + +add_meter(M, Acc) -> + case lists:keysearch(M#meter.desc, #result.desc, Acc) of + {value, R} -> + R#result{list = [M#meter.micros | R#result.list]}; + false -> + #result{desc = M#meter.desc, list = [M#meter.micros]} + end. + +display(Res) -> + MaxDesc = lists:max([length(R#result.desc) || R <- Res]), + Format = lists:concat(["! ~-", MaxDesc, "s"]), + display(Res, Format, MaxDesc). + +display([R | Res], Format, MaxDesc) -> + case R#result.desc of + "" -> + io:format(Format, [lists:duplicate(MaxDesc, "-")]); + Desc -> + io:format(Format, [Desc]) + end, + display_items(R#result.list, R#result.desc), + io:format(" !~n", []), + display(Res, Format, MaxDesc); +display([], _Format, _MaxDesc) -> + ok. + +display_items([_Item | Items], "") -> + io:format(" ! ~s", [lists:duplicate(10, $-)]), + display_items(Items, ""); +display_items([Micros | Items], Desc) -> + io:format(" ! ~10w", [Micros]), + display_items(Items, Desc); +display_items([], _Desc) -> + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +meters() -> + [#meter{desc = "transaction update two records with read and write", + init = fun write_records/2, + meter = fun update_records/1}, + #meter{desc = "transaction update two records with wread and write", + init = fun write_records/2, + meter = fun w_update_records/1}, + #meter{desc = "transaction update two records with read and s_write", + init = fun s_write_records/2, + meter = fun s_update_records/1}, + #meter{desc = "sync_dirty update two records with read and write", + init = fun sync_dirty_write_records/2, + meter = fun sync_dirty_update_records/1}, + #meter{desc = "async_dirty update two records with read and write", + init = fun async_dirty_write_records/2, + meter = fun async_dirty_update_records/1}, + #meter{desc = "plain fun update two records with dirty_read and dirty_write", + init = fun dirty_write_records/2, + meter = fun dirty_update_records/1}, + #meter{desc = "ets update two records with read and write (local only)", + init = fun ets_opt_write_records/2, + meter = fun ets_update_records/1}, + #meter{desc = "plain fun update two records with ets:lookup and ets:insert (local only)", + init = fun bif_opt_write_records/2, + meter = fun bif_update_records/1}, + #meter{desc = "plain fun update two records with dets:lookup and dets:insert (local only)", + init = fun dets_opt_write_records/2, + meter = fun dets_update_records/1}, + + #meter{desc = "transaction write two records with write", + init = fun write_records/2, + meter = fun(X) -> write_records(X, 0-X) end}, + #meter{desc = "transaction write two records with s_write", + init = fun s_write_records/2, + meter = fun(X) -> s_write_records(X, 0-X) end}, + #meter{desc = "sync_dirty write two records with write", + init = fun sync_dirty_write_records/2, + meter = fun(X) -> sync_dirty_write_records(X, 0-X) end}, + #meter{desc = "async_dirty write two records with write", + init = fun async_dirty_write_records/2, + meter = fun(X) -> async_dirty_write_records(X, 0-X) end}, + #meter{desc = "plain fun write two records with dirty_write", + init = fun dirty_write_records/2, + meter = fun(X) -> dirty_write_records(X, 0-X) end}, + #meter{desc = "ets write two records with write (local only)", + init = fun ets_opt_write_records/2, + meter = fun(X) -> ets_write_records(X, 0-X) end}, + #meter{desc = "plain fun write two records with ets:insert (local only)", + init = fun bif_opt_write_records/2, + meter = fun(X) -> bif_write_records(X, 0-X) end}, + #meter{desc = "plain fun write two records with dets:insert (local only)", + init = fun dets_opt_write_records/2, + meter = fun(X) -> dets_write_records(X, 0-X) end}, + + #meter{desc = "transaction read two records with read", + init = fun write_records/2, + meter = fun(X) -> read_records(X, 0-X) end}, + #meter{desc = "sync_dirty read two records with read", + init = fun sync_dirty_write_records/2, + meter = fun(X) -> sync_dirty_read_records(X, 0-X) end}, + #meter{desc = "async_dirty read two records with read", + init = fun async_dirty_write_records/2, + meter = fun(X) -> async_dirty_read_records(X, 0-X) end}, + #meter{desc = "plain fun read two records with dirty_read", + init = fun dirty_write_records/2, + meter = fun(X) -> dirty_read_records(X, 0-X) end}, + #meter{desc = "ets read two records with read", + init = fun ets_opt_write_records/2, + meter = fun(X) -> ets_read_records(X, 0-X) end}, + #meter{desc = "plain fun read two records with ets:lookup", + init = fun bif_opt_write_records/2, + meter = fun(X) -> bif_read_records(X, 0-X) end}, + #meter{desc = "plain fun read two records with dets:lookup", + init = fun dets_opt_write_records/2, + meter = fun(X) -> dets_read_records(X, 0-X) end} + ]. + +update_fun(Name) -> + fun() -> + case mnesia:read({person, Name}) of + [] -> + mnesia:abort(no_such_person); + [Pers] -> + [Partner] = mnesia:read({person, Pers#person.married_to}), + mnesia:write(Pers#person{married_to = undefined}), + mnesia:write(Partner#person{married_to = undefined}) + end + end. + +update_records(Name) -> + mnesia:transaction(update_fun(Name)). + +sync_dirty_update_records(Name) -> + {atomic, mnesia:sync_dirty(update_fun(Name))}. + +async_dirty_update_records(Name) -> + {atomic, mnesia:async_dirty(update_fun(Name))}. + +ets_update_records(Name) -> + {atomic, mnesia:ets(update_fun(Name))}. + +w_update_records(Name) -> + F = fun() -> + case mnesia:wread({person, Name}) of + [] -> + mnesia:abort(no_such_person); + [Pers] -> + [Partner] = mnesia:wread({person, Pers#person.married_to}), + mnesia:write(Pers#person{married_to = undefined}), + mnesia:write(Partner#person{married_to = undefined}) + end + end, + mnesia:transaction(F). + +s_update_records(Name) -> + F = fun() -> + case mnesia:read({person, Name}) of + [] -> + mnesia:abort(no_such_person); + [Pers] -> + [Partner] = mnesia:read({person, Pers#person.married_to}), + mnesia:s_write(Pers#person{married_to = undefined}), + mnesia:s_write(Partner#person{married_to = undefined}) + end + end, + mnesia:transaction(F). + +dirty_update_records(Name) -> + case mnesia:dirty_read({person, Name}) of + [] -> + mnesia:abort(no_such_person); + [Pers] -> + [Partner] = mnesia:dirty_read({person, Pers#person.married_to}), + mnesia:dirty_write(Pers#person{married_to = undefined}), + mnesia:dirty_write(Partner#person{married_to = undefined}) + end, + {atomic, ok}. + +bif_update_records(Name) -> + case ets:lookup(person, Name) of + [] -> + mnesia:abort(no_such_person); + [Pers] -> + [Partner] = ets:lookup(person, Pers#person.married_to), + ets:insert(person, Pers#person{married_to = undefined}), + ets:insert(person, Partner#person{married_to = undefined}) + end, + {atomic, ok}. + +dets_update_records(Name) -> + case dets:lookup(person, Name) of + [] -> + mnesia:abort(no_such_person); + [Pers] -> + [Partner] = dets:lookup(person, Pers#person.married_to), + dets:insert(person, Pers#person{married_to = undefined}), + dets:insert(person, Partner#person{married_to = undefined}) + end, + {atomic, ok}. + +write_records_fun(Pers, Partner) -> + fun() -> + P = #person{children = [ulla, bella]}, + mnesia:write(P#person{name = Pers, married_to = Partner}), + mnesia:write(P#person{name = Partner, married_to = Pers}) + end. + +write_records(Pers, Partner) -> + mnesia:transaction(write_records_fun(Pers, Partner)). + +sync_dirty_write_records(Pers, Partner) -> + {atomic, mnesia:sync_dirty(write_records_fun(Pers, Partner))}. + +async_dirty_write_records(Pers, Partner) -> + {atomic, mnesia:async_dirty(write_records_fun(Pers, Partner))}. + +ets_write_records(Pers, Partner) -> + {atomic, mnesia:ets(write_records_fun(Pers, Partner))}. + +s_write_records(Pers, Partner) -> + F = fun() -> + P = #person{children = [ulla, bella]}, + mnesia:s_write(P#person{name = Pers, married_to = Partner}), + mnesia:s_write(P#person{name = Partner, married_to = Pers}) + end, + mnesia:transaction(F). + +dirty_write_records(Pers, Partner) -> + P = #person{children = [ulla, bella]}, + mnesia:dirty_write(P#person{name = Pers, married_to = Partner}), + mnesia:dirty_write(P#person{name = Partner, married_to = Pers}), + {atomic, ok}. + +ets_opt_write_records(Pers, Partner) -> + case mnesia:table_info(person, where_to_commit) of + [{N, ram_copies}] when N == node() -> + ets_write_records(Pers, Partner); + _ -> + throw(skipped) + end. + +bif_opt_write_records(Pers, Partner) -> + case mnesia:table_info(person, where_to_commit) of + [{N, ram_copies}] when N == node() -> + bif_write_records(Pers, Partner); + _ -> + throw(skipped) + end. + +bif_write_records(Pers, Partner) -> + P = #person{children = [ulla, bella]}, + ets:insert(person, P#person{name = Pers, married_to = Partner}), + ets:insert(person, P#person{name = Partner, married_to = Pers}), + {atomic, ok}. + +dets_opt_write_records(Pers, Partner) -> + case mnesia:table_info(person, where_to_commit) of + [{N, disc_only_copies}] when N == node() -> + dets_write_records(Pers, Partner); + _ -> + throw(skipped) + end. + +dets_write_records(Pers, Partner) -> + P = #person{children = [ulla, bella]}, + dets:insert(person, P#person{name = Pers, married_to = Partner}), + dets:insert(person, P#person{name = Partner, married_to = Pers}), + {atomic, ok}. + +read_records_fun(Pers, Partner) -> + fun() -> + case {mnesia:read({person, Pers}), + mnesia:read({person, Partner})} of + {[_], [_]} -> + ok; + _ -> + mnesia:abort(no_such_person) + end + end. + +read_records(Pers, Partner) -> + mnesia:transaction(read_records_fun(Pers, Partner)). + +sync_dirty_read_records(Pers, Partner) -> + {atomic, mnesia:sync_dirty(read_records_fun(Pers, Partner))}. + +async_dirty_read_records(Pers, Partner) -> + {atomic, mnesia:async_dirty(read_records_fun(Pers, Partner))}. + +ets_read_records(Pers, Partner) -> + {atomic, mnesia:ets(read_records_fun(Pers, Partner))}. + +dirty_read_records(Pers, Partner) -> + case {mnesia:dirty_read({person, Pers}), + mnesia:dirty_read({person, Partner})} of + {[_], [_]} -> + {atomic, ok}; + _ -> + mnesia:abort(no_such_person) + end. + +bif_read_records(Pers, Partner) -> + case {ets:lookup(person, Pers), + ets:lookup(person, Partner)} of + {[_], [_]} -> + {atomic, ok}; + _ -> + mnesia:abort(no_such_person) + end. + +dets_read_records(Pers, Partner) -> + case {dets:lookup(person, Pers), + dets:lookup(person, Partner)} of + {[_], [_]} -> + {atomic, ok}; + _ -> + mnesia:abort(no_such_person) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +run(Nodes, Config, FunOverhead) -> + Meters = meters(), + io:format("Run ~w meters with table config: ~w~n", [length(Meters), Config]), + rpc:multicall(Nodes, mnesia, lkill, []), + start(Nodes, Config), + Res = [run_meter(Data, Nodes, FunOverhead) || Data <- Meters], + stop(Nodes), + Res. + +run_meter(M, Nodes, FunOverhead) when is_record(M, meter) -> + io:format(".", []), + case catch init_records(M#meter.init, ?TIMES) of + {atomic, ok} -> + rpc:multicall(Nodes, mnesia, dump_log, []), + case tc(M#meter.meter, ?TIMES) of + {ok, Micros} -> + M#meter{micros = lists:max([0, Micros - FunOverhead])}; + {error, Reason} -> + M#meter{micros = Reason} + end; + Res -> + M#meter{micros = Res} + end. + +start(Nodes, Config) -> + mnesia:delete_schema(Nodes), + ok = mnesia:create_schema(Nodes), + Args = [[{dump_log_write_threshold, ?TIMES div 2}, + {dump_log_time_threshold, timer:hours(10)}]], + lists:foreach(fun(Node) -> rpc:call(Node, mnesia, start, Args) end, Nodes), + Attrs = record_info(fields, person), + TabDef = [{attributes, Attrs} | Config], + {atomic, _} = mnesia:create_table(person, TabDef). + +stop(Nodes) -> + rpc:multicall(Nodes, mnesia, stop, []). + +%% Generate some dummy persons +init_records(_Fun, 0) -> + {atomic, ok}; +init_records(Fun, Times) -> + {atomic, ok} = Fun(Times, 0 - Times), + init_records(Fun, Times - 1). + +tc(Fun, Times) -> + case catch timer:tc(?MODULE, repeat_meter, [Fun, Times]) of + {Micros, ok} -> + {ok, Micros div Times}; + {_Micros, {error, Reason}} -> + {error, Reason}; + {'EXIT', Reason} -> + {error, Reason} + end. + +%% The meter must return {atomic, ok} +repeat_meter(Meter, Times) -> + repeat_meter(Meter, {atomic, ok}, Times). + +repeat_meter(_, {atomic, ok}, 0) -> + ok; +repeat_meter(Meter, {atomic, _Result}, Times) when Times > 0 -> + repeat_meter(Meter, Meter(Times), Times - 1); +repeat_meter(_Meter, Reason, _Times) -> + {error, Reason}. + diff --git a/lib/mnesia/test/mnesia_nice_coverage_test.erl b/lib/mnesia/test/mnesia_nice_coverage_test.erl new file mode 100644 index 0000000000..aa9339f6b9 --- /dev/null +++ b/lib/mnesia/test/mnesia_nice_coverage_test.erl @@ -0,0 +1,227 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_nice_coverage_test). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +-record(nice_tab, {key, val}). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Test nice usage of the entire API", + "Invoke all functions in the API, at least once.", + "Try to verify that all functions exists and that they perform", + "reasonable things when used in the most simple way."]; +all(suite) -> [nice]. + +nice(doc) -> [""]; +nice(suite) -> []; +nice(Config) when is_list(Config) -> + %% The whole test suite is one huge test case for the time beeing + + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Attrs = record_info(fields, nice_tab), + + initialize(Attrs, Node1), + dirty_access(Node1), + success_and_fail(), + index_mgt(), + + adm(Attrs, Node1, Node2), + snmp(Node1, Node2), + backup(Node1), + ?verify_mnesia(Nodes, []). + +initialize(Attrs, Node1) -> + ?match(Version when is_list(Version), mnesia:system_info(version)), + + Schema = [{name, nice_tab}, + {attributes, Attrs}, {ram_copies, [Node1]}], + + ?match({_, _}, mnesia:system_info(schema_version)), + ?match({atomic, ok}, mnesia:create_table(Schema)), + + ?match(ok, mnesia:info()), + ?match(set, mnesia:table_info(nice_tab, type)), + ?match(ok, mnesia:schema()), + ?match(ok, mnesia:schema(nice_tab)), + ok. + +dirty_access(Node1) -> + TwoThree = #nice_tab{key=23, val=23}, + TwoFive = #nice_tab{key=25, val=25}, + ?match([], mnesia:dirty_slot(nice_tab, 0)), + ?match(ok, mnesia:dirty_write(TwoThree)), + ?match([TwoThree], mnesia:dirty_read({nice_tab, 23})), + ?match(ok, mnesia:dirty_write(TwoFive)), + ?match(ok, mnesia:dirty_delete_object(TwoFive)), + + ?match(23, mnesia:dirty_first(nice_tab)), + ?match('$end_of_table', mnesia:dirty_next(nice_tab, 23)), + ?match([TwoThree], mnesia:dirty_match_object(TwoThree)), + ?match(ok, mnesia:dirty_delete({nice_tab, 23})), + + CounterSchema = [{ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(nice_counter_tab, CounterSchema)), + TwoFour = {nice_counter_tab, 24, 24}, + ?match(ok, mnesia:dirty_write(TwoFour)), + ?match(34, mnesia:dirty_update_counter({nice_counter_tab, 24}, 10)), + TF = {nice_counter_tab, 24, 34}, + ?match([TF], mnesia:dirty_read({nice_counter_tab, 24})), + ?match(ok, mnesia:dirty_delete({nice_counter_tab, 24})), + ?match(ok, mnesia:dirty_delete_object(TF)), + ok. + +success_and_fail() -> + ?match({atomic, a_good_trans}, mnesia:transaction(fun() ->good_trans()end)), + + BadFun = + fun() -> + Two = #nice_tab{key=2, val=12}, + ?match([Two], mnesia:match_object(#nice_tab{key='$1', val=12})), + ?match([#nice_tab{key=3, val=13}], mnesia:wread({nice_tab, 3})), + ?match(ok, mnesia:delete({nice_tab, 1})), + ?match(ok, mnesia:delete_object(Two)), + mnesia:abort(bad_trans), + ?match(bad, trans) + end, + ?match({aborted, bad_trans}, mnesia:transaction(BadFun)), + ?match(L when is_list(L), mnesia:error_description(no_exists)), + ?match({atomic, ok}, mnesia:transaction(fun(A) -> lock(), A end, [ok])), + ?match({atomic, ok}, mnesia:transaction(fun(A) -> lock(), A end, [ok], 3)), + ok. + +good_trans() -> + ?match([], mnesia:read(nice_tab, 3)), + ?match([], mnesia:read({nice_tab, 3})), + ?match(ok, mnesia:write(#nice_tab{key=14, val=4})), + ?match([14], mnesia:all_keys(nice_tab)), + + Records = [ #nice_tab{key=K, val=K+10} || K <- lists:seq(1, 10) ], + Ok = [ ok || _ <- Records], + ?match(Ok, lists:map(fun(R) -> mnesia:write(R) end, Records)), + a_good_trans. + + +lock() -> + ?match(ok, mnesia:s_write(#nice_tab{key=22, val=22})), + ?match(ok, mnesia:read_lock_table(nice_tab)), + ?match(ok, mnesia:write_lock_table(nice_tab)), + ok. + +index_mgt() -> + UniversalRec = #nice_tab{key=4711, val=4711}, + ?match(ok, mnesia:dirty_write(UniversalRec)), + ValPos = #nice_tab.val, + ?match({atomic, ok}, mnesia:add_table_index(nice_tab, ValPos)), + + IndexFun = + fun() -> + ?match([UniversalRec], + mnesia:index_read(nice_tab, 4711, ValPos)), + Pat = #nice_tab{key='$1', val=4711}, + ?match([UniversalRec], + mnesia:index_match_object(Pat, ValPos)), + index_trans + end, + ?match({atomic, index_trans}, mnesia:transaction(IndexFun, infinity)), + ?match([UniversalRec], + mnesia:dirty_index_read(nice_tab, 4711, ValPos)), + ?match([UniversalRec], + mnesia:dirty_index_match_object(#nice_tab{key='$1', val=4711}, ValPos)), + + ?match({atomic, ok}, mnesia:del_table_index(nice_tab, ValPos)), + ok. + +adm(Attrs, Node1, Node2) -> + This = node(), + ?match({ok, This}, mnesia:subscribe(system)), + ?match({atomic, ok}, + mnesia:add_table_copy(nice_tab, Node2, disc_only_copies)), + ?match({atomic, ok}, + mnesia:change_table_copy_type(nice_tab, Node2, ram_copies)), + ?match({atomic, ok}, mnesia:del_table_copy(nice_tab, Node1)), + ?match(stopped, rpc:call(Node1, mnesia, stop, [])), + ?match([], mnesia_test_lib:start_mnesia([Node1, Node2], [nice_tab])), + ?match(ok, mnesia:wait_for_tables([schema], infinity)), + + Transformer = fun(Rec) -> + list_to_tuple(tuple_to_list(Rec) ++ [initial_value]) + end, + ?match({atomic, ok}, + mnesia:transform_table(nice_tab, Transformer, Attrs ++ [extra])), + + ?match({atomic, ok}, mnesia:delete_table(nice_tab)), + DumpSchema = [{name, nice_tab}, {attributes, Attrs}, {ram_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(DumpSchema)), + ?match({atomic, ok}, mnesia:dump_tables([nice_tab])), + ?match({atomic, ok}, mnesia:move_table_copy(nice_tab, Node2, Node1)), + + ?match(yes, mnesia:force_load_table(nice_counter_tab)), + ?match(dumped, mnesia:dump_log()), + ok. + +backup(Node1) -> + Tab = backup_nice, + Def = [{disc_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match({ok,_,_}, mnesia:activate_checkpoint([{name, cp}, {max, [Tab]}])), + File = "nice_backup.BUP", + File2 = "nice_backup2.BUP", + File3 = "nice_backup3.BUP", + ?match(ok, mnesia:backup_checkpoint(cp, File)), + ?match(ok, mnesia:backup_checkpoint(cp, File, mnesia_backup)), + ?match(ok, mnesia:deactivate_checkpoint(cp)), + ?match(ok, mnesia:backup(File)), + ?match(ok, mnesia:backup(File, mnesia_backup)), + + Fun = fun(X, Acc) -> {[X], Acc} end, + ?match({ok, 0}, mnesia:traverse_backup(File, File2, Fun, 0)), + ?match({ok, 0}, mnesia:traverse_backup(File, mnesia_backup, dummy, read_only, Fun, 0)), + ?match(ok, mnesia:install_fallback(File)), + ?match(ok, mnesia:uninstall_fallback()), + ?match(ok, mnesia:install_fallback(File, mnesia_backup)), + ?match(ok, mnesia:dump_to_textfile(File3)), + ?match({atomic, ok}, mnesia:load_textfile(File3)), + ?match(ok, file:delete(File)), + ?match(ok, file:delete(File2)), + ?match(ok, file:delete(File3)), + ok. + +snmp(Node1, Node2) -> + Tab = nice_snmp, + Def = [{disc_copies, [Node1]}, {ram_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match({aborted, {badarg, Tab, _}}, mnesia:snmp_open_table(Tab, [])), + ?match({atomic, ok}, mnesia:snmp_open_table(Tab, [{key, integer}])), + ?match(endOfTable, mnesia:snmp_get_next_index(Tab, [0])), + ?match(undefined, mnesia:snmp_get_row(Tab, [0])), + ?match(undefined, mnesia:snmp_get_mnesia_key(Tab, [0])), + ?match({atomic, ok}, mnesia:snmp_close_table(Tab)), + ok. + diff --git a/lib/mnesia/test/mnesia_qlc_test.erl b/lib/mnesia/test/mnesia_qlc_test.erl new file mode 100644 index 0000000000..1e4f776c7d --- /dev/null +++ b/lib/mnesia/test/mnesia_qlc_test.erl @@ -0,0 +1,475 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_qlc_test). + +-compile(export_all). + +-export([all/1]). + +-include("mnesia_test_lib.hrl"). +-include_lib("stdlib/include/qlc.hrl"). + +init_per_testcase(Func, Conf) -> + setup(Conf), + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +all(doc) -> + ["Test that the qlc mnesia interface works as expected."]; +all(suite) -> + case code:which(qlc) of + non_existing -> []; + _ -> + all_qlc() + end. + +all_qlc() -> + [dirty, trans, frag, info, mnesia_down]. + +init_testcases(Type,Config) -> + Nodes = [N1,N2] = ?acquire_nodes(2, Config), + ?match({atomic, ok}, mnesia:create_table(a, [{Type,[N1]}, {index,[3]}])), + ?match({atomic, ok}, mnesia:create_table(b, [{Type,[N2]}])), + Write = fun(Id) -> + ok = mnesia:write({a, {a,Id}, 100 - Id}), + ok = mnesia:write({b, {b,100-Id}, Id}) + end, + All = fun() -> [Write(Id) || Id <- lists:seq(1,10)], ok end, + ?match({atomic, ok}, mnesia:sync_transaction(All)), + Nodes. + +%% Test cases +dirty(suite) -> + [dirty_nice_ram_copies, + dirty_nice_disc_copies, + dirty_nice_disc_only_copies]. + +dirty_nice_ram_copies(Setup) -> dirty_nice(Setup,ram_copies). +dirty_nice_disc_copies(Setup) -> dirty_nice(Setup,disc_copies). +dirty_nice_disc_only_copies(Setup) -> dirty_nice(Setup,disc_only_copies). + +dirty_nice(suite, _) -> []; +dirty_nice(doc, _) -> []; +dirty_nice(Config, Type) when is_list(Config) -> + Ns = init_testcases(Type,Config), + QA = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(a)," + " Val == 90 + Key]">>), + QB = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(b)," + " Key == 90 + Val]">>), + QC = qlc:sort(mnesia:table(a, [{n_objects,1}, {lock,write}, {traverse, select}])), + QD = qlc:sort(mnesia:table(a, [{n_objects,1}, {traverse,{select,[{'$1',[],['$1']}]}}])), + + FA = fun() -> qlc:e(QA) end, + FB = fun() -> qlc:e(QB) end, + FC = fun() -> qlc:e(QC) end, + FD = fun() -> qlc:e(QD) end, + + %% Currently unsupported + ?match({'EXIT',{aborted,no_transaction}}, FA()), + ?match({'EXIT',{aborted,no_transaction}}, FB()), + %% + CRes = lists:sort(mnesia:dirty_match_object(a, {'_','_','_'})), + ?match([{a,{a,5},95}], mnesia:async_dirty(FA)), + ?match([{b,{b,95},5}], mnesia:async_dirty(FB)), + ?match(CRes, mnesia:async_dirty(FC)), + ?match(CRes, mnesia:async_dirty(FD)), + ?match([{a,{a,5},95}], mnesia:sync_dirty(FA)), + ?match([{b,{b,95},5}], mnesia:sync_dirty(FB)), + ?match(CRes, mnesia:sync_dirty(FC)), + ?match([{a,{a,5},95}], mnesia:activity(async_dirty, FA)), + ?match([{b,{b,95},5}], mnesia:activity(async_dirty, FB)), + ?match([{a,{a,5},95}], mnesia:activity(sync_dirty, FA)), + ?match([{b,{b,95},5}], mnesia:activity(sync_dirty, FB)), + ?match(CRes, mnesia:activity(async_dirty,FC)), + case Type of + disc_only_copies -> skip; + _ -> + ?match([{a,{a,5},95}], mnesia:ets(FA)), + ?match([{a,{a,5},95}], mnesia:activity(ets, FA)) + end, + ?verify_mnesia(Ns, []). + +trans(suite) -> + [trans_nice_ram_copies, + trans_nice_disc_copies, + trans_nice_disc_only_copies, + atomic + ]. + +trans_nice_ram_copies(Setup) -> trans_nice(Setup,ram_copies). +trans_nice_disc_copies(Setup) -> trans_nice(Setup,disc_copies). +trans_nice_disc_only_copies(Setup) -> trans_nice(Setup,disc_only_copies). + +trans_nice(suite, _) -> []; +trans_nice(doc, _) -> []; +trans_nice(Config, Type) when is_list(Config) -> + Ns = init_testcases(Type,Config), + QA = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(a)," + " Val == 90 + Key]">>), + QB = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(b)," + " Key == 90 + Val]">>), + QC = handle(recs(), + <<"[Q || Q = #a{v=91} <- mnesia:table(a)]" + >>), + + QD = qlc:sort(mnesia:table(a, [{n_objects,1}, {lock,write}, {traverse, select}])), + QE = qlc:sort(mnesia:table(a, [{n_objects,1}, {traverse,{select,[{'$1',[],['$1']}]}}])), + + DRes = lists:sort(mnesia:dirty_match_object(a, {'_','_','_'})), + + FA = fun() -> qlc:e(QA) end, + FB = fun() -> qlc:e(QB) end, + FC = fun() -> qlc:e(QC) end, + FD = fun() -> qlc:e(QD) end, + FE = fun() -> qlc:e(QE) end, + + ?match({atomic,[{a,{a,5},95}]}, mnesia:transaction(FA)), + ?match({atomic,[{b,{b,95},5}]}, mnesia:transaction(FB)), + ?match({atomic,[{a,{a,9},91}]}, mnesia:transaction(FC)), + ?match({atomic,[{a,{a,5},95}]}, mnesia:sync_transaction(FA)), + ?match({atomic,[{b,{b,95},5}]}, mnesia:sync_transaction(FB)), + ?match({atomic,[{a,{a,9},91}]}, mnesia:sync_transaction(FC)), + ?match([{a,{a,5},95}], mnesia:activity(transaction,FA)), + ?match([{b,{b,95},5}], mnesia:activity(transaction,FB)), + ?match([{a,{a,9},91}], mnesia:activity(transaction,FC)), + ?match([{a,{a,5},95}], mnesia:activity(sync_transaction,FA)), + ?match([{b,{b,95},5}], mnesia:activity(sync_transaction,FB)), + ?match([{a,{a,9},91}], mnesia:activity(sync_transaction,FC)), + + ?match({atomic, DRes}, mnesia:transaction(FD)), + ?match({atomic, DRes}, mnesia:transaction(FE)), + + Rest = fun(Cursor,Loop) -> + case qlc:next_answers(Cursor, 1) of + [] -> []; + [A]-> [A|Loop(Cursor,Loop)] + end + end, + Loop = fun() -> + Cursor = qlc:cursor(QD), + Rest(Cursor,Rest) + end, + ?match({atomic, DRes}, mnesia:transaction(Loop)), + + ?verify_mnesia(Ns, []). + +%% -record(a, {k,v}). +%% -record(b, {k,v}). +%% -record(k, {t,v}). + +recs() -> + <<"-record(a, {k,v}). " + "-record(b, {k,v}). " + "-record(k, {t,v}). " + >>. + +atomic(suite) -> [atomic_eval]; +atomic(doc) -> []. + +atomic_eval(suite) -> []; +atomic_eval(doc) -> []; +atomic_eval(Config) -> + Ns = init_testcases(ram_copies, Config), + Q1 = handle(recs(), + <<"[Q || Q = #a{k={_,9}} <- mnesia:table(a)]" + >>), + Eval = fun(Q) -> + {qlc:e(Q), + mnesia:system_info(held_locks)} + end, + Self = self(), + ?match({[{a,{a,9},91}], [{{a,'______WHOLETABLE_____'},read,{tid,_,Self}}]}, + ok(Eval,[Q1])), + + Q2 = handle(recs(), + <<"[Q || Q = #a{k={a,9}} <- mnesia:table(a)]" + >>), + + ?match({[{a,{a,9},91}],[{{a,{a,9}},read,{tid,_,Self}}]}, + ok(Eval,[Q2])), + + Flush = fun(Loop) -> %% Clean queue + receive _ -> Loop(Loop) + after 0 -> ok end + end, + + Flush(Flush), + + GrabLock = fun(Father) -> + mnesia:read(a, {a,9}, write), + Father ! locked, + receive cont -> ok end end, + + Pid1 = spawn(fun() -> ?match(ok, ok(GrabLock, [Self])) end), + ?match(locked,receive locked -> locked after 5000 -> timeout end), %% Wait + + put(count,0), + Restart = fun(Locker,Fun) -> + Count = get(count), + case {Count,(catch Fun())} of + {0, {'EXIT', R}} -> + Locker ! cont, + put(count, Count+1), + erlang:yield(), + exit(R); + Else -> + Else + end + end, + + ?match({1,{[{a,{a,9},91}], [{{a,'______WHOLETABLE_____'},read,{tid,_,Self}}]}}, + ok(Restart,[Pid1,fun() -> Eval(Q1) end])), + + Pid2 = spawn(fun() -> ?match(ok, ok(GrabLock, [Self])) end), + ?match(locked,receive locked -> locked after 5000 -> timeout end), %% Wait + put(count,0), + ?match({1,{[{a,{a,9},91}],[{{a,{a,9}},read,{tid,_,Self}}]}}, + ok(Restart,[Pid2, fun() -> Eval(Q2) end])), + +%% Basic test + Cursor = fun() -> + QC = qlc:cursor(Q1), + qlc:next_answers(QC) + end, + + ?match([{a,{a,9},91}], ok(Cursor, [])), + %% Lock + + Pid3 = spawn(fun() -> ?match(ok, ok(GrabLock, [Self])) end), + ?match(locked,receive locked -> locked after 5000 -> timeout end), %% Wait + put(count,0), + + ?match({1,[{a,{a,9},91}]}, ok(Restart,[Pid3, Cursor])), + QC1 = ok(fun() -> qlc:cursor(Q1) end, []), + ?match({'EXIT', _}, qlc:next_answers(QC1)), + ?match({aborted,_}, ok(fun()->qlc:next_answers(QC1)end,[])), + ?verify_mnesia(Ns, []). + + +frag(suite) -> []; +frag(doc) -> []; +frag(Config) -> + Ns = init_testcases(ram_copies,Config), + QA = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(a)," + " Val == 90 + Key]">>), + QB = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(b)," + " Key == 90 + Val]">>), + + Activate = + fun(Tab) -> + ?match({atomic,ok},mnesia:change_table_frag(Tab, {activate, []})), + Dist = mnesia_frag_test:frag_dist(Tab), + ?match({atomic,ok},mnesia:change_table_frag(Tab,{add_frag,Dist})) + end, + Activate(a), + Activate(b), + + Fun = fun(Tab) -> mnesia:table_info(Tab, frag_names) end, + FTs = mnesia:activity(sync_dirty, Fun, [a], mnesia_frag) ++ + mnesia:activity(sync_dirty, Fun, [b], mnesia_frag), + Size = fun(Tab) -> mnesia:dirty_rpc(Tab, mnesia, table_info, [Tab,size]) end, + + %% Verify that all data doesn't belong to the same frag. + ?match([], [{Tab,Size(Tab)} || Tab <- FTs, + Size(Tab) =< 0]), + + FA = fun() -> qlc:e(QA) end, + FB = fun() -> qlc:e(QB) end, + ?match([{a,{a,5},95}], mnesia:activity(transaction,FA,[],mnesia_frag)), + ?match([{b,{b,95},5}], mnesia:activity(transaction,FB,[],mnesia_frag)), + + ?verify_mnesia(Ns, []). + +info(suite) -> []; +info(doc) -> []; +info(Config) -> + Ns = init_testcases(ram_copies, Config), + Q1 = handle(recs(), + <<"[Q || Q = #a{k={_,9}} <- mnesia:table(a)]" + >>), + + Q2 = handle(recs(), + <<"[Q || Q = #a{k={a,9}} <- mnesia:table(a)]" + >>), + + Q3 = handle(recs(), + <<"[Q || Q = #a{v=91} <- mnesia:table(a)]" + >>), + + %% FIXME compile and check results! + + ?match(ok,io:format("~s~n",[qlc:info(Q1)])), + ?match(ok,io:format("~s~n",[qlc:info(Q2)])), + ?match(ok,io:format("~s~n",[qlc:info(Q3)])), + + ?verify_mnesia(Ns, []). + +ok(Fun,A) -> + case mnesia:transaction(Fun,A) of + {atomic, R} -> R; + E -> E + end. + + +mnesia_down(suite) -> []; +mnesia_down(doc) -> + ["Test bug OTP-7968, which crashed mnesia when a" + "mnesia_down came after qlc had been invoked"]; +mnesia_down(Config) when is_list(Config) -> + [N1,N2] = init_testcases(ram_copies,Config), + QB = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(b)," + " Val == Key - 90]">>), + + Tester = self(), + + Eval = fun() -> + Cursor = qlc:cursor(QB), %% Forces another process + Res = qlc:next_answers(Cursor), + Tester ! {qlc, self(), Res}, + {Mod, Tid, Ts} = get(mnesia_activity_state), + receive + continue -> + io:format("Continuing ~p ~p ~n",[self(), {Mod, Tid, Ts}]), + io:format("ETS ~p~n",[ets:tab2list(element(2,Ts))]), + io:format("~p~n",[process_info(self(),messages)]), + Res + end + end, + spawn(fun() -> TransRes = mnesia:transaction(Eval), Tester ! {test,TransRes} end), + + TMInfo = fun() -> + TmInfo = mnesia_tm:get_info(5000), + mnesia_tm:display_info(user, TmInfo) + end, + receive + {qlc, QPid, QRes} -> + ?match([{b,{b,95},5}], QRes), + TMInfo(), + mnesia_test_lib:kill_mnesia([N2]), + %%timer:sleep(1000), + QPid ! continue + after 2000 -> + exit(timeout1) + end, + + receive + {test, QRes2} -> + ?match({atomic, [{b,{b,95},5}]}, QRes2) + after 2000 -> + exit(timeout2) + end, + + ?verify_mnesia([N1], [N2]). + + +nested_qlc(suite) -> []; +nested_qlc(doc) -> + ["Test bug in OTP-7968 (the second problem) where nested" + "transaction don't work as expected"]; +nested_qlc(Config) when is_list(Config) -> + Ns = init_testcases(ram_copies,Config), + Res = as_with_bs(), + ?match([_|_], Res), + top_as_with_some_bs(10), + + ?verify_mnesia(Ns, []). + + +%% Code from Daniel +bs_by_a_id(A_id) -> + find(qlc:q([ B || B={_,_,F_id} <- mnesia:table(b), F_id == A_id])). + +as_with_bs() -> + find(qlc:q([ {A,bs_by_a_id(Id)} || + A = {_, {a,Id}, _} <- mnesia:table(a)])). + +top_as_with_some_bs(Limit) -> + top( + qlc:q([ {A,bs_by_a_id(Id)} || + A = {_, {a,Id}, _} <- mnesia:table(a)]), + Limit, + fun(A1,A2) -> A1 < A2 end + ). + +% --- utils + +find(Q) -> + F = fun() -> qlc:e(Q) end, + {atomic, Res} = mnesia:transaction(F), + Res. + +% --- it returns top Limit results from query Q ordered by Order sort function +top(Q, Limit, Order) -> + Do = fun() -> + OQ = qlc:sort(Q, [{order,Order}]), + QC = qlc:cursor(OQ), + Res = qlc:next_answers(QC, Limit), + qlc:delete_cursor(QC), + Res + end, + {atomic, Res} = mnesia:transaction(Do), + Res. + +%% To keep mnesia suite backward compatible, +%% we compile the queries in runtime when qlc is available +%% Compiles and returns a handle to a qlc +handle(Expr) -> + handle(<<>>,Expr). +handle(Records,Expr) -> + case catch handle2(Records,Expr) of + {ok, Handle} -> + Handle; + Else -> + ?match(ok, Else) + end. + +handle2(Records,Expr) -> + {FN,Mod} = temp_name(), + ModStr = list_to_binary("-module(" ++ atom_to_list(Mod) ++ ").\n"), + Prog = << + ModStr/binary, + "-include_lib(\"stdlib/include/qlc.hrl\").\n", + "-export([tmp/0]).\n", + Records/binary,"\n", + "tmp() ->\n", +%% " _ = (catch throw(fvalue_not_reset))," + " qlc:q( ", + Expr/binary,").\n">>, + + ?match(ok,file:write_file(FN,Prog)), + {ok,Forms} = epp:parse_file(FN,"",""), + {ok,Mod,Bin} = compile:forms(Forms), + code:load_binary(Mod,FN,Bin), + {ok, Mod:tmp()}. + +setup(Config) -> + put(mts_config,Config), + put(mts_tf_counter,0). + +temp_name() -> + Conf = get(mts_config), + C = get(mts_tf_counter), + put(mts_tf_counter,C+1), + {filename:join([proplists:get_value(priv_dir,Conf, "."), + "tempfile"++integer_to_list(C)++".tmp"]), + list_to_atom("tmp" ++ integer_to_list(C))}. diff --git a/lib/mnesia/test/mnesia_recovery_test.erl b/lib/mnesia/test/mnesia_recovery_test.erl new file mode 100644 index 0000000000..f6ecf2ce2e --- /dev/null +++ b/lib/mnesia/test/mnesia_recovery_test.erl @@ -0,0 +1,1701 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_recovery_test). +-author('[email protected]'). +-compile([export_all]). + +-include("mnesia_test_lib.hrl"). +-include_lib("kernel/include/file.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +-define(receive_messages(Msgs), receive_messages(Msgs, ?FILE, ?LINE)). + +% First Some debug logging +-define(dgb, true). +-ifdef(dgb). +-define(dl(X, Y), ?verbose("**TRACING: " ++ X ++ "**~n", Y)). +-else. +-define(dl(X, Y), ok). +-endif. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Verify recoverability", + "Verify that the effects of committed transactions are preserved", + "after recovery from system failures. It must be possible to", + "restore the tables to a consistent state on a node, from (any kind", + "of) replica on other nodes as well as from local disk on the failed", + "node. The system must also recover from instantaneous", + "interruption causing disk files to not be completely synchronized."]; + +all(suite) -> + [ + mnesia_down, + explicit_stop, + coord_dies, + schema_trans, + async_dirty, + sync_dirty, + sym_trans, + asym_trans, + after_full_disc_partition, + after_corrupt_files, + disc_less, + garb_decision, + system_upgrade + ]. + +schema_trans(suite) -> + [{mnesia_schema_recovery_test, all}]. + +tpcb_config(ReplicaType, _NodeConfig, Nodes) -> + [{n_branches, 5}, + {n_drivers_per_node, 5}, + {replica_nodes, Nodes}, + {driver_nodes, Nodes}, + {use_running_mnesia, true}, + {report_interval, infinity}, + {n_accounts_per_branch, 20}, + {replica_type, ReplicaType}]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +mnesia_down(doc) -> + [" Various tests about recovery when mnesia goes down on one or several nodes."]; +mnesia_down(suite) -> + [ + mnesia_down_during_startup, + master_node_tests, + read_during_down, + with_checkpoint, + delete_during_start + ]. + +master_node_tests(doc) -> + ["Verify that mnesia loads the correct data after it has been down, regarding master node settings."]; +master_node_tests(suite) -> + [ + no_master_2, + no_master_3, + one_master_2, + one_master_3, + two_master_2, + two_master_3, + all_master_2, + all_master_3 + ]. + +no_master_2(suite) -> []; +no_master_2(Config) when is_list(Config) -> mnesia_down_2(no, Config). + +no_master_3(suite) -> []; +no_master_3(Config) when is_list(Config) -> mnesia_down_3(no, Config). + +one_master_2(suite) -> []; +one_master_2(Config) when is_list(Config) -> mnesia_down_2(one, Config). + +one_master_3(suite) -> []; +one_master_3(Config) when is_list(Config) -> mnesia_down_3(one, Config). + +two_master_2(suite) -> []; +two_master_2(Config) when is_list(Config) -> mnesia_down_2(two, Config). + +two_master_3(suite) -> []; +two_master_3(Config) when is_list(Config) -> mnesia_down_3(two, Config). + +all_master_2(suite) -> []; +all_master_2(Config) when is_list(Config) -> mnesia_down_2(all, Config). + +all_master_3(suite) -> []; +all_master_3(Config) when is_list(Config) -> mnesia_down_3(all, Config). + +mnesia_down_2(Masters, Config) -> + Nodes = [N1, N2] = ?acquire_nodes(2, Config), + ?match({atomic, ok}, mnesia:create_table(tab1, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(tab2, [{disc_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(tab3, [{disc_only_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(tab4, [{ram_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab5, [{ram_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab6, [{disc_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab7, [{disc_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab8, [{disc_only_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab9, [{disc_only_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab10, [{ram_copies, [N1]}, {disc_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab11, [{ram_copies, [N2]}, {disc_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab12, [{ram_copies, [N1]}, {disc_only_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab13, [{ram_copies, [N2]}, {disc_only_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab14, [{disc_only_copies, [N1]}, {disc_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab15, [{disc_only_copies, [N2]}, {disc_copies, [N1]}])), + + Tabs = [tab1, tab2, tab3, tab4, tab5, tab6, tab7, tab8, + tab9, tab10, tab11, tab12, tab13, tab14, tab15], + [?match(ok, rpc:call(Node, mnesia, wait_for_tables, [Tabs, 10000])) || Node <- Nodes], + [insert_data(Tab, 20) || Tab <- Tabs], + + VTabs = + case Masters of + no -> + Tabs -- [tab4, tab5]; % ram copies + one -> + ?match(ok, rpc:call(N1, mnesia, set_master_nodes, [[N1]])), + Tabs -- [tab1, tab4, tab5, tab10, tab12]; % ram_copies + two -> + ?match(ok, rpc:call(N1, mnesia, set_master_nodes, [Nodes])), + Tabs -- [tab4, tab5]; + all -> + [?match(ok, rpc:call(Node, mnesia, set_master_nodes, [[Node]])) || Node <- Nodes], + Tabs -- [tab1, tab4, tab5, tab10, tab11, tab12, tab13] + end, + + mnesia_test_lib:kill_mnesia([N1]), + ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)), + + ?match([], mnesia_test_lib:kill_mnesia([N2])), + ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)), + + [?match(ok, rpc:call(N1, ?MODULE, verify_data, [Tab, 20])) || Tab <- VTabs], + [?match(ok, rpc:call(N2, ?MODULE, verify_data, [Tab, 20])) || Tab <- VTabs], + ?verify_mnesia(Nodes, []). + +mnesia_down_3(Masters, Config) -> + Nodes = [N1, N2, N3] = ?acquire_nodes(3, Config), + ?match({atomic, ok}, mnesia:create_table(tab1, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(tab2, [{disc_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(tab3, [{disc_only_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(tab4, [{ram_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab5, [{ram_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab16, [{ram_copies, [N3]}])), + ?match({atomic, ok}, mnesia:create_table(tab6, [{disc_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab7, [{disc_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab17, [{disc_copies, [N3]}])), + ?match({atomic, ok}, mnesia:create_table(tab8, [{disc_only_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab9, [{disc_only_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab18, [{disc_only_copies, [N3]}])), + ?match({atomic, ok}, mnesia:create_table(tab10, [{ram_copies, [N1]}, {disc_copies, [N2, N3]}])), + ?match({atomic, ok}, mnesia:create_table(tab11, [{ram_copies, [N2]}, {disc_copies, [N3, N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab19, [{ram_copies, [N3]}, {disc_copies, [N1, N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab12, [{ram_copies, [N1]}, {disc_only_copies, [N2, N3]}])), + ?match({atomic, ok}, mnesia:create_table(tab13, [{ram_copies, [N2]}, {disc_only_copies, [N3, N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab20, [{ram_copies, [N3]}, {disc_only_copies, [N1, N2]}])), + ?match({atomic, ok}, mnesia:create_table(tab14, [{disc_only_copies, [N1]}, {disc_copies, [N2, N3]}])), + ?match({atomic, ok}, mnesia:create_table(tab15, [{disc_only_copies, [N2]}, {disc_copies, [N3, N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab21, [{disc_only_copies, [N3]}, {disc_copies, [N1, N2]}])), + + Tabs = [tab1, tab2, tab3, tab4, tab5, tab6, tab7, tab8, + tab9, tab10, tab11, tab12, tab13, tab14, tab15, + tab16, tab17, tab18, tab19, tab20, tab21], + [?match(ok, rpc:call(Node, mnesia, wait_for_tables, [Tabs, 10000])) || Node <- Nodes], + [insert_data(Tab, 20) || Tab <- Tabs], + + VTabs = + case Masters of + no -> + Tabs -- [tab4, tab5, tab16]; % ram copies + one -> + ?match(ok, rpc:call(N1, mnesia, set_master_nodes, [[N1]])), + Tabs -- [tab1, tab4, tab5, tab16, tab10, tab12]; % ram copies + two -> + ?match(ok, rpc:call(N1, mnesia, set_master_nodes, [Nodes])), + Tabs -- [tab4, tab5, tab16]; % ram copies + all -> + [?match(ok, rpc:call(Node, mnesia, set_master_nodes, [[Node]])) || Node <- Nodes], + Tabs -- [tab1, tab4, tab5, tab16, tab10, + tab11, tab19, tab12, tab13, tab20] % ram copies + end, + + mnesia_test_lib:kill_mnesia([N1]), + ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)), + + ?match([], mnesia_test_lib:kill_mnesia([N2])), + ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)), + + ?match([], mnesia_test_lib:kill_mnesia([N3])), + ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)), + + ?match([], mnesia_test_lib:kill_mnesia([N2, N1])), + ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)), + + ?match([], mnesia_test_lib:kill_mnesia([N2, N3])), + ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)), + + ?match([], mnesia_test_lib:kill_mnesia([N1, N3])), + ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)), + + [?match(ok, rpc:call(N1, ?MODULE, verify_data, [Tab, 20])) || Tab <- VTabs], + [?match(ok, rpc:call(N2, ?MODULE, verify_data, [Tab, 20])) || Tab <- VTabs], + [?match(ok, rpc:call(N3, ?MODULE, verify_data, [Tab, 20])) || Tab <- VTabs], + + ?verify_mnesia(Nodes, []). + + +read_during_down(doc) -> + ["Verify that read operation can continue to read when mnesia goes down"]; +read_during_down(suite) -> + [ + dirty_read_during_down, + trans_read_during_down + ]. + +dirty_read_during_down(suite) -> + []; +dirty_read_during_down(Config) when is_list(Config) -> + read_during_down(dirty, Config). + +trans_read_during_down(suite) -> + []; +trans_read_during_down(Config) when is_list(Config) -> + read_during_down(trans, Config). + + +read_during_down(Op, Config) when is_list(Config) -> + Ns = [N1|TNs] = ?acquire_nodes(3, Config), + Tabs = [ram, disc, disco], + + ?match({atomic, ok}, mnesia:create_table(ram, [{ram_copies, TNs}])), + ?match({atomic, ok}, mnesia:create_table(disc, [{disc_copies, TNs}])), + ?match({atomic, ok}, mnesia:create_table(disco, [{disc_only_copies, TNs}])), + + %% Create some work for mnesia_controller when a node goes down + [{atomic, ok} = mnesia:create_table(list_to_atom("temp" ++ integer_to_list(N)), + [{ram_copies, Ns}]) || N <- lists:seq(1, 50)], + + Write = fun(Tab) -> mnesia:write({Tab, key, val}) end, + ?match([ok,ok,ok], + [mnesia:sync_dirty(Write, [Tab]) || Tab <- Tabs]), + + Readers = [spawn_link(N1, ?MODULE, reader, [Tab, Op]) || Tab <- Tabs], + [_|_] = W2R= [mnesia:table_info(Tab, where_to_read) || Tab <- Tabs], + ?log("W2R ~p~n", [W2R]), + loop_and_kill_mnesia(10, hd(W2R), Tabs), + [Pid ! self() || Pid <- Readers], + ?match([ok, ok, ok], [receive ok -> ok after 1000 -> {Pid, mnesia_lib:dist_coredump()} end || Pid <- Readers]), + ?verify_mnesia(Ns, []). + +reader(Tab, OP) -> + Res = case OP of + dirty -> + catch mnesia:dirty_read({Tab, key}); + trans -> + Read = fun() -> mnesia:read({Tab, key}) end, + {_, Temp} = mnesia:transaction(Read), + Temp + end, + case Res of + [{Tab, key, val}] -> ok; + Else -> + ?error("Expected ~p Got ~p ~n", [[{Tab, key, val}], Else]), + erlang:error(test_failed) + end, + receive Pid -> + Pid ! ok + after 50 -> + reader(Tab, OP) + end. + +loop_and_kill_mnesia(0, _Node, _Tabs) -> ok; +loop_and_kill_mnesia(N, Node, Tabs) -> + mnesia_test_lib:kill_mnesia([Node]), + timer:sleep(100), + ?match([], mnesia_test_lib:start_mnesia([Node], Tabs)), + [KN | _] = W2R= [mnesia:table_info(Tab, where_to_read) || Tab <- Tabs], + ?match([KN, KN,KN], W2R), + timer:sleep(100), + loop_and_kill_mnesia(N-1, KN, Tabs). + +mnesia_down_during_startup(doc) -> + ["Verify that mnesia can come back up again in a consistent state", + "after it has gone down during startup (with different store and", + "when it goes down in different situations"]; +mnesia_down_during_startup(suite) -> + [ + mnesia_down_during_startup_disk_ram, + mnesia_down_during_startup_init_ram, + mnesia_down_during_startup_init_disc, + mnesia_down_during_startup_init_disc_only, + mnesia_down_during_startup_tm_ram, + mnesia_down_during_startup_tm_disc, + mnesia_down_during_startup_tm_disc_only + ]. + +mnesia_down_during_startup_disk_ram(suite) -> []; +mnesia_down_during_startup_disk_ram(Config) when is_list(Config)-> + [Node1, Node2] = ?acquire_nodes(2, Config ++ + [{tc_timeout, timer:minutes(2)}]), + Tab = down_during_startup, + Def = [{ram_copies, [Node2]}, {disc_copies, [Node1]}], + + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match(ok, mnesia:dirty_write({Tab, 876234, test_ok})), + timer:sleep(500), + mnesia_test_lib:kill_mnesia([Node1, Node2]), + timer:sleep(500), + mnesia_test_lib:start_mnesia([Node1, Node2], [Tab]), + mnesia_test_lib:kill_mnesia([Node1]), + timer:sleep(500), + ?match([], mnesia_test_lib:start_mnesia([Node1], [Tab])), + ?match([{Tab, 876234, test_ok}], mnesia:dirty_read({Tab,876234})), + ?verify_mnesia([Node1, Node2], []). + +mnesia_down_during_startup_init_ram(suite) -> []; +mnesia_down_during_startup_init_ram(Config) when is_list(Config) -> + ?is_debug_compiled, + DP = {mnesia_loader, do_get_network_copy}, + Type = ram_copies, + mnesia_down_during_startup2(Config, Type, DP, self()). + +mnesia_down_during_startup_init_disc(suite) -> []; +mnesia_down_during_startup_init_disc(Config) when is_list(Config) -> + ?is_debug_compiled, + DP = {mnesia_loader, do_get_network_copy}, + Type = disc_copies, + mnesia_down_during_startup2(Config, Type, DP, self()). + +mnesia_down_during_startup_init_disc_only(suite) -> []; +mnesia_down_during_startup_init_disc_only(Config) when is_list(Config) -> + ?is_debug_compiled, + DP = {mnesia_loader, do_get_network_copy}, + Type = disc_only_copies, + mnesia_down_during_startup2(Config, Type, DP, self()). + +mnesia_down_during_startup_tm_ram(suite) -> []; +mnesia_down_during_startup_tm_ram(Config) when is_list(Config) -> + ?is_debug_compiled, + DP = {mnesia_tm, init}, + Type = ram_copies, + mnesia_down_during_startup2(Config, Type, DP, self()). + +mnesia_down_during_startup_tm_disc(suite) -> []; +mnesia_down_during_startup_tm_disc(Config) when is_list(Config) -> + ?is_debug_compiled, + DP = {mnesia_tm, init}, + Type = disc_copies, + mnesia_down_during_startup2(Config, Type, DP, self()). + +mnesia_down_during_startup_tm_disc_only(suite) -> []; +mnesia_down_during_startup_tm_disc_only(Config) when is_list(Config) -> + ?is_debug_compiled, + DP = {mnesia_tm, init}, + Type = disc_only_copies, + mnesia_down_during_startup2(Config, Type, DP, self()). + +mnesia_down_during_startup2(Config, ReplicaType, Debug_Point, _Father) -> + ?log("TC~n mnesia_down_during_startup with type ~w and stops at ~w~n", + [ReplicaType, Debug_Point]), + Tpcb_tabs = [history,teller,account,branch], + Nodes = ?acquire_nodes(2, Config), + Node1 = hd(Nodes), + {success, [A]} = ?start_activities([Node1]), + TpcbConfig = tpcb_config(ReplicaType, 2, Nodes), + mnesia_tpcb:init(TpcbConfig), + A ! fun () -> mnesia_tpcb:run(TpcbConfig) end, + ?match_receive(timeout), + timer:sleep(timer:seconds(10)), % Let tpcb run for a while + mnesia_tpcb:stop(), + ?match(ok, mnesia_tpcb:verify_tabs()), + mnesia_test_lib:kill_mnesia([Node1]), + timer:sleep(timer:seconds(2)), + Self = self(), + TestFun = fun(_MnesiaEnv, _EvalEnv) -> + ?deactivate_debug_fun(Debug_Point), + Self ! fun_done, + spawn(mnesia_test_lib, kill_mnesia, [[Node1]]) + end, + ?activate_debug_fun(Debug_Point, TestFun, []), % Kill when debug has been reached + mnesia:start(), + Res = receive fun_done -> ok after timer:minutes(3) -> timeout end, % Wait till it's killed + ?match(ok, Res), + ?match(ok, timer:sleep(timer:seconds(2))), % Wait a while, at least till it dies; + ?match([], mnesia_test_lib:start_mnesia([Node1], Tpcb_tabs)), + ?match(ok, mnesia_tpcb:verify_tabs()), % Verify it + ?verify_mnesia(Nodes, []). + + +with_checkpoint(doc) -> + ["Restart mnesia with checkpoint"]; +with_checkpoint(suite) -> + [with_checkpoint_same, with_checkpoint_other]. + +with_checkpoint_same(suite) -> []; +with_checkpoint_same(Config) when is_list(Config) -> + with_checkpoint(Config, same). + +with_checkpoint_other(suite) -> []; +with_checkpoint_other(Config) when is_list(Config) -> + with_checkpoint(Config, other). + +with_checkpoint(Config, Type) when is_list(Config) -> + Nodes = [Node1, Node2] = ?acquire_nodes(2, Config), + Kill = case Type of + same -> %% Node1 is the one used for creating the checkpoint + Node1; %% and which we bring down + other -> + Node2 %% Here we bring node2 down.. + end, + + ?match({atomic, ok}, mnesia:create_table(ram, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(disc, [{disc_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(disco, [{disc_only_copies, Nodes}])), + Tabs = [ram, disc, disco], + + ?match({ok, sune, _}, mnesia:activate_checkpoint([{name, sune}, + {max, mnesia:system_info(tables)}, + {ram_overrides_dump, true}])), + + ?match([], check_retainers(sune, Nodes)), + + ?match(ok, mnesia:deactivate_checkpoint(sune)), + ?match([], check_chkp(Nodes)), + + timer:sleep(500), %% Just to help debugging the io:formats now comes in the + %% correct order... :-) + + ?match({ok, sune, _}, mnesia:activate_checkpoint([{name, sune}, + {max, mnesia:system_info(tables)}, + {ram_overrides_dump, true}])), + + [[mnesia:dirty_write({Tab,Key,Key}) || Key <- lists:seq(1,10)] || Tab <- Tabs], + + mnesia_test_lib:kill_mnesia([Kill]), + timer:sleep(100), + mnesia_test_lib:start_mnesia([Kill], Tabs), + io:format("Mnesia on ~p started~n", [Kill]), + ?match([], check_retainers(sune, Nodes)), + ?match(ok, mnesia:deactivate_checkpoint(sune)), + ?match([], check_chkp(Nodes)), + + case Kill of + Node1 -> + ignore; + Node2 -> + mnesia_test_lib:kill_mnesia([Kill]), + timer:sleep(500), %% Just to help debugging + ?match({ok, sune, _}, mnesia:activate_checkpoint([{name, sune}, + {max, mnesia:system_info(tables)}, + {ram_overrides_dump, true}])), + + [[mnesia:dirty_write({Tab,Key,Key+2}) || Key <- lists:seq(1,10)] || + Tab <- Tabs], + + mnesia_test_lib:start_mnesia([Kill], Tabs), + io:format("Mnesia on ~p started ~n", [Kill]), + ?match([], check_retainers(sune, Nodes)), + ?match(ok, mnesia:deactivate_checkpoint(sune)), + ?match([], check_chkp(Nodes)), + ok + end, + ?verify_mnesia(Nodes, []). + +check_chkp(Nodes) -> + {Good, Bad} = rpc:multicall(Nodes, ?MODULE, check, []), + lists:flatten(Good ++ Bad). + +check() -> + [PCP] = ets:match_object(mnesia_gvar, {pending_checkpoint_pids, '_'}), + [PC] = ets:match_object(mnesia_gvar, {pending_checkpoints, '_'}), + [CPN] = ets:match_object(mnesia_gvar, {checkpoints, '_'}), + F = lists:filter(fun({_, []}) -> false; (_W) -> true end, + [PCP,PC,CPN]), + CPP = ets:match_object(mnesia_gvar, {{checkpoint, '_'}, '_'}), + Rt = ets:match_object(mnesia_gvar, {{'_', {retainer, '_'}}, '_'}), + F ++ CPP ++ Rt. + + +check_retainers(CHP, Nodes) -> + {[R1,R2], []} = rpc:multicall(Nodes, ?MODULE, get_all_retainers, [CHP]), + (R1 -- R2) ++ (R2 -- R1). + +get_all_retainers(CHP) -> + Tabs = mnesia:system_info(local_tables), + Iter = fun(Tab) -> + {ok, Res} = + mnesia_checkpoint:iterate(CHP, Tab, fun(R, A) -> [R|A] end, [], + retainer, checkpoint), +%% io:format("Retainer content ~w ~n", [Res]), + Res + end, + Elements = [Iter(Tab) || Tab <- Tabs], + lists:sort(lists:flatten(Elements)). + +delete_during_start(doc) -> + ["Test that tables can be delete during start, hopefully with tables" + " in the loader queue or soon to be"]; +delete_during_start(suite) -> []; +delete_during_start(Config) when is_list(Config) -> + [N1, N2, N3] = Nodes = ?acquire_nodes(3, Config), + Tabs = [list_to_atom("tab" ++ integer_to_list(I)) || I <- lists:seq(1, 30)], + ?match({atomic, ok}, mnesia:change_table_copy_type(schema, N2, ram_copies)), + ?match({atomic, ok}, mnesia:change_table_copy_type(schema, N3, ram_copies)), + + [?match({atomic, ok},mnesia:create_table(Tab, [{ram_copies,Nodes}])) || Tab <- Tabs], + lists:foldl(fun(Tab, I) -> + ?match({atomic, ok}, + mnesia:change_table_load_order(Tab,I)), + I+1 + end, 1, Tabs), + mnesia_test_lib:kill_mnesia([N2,N3]), +%% timer:sleep(500), + ?match({[ok,ok],[]}, rpc:multicall([N2,N3], mnesia,start, + [[{extra_db_nodes,[N1]}]])), + [Tab1,Tab2,Tab3|_] = Tabs, + ?match({atomic, ok}, mnesia:delete_table(Tab1)), + ?match({atomic, ok}, mnesia:delete_table(Tab2)), + + ?log("W4T ~p~n", [rpc:multicall([N2,N3], mnesia, wait_for_tables, [[Tab1,Tab2,Tab3],1])]), + + Remain = Tabs--[Tab1,Tab2], + ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [Remain,10000])), + ?match(ok, rpc:call(N3, mnesia, wait_for_tables, [Remain,10000])), + + ?match(ok, rpc:call(N2, ?MODULE, verify_where2read, [Remain])), + ?match(ok, rpc:call(N3, ?MODULE, verify_where2read, [Remain])), + + ?verify_mnesia(Nodes, []). + +verify_where2read([Tab|Tabs]) -> + true = (node() == mnesia:table_info(Tab,where_to_read)), + verify_where2read(Tabs); +verify_where2read([]) -> ok. + + +%%------------------------------------------------------------------------------------------- +explicit_stop(doc) -> + ["Stop Mnesia in different situations"]; +explicit_stop(suite) -> + [explicit_stop_during_snmp]. +%% This is a bad implementation, but at least gives a indication if something is wrong +explicit_stop_during_snmp(suite) -> []; +explicit_stop_during_snmp(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(2, Config), + [Node1, Node2] = Nodes, + Tab = snmp_tab, + Def = [{attributes, [key, value]}, + {snmp, [{key, integer}]}, + {mnesia_test_lib:storage_type(disc_copies, Config), + [Node1, Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write({Tab, 1, 1}) end)), + + Do_trans_Pid1 = spawn_link(Node2, ?MODULE, do_trans_loop, [Tab, self()]), + Do_trans_Pid2 = spawn_link(?MODULE, do_trans_loop, [Tab, self()]), + Start_stop_Pid = spawn_link(?MODULE, start_stop, [Node1, 10, self()]), + receive + test_done -> + ok + after timer:minutes(5) -> + ?error("test case time out~n", []) + end, + ?verify_mnesia(Nodes, []), + exit(Do_trans_Pid1, kill), + exit(Do_trans_Pid2, kill), + exit(Start_stop_Pid, kill), + ok. + +do_trans_loop(Tab, Father) -> + %% Do not trap exit + do_trans_loop2(Tab, Father). +do_trans_loop2(Tab, Father) -> + Trans = + fun() -> + [{Tab, 1, Val}] = mnesia:read({Tab, 1}), + mnesia:write({Tab, 1, Val + 1}) + end, + case mnesia:transaction(Trans) of + {atomic, ok} -> + timer:sleep(200), + do_trans_loop2(Tab, Father); + {aborted, {node_not_running, N}} when N == node() -> + timer:sleep(200), + do_trans_loop2(Tab, Father); + {aborted, {no_exists, Tab}} -> + timer:sleep(200), + do_trans_loop2(Tab, Father); + Else -> + ?error("Transaction failed: ~p ~n", [Else]), + Father ! test_done, + exit(shutdown) + end. + +start_stop(_Node1, 0, Father) -> + Father ! test_done, + exit(shutdown); +start_stop(Node1, N, Father) when N > 0-> + timer:sleep(timer:seconds(5)), + ?match(stopped, rpc:call(Node1, mnesia, stop, [])), + timer:sleep(timer:seconds(2)), + ?match([], mnesia_test_lib:start_mnesia([Node1])), + start_stop(Node1, N-1, Father). + +coord_dies(suite) -> []; +coord_dies(doc) -> [""]; +coord_dies(Config) when is_list(Config) -> + Nodes = [N1, N2] = ?acquire_nodes(2, Config), + ?match({atomic, ok}, mnesia:create_table(tab1, [{ram_copies, Nodes}])), + ?match({atomic, ok}, mnesia:create_table(tab2, [{ram_copies, [N1]}])), + ?match({atomic, ok}, mnesia:create_table(tab3, [{ram_copies, [N2]}])), + Tester = self(), + + U1 = fun(Tab) -> + [{Tab,key,Val}] = mnesia:read(Tab,key,write), + mnesia:write({Tab,key, Val+1}), + Tester ! {self(),continue}, + receive + continue -> exit(crash) + end + end, + U2 = fun(Tab) -> + [{Tab,key,Val}] = mnesia:read(Tab,key,write), + mnesia:write({Tab,key, Val+1}), + mnesia:transaction(U1, [Tab]) + end, + [mnesia:dirty_write(Tab,{Tab,key,0}) || Tab <- [tab1,tab2,tab3]], + Pid1 = spawn(fun() -> mnesia:transaction(U2, [tab1]) end), + Pid2 = spawn(fun() -> mnesia:transaction(U2, [tab2]) end), + Pid3 = spawn(fun() -> mnesia:transaction(U2, [tab3]) end), + [receive {Pid,continue} -> ok end || Pid <- [Pid1,Pid2,Pid3]], + Pid1 ! continue, Pid2 ! continue, Pid3 ! continue, + ?match({atomic,[{_,key,1}]}, mnesia:transaction(fun() -> mnesia:read({tab1,key}) end)), + ?match({atomic,[{_,key,1}]}, mnesia:transaction(fun() -> mnesia:read({tab2,key}) end)), + ?match({atomic,[{_,key,1}]}, mnesia:transaction(fun() -> mnesia:read({tab3,key}) end)), + + Pid4 = spawn(fun() -> mnesia:transaction(U2, [tab1]) end), + Pid5 = spawn(fun() -> mnesia:transaction(U2, [tab2]) end), + Pid6 = spawn(fun() -> mnesia:transaction(U2, [tab3]) end), + erlang:monitor(process, Pid4),erlang:monitor(process, Pid5),erlang:monitor(process, Pid6), + + [receive {Pid,continue} -> ok end || Pid <- [Pid4,Pid5,Pid6]], + exit(Pid4,crash), + ?match_receive({'DOWN',_,_,Pid4, _}), + ?match({atomic,[{_,key,1}]}, mnesia:transaction(fun() -> mnesia:read({tab1,key}) end)), + exit(Pid5,crash), + ?match_receive({'DOWN',_,_,Pid5, _}), + ?match({atomic,[{_,key,1}]}, mnesia:transaction(fun() -> mnesia:read({tab2,key}) end)), + exit(Pid6,crash), + ?match_receive({'DOWN',_,_,Pid6, _}), + ?match({atomic,[{_,key,1}]}, mnesia:transaction(fun() -> mnesia:read({tab3,key}) end)), + + ?verify_mnesia(Nodes, []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sym_trans(doc) -> + ["Recovery of symmetrical transactions in a couple of different", + "situations; when coordinator or participant or node dies"]; + +sym_trans(suite) -> + [sym_trans_before_commit_kill_coord_node, %% coordinator node dies + sym_trans_before_commit_kill_coord_pid, %% coordinator process dies + sym_trans_before_commit_kill_part_after_ask, %% participating node dies + sym_trans_before_commit_kill_part_before_ask, + sym_trans_after_commit_kill_coord_node, + sym_trans_after_commit_kill_coord_pid, + sym_trans_after_commit_kill_part_after_ask, + sym_trans_after_commit_kill_part_do_commit_pre, + sym_trans_after_commit_kill_part_do_commit_post]. + +%kill_after_debug_point(Config, TestCase, {Debug_node, Debug_Point}, TransFun, Tab) + +sym_trans_before_commit_kill_coord_node(suite) -> []; +sym_trans_before_commit_kill_coord_node(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_before_commit_kill_coord, + Def = [{attributes, [key, value]}, {ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + kill_after_debug_point(Coord, {Coord, {mnesia_tm, multi_commit_sym}}, + do_sym_trans, [{Tab, Def}], Nodes). + +sym_trans_before_commit_kill_coord_pid(suite) -> []; +sym_trans_before_commit_kill_coord_pid(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_before_commit_kill_coord, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, multi_commit_sym}}, + do_sym_trans, [{Tab, Def}], Nodes). + +sym_trans_before_commit_kill_part_after_ask(suite) -> []; +sym_trans_before_commit_kill_part_after_ask(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_before_commit_kill_part_after_ask, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + kill_after_debug_point(Part1, {Coord, {mnesia_tm, multi_commit_sym}}, + do_sym_trans, [{Tab, Def}], Nodes). + +sym_trans_before_commit_kill_part_before_ask(suite) -> []; +sym_trans_before_commit_kill_part_before_ask(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_before_commit_kill_part_before_ask, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + kill_after_debug_point(Part1, {Part1, {mnesia_tm, doit_ask_commit}}, + do_sym_trans, [{Tab, Def}], Nodes). + +sym_trans_after_commit_kill_coord_node(suite) -> []; +sym_trans_after_commit_kill_coord_node(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_after_commit_kill_coord, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + kill_after_debug_point(Coord, {Coord, {mnesia_tm, multi_commit_sym, post}}, + do_sym_trans, [{Tab, Def}], Nodes). + +sym_trans_after_commit_kill_coord_pid(suite) -> []; +sym_trans_after_commit_kill_coord_pid(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_after_commit_kill_coord, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, multi_commit_sym, post}}, + do_sym_trans, [{Tab,Def}], Nodes). + +sym_trans_after_commit_kill_part_after_ask(suite) -> []; +sym_trans_after_commit_kill_part_after_ask(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_after_commit_kill_part_after_ask, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + kill_after_debug_point(Part1, {Coord, {mnesia_tm, multi_commit_sym, post}}, + do_sym_trans, [{Tab, Def}], Nodes). + +sym_trans_after_commit_kill_part_do_commit_pre(suite) -> []; +sym_trans_after_commit_kill_part_do_commit_pre(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_after_commit_kill_part_do_commit_pre, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_sym_trans, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, do_commit, pre}}, + TransFun, [{Tab, Def}], Nodes). + +sym_trans_after_commit_kill_part_do_commit_post(suite) -> []; +sym_trans_after_commit_kill_part_do_commit_post(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sym_trans_after_commit_kill_part_do_commit_post, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_sym_trans, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, do_commit, post}}, + TransFun, [{Tab, Def}], Nodes). + +do_sym_trans([Tab], _Fahter) -> + ?dl("Starting SYM_TRANS with active debug fun ", []), + Trans = fun() -> + [{_,_,Val}] = mnesia:read({Tab, 1}), + mnesia:write({Tab, 1, Val+1}) + end, + Res = mnesia:transaction(Trans), + case Res of + {atomic, ok} -> ok; + {aborted, _Reason} -> ok; + Else -> ?error("Wrong output from mensia:transaction(FUN):~n ~p~n", + [Else]) + end, + ?dl("SYM_TRANSACTION done: ~p (deactiv dbgfun) ", [Res]), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sync_dirty(doc) -> + ["Verify recovery of synchronously operations in a couple of different", + "situations"]; +sync_dirty(suite) -> + [sync_dirty_pre_kill_part, + sync_dirty_pre_kill_coord_node, + sync_dirty_pre_kill_coord_pid, + sync_dirty_post_kill_part, + sync_dirty_post_kill_coord_node, + sync_dirty_post_kill_coord_pid + ]. + +sync_dirty_pre_kill_part(suite) -> []; +sync_dirty_pre_kill_part(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sync_dirty_pre, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_sync_dirty, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, sync_dirty, pre}}, + TransFun, [{Tab, Def}], Nodes). + +sync_dirty_pre_kill_coord_node(suite) -> []; +sync_dirty_pre_kill_coord_node(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sync_dirty_pre, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_sync_dirty, + kill_after_debug_point(Coord, {Part1, {mnesia_tm, sync_dirty, pre}}, + TransFun, [{Tab, Def}], Nodes). + +sync_dirty_pre_kill_coord_pid(suite) -> []; +sync_dirty_pre_kill_coord_pid(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sync_dirty_pre, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_sync_dirty, + kill_after_debug_point(coord_pid, {Part1, {mnesia_tm, sync_dirty, pre}}, + TransFun, [{Tab, Def}], Nodes). + +sync_dirty_post_kill_part(suite) -> []; +sync_dirty_post_kill_part(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sync_dirty_post, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_sync_dirty, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, sync_dirty, post}}, + TransFun, [{Tab, Def}], Nodes). + +sync_dirty_post_kill_coord_node(suite) -> []; +sync_dirty_post_kill_coord_node(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sync_dirty_post, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_sync_dirty, + kill_after_debug_point(Coord, {Part1, {mnesia_tm, sync_dirty, post}}, + TransFun, [{Tab, Def}], Nodes). + +sync_dirty_post_kill_coord_pid(suite) -> []; +sync_dirty_post_kill_coord_pid(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = sync_dirty_post, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_sync_dirty, + kill_after_debug_point(coord_pid, {Part1, {mnesia_tm, sync_dirty, post}}, + TransFun, [{Tab, Def}], Nodes). + +do_sync_dirty([Tab], _Father) -> + ?dl("Starting SYNC_DIRTY", []), + SYNC = fun() -> + [{_,_,Val}] = mnesia:read({Tab, 1}), + mnesia:write({Tab, 1, Val+1}) + end, + {_, Res} = ?match(ok, mnesia:sync_dirty(SYNC)), + ?dl("SYNC_DIRTY done: ~p ", [Res]), + ok. + +async_dirty(doc) -> + ["Verify recovery of asynchronously dirty operations in a couple of different", + "situations"]; +async_dirty(suite) -> + [async_dirty_pre_kill_part, + async_dirty_pre_kill_coord_node, + async_dirty_pre_kill_coord_pid, + async_dirty_post_kill_part, + async_dirty_post_kill_coord_node, + async_dirty_post_kill_coord_pid]. + +async_dirty_pre_kill_part(suite) -> []; +async_dirty_pre_kill_part(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = async_dirty_pre, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_async_dirty, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, async_dirty, pre}}, + TransFun, [{Tab, Def}], Nodes). + +async_dirty_pre_kill_coord_node(suite) -> []; +async_dirty_pre_kill_coord_node(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = async_dirty_pre, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_async_dirty, + kill_after_debug_point(Coord, {Part1, {mnesia_tm, async_dirty, pre}}, + TransFun, [{Tab, Def}], Nodes). + +async_dirty_pre_kill_coord_pid(suite) -> []; +async_dirty_pre_kill_coord_pid(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = async_dirty_pre, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_async_dirty, + kill_after_debug_point(coord_pid, {Part1, {mnesia_tm, async_dirty, pre}}, + TransFun, [{Tab, Def}], Nodes). + +async_dirty_post_kill_part(suite) -> []; +async_dirty_post_kill_part(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = async_dirty_post, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_async_dirty, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, async_dirty, post}}, + TransFun, [{Tab, Def}], Nodes). + +async_dirty_post_kill_coord_node(suite) -> []; +async_dirty_post_kill_coord_node(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = async_dirty_post, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_async_dirty, + kill_after_debug_point(Coord, {Part1, {mnesia_tm, async_dirty, post}}, + TransFun, [{Tab, Def}], Nodes). + +async_dirty_post_kill_coord_pid(suite) -> []; +async_dirty_post_kill_coord_pid(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab = async_dirty_post, + Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}], + TransFun = do_async_dirty, + kill_after_debug_point(coord_pid, {Part1, {mnesia_tm, async_dirty, post}}, + TransFun, [{Tab, Def}], Nodes). + +do_async_dirty([Tab], _Fahter) -> + ?dl("Starting ASYNC", []), + ASYNC = fun() -> + [{_,_,Val}] = mnesia:read({Tab, 1}), + mnesia:write({Tab, 1, Val+1}) + end, + {_, Res} = ?match(ok, mnesia:async_dirty(ASYNC)), + ?dl("ASYNC done: ~p ", [Res]), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +asym_trans(doc) -> + ["Recovery of asymmetrical transactions in a couple of different", + "situations, currently the error cases are not covered, i.e. ", + "not tested are the situations when we kill mnesia or a process", + "during a recovery"]; +asym_trans(suite) -> + [ + asym_trans_kill_part_ask, + asym_trans_kill_part_commit_vote, + asym_trans_kill_part_pre_commit, + asym_trans_kill_part_log_commit, + asym_trans_kill_part_do_commit, + asym_trans_kill_coord_got_votes, + asym_trans_kill_coord_pid_got_votes, + asym_trans_kill_coord_log_commit_rec, + asym_trans_kill_coord_pid_log_commit_rec, + asym_trans_kill_coord_log_commit_dec, + asym_trans_kill_coord_pid_log_commit_dec, + asym_trans_kill_coord_rec_acc_pre_commit_log_commit, + asym_trans_kill_coord_pid_rec_acc_pre_commit_log_commit, + asym_trans_kill_coord_rec_acc_pre_commit_done_commit, + asym_trans_kill_coord_pid_rec_acc_pre_commit_done_commit + ]. + +asym_trans_kill_part_ask(suite) -> []; +asym_trans_kill_part_ask(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, doit_ask_commit}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_part_commit_vote(suite) -> []; +asym_trans_kill_part_commit_vote(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, commit_participant, vote_yes}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_part_pre_commit(suite) -> []; +asym_trans_kill_part_pre_commit(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, commit_participant, pre_commit}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_part_log_commit(suite) -> []; +asym_trans_kill_part_log_commit(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, commit_participant, log_commit}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_part_do_commit(suite) -> []; +asym_trans_kill_part_do_commit(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Part1, {Part1, {mnesia_tm, commit_participant, do_commit}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_got_votes(suite) -> []; +asym_trans_kill_coord_got_votes(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Coord, {Coord, {mnesia_tm, multi_commit_asym_got_votes}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_pid_got_votes(suite) -> []; +asym_trans_kill_coord_pid_got_votes(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, multi_commit_asym_got_votes}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_log_commit_rec(suite) -> []; +asym_trans_kill_coord_log_commit_rec(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Coord, {Coord, {mnesia_tm, multi_commit_asym_log_commit_rec}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_pid_log_commit_rec(suite) -> []; +asym_trans_kill_coord_pid_log_commit_rec(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, multi_commit_asym_log_commit_rec}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_log_commit_dec(suite) -> []; +asym_trans_kill_coord_log_commit_dec(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Coord, {Coord, {mnesia_tm, multi_commit_asym_log_commit_dec}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_pid_log_commit_dec(suite) -> []; +asym_trans_kill_coord_pid_log_commit_dec(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, multi_commit_asym_log_commit_dec}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_rec_acc_pre_commit_log_commit(suite) -> []; +asym_trans_kill_coord_rec_acc_pre_commit_log_commit(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Coord, {Coord, {mnesia_tm, rec_acc_pre_commit_log_commit}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_pid_rec_acc_pre_commit_log_commit(suite) -> []; +asym_trans_kill_coord_pid_rec_acc_pre_commit_log_commit(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, rec_acc_pre_commit_log_commit}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_rec_acc_pre_commit_done_commit(suite) -> []; +asym_trans_kill_coord_rec_acc_pre_commit_done_commit(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(Coord, {Coord, {mnesia_tm, rec_acc_pre_commit_done_commit}}, + TransFun, [Tab1, Tab2], Nodes). + +asym_trans_kill_coord_pid_rec_acc_pre_commit_done_commit(suite) -> []; +asym_trans_kill_coord_pid_rec_acc_pre_commit_done_commit(Config) when is_list(Config) -> + ?is_debug_compiled, + Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), + [Coord, Part1, Part2] = Nodes, + Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]}, + Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]}, + TransFun = do_asym_trans, + kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, rec_acc_pre_commit_done_commit}}, + TransFun, [Tab1, Tab2], Nodes). + +do_asym_trans([Tab1, Tab2 | _R], Garbhandler) -> + ?dl("Starting asym trans ", []), + ASym_Trans = fun() -> + TidTs = {_Mod, Tid, _Store} = + mnesia:get_activity_id(), + ?verbose("===> asym_trans: ~w~n", [TidTs]), + Garbhandler ! {trans_id, Tid}, + [{_, _, Val1}] = mnesia:read({Tab1, 1}), + [{_, _, Val2}] = mnesia:read({Tab2, 1}), + mnesia:write({Tab1, 1, Val1+1}), + mnesia:write({Tab2, 1, Val2+1}) + end, + Res = mnesia:transaction(ASym_Trans), + case Res of + {atomic, ok} -> ok; + {aborted, _Reason} -> ok; + _Else -> ?error("Wrong output from mensia:transaction(FUN):~n ~p~n", [Res]) + end, + ?dl("Asym trans finished with: ~p ", [Res]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +kill_after_debug_point(Kill, {DebugNode, Debug_Point}, TransFun, TabsAndDefs, Nodes) -> + [Coord | _rest] = Nodes, + + Create = fun({Tab, Def}) -> ?match({atomic, ok}, mnesia:create_table(Tab, Def)) end, + lists:foreach(Create, TabsAndDefs), + Tabs = [T || {T, _} <- TabsAndDefs], + Write = fun(Tab) -> ?match(ok, mnesia:dirty_write({Tab, 1, 100})) end, + lists:foreach(Write, Tabs), + + Self = self(), + SyncFun = fun(_Env1, _Env2) -> % Just Sync with test prog + Self ! {self(), fun_in_position}, + ?dl("SyncFun, sending fun_in_position ", []), + receive continue -> + ?dl("SyncFun received continue ",[]), + ok + after timer:seconds(60) -> + ?error("Timeout in sync_fun on ~p~n", [node()]) + end + end, + + Garb_handler = spawn_link(?MODULE, garb_handler, [[]]), + + ?remote_activate_debug_fun(DebugNode, Debug_Point, SyncFun, []), + ?dl("fun_in_position activated at ~p with ~p", [DebugNode, Debug_Point]), + %% Spawn and do the transaction + Pid = spawn(Coord, ?MODULE, TransFun, [Tabs, Garb_handler]), + %% Wait till all the Nodes are in correct position + [{StoppedPid,_}] = ?receive_messages([fun_in_position]), + ?dl("Received fun_in_position; Removing the debug funs ~p", [DebugNode]), + ?remote_deactivate_debug_fun(DebugNode, Debug_Point), + + case Kill of + coord_pid -> + ?dl("Intentionally killing pid ~p ", [Pid]), + exit(Pid, normal); + Node -> + mnesia_test_lib:kill_mnesia([Node]) + end, + + StoppedPid ! continue, %% Send continue, it may still be alive + + %% Start and check that the databases are consistent + ?dl("Done, Restarting and verifying result ",[]), + case Kill of + coord_pid -> ok; + _ -> % Ok, mnesia on some node was killed restart it + timer:sleep(timer:seconds(3)), %% Just let it have the time to die + ?match(ok, rpc:call(Kill, mnesia, start, [[]])), + ?match(ok, rpc:call(Kill, mnesia, wait_for_tables, [Tabs, 60000])) + end, + Trans_res = verify_tabs(Tabs, Nodes), + case TransFun of + do_asym_trans -> + %% Verifies that decisions are garbed, only valid for asym_tran + Garb_handler ! {get_tids, self()}, + Tid_list = receive + {tids, List} -> + ?dl("Fun rec ~w", [List]), + List + end, + garb_of_decisions(Kill, Nodes, Tid_list, Trans_res); + _ -> + ignore + end, + ?verify_mnesia(Nodes, []). + +garb_of_decisions(Kill, Nodes, Tid_list, Trans_res) -> + [Coord, Part1, Part2] = Nodes, + %% Check that decision log is empty on all nodes after the trans is finished + verify_garb_decision_log(Nodes, Tid_list), + case Trans_res of + aborted -> + %% Check that aborted trans have not been restarted!! + ?match(1, length(Tid_list)), + %% Check the transient decision logs + %% A transaction should only be aborted in an early stage of + %% the trans before the any Node have logged anything + verify_garb_transient_logs(Nodes, Tid_list, aborted), + %% And only when the coordinator are have died + %% Else he would have restarted the transaction + ?match(Kill, Coord); + updated -> + case length(Tid_list) of + 1 -> + %% If there was only one transaction, it should be logged as + %% comitted on every node! + [Tid1] = Tid_list, + verify_garb_transient_logs(Nodes, [Tid1], committed); + 2 -> + %% If there is two transaction id, then the first + %% TID should have been aborted and the transaction + %% restarted with a new TID + [Tid1, Tid2] = Tid_list, + verify_garb_transient_logs(Nodes, [Tid1], aborted), + %% If mnesia is killed on a node i.e Coord and Part1 than they + %% won't know about the restarted trans! The rest of the nodes + %% should know that the trans was committed + case Kill of + coord_pid -> + verify_garb_transient_logs(Nodes, [Tid2], committed); + Coord -> + verify_garb_transient_logs([Part1, Part2], [Tid2], committed), + verify_garb_transient_logs([Coord], [Tid2], not_found); + Part1 -> + verify_garb_transient_logs([Coord, Part2], [Tid2], committed), + verify_garb_transient_logs([Part1], [Tid2], not_found) + end + end + end. + +verify_garb_decision_log([], _Tids) -> ok; +verify_garb_decision_log([Node|R], Tids) -> + Check = fun(Tid) -> %% Node, Tid used in debugging! + ?match({{not_found, _}, Node, Tid}, + {outcome(Tid, [mnesia_decision]), Node, Tid}) + end, + rpc:call(Node, lists, foreach, [Check, Tids]), + verify_garb_decision_log(R, Tids). + +verify_garb_transient_logs([], _Tids, _) -> ok; +verify_garb_transient_logs([Node|R], Tids, Exp_Res) -> + Check = fun(Tid) -> + LatestTab = mnesia_lib:val(latest_transient_decision), + PrevTabs = mnesia_lib:val(previous_transient_decisions), + case outcome(Tid, [LatestTab | PrevTabs]) of + {found, {_, [{_,_Tid, Exp_Res}]}} -> ok; + {not_found, _} when Exp_Res == not_found -> ok; + {not_found, _} when Exp_Res == aborted -> ok; + Else -> ?error("Expected ~p in trans ~p on ~p got ~p~n", + [Exp_Res, Tid, Node, Else]) + end + end, + rpc:call(Node, lists, foreach, [Check, Tids]), + verify_garb_transient_logs(R, Tids, Exp_Res). + +outcome(Tid, Tabs) -> + outcome(Tid, Tabs, Tabs). + +outcome(Tid, [Tab | Tabs], AllTabs) -> + case catch ets:lookup(Tab, Tid) of + {'EXIT', _} -> + outcome(Tid, Tabs, AllTabs); + [] -> + outcome(Tid, Tabs, AllTabs); + Val -> + {found, {Tab, Val}} + end; +outcome(_Tid, [], AllTabs) -> + {not_found, AllTabs}. + + +verify_tabs([Tab|R], Nodes) -> + [_Coord, Part1, Part2 | _rest] = Nodes, + Read = fun() -> mnesia:read({Tab, 1}) end, + {success, A} = ?match({atomic, _}, mnesia:transaction(Read)), + ?match(A, rpc:call(Part1, mnesia, transaction, [Read])), + ?match(A, rpc:call(Part2, mnesia, transaction, [Read])), + {atomic, [{Tab, 1, Res}]} = A, + verify_tabs(R, Nodes, Res). + +verify_tabs([], _Nodes, Res) -> + case Res of + 100 -> aborted; + 101 -> updated + end; + +verify_tabs([Tab | Rest], Nodes, Res) -> + [Coord, Part1, Part2 | _rest] = Nodes, + Read = fun() -> mnesia:read({Tab, 1}) end, + Exp = {atomic, [{Tab, 1, Res}]}, + ?match(Exp, rpc:call(Coord, mnesia, transaction, [Read])), + ?match(Exp, rpc:call(Part1, mnesia, transaction, [Read])), + ?match(Exp, rpc:call(Part2, mnesia, transaction, [Read])), + verify_tabs(Rest, Nodes, Res). + +%% Gather TIDS and send them to requesting process and exit! +garb_handler(List) -> + receive + {trans_id, ID} -> garb_handler([ID|List]); + {get_tids, Pid} -> Pid ! {tids, lists:reverse(List)} + end. + +%%%%%%%%%%%%%%%%%%%%%%% +receive_messages([], _File, _Line) -> []; +receive_messages(ListOfMsgs, File, Line) -> + receive + {Pid, Msg} -> + case lists:member(Msg, ListOfMsgs) of + false -> + mnesia_test_lib:log("<>WARNING<>~n" + "Received unexpected msg~n ~p ~n" + "While waiting for ~p~n", + [{Pid, Msg}, ListOfMsgs], File, Line), + receive_messages(ListOfMsgs, File, Line); + true -> + ?dl("Got msg ~p from ~p ", [Msg, node(Pid)]), + [{Pid, Msg} | receive_messages(ListOfMsgs -- [Msg], File, Line)] + end; + Else -> mnesia_test_lib:log("<>WARNING<>~n" + "Recevied unexpected or bad formatted msg~n ~p ~n" + "While waiting for ~p~n", + [Else, ListOfMsgs], File, Line), + receive_messages(ListOfMsgs, File, Line) + after timer:minutes(2) -> + ?error("Timeout in receive msgs while waiting for ~p~n", + [ListOfMsgs]) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +after_full_disc_partition(doc) -> + ["Verify that the database does not get corrupt", + "when Mnesia encounters a full disc partition"]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% interrupted_fallback_start +%% is implemented in consistency interupted_install_fallback! +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +after_corrupt_files(doc) -> + ["Verify that mnesia (and dets) can handle corrupt files"]; +after_corrupt_files(suite) -> % cope with unsynced disks + [after_corrupt_files_decision_log_head, + after_corrupt_files_decision_log_tail, + after_corrupt_files_latest_log_head, + after_corrupt_files_latest_log_tail, + after_corrupt_files_table_dat_head, + after_corrupt_files_table_dat_tail, + after_corrupt_files_schema_dat_head, + after_corrupt_files_schema_dat_tail + ]. + +after_corrupt_files_decision_log_head(suite) -> []; +after_corrupt_files_decision_log_head(Config) when is_list(Config) -> + after_corrupt_files(Config, "DECISION.LOG", head, repair). + +after_corrupt_files_decision_log_tail(suite) -> []; +after_corrupt_files_decision_log_tail(Config) when is_list(Config) -> + after_corrupt_files(Config, "DECISION.LOG", tail, repair). + +after_corrupt_files_latest_log_head(suite) -> []; +after_corrupt_files_latest_log_head(Config) when is_list(Config) -> + after_corrupt_files(Config, "LATEST.LOG", head, repair). + +after_corrupt_files_latest_log_tail(suite) -> []; +after_corrupt_files_latest_log_tail(Config) when is_list(Config) -> + after_corrupt_files(Config, "LATEST.LOG", tail, repair). + +after_corrupt_files_table_dat_head(suite) -> []; +after_corrupt_files_table_dat_head(Config) when is_list(Config) -> + after_corrupt_files(Config, "rec_files.DAT", head, crash). + +after_corrupt_files_table_dat_tail(suite) -> []; +after_corrupt_files_table_dat_tail(Config) when is_list(Config) -> + after_corrupt_files(Config, "rec_files.DAT", tail, repair). + +after_corrupt_files_schema_dat_head(suite) -> []; +after_corrupt_files_schema_dat_head(Config) when is_list(Config) -> + after_corrupt_files(Config, "schema.DAT", head, crash). + +after_corrupt_files_schema_dat_tail(suite) -> []; +after_corrupt_files_schema_dat_tail(Config) when is_list(Config) -> + after_corrupt_files(Config, "schema.DAT", tail, crash). + + + +%%% BUGBUG: We should also write testcase's for autorepair=false i.e. +%%% not the standard case! +after_corrupt_files(Config, File, Where, Behaviour) -> + [Node] = ?acquire_nodes(1, Config ++ [{tc_timeout, timer:minutes(2)}]), + Tab = rec_files, + Def = [{disc_only_copies, [Node]}], + ?match({atomic, ok}, mnesia:create_table(Tab, Def)), + insert_data(Tab, 100), + Dir = mnesia:system_info(directory), + mnesia_test_lib:kill_mnesia([Node]), + timer:sleep(timer:seconds(10)), % Let dets finish whatever it does + + DirFile = Dir ++ "/" ++ File, + + {ok, Fd} = file:open(DirFile, read_write), + {ok, FileInfo} = file:read_file_info(DirFile), + case Where of + head -> + ?match({ok, _NewP}, file:position(Fd, {bof, 1})), + ?match(ok, file:write(Fd, [255, 255, 255, 255, 255, 255, 255, 255, 254])), + ok; + tail -> + Size = FileInfo#file_info.size, + Half = Size div 2, + + ?dl(" Size = ~p Half = ~p ", [Size, Half]), + ?match({ok, _NewP}, file:position(Fd, {bof, Half})), + ?match(ok, file:truncate(Fd)), + ok + end, + ?match(ok, file:close(Fd)), + + ?warning("++++++SOME OF THE after_corrupt* TEST CASES WILL INTENTIONALLY CRASH MNESIA+++++++~n", []), + Pid = spawn_link(?MODULE, mymnesia_start, [self()]), + receive + {Pid, ok} -> + ?match(ok, mnesia:wait_for_tables([schema, Tab], 10000)), + ?match(ok, verify_data(Tab, 100)), + case mnesia_monitor:get_env(auto_repair) of + false -> + ?error("Mnesia should have crashed in ~p ~p ~n", + [File, Where]); + true -> + ok + end, + ?verify_mnesia([Node], []); + {Pid, {error, ED}} -> + case {mnesia_monitor:get_env(auto_repair), Behaviour} of + {true, repair} -> + ?error("Mnesia crashed with ~p: in ~p ~p ~n", + [ED, File, Where]); + _ -> %% Every other can crash! + ok + end, + ?verify_mnesia([], [Node]); + Msg -> + ?error("~p ~p: Got ~p during start of Mnesia~n", + [File, Where, Msg]) + end. + +mymnesia_start(Tester) -> + Res = mnesia:start(), + unlink(Tester), + Tester ! {self(), Res}. + +verify_data(_, 0) -> ok; +verify_data(Tab, N) -> + Actual = mnesia:dirty_read({Tab, N}), + Expected = [{Tab, N, N}], + if + Expected == Actual -> + verify_data(Tab, N - 1); + true -> + mnesia:schema(Tab), + {not_equal, node(), Expected, Actual} + end. + +insert_data(_Tab, 0) -> ok; +insert_data(Tab, N) -> + ok = mnesia:sync_dirty(fun() -> mnesia:write({Tab, N, N}) end), + insert_data(Tab, N-1). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +disc_less(doc) -> + ["Here is a simple test case of a simple recovery of a disc less node. " + "However a lot more test cases involving disc less nodes should " + "be written"]; +disc_less(suite) -> []; +disc_less(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + case mnesia_test_lib:diskless(Config) of + true -> skip; + false -> + ?match({atomic, ok}, mnesia:change_table_copy_type(schema, Node3, ram_copies)) + end, + Tab1 = disc_less1, + Tab2 = disc_less2, + Tab3 = disc_less3, + Def1 = [{ram_copies, [Node3]}, {disc_copies, [Node1, Node2]}], + Def2 = [{ram_copies, [Node3]}, {disc_copies, [Node1]}], + Def3 = [{ram_copies, [Node3]}, {disc_copies, [Node2]}], + ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)), + ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)), + ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)), + insert_data(Tab1, 100), + insert_data(Tab2, 100), + insert_data(Tab3, 100), + + mnesia_test_lib:kill_mnesia([Node1, Node2]), + timer:sleep(500), + mnesia_test_lib:kill_mnesia([Node3]), + ?match(ok, rpc:call(Node1, mnesia, start, [])), + ?match(ok, rpc:call(Node2, mnesia, start, [])), + + timer:sleep(500), + ?match(ok, rpc:call(Node3, mnesia, start, [[{extra_db_nodes, [Node1, Node2]}]])), + ?match(ok, rpc:call(Node3, mnesia, wait_for_tables, [[Tab1, Tab2, Tab3], 20000])), + + ?match(ok, rpc:call(Node3, ?MODULE, verify_data, [Tab1, 100])), + ?match(ok, rpc:call(Node3, ?MODULE, verify_data, [Tab2, 100])), + ?match(ok, rpc:call(Node3, ?MODULE, verify_data, [Tab3, 100])), + + + ?match(ok, rpc:call(Node2, ?MODULE, verify_data, [Tab1, 100])), + ?match(ok, rpc:call(Node2, ?MODULE, verify_data, [Tab2, 100])), + ?match(ok, rpc:call(Node2, ?MODULE, verify_data, [Tab3, 100])), + + ?match(ok, rpc:call(Node1, ?MODULE, verify_data, [Tab1, 100])), + ?match(ok, rpc:call(Node1, ?MODULE, verify_data, [Tab2, 100])), + ?match(ok, rpc:call(Node1, ?MODULE, verify_data, [Tab3, 100])), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +system_upgrade(doc) -> + ["Test on-line and off-line upgrade of the Mnesia application"]. + +garb_decision(doc) -> + ["Test that decisions are garbed correctly."]; +garb_decision(suite) -> []; +garb_decision(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + check_garb(Nodes), + ?match({atomic, ok},mnesia:create_table(a, [{disc_copies, Nodes}])), + check_garb(Nodes), + ?match({atomic, ok},mnesia:create_table(b, [{ram_copies, Nodes}])), + check_garb(Nodes), + ?match({atomic, ok},mnesia:create_table(c, [{ram_copies, [Node1, Node3]}, + {disc_copies, [Node2]}])), + check_garb(Nodes), + ?match({atomic, ok},mnesia:create_table(d, [{disc_copies, [Node1, Node3]}, + {ram_copies, [Node2]}])), + check_garb(Nodes), + + W = fun(Tab) -> mnesia:write({Tab,1,1}) end, + A = fun(Tab) -> mnesia:write({Tab,1,1}), exit(1) end, + + ?match({atomic, ok}, mnesia:transaction(W,[a])), + check_garb(Nodes), + ?match({atomic, ok}, mnesia:transaction(W,[b])), + check_garb(Nodes), + ?match({atomic, ok}, mnesia:transaction(W,[c])), + check_garb(Nodes), + ?match({atomic, ok}, mnesia:transaction(W,[d])), + check_garb(Nodes), + ?match({aborted,1}, mnesia:transaction(A,[a])), + check_garb(Nodes), + ?match({aborted,1}, mnesia:transaction(A,[b])), + check_garb(Nodes), + ?match({aborted,1}, mnesia:transaction(A,[c])), + check_garb(Nodes), + ?match({aborted,1}, mnesia:transaction(A,[d])), + check_garb(Nodes), + + rpc:call(Node2, mnesia, lkill, []), + ?match({atomic, ok}, mnesia:transaction(W,[a])), + ?match({atomic, ok}, mnesia:transaction(W,[b])), + ?match({atomic, ok}, mnesia:transaction(W,[c])), + ?match({atomic, ok}, mnesia:transaction(W,[d])), + check_garb(Nodes), + ?match([], mnesia_test_lib:start_mnesia([Node2])), + check_garb(Nodes), + timer:sleep(2000), + check_garb(Nodes), + %%%%%% Check transient_decision logs %%%%% + + ?match(dumped, mnesia:dump_log()), sys:get_status(mnesia_recover), % sync + [{atomic, ok} = mnesia:transaction(W,[a]) || _ <- lists:seq(1,30)], + ?match(dumped, mnesia:dump_log()), sys:get_status(mnesia_recover), % sync + TD0 = mnesia_lib:val(latest_transient_decision), + ?match(0, ets:info(TD0, size)), + {atomic, ok} = mnesia:transaction(W,[a]), + ?match(dumped, mnesia:dump_log()), sys:get_status(mnesia_recover), % sync + ?match(TD0, mnesia_lib:val(latest_transient_decision)), + [{atomic, ok} = mnesia:transaction(W,[a]) || _ <- lists:seq(1,30)], + ?match(dumped, mnesia:dump_log()), sys:get_status(mnesia_recover), % sync + ?match(false, TD0 =:= mnesia_lib:val(latest_transient_decision)), + ?match(true, lists:member(TD0, mnesia_lib:val(previous_transient_decisions))), + ?verify_mnesia(Nodes, []). + +check_garb(Nodes) -> + rpc:multicall(Nodes, sys, get_status, [mnesia_recover]), + ?match({_, []},rpc:multicall(Nodes, erlang, apply, [fun check_garb/0, []])). + +check_garb() -> + try + Ds = ets:tab2list(mnesia_decision), + Check = fun({trans_tid,serial, _}) -> false; + ({mnesia_down,_,_,_}) -> false; + (_Else) -> true + end, + Node = node(), + ?match({Node, []}, {node(), lists:filter(Check, Ds)}) + catch _:_ -> ok + end, + ok. diff --git a/lib/mnesia/test/mnesia_registry_test.erl b/lib/mnesia/test/mnesia_registry_test.erl new file mode 100644 index 0000000000..2305ef93b7 --- /dev/null +++ b/lib/mnesia/test/mnesia_registry_test.erl @@ -0,0 +1,137 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1998-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_registry_test). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Test the mnesia_registry module"]; +all(suite) -> + [ + good_dump, + bad_dump + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +good_dump(doc) -> + ["Dump a faked C-node registry"]; +good_dump(suite) -> []; +good_dump(Config) when is_list(Config) -> + [Node] = Nodes = ?acquire_nodes(1, Config), + T1 = gordon, + ?match(ok, mnesia_registry:create_table(T1)), + One = {T1, 1, 0, integer, 0, 10}, + Two = {T1, "two", 3, integer, 0, 20}, + Three = {T1, 3, 0, string, 6, "thirty"}, + ?match(ok, mnesia:dirty_write(One)), + ?match(ok, mnesia:dirty_write(Two)), + ?match(ok, mnesia:dirty_write(Three)), + ?match([One], mnesia:dirty_read({T1, 1})), + ?match([_ | _], dump_registry(Node, T1)), + + NewOne = {T1, 1, 0, integer, 0, 1}, + NewFour = {T1, "4", 1, string, 4, "four"}, + + ?match([NewOne], mnesia:dirty_read({T1, 1})), + ?match([Two], mnesia:dirty_read({T1, "two"})), + ?match([], mnesia:dirty_read({T1, 3})), + ?match([NewFour], mnesia:dirty_read({T1, "4"})), + + T2 = blixt, + ?match({'EXIT', {aborted, {no_exists, _}}}, + mnesia:dirty_read({T2, 1})), + ?match([_ |_], dump_registry(Node, T2)), + + NewOne2 = setelement(1, NewOne, T2), + NewFour2 = setelement(1, NewFour, T2), + + ?match([NewOne2], mnesia:dirty_read({T2, 1})), + ?match([], mnesia:dirty_read({T2, "two"})), + ?match([], mnesia:dirty_read({T2, 3})), + ?match([NewFour2], mnesia:dirty_read({T2, "4"})), + ?match([_One2, NewFour2], lists:sort(restore_registry(Node, T2))), + + ?verify_mnesia(Nodes, []). + +dump_registry(Node, Tab) -> + case rpc:call(Node, mnesia_registry, start_dump, [Tab, self()]) of + Pid when is_pid(Pid) -> + Pid ! {write, 1, 0, integer, 0, 1}, + Pid ! {delete, 3}, + Pid ! {write, "4", 1, string, 4, "four"}, + Pid ! {commit, self()}, + receive + {ok, Pid} -> + [{Tab, "4", 1, string, 4, "four"}, + {Tab, 1, 0, integer, 0, 1}]; + {'EXIT', Pid, Reason} -> + exit(Reason) + end; + {badrpc, Reason} -> + exit(Reason) + end. + +restore_registry(Node, Tab) -> + case rpc:call(Node, mnesia_registry, start_restore, [Tab, self()]) of + {size, Pid, N, _LargestKeySize, _LargestValSize} -> + Pid ! {send_records, self()}, + receive_records(Tab, N); + {badrpc, Reason} -> + exit(Reason) + end. + +receive_records(Tab, N) when N > 0 -> + receive + {restore, KeySize, ValSize, ValType, Key, Val} -> + [{Tab, Key, KeySize, ValType, ValSize, Val} | receive_records(Tab, N -1)]; + {'EXIT', _Pid, Reason} -> + exit(Reason) + end; +receive_records(_Tab, 0) -> + []. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +bad_dump(doc) -> + ["Intentionally fail with the dump of a faked C-node registry"]; +bad_dump(suite) -> []; +bad_dump(Config) when is_list(Config) -> + [Node] = Nodes = ?acquire_nodes(1, Config), + + OldTab = ming, + ?match({'EXIT', {aborted, _}}, mnesia_registry:start_restore(no_tab, self())), + ?match({atomic, ok}, mnesia:create_table(OldTab, [{attributes, [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q]}])), + ?match({'EXIT',{aborted,{bad_type,_}}}, dump_registry(Node, OldTab)), + ?match(stopped, mnesia:stop()), + + ?match({'EXIT', {aborted, _}}, mnesia_registry:create_table(down_table)), + ?match({'EXIT', {aborted, _}}, mnesia_registry:start_restore(no_tab, self())), + ?match({'EXIT', {aborted, _}}, dump_registry(Node, down_dump)), + + ?verify_mnesia([], Nodes). + diff --git a/lib/mnesia/test/mnesia_schema_recovery_test.erl b/lib/mnesia/test/mnesia_schema_recovery_test.erl new file mode 100644 index 0000000000..387238ae6b --- /dev/null +++ b/lib/mnesia/test/mnesia_schema_recovery_test.erl @@ -0,0 +1,787 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1998-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_schema_recovery_test). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +-define(receive_messages(Msgs), receive_messages(Msgs, ?FILE, ?LINE)). + +% First Some debug logging +-define(dgb, true). +-ifdef(dgb). +-define(dl(X, Y), ?verbose("**TRACING: " ++ X ++ "**~n", Y)). +-else. +-define(dl(X, Y), ok). +-endif. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +all(doc) -> + ["Verify recoverabiliy of schema transactions.", + " Verify that a schema transaction", + " can be completed when it has been logged correctly and Mnesia", + " crashed before the log has been dumped. Then the transaction ", + " should be handled during the log dump at startup" + ]; +all(suite) -> + [interrupted_before_log_dump, + interrupted_after_log_dump]. + +interrupted_before_log_dump(suite) -> + [interrupted_before_create_ram, + interrupted_before_create_disc, + interrupted_before_create_disc_only, + interrupted_before_create_nostore, + interrupted_before_delete_ram, + interrupted_before_delete_disc, + interrupted_before_delete_disc_only, + interrupted_before_add_ram, + interrupted_before_add_disc, + interrupted_before_add_disc_only, + interrupted_before_add_kill_copier, + interrupted_before_move_ram, + interrupted_before_move_disc, + interrupted_before_move_disc_only, + interrupted_before_move_kill_copier, + interrupted_before_delcopy_ram, + interrupted_before_delcopy_disc, + interrupted_before_delcopy_disc_only, + interrupted_before_delcopy_kill_copier, + interrupted_before_addindex_ram, + interrupted_before_addindex_disc, + interrupted_before_addindex_disc_only, + interrupted_before_delindex_ram, + interrupted_before_delindex_disc, + interrupted_before_delindex_disc_only, + interrupted_before_change_type_ram2disc, + interrupted_before_change_type_ram2disc_only, + interrupted_before_change_type_disc2ram, + interrupted_before_change_type_disc2disc_only, + interrupted_before_change_type_disc_only2ram, + interrupted_before_change_type_disc_only2disc, + interrupted_before_change_type_other_node, + interrupted_before_change_schema_type %% Change schema table copy type!! + ]. + +interrupted_after_log_dump(suite) -> + [interrupted_after_create_ram, + interrupted_after_create_disc, + interrupted_after_create_disc_only, + interrupted_after_create_nostore, + interrupted_after_delete_ram, + interrupted_after_delete_disc, + interrupted_after_delete_disc_only, + interrupted_after_add_ram, + interrupted_after_add_disc, + interrupted_after_add_disc_only, + interrupted_after_add_kill_copier, + interrupted_after_move_ram, + interrupted_after_move_disc, + interrupted_after_move_disc_only, + interrupted_after_move_kill_copier, + interrupted_after_delcopy_ram, + interrupted_after_delcopy_disc, + interrupted_after_delcopy_disc_only, + interrupted_after_delcopy_kill_copier, + interrupted_after_addindex_ram, + interrupted_after_addindex_disc, + interrupted_after_addindex_disc_only, + interrupted_after_delindex_ram, + interrupted_after_delindex_disc, + interrupted_after_delindex_disc_only, + interrupted_after_change_type_ram2disc, + interrupted_after_change_type_ram2disc_only, + interrupted_after_change_type_disc2ram, + interrupted_after_change_type_disc2disc_only, + interrupted_after_change_type_disc_only2ram, + interrupted_after_change_type_disc_only2disc, + interrupted_after_change_type_other_node, + interrupted_after_change_schema_type %% Change schema table copy type!! + +% interrupted_before_change_access_mode, +% interrupted_before_transform, +% interrupted_before_restore, + ]. + +interrupted_before_create_ram(suite) -> []; +interrupted_before_create_ram(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, dump_schema_op}, + interrupted_create(Config, ram_copies, all, KillAt). + +interrupted_before_create_disc(suite) -> []; +interrupted_before_create_disc(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, dump_schema_op}, + interrupted_create(Config, disc_copies, all, KillAt). + +interrupted_before_create_disc_only(suite) -> []; +interrupted_before_create_disc_only(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, dump_schema_op}, + interrupted_create(Config, disc_only_copies, all, KillAt). + +interrupted_before_create_nostore(suite) -> []; +interrupted_before_create_nostore(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, dump_schema_op}, + interrupted_create(Config, ram_copies, one, KillAt). + +interrupted_after_create_ram(suite) -> []; +interrupted_after_create_ram(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, post_dump}, + interrupted_create(Config, ram_copies, all, KillAt). + +interrupted_after_create_disc(suite) -> []; +interrupted_after_create_disc(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, post_dump}, + interrupted_create(Config, disc_copies, all, KillAt). + +interrupted_after_create_disc_only(suite) -> []; +interrupted_after_create_disc_only(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, post_dump}, + interrupted_create(Config, disc_only_copies, all, KillAt). + +interrupted_after_create_nostore(suite) -> []; +interrupted_after_create_nostore(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, post_dump}, + interrupted_create(Config, ram_copies, one, KillAt). + +%%% After dump don't need debug point +interrupted_create(Config, Type, _Where, {mnesia_dumper, post_dump}) -> + [Node1] = Nodes = ?acquire_nodes(1, [{tc_timeout, timer:seconds(30)} | Config]), + ?match({atomic, ok},mnesia:create_table(itrpt, [{Type, Nodes}])), + ?match({atomic, ok},mnesia:create_table(test, [{disc_copies,[Node1]}])), + ?match(ok, mnesia:dirty_write({itrpt, before, 1})), + ?match(ok, mnesia:dirty_write({test, found_in_log, 1})), + ?match(stopped, mnesia:stop()), + ?match([], mnesia_test_lib:start_mnesia([Node1], [itrpt,test])), + %% Verify + ?match([{test, found_in_log, 1}], mnesia:dirty_read({test, found_in_log})), + case Type of + ram_copies -> + ?match([], mnesia:dirty_read({itrpt, before})); + _ -> + ?match([{itrpt, before, 1}], mnesia:dirty_read({itrpt, before})) + end, + ?verify_mnesia(Nodes, []); +interrupted_create(Config, Type, Where, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + {success, [A]} = ?start_activities([Node2]), + setup_dbgpoint(KillAt, Node2), + + if %% CREATE TABLE + Where == all -> % tables on both nodes + A ! fun() -> mnesia:create_table(itrpt, [{Type, Nodes}]) end; + true -> % no table on the killed node + A ! fun() -> mnesia:create_table(itrpt, [{Type, [Node1]}]) end + end, + + kill_at_debug(), + ?match([], mnesia_test_lib:start_mnesia([Node2], [itrpt])), + %% Verify + ?match(ok, mnesia:dirty_write({itrpt, before, 1})), + verify_tab(Node1, Node2), + ?verify_mnesia(Nodes, []). + +interrupted_before_delete_ram(suite) -> []; +interrupted_before_delete_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delete(Config, ram_copies, Debug_Point). +interrupted_before_delete_disc(suite) -> []; +interrupted_before_delete_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delete(Config, disc_copies, Debug_Point). +interrupted_before_delete_disc_only(suite) -> []; +interrupted_before_delete_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delete(Config, disc_only_copies, Debug_Point). + +interrupted_after_delete_ram(suite) -> []; +interrupted_after_delete_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delete(Config, ram_copies, Debug_Point). +interrupted_after_delete_disc(suite) -> []; +interrupted_after_delete_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delete(Config, disc_copies, Debug_Point). +interrupted_after_delete_disc_only(suite) -> []; +interrupted_after_delete_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delete(Config, disc_only_copies, Debug_Point). + +interrupted_delete(Config, Type, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node2]}])), + ?match(ok, mnesia:dirty_write({Tab, before, 1})), + {_Alive, Kill} = {Node1, Node2}, + {success, [A]} = ?start_activities([Kill]), + + setup_dbgpoint(KillAt, Kill), + A ! fun() -> mnesia:delete_table(Tab) end, + + kill_at_debug(), + ?match([], mnesia_test_lib:start_mnesia([Node2], [])), + Bad = {badrpc, {'EXIT', {aborted,{no_exists, Tab, all}}}}, + ?match(Bad, rpc:call(Node1, mnesia, table_info, [Tab, all])), + ?match(Bad, rpc:call(Node2, mnesia, table_info, [Tab, all])), + ?verify_mnesia(Nodes, []). + +interrupted_before_add_ram(suite) -> []; +interrupted_before_add_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_add(Config, ram_copies, kill_reciever, Debug_Point). +interrupted_before_add_disc(suite) -> []; +interrupted_before_add_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_add(Config, disc_copies, kill_reciever, Debug_Point). +interrupted_before_add_disc_only(suite) -> []; +interrupted_before_add_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_add(Config, disc_only_copies, kill_reciever, Debug_Point). +interrupted_before_add_kill_copier(suite) -> []; +interrupted_before_add_kill_copier(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_add(Config, ram_copies, kill_copier, Debug_Point). + +interrupted_after_add_ram(suite) -> []; +interrupted_after_add_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_add(Config, ram_copies, kill_reciever, Debug_Point). +interrupted_after_add_disc(suite) -> []; +interrupted_after_add_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_add(Config, disc_copies, kill_reciever, Debug_Point). +interrupted_after_add_disc_only(suite) -> []; +interrupted_after_add_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_add(Config, disc_only_copies, kill_reciever, Debug_Point). +interrupted_after_add_kill_copier(suite) -> []; +interrupted_after_add_kill_copier(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_add(Config, ram_copies, kill_copier, Debug_Point). + +%%% After dump don't need debug point +interrupted_add(Config, Type, _Where, {mnesia_dumper, post_dump}) -> + [Node1, Node2] = Nodes = + ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node2]}, {local_content,true}])), + ?match({atomic, ok},mnesia:create_table(test, [{disc_copies,[Node1]}])), + ?match({atomic, ok}, mnesia:add_table_copy(Tab, Node1, Type)), + ?match(ok, mnesia:dirty_write({itrpt, before, 1})), + ?match(ok, mnesia:dirty_write({test, found_in_log, 1})), + ?match(stopped, mnesia:stop()), + ?match([], mnesia_test_lib:start_mnesia([Node1], [itrpt,test])), + %% Verify + ?match([{test, found_in_log, 1}], mnesia:dirty_read({test, found_in_log})), + case Type of + ram_copies -> + ?match([], mnesia:dirty_read({itrpt, before})); + _ -> + ?match([{itrpt, before, 1}], mnesia:dirty_read({itrpt, before})) + end, + ?verify_mnesia(Nodes, []); +interrupted_add(Config, Type, Who, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = + ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + {_Alive, Kill} = + if Who == kill_reciever -> + {Node1, Node2}; + true -> + {Node2, Node1} + end, + {success, [A]} = ?start_activities([Kill]), + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node1]}])), + ?match(ok, mnesia:dirty_write({Tab, before, 1})), + + setup_dbgpoint(KillAt, Kill), + + A ! fun() -> mnesia:add_table_copy(Tab, Node2, Type) end, + kill_at_debug(), + ?match([], mnesia_test_lib:start_mnesia([Kill], [itrpt])), + verify_tab(Node1, Node2), + ?verify_mnesia(Nodes, []). + +interrupted_before_move_ram(suite) -> []; +interrupted_before_move_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_move(Config, ram_copies, kill_reciever, Debug_Point). +interrupted_before_move_disc(suite) -> []; +interrupted_before_move_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_move(Config, disc_copies, kill_reciever, Debug_Point). +interrupted_before_move_disc_only(suite) -> []; +interrupted_before_move_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_move(Config, disc_only_copies, kill_reciever, Debug_Point). +interrupted_before_move_kill_copier(suite) -> []; +interrupted_before_move_kill_copier(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_move(Config, ram_copies, kill_copier, Debug_Point). + +interrupted_after_move_ram(suite) -> []; +interrupted_after_move_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_move(Config, ram_copies, kill_reciever, Debug_Point). +interrupted_after_move_disc(suite) -> []; +interrupted_after_move_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_move(Config, disc_copies, kill_reciever, Debug_Point). +interrupted_after_move_disc_only(suite) -> []; +interrupted_after_move_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_move(Config, disc_only_copies, kill_reciever, Debug_Point). +interrupted_after_move_kill_copier(suite) -> []; +interrupted_after_move_kill_copier(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_move(Config, ram_copies, kill_copier, Debug_Point). + +%%% After dump don't need debug point +interrupted_move(Config, Type, _Where, {mnesia_dumper, post_dump}) -> + [Node1, Node2] = Nodes = + ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok},mnesia:create_table(test, [{disc_copies,[Node1]}])), + ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node1]}])), + ?match(ok, mnesia:dirty_write({itrpt, before, 1})), + ?match({atomic, ok}, mnesia:move_table_copy(Tab, Node1, Node2)), + ?match(ok, mnesia:dirty_write({itrpt, aFter, 1})), + ?match(ok, mnesia:dirty_write({test, found_in_log, 1})), + ?match(stopped, mnesia:stop()), + ?match([], mnesia_test_lib:start_mnesia([Node1], [itrpt,test])), + %% Verify + ?match([{test, found_in_log, 1}], mnesia:dirty_read({test, found_in_log})), + ?match([{itrpt, before, 1}], mnesia:dirty_read({itrpt, before})), + ?match([{itrpt, aFter, 1}], mnesia:dirty_read({itrpt, aFter})), + ?verify_mnesia(Nodes, []); +interrupted_move(Config, Type, Who, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = + ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node1]}])), + ?match(ok, mnesia:dirty_write({Tab, before, 1})), + + {_Alive, Kill} = + if Who == kill_reciever -> + if Type == ram_copies -> + {atomic, ok} = mnesia:dump_tables([Tab]); + true -> + ignore + end, + {Node1, Node2}; + true -> + {Node2, Node1} + end, + + {success, [A]} = ?start_activities([Kill]), + + setup_dbgpoint(KillAt, Kill), + A ! fun() -> mnesia:move_table_copy(Tab, Node1, Node2) end, + kill_at_debug(), + ?match([], mnesia_test_lib:start_mnesia([Kill], [itrpt])), + verify_tab(Node1, Node2), + ?verify_mnesia(Nodes, []). + +interrupted_before_delcopy_ram(suite) -> []; +interrupted_before_delcopy_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delcopy(Config, ram_copies, kill_reciever, Debug_Point). +interrupted_before_delcopy_disc(suite) -> []; +interrupted_before_delcopy_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delcopy(Config, disc_copies, kill_reciever, Debug_Point). +interrupted_before_delcopy_disc_only(suite) -> []; +interrupted_before_delcopy_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delcopy(Config, disc_only_copies, kill_reciever, Debug_Point). +interrupted_before_delcopy_kill_copier(suite) -> []; +interrupted_before_delcopy_kill_copier(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delcopy(Config, ram_copies, kill_copier, Debug_Point). + +interrupted_after_delcopy_ram(suite) -> []; +interrupted_after_delcopy_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delcopy(Config, ram_copies, kill_reciever, Debug_Point). +interrupted_after_delcopy_disc(suite) -> []; +interrupted_after_delcopy_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delcopy(Config, disc_copies, kill_reciever, Debug_Point). +interrupted_after_delcopy_disc_only(suite) -> []; +interrupted_after_delcopy_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delcopy(Config, disc_only_copies, kill_reciever, Debug_Point). +interrupted_after_delcopy_kill_copier(suite) -> []; +interrupted_after_delcopy_kill_copier(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delcopy(Config, ram_copies, kill_copier, Debug_Point). + + +%%% After dump don't need debug point +interrupted_delcopy(Config, Type, _Where, {mnesia_dumper, post_dump}) -> + [Node1, Node2] = Nodes = + ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok},mnesia:create_table(test, [{disc_copies,[Node1]}])), + ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node1,Node2]}])), + ?match({atomic, ok}, mnesia:del_table_copy(Tab, Node1)), + ?match(ok, mnesia:dirty_write({test, found_in_log, 1})), + ?match(stopped, mnesia:stop()), + ?match([], mnesia_test_lib:start_mnesia([Node1], [test])), + %% Verify + ?match([{test, found_in_log, 1}], mnesia:dirty_read({test, found_in_log})), + ?match([Node2], mnesia:table_info(itrpt,Type)), + ?verify_mnesia(Nodes, []); +interrupted_delcopy(Config, Type, Who, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = + ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node1, Node2]}])), + ?match(ok, mnesia:dirty_write({Tab, before, 1})), + + {_Alive, Kill} = + if Who == kill_reciever -> + {Node1, Node2}; + true -> + if + Type == ram_copies -> + {atomic, ok} = mnesia:dump_tables([Tab]); + true -> + ignore + end, + {Node2, Node1} + end, + + {success, [A]} = ?start_activities([Kill]), + setup_dbgpoint(KillAt, Kill), + A ! fun() -> mnesia:del_table_copy(Tab, Node2) end, + kill_at_debug(), + ?match([], mnesia_test_lib:start_mnesia([Kill], [itrpt])), + verify_tab(Node1, Node2), + ?verify_mnesia(Nodes, []). + +interrupted_before_addindex_ram(suite) -> []; +interrupted_before_addindex_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_addindex(Config, ram_copies, Debug_Point). +interrupted_before_addindex_disc(suite) -> []; +interrupted_before_addindex_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_addindex(Config, disc_copies, Debug_Point). +interrupted_before_addindex_disc_only(suite) -> []; +interrupted_before_addindex_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_addindex(Config, disc_only_copies, Debug_Point). + +interrupted_after_addindex_ram(suite) -> []; +interrupted_after_addindex_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_addindex(Config, ram_copies, Debug_Point). +interrupted_after_addindex_disc(suite) -> []; +interrupted_after_addindex_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_addindex(Config, disc_copies, Debug_Point). +interrupted_after_addindex_disc_only(suite) -> []; +interrupted_after_addindex_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_addindex(Config, disc_only_copies, Debug_Point). + + +%%% After dump don't need debug point +interrupted_addindex(Config, Type, {mnesia_dumper, post_dump}) -> + [Node1] = Nodes = ?acquire_nodes(1, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic,ok},mnesia:create_table(Tab, [{Type, Nodes}])), + ?match({atomic,ok},mnesia:create_table(test, [{disc_copies,[Node1]}])), + ?match({atomic,ok}, mnesia:add_table_index(Tab, val)), + ?match(ok, mnesia:dirty_write({itrpt, before, 1})), + ?match(ok, mnesia:dirty_write({test, found_in_log, 1})), + ?match(stopped, mnesia:stop()), + ?match([], mnesia_test_lib:start_mnesia([Node1], [itrpt,test])), + %% Verify + ?match([{test, found_in_log, 1}], mnesia:dirty_read({test, found_in_log})), + case Type of + ram_copies -> + ?match([], mnesia:dirty_index_read(itrpt, 1, val)); + _ -> + ?match([{itrpt, before, 1}], mnesia:dirty_index_read(itrpt, 1, val)) + end, + ?verify_mnesia(Nodes, []); +interrupted_addindex(Config, Type, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node1]}])), + ?match(ok, mnesia:dirty_write({Tab, before, 1})), + {_Alive, Kill} = {Node1, Node2}, + {success, [A]} = ?start_activities([Kill]), + + setup_dbgpoint(KillAt, Kill), + A ! fun() -> mnesia:add_table_index(Tab, val) end, + kill_at_debug(), + ?match([], mnesia_test_lib:start_mnesia([Node2], [])), + + verify_tab(Node1, Node2), + ?match([{Tab, b, a}, {Tab, a, a}], + rpc:call(Node1, mnesia, dirty_index_read, [itrpt, a, val])), + ?match([{Tab, b, a}, {Tab, a, a}], + rpc:call(Node2, mnesia, dirty_index_read, [itrpt, a, val])), + ?verify_mnesia(Nodes, []). + +interrupted_before_delindex_ram(suite) -> []; +interrupted_before_delindex_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delindex(Config, ram_copies, Debug_Point). +interrupted_before_delindex_disc(suite) -> []; +interrupted_before_delindex_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delindex(Config, disc_copies, Debug_Point). +interrupted_before_delindex_disc_only(suite) -> []; +interrupted_before_delindex_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_delindex(Config, disc_only_copies, Debug_Point). + +interrupted_after_delindex_ram(suite) -> []; +interrupted_after_delindex_ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delindex(Config, ram_copies, Debug_Point). +interrupted_after_delindex_disc(suite) -> []; +interrupted_after_delindex_disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delindex(Config, disc_copies, Debug_Point). +interrupted_after_delindex_disc_only(suite) -> []; +interrupted_after_delindex_disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_delindex(Config, disc_only_copies, Debug_Point). + +%%% After dump don't need debug point +interrupted_delindex(Config, Type, {mnesia_dumper, post_dump}) -> + [Node1] = Nodes = ?acquire_nodes(1, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic,ok},mnesia:create_table(Tab, [{Type, Nodes},{index,[val]}])), + ?match({atomic,ok},mnesia:create_table(test, [{disc_copies,[Node1]}])), + ?match({atomic,ok}, mnesia:del_table_index(Tab, val)), + ?match(ok, mnesia:dirty_write({itrpt, before, 1})), + ?match(ok, mnesia:dirty_write({test, found_in_log, 1})), + ?match(stopped, mnesia:stop()), + ?match([], mnesia_test_lib:start_mnesia([Node1], [itrpt,test])), + %% Verify + ?match([{test, found_in_log, 1}], mnesia:dirty_read({test, found_in_log})), + ?match({'EXIT',{aborted,{badarg,_}}}, mnesia:dirty_index_read(itrpt, 1, val)), + ?verify_mnesia(Nodes, []); + +interrupted_delindex(Config, Type, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{index, [val]}, + {Type, [Node1]}])), + ?match(ok, mnesia:dirty_write({Tab, before, 1})), + {_Alive, Kill} = {Node1, Node2}, + {success, [A]} = ?start_activities([Kill]), + setup_dbgpoint(KillAt, Kill), + A ! fun() -> mnesia:del_table_index(Tab, val) end, + kill_at_debug(), + ?match([], mnesia_test_lib:start_mnesia([Node2], [])), + verify_tab(Node1, Node2), + ?match({badrpc, _}, rpc:call(Node1, mnesia, dirty_index_read, [itrpt, a, val])), + ?match({badrpc, _}, rpc:call(Node2, mnesia, dirty_index_read, [itrpt, a, val])), + ?match([], rpc:call(Node1, mnesia, table_info, [Tab, index])), + ?match([], rpc:call(Node2, mnesia, table_info, [Tab, index])), + ?verify_mnesia(Nodes, []). + +interrupted_before_change_type_ram2disc(suite) -> []; +interrupted_before_change_type_ram2disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_change_type(Config, ram_copies, disc_copies, changer, Debug_Point). +interrupted_before_change_type_ram2disc_only(suite) -> []; +interrupted_before_change_type_ram2disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_change_type(Config, ram_copies, disc_only_copies, changer, Debug_Point). +interrupted_before_change_type_disc2ram(suite) -> []; +interrupted_before_change_type_disc2ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_change_type(Config, disc_copies, ram_copies, changer, Debug_Point). +interrupted_before_change_type_disc2disc_only(suite) -> []; +interrupted_before_change_type_disc2disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_change_type(Config, disc_copies, disc_only_copies, changer, Debug_Point). +interrupted_before_change_type_disc_only2ram(suite) -> []; +interrupted_before_change_type_disc_only2ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_change_type(Config, disc_only_copies, ram_copies, changer, Debug_Point). +interrupted_before_change_type_disc_only2disc(suite) -> []; +interrupted_before_change_type_disc_only2disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_change_type(Config, disc_only_copies, disc_copies, changer, Debug_Point). +interrupted_before_change_type_other_node(suite) -> []; +interrupted_before_change_type_other_node(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, dump_schema_op}, + interrupted_change_type(Config, ram_copies, disc_copies, the_other_one, Debug_Point). + +interrupted_after_change_type_ram2disc(suite) -> []; +interrupted_after_change_type_ram2disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_change_type(Config, ram_copies, disc_copies, changer, Debug_Point). +interrupted_after_change_type_ram2disc_only(suite) -> []; +interrupted_after_change_type_ram2disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_change_type(Config, ram_copies, disc_only_copies, changer, Debug_Point). +interrupted_after_change_type_disc2ram(suite) -> []; +interrupted_after_change_type_disc2ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_change_type(Config, disc_copies, ram_copies, changer, Debug_Point). +interrupted_after_change_type_disc2disc_only(suite) -> []; +interrupted_after_change_type_disc2disc_only(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_change_type(Config, disc_copies, disc_only_copies, changer, Debug_Point). +interrupted_after_change_type_disc_only2ram(suite) -> []; +interrupted_after_change_type_disc_only2ram(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_change_type(Config, disc_only_copies, ram_copies, changer, Debug_Point). +interrupted_after_change_type_disc_only2disc(suite) -> []; +interrupted_after_change_type_disc_only2disc(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_change_type(Config, disc_only_copies, disc_copies, changer, Debug_Point). +interrupted_after_change_type_other_node(suite) -> []; +interrupted_after_change_type_other_node(Config) when is_list(Config) -> + Debug_Point = {mnesia_dumper, post_dump}, + interrupted_change_type(Config, ram_copies, disc_copies, the_other_one, Debug_Point). + +interrupted_change_type(Config, FromType, ToType, Who, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{FromType, [Node2, Node1]}])), + ?match(ok, mnesia:dirty_write({Tab, before, 1})), + + {_Alive, Kill} = + if Who == changer -> {Node1, Node2}; + true -> {Node2, Node1} + end, + + {success, [A]} = ?start_activities([Kill]), + setup_dbgpoint(KillAt, Kill), + A ! fun() -> mnesia:change_table_copy_type(Tab, Node2, ToType) end, + kill_at_debug(), + ?match([], mnesia_test_lib:start_mnesia(Nodes, [itrpt])), + verify_tab(Node1, Node2), + ?match(FromType, rpc:call(Node1, mnesia, table_info, [Tab, storage_type])), + ?match(ToType, rpc:call(Node2, mnesia, table_info, [Tab, storage_type])), + ?verify_mnesia(Nodes, []). + +interrupted_before_change_schema_type(suite) -> []; +interrupted_before_change_schema_type(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, dump_schema_op}, + interrupted_change_schema_type(Config, KillAt). + +interrupted_after_change_schema_type(suite) -> []; +interrupted_after_change_schema_type(Config) when is_list(Config) -> + KillAt = {mnesia_dumper, post_dump}, + interrupted_change_schema_type(Config, KillAt). + +-define(cleanup(N, Config), + mnesia_test_lib:prepare_test_case([{reload_appls, [mnesia]}], + N, Config, ?FILE, ?LINE)). + +interrupted_change_schema_type(Config, KillAt) -> + ?is_debug_compiled, + [Node1, Node2] = Nodes = ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]), + + Tab = itrpt, + ?match({atomic, ok}, mnesia:create_table(Tab, [{ram_copies, [Node2, Node1]}])), + ?match(ok, mnesia:dirty_write({Tab, before, 1})), + + {success, [A]} = ?start_activities([Node2]), + setup_dbgpoint(KillAt, Node2), + + A ! fun() -> mnesia:change_table_copy_type(schema, Node2, ram_copies) end, + kill_at_debug(), + ?match(ok, rpc:call(Node2, mnesia, start, [[{extra_db_nodes, [Node1, Node2]}]])), + ?match(ok, rpc:call(Node2, mnesia, wait_for_tables, [[itrpt, schema], 2000])), + ?match(disc_copies, rpc:call(Node1, mnesia, table_info, [schema, storage_type])), + ?match(ram_copies, rpc:call(Node2, mnesia, table_info, [schema, storage_type])), + + %% Go back to disc_copies !! + {success, [B]} = ?start_activities([Node2]), + setup_dbgpoint(KillAt, Node2), + B ! fun() -> mnesia:change_table_copy_type(schema, Node2, disc_copies) end, + kill_at_debug(), + + ?match(ok, rpc:call(Node2, mnesia, start, [[{extra_db_nodes, [Node1, Node2]}]])), + ?match(ok, rpc:call(Node2, mnesia, wait_for_tables, [[itrpt, schema], 2000])), + ?match(disc_copies, rpc:call(Node1, mnesia, table_info, [schema, storage_type])), + ?match(disc_copies, rpc:call(Node2, mnesia, table_info, [schema, storage_type])), + + ?verify_mnesia(Nodes, []), + ?cleanup(2, Config). + +%%% Helpers +verify_tab(Node1, Node2) -> + ?match({atomic, ok}, + rpc:call(Node1, mnesia, transaction, [fun() -> mnesia:dirty_write({itrpt, a, a}) end])), + ?match({atomic, ok}, + rpc:call(Node2, mnesia, transaction, [fun() -> mnesia:dirty_write({itrpt, b, a}) end])), + ?match([{itrpt,a,a}], rpc:call(Node1, mnesia, dirty_read, [{itrpt, a}])), + ?match([{itrpt,a,a}], rpc:call(Node2, mnesia, dirty_read, [{itrpt, a}])), + ?match([{itrpt,b,a}], rpc:call(Node1, mnesia, dirty_read, [{itrpt, b}])), + ?match([{itrpt,b,a}], rpc:call(Node2, mnesia, dirty_read, [{itrpt, b}])), + ?match([{itrpt,before,1}], rpc:call(Node1, mnesia, dirty_read, [{itrpt, before}])), + ?match([{itrpt,before,1}], rpc:call(Node2, mnesia, dirty_read, [{itrpt, before}])). + +setup_dbgpoint(DbgPoint, Where) -> + Self = self(), + TestFun = fun(_, [InitBy]) -> + case InitBy of + schema_prepare -> + ignore; + schema_begin -> + ignore; + _Other -> + ?deactivate_debug_fun(DbgPoint), + unlink(Self), + Self ! {fun_done, node()}, + timer:sleep(infinity) + end + end, + %% Kill when debug has been reached + ?remote_activate_debug_fun(Where, DbgPoint, TestFun, []). + +kill_at_debug() -> + %% Wait till it's killed + receive + {fun_done, Node} -> + ?match([], mnesia_test_lib:kill_mnesia([Node])) + after + timer:minutes(1) -> ?error("Timeout in kill_at_debug", []) + end. + diff --git a/lib/mnesia/test/mnesia_test_lib.erl b/lib/mnesia/test/mnesia_test_lib.erl new file mode 100644 index 0000000000..1e98f017f7 --- /dev/null +++ b/lib/mnesia/test/mnesia_test_lib.erl @@ -0,0 +1,1058 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%% Author: Hakan Mattsson [email protected] +%%% Purpose: Test case support library +%%% +%%% This test suite may be run as a part of the Grand Test Suite +%%% of Erlang. The Mnesia test suite is structured in a hierarchy. +%%% Each test case is implemented as an exported function with arity 1. +%%% Test case identifiers must have the following syntax: {Module, Function}. +%%% +%%% The driver of the test suite runs in two passes as follows: +%%% first the test case function is invoked with the atom 'suite' as +%%% single argument. The returned value is treated as a list of sub +%%% test cases. If the list of sub test cases is [] the test case +%%% function is invoked again, this time with a list of nodes as +%%% argument. If the list of sub test cases is not empty, the test +%%% case driver applies the algorithm recursively on each element +%%% in the list. +%%% +%%% All test cases are written in such a manner +%%% that they start to invoke ?acquire_nodes(X, Config) +%%% in order to prepare the test case execution. When that is +%%% done, the test machinery ensures that at least X number +%%% of nodes are connected to each other. If too few nodes was +%%% specified in the Config, the test case is skipped. If there +%%% was enough node names in the Config, X of them are selected +%%% and if some of them happens to be down they are restarted +%%% via the slave module. When all nodes are up and running a +%%% disk resident schema is created on all nodes and Mnesia is +%%% started a on all nodes. This means that all test cases may +%%% assume that Mnesia is up and running on all acquired nodes. +%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% +%%% doc(TestCases) +%%% +%%% Generates a test spec from parts of the test case structure +%%% +%%% struct(TestCases) +%%% +%%% Prints out the test case structure +%%% +%%% test(TestCases) +%%% +%%% Run parts of the test suite. Uses test/2. +%%% Reads Config from mnesia_test.config and starts them if neccessary. +%%% Kills Mnesia and wipes out the Mnesia directories as a starter. +%%% +%%% test(TestCases, Config) +%%% +%%% Run parts of the test suite on the given Nodes, +%%% assuming that the nodes are up and running. +%%% Kills Mnesia and wipes out the Mnesia directories as a starter. +%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-module(mnesia_test_lib). +-author('[email protected]'). +-export([ + log/2, + log/4, + verbose/4, + default_config/0, + diskless/1, + eval_test_case/3, + test_driver/2, + test_case_evaluator/3, + activity_evaluator/1, + flush/0, + pick_msg/0, + start_activities/1, + start_transactions/1, + start_transactions/2, + start_sync_transactions/1, + start_sync_transactions/2, + sync_trans_tid_serial/1, + prepare_test_case/5, + select_nodes/4, + init_nodes/3, + error/4, + slave_start_link/0, + slave_start_link/1, + slave_sup/0, + + start_mnesia/1, + start_mnesia/2, + start_appls/2, + start_appls/3, + start_wait/2, + storage_type/2, + stop_mnesia/1, + stop_appls/2, + sort/1, + kill_mnesia/1, + kill_appls/2, + verify_mnesia/4, + shutdown/0, + verify_replica_location/5, + lookup_config/2, + sync_tables/2, + remote_start/3, + remote_stop/1, + remote_kill/1, + + reload_appls/2, + + remote_activate_debug_fun/6, + do_remote_activate_debug_fun/6, + + test/1, + test/2, + doc/1, + struct/1, + init_per_testcase/2, + fin_per_testcase/2, + kill_tc/2 + ]). + +-include("mnesia_test_lib.hrl"). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% included for test server compatibility +%% assume that all test cases only takes Config as sole argument +init_per_testcase(_Func, Config) -> + global:register_name(mnesia_global_logger, group_leader()), + Config. + +fin_per_testcase(_Func, Config) -> + global:unregister_name(mnesia_global_logger), + %% Nodes = select_nodes(all, Config, ?FILE, ?LINE), + %% rpc:multicall(Nodes, mnesia, lkill, []), + Config. + +%% Use ?log(Format, Args) as wrapper +log(Format, Args, LongFile, Line) -> + File = filename:basename(LongFile), + Format2 = lists:concat([File, "(", Line, ")", ": ", Format]), + log(Format2, Args). + +log(Format, Args) -> + case global:whereis_name(mnesia_global_logger) of + undefined -> + io:format(user, Format, Args); + Pid -> + io:format(Pid, Format, Args) + end. + +verbose(Format, Args, File, Line) -> + Arg = mnesia_test_verbose, + case get(Arg) of + false -> + ok; + true -> + log(Format, Args, File, Line); + undefined -> + case init:get_argument(Arg) of + {ok, List} when is_list(List) -> + case lists:last(List) of + ["true"] -> + put(Arg, true), + log(Format, Args, File, Line); + _ -> + put(Arg, false), + ok + end; + _ -> + put(Arg, false), + ok + end + end. + +-record('REASON', {file, line, desc}). + +error(Format, Args, File, Line) -> + global:send(mnesia_global_logger, {failed, File, Line}), + Fail = #'REASON'{file = filename:basename(File), + line = Line, + desc = Args}, + case global:whereis_name(mnesia_test_case_sup) of + undefined -> + ignore; + Pid -> + Pid ! Fail +%% global:send(mnesia_test_case_sup, Fail), + end, + log("<>ERROR<>~n" ++ Format, Args, File, Line). + +storage_type(Default, Config) -> + case diskless(Config) of + true -> + ram_copies; + false -> + Default + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +default_config() -> + [{nodes, default_nodes()}]. + +default_nodes() -> + mk_nodes(3, []). + +mk_nodes(0, Nodes) -> + Nodes; +mk_nodes(N, []) -> + mk_nodes(N - 1, [node()]); +mk_nodes(N, Nodes) when N > 0 -> + Head = hd(Nodes), + [Name, Host] = node_to_name_and_host(Head), + Nodes ++ [mk_node(I, Name, Host) || I <- lists:seq(1, N)]. + +mk_node(N, Name, Host) -> + list_to_atom(lists:concat([Name ++ integer_to_list(N) ++ "@" ++ Host])). + +slave_start_link() -> + slave_start_link(node()). + +slave_start_link(Node) -> + [Local, Host] = node_to_name_and_host(Node), + {Mega, Sec, Micro} = erlang:now(), + List = [Local, "_", Mega, "_", Sec, "_", Micro], + Name = list_to_atom(lists:concat(List)), + slave_start_link(list_to_atom(Host), Name). + +slave_start_link(Host, Name) -> + slave_start_link(Host, Name, 10). + +slave_start_link(Host, Name, Retries) -> + Debug = atom_to_list(mnesia:system_info(debug)), + Args = "-mnesia debug " ++ Debug ++ + " -pa " ++ + filename:dirname(code:which(?MODULE)) ++ + " -pa " ++ + filename:dirname(code:which(mnesia)), + case starter(Host, Name, Args) of + {ok, NewNode} -> + ?match(pong, net_adm:ping(NewNode)), + {ok, Cwd} = file:get_cwd(), + Path = code:get_path(), + ok = rpc:call(NewNode, file, set_cwd, [Cwd]), + true = rpc:call(NewNode, code, set_path, [Path]), + spawn_link(NewNode, ?MODULE, slave_sup, []), + rpc:multicall([node() | nodes()], global, sync, []), + {ok, NewNode}; + {error, Reason} when Retries == 0-> + {error, Reason}; + {error, Reason} -> + io:format("Could not start slavenode ~p ~p retrying~n", + [{Host, Name, Args}, Reason]), + timer:sleep(500), + slave_start_link(Host, Name, Retries - 1) + end. + +starter(Host, Name, Args) -> + case os:type() of + vxworks -> + X = test_server:start_node(Name, slave, [{args,Args}]), + timer:sleep(5000), + X; + _ -> + slave:start(Host, Name, Args) + end. + +slave_sup() -> + process_flag(trap_exit, true), + receive + {'EXIT', _, _} -> + case os:type() of + vxworks -> + erlang:halt(); + _ -> + ignore + end + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Index the test case structure + +doc(TestCases) when is_list(TestCases) -> + test(TestCases, suite), + SuiteFname = "index.html", + io:format("Generating HTML test specification to file: ~s~n", + [SuiteFname]), + {ok, Fd} = file:open(SuiteFname, [write]), + io:format(Fd, "<TITLE>Test specification for ~p</TITLE>.~n", [TestCases]), + io:format(Fd, "<H1>Test specification for ~p</H1>~n", [TestCases]), + io:format(Fd, "Test cases which not are implemented yet are written in <B>bold face</B>.~n~n", []), + + io:format(Fd, "<BR><BR>~n", []), + io:format(Fd, "~n<DL>~n", []), + do_doc(Fd, TestCases, []), + io:format(Fd, "</DL>~n", []), + file:close(Fd); +doc(TestCases) -> + doc([TestCases]). + +do_doc(Fd, [H | T], List) -> + case H of + {Module, TestCase} when is_atom(Module), is_atom(TestCase) -> + do_doc(Fd, Module, TestCase, List); + TestCase when is_atom(TestCase), List == [] -> + do_doc(Fd, mnesia_SUITE, TestCase, List); + TestCase when is_atom(TestCase) -> + do_doc(Fd, hd(List), TestCase, List) + end, + do_doc(Fd, T, List); +do_doc(_, [], _) -> + ok. + +do_doc(Fd, Module, TestCase, List) -> + case get_suite(Module, TestCase) of + [] -> + %% Implemented leaf test case + Head = ?flat_format("<A HREF=~p.html#~p_1>{~p, ~p}</A>}", + [Module, TestCase, Module, TestCase]), + print_doc(Fd, Module, TestCase, Head); + Suite when is_list(Suite) -> + %% Test suite + Head = ?flat_format("{~p, ~p}", [Module, TestCase]), + print_doc(Fd, Module, TestCase, Head), + io:format(Fd, "~n<DL>~n", []), + do_doc(Fd, Suite, [Module | List]), + io:format(Fd, "</DL>~n", []); + 'NYI' -> + %% Not yet implemented + Head = ?flat_format("<B>{~p, ~p}</B>", [Module, TestCase]), + print_doc(Fd, Module, TestCase, Head) + end. + +print_doc(Fd, Mod, Fun, Head) -> + case catch (apply(Mod, Fun, [doc])) of + {'EXIT', _} -> + io:format(Fd, "<DT>~s</DT>~n", [Head]); + Doc when is_list(Doc) -> + io:format(Fd, "<DT><U>~s</U><BR><DD>~n", [Head]), + print_rows(Fd, Doc), + io:format(Fd, "</DD><BR><BR>~n", []) + end. + +print_rows(_Fd, []) -> + ok; +print_rows(Fd, [H | T]) when is_list(H) -> + io:format(Fd, "~s~n", [H]), + print_rows(Fd, T); +print_rows(Fd, [H | T]) when is_integer(H) -> + io:format(Fd, "~s~n", [[H | T]]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Show the test case structure + +struct(TestCases) -> + T = test(TestCases, suite), + struct(T, ""). + +struct({Module, TestCase}, Indentation) + when is_atom(Module), is_atom(TestCase) -> + log("~s{~p, ~p} ...~n", [Indentation, Module, TestCase]); +struct({Module, TestCase, Other}, Indentation) + when is_atom(Module), is_atom(TestCase) -> + log("~s{~p, ~p} ~p~n", [Indentation, Module, TestCase, Other]); +struct([], _) -> + ok; +struct([TestCase | TestCases], Indentation) -> + struct(TestCase, Indentation), + struct(TestCases, Indentation); +struct({TestCase, []}, Indentation) -> + struct(TestCase, Indentation); +struct({TestCase, SubTestCases}, Indentation) when is_list(SubTestCases) -> + struct(TestCase, Indentation), + struct(SubTestCases, Indentation ++ " "). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Execute the test cases + +test(TestCases) -> + test(TestCases, []). + +test(TestCases, suite) when is_list(TestCases) -> + test_driver(TestCases, suite); +test(TestCases, Config) when is_list(TestCases) -> + D1 = lists:duplicate(10, $=), + D2 = lists:duplicate(10, $ ), + log("~n~s TEST CASES: ~p~n ~sCONFIG: ~p~n~n", [D1, TestCases, D2, Config]), + test_driver(TestCases, Config); +test(TestCase, Config) -> + test([TestCase], Config). + +test_driver([], _Config) -> + []; +test_driver([T|TestCases], Config) -> + L1 = test_driver(T, Config), + L2 = test_driver(TestCases, Config), + [L1|L2]; +test_driver({Module, TestCases}, Config) when is_list(TestCases)-> + test_driver(default_module(Module, TestCases), Config); +test_driver({_, {Module, TestCase}}, Config) -> + test_driver({Module, TestCase}, Config); +test_driver({Module, TestCase}, Config) -> + Sec = timer:seconds(1) * 1000, + case get_suite(Module, TestCase) of + [] when Config == suite -> + {Module, TestCase, 'IMPL'}; + [] -> + log("Eval test case: ~w~n", [{Module, TestCase}]), + {T, Res} = + timer:tc(?MODULE, eval_test_case, [Module, TestCase, Config]), + log("Tested ~w in ~w sec~n", [TestCase, T div Sec]), + {T div Sec, Res}; + Suite when is_list(Suite), Config == suite -> + Res = test_driver(default_module(Module, Suite), Config), + {{Module, TestCase}, Res}; + Suite when is_list(Suite) -> + log("Expand test case ~w~n", [{Module, TestCase}]), + Def = default_module(Module, Suite), + {T, Res} = timer:tc(?MODULE, test_driver, [Def, Config]), + {T div Sec, {{Module, TestCase}, Res}}; + 'NYI' when Config == suite -> + {Module, TestCase, 'NYI'}; + 'NYI' -> + log("<WARNING> Test case ~w NYI~n", [{Module, TestCase}]), + {0, {skip, {Module, TestCase}, "NYI"}} + end; +test_driver(TestCase, Config) -> + DefaultModule = mnesia_SUITE, + log("<>WARNING<> Missing module in test case identifier. " + "{~w, ~w} assumed~n", [DefaultModule, TestCase]), + test_driver({DefaultModule, TestCase}, Config). + +default_module(DefaultModule, TestCases) when is_list(TestCases) -> + Fun = fun(T) -> + case T of + {_, _} -> true; + T -> {true, {DefaultModule, T}} + end + end, + lists:zf(Fun, TestCases). + +%% Returns a list (possibly empty) or the atom 'NYI' +get_suite(Mod, Fun) -> + case catch (apply(Mod, Fun, [suite])) of + {'EXIT', _} -> 'NYI'; + List when is_list(List) -> List + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +eval_test_case(Mod, Fun, Config) -> + flush(), + global:register_name(mnesia_test_case_sup, self()), + Flag = process_flag(trap_exit, true), + Pid = spawn_link(?MODULE, test_case_evaluator, [Mod, Fun, [Config]]), + R = wait_for_evaluator(Pid, Mod, Fun, Config), + global:unregister_name(mnesia_test_case_sup), + process_flag(trap_exit, Flag), + R. + +flush() -> + receive Msg -> [Msg | flush()] + after 0 -> [] + end. + +wait_for_evaluator(Pid, Mod, Fun, Config) -> + receive + {'EXIT', Pid, {test_case_ok, _PidRes}} -> + Errors = flush(), + Res = + case Errors of + [] -> ok; + Errors -> failed + end, + {Res, {Mod, Fun}, Errors}; + {'EXIT', Pid, {skipped, Reason}} -> + log("<WARNING> Test case ~w skipped, because ~p~n", + [{Mod, Fun}, Reason]), + Mod:fin_per_testcase(Fun, Config), + {skip, {Mod, Fun}, Reason}; + {'EXIT', Pid, Reason} -> + log("<>ERROR<> Eval process ~w exited, because ~p~n", + [{Mod, Fun}, Reason]), + Mod:fin_per_testcase(Fun, Config), + {crash, {Mod, Fun}, Reason} + end. + +test_case_evaluator(Mod, Fun, [Config]) -> + NewConfig = Mod:init_per_testcase(Fun, Config), + R = apply(Mod, Fun, [NewConfig]), + Mod:fin_per_testcase(Fun, NewConfig), + exit({test_case_ok, R}). + +activity_evaluator(Coordinator) -> + activity_evaluator_loop(Coordinator), + exit(normal). + +activity_evaluator_loop(Coordinator) -> + receive + begin_trans -> + transaction(Coordinator, 0); + {begin_trans, MaxRetries} -> + transaction(Coordinator, MaxRetries); + end_trans -> + end_trans; + Fun when is_function(Fun) -> + Coordinator ! {self(), Fun()}, + activity_evaluator_loop(Coordinator); +% {'EXIT', Coordinator, Reason} -> +% Reason; + ExitExpr -> +% ?error("activity_evaluator_loop ~p ~p: exit(~p)~n}", [Coordinator, self(), ExitExpr]), + exit(ExitExpr) + end. + +transaction(Coordinator, MaxRetries) -> + Fun = fun() -> + Coordinator ! {self(), begin_trans}, + activity_evaluator_loop(Coordinator) + end, + Coordinator ! {self(), mnesia:transaction(Fun, MaxRetries)}, + activity_evaluator_loop(Coordinator). + +pick_msg() -> + receive + Message -> Message + after 4000 -> timeout + end. + +start_activities(Nodes) -> + Fun = fun(N) -> spawn_link(N, ?MODULE, activity_evaluator, [self()]) end, + Pids = mapl(Fun, Nodes), + {success, Pids}. + +mapl(Fun, [H|T]) -> + Res = Fun(H), + [Res|mapl(Fun, T)]; +mapl(_Fun, []) -> + []. + +diskless(Config) -> + case lists:keysearch(diskless, 1, Config) of + {value, {diskless, true}} -> + true; + _Else -> + false + end. + + +start_transactions(Pids) -> + Fun = fun(Pid) -> + Pid ! begin_trans, + ?match_receive({Pid, begin_trans}) + end, + mapl(Fun, Pids). + +start_sync_transactions(Pids) -> + Nodes = [node(Pid) || Pid <- Pids], + Fun = fun(Pid) -> + sync_trans_tid_serial(Nodes), + Pid ! begin_trans, + ?match_receive({Pid, begin_trans}) + end, + mapl(Fun, Pids). + + +start_transactions(Pids, MaxRetries) -> + Fun = fun(Pid) -> + Pid ! {begin_trans, MaxRetries}, + ?match_receive({Pid, begin_trans}) + end, + mapl(Fun, Pids). + +start_sync_transactions(Pids, MaxRetries) -> + Nodes = [node(Pid) || Pid <- Pids], + Fun = fun(Pid) -> + sync_trans_tid_serial(Nodes), + Pid ! {begin_trans, MaxRetries}, + ?match_receive({Pid, begin_trans}) + end, + mapl(Fun, Pids). + +sync_trans_tid_serial(Nodes) -> + Fun = fun() -> mnesia:write_lock_table(schema) end, + rpc:multicall(Nodes, mnesia, transaction, [Fun]). + +select_nodes(N, Config, File, Line) -> + prepare_test_case([], N, Config, File, Line). + +prepare_test_case(Actions, N, Config, File, Line) -> + NodeList1 = lookup_config(nodes, Config), + NodeList2 = lookup_config(nodenames, Config), %% For testserver + NodeList3 = append_unique(NodeList1, NodeList2), + This = node(), + All = [This | lists:delete(This, NodeList3)], + Selected = pick_nodes(N, All, File, Line), + case diskless(Config) of + true -> + ok; + false -> + rpc:multicall(Selected, application, set_env,[mnesia, schema_location, opt_disc]) + end, + do_prepare(Actions, Selected, All, Config, File, Line). + +do_prepare([], Selected, _All, _Config, _File, _Line) -> + Selected; +do_prepare([{init_test_case, Appls} | Actions], Selected, All, Config, File, Line) -> + set_kill_timer(Config), + Started = init_nodes(Selected, File, Line), + All2 = append_unique(Started, All), + Alive = mnesia_lib:intersect(nodes() ++ [node()], All2), + kill_appls(Appls, Alive), + process_flag(trap_exit, true), + do_prepare(Actions, Started, All2, Config, File, Line); +do_prepare([delete_schema | Actions], Selected, All, Config, File, Line) -> + Alive = mnesia_lib:intersect(nodes() ++ [node()], All), + case diskless(Config) of + true -> + skip; + false -> + Del = fun(Node) -> + case mnesia:delete_schema([Node]) of + ok -> ok; + {error, {"All nodes not running",_}} -> + ok; + Else -> + ?log("Delete schema error ~p ~n", [Else]) + end + end, + lists:foreach(Del, Alive) + end, + do_prepare(Actions, Selected, All, Config, File, Line); +do_prepare([create_schema | Actions], Selected, All, Config, File, Line) -> + case diskless(Config) of + true -> + skip; + _Else -> + case mnesia:create_schema(Selected) of + ok -> + ignore; + BadNodes -> + ?fatal("Cannot create Mnesia schema on ~p~n", [BadNodes]) + end + end, + do_prepare(Actions, Selected, All, Config, File, Line); +do_prepare([{start_appls, Appls} | Actions], Selected, All, Config, File, Line) -> + case start_appls(Appls, Selected, Config) of + [] -> ok; + Bad -> ?fatal("Cannot start appls ~p: ~p~n", [Appls, Bad]) + end, + do_prepare(Actions, Selected, All, Config, File, Line); +do_prepare([{reload_appls, Appls} | Actions], Selected, All, Config, File, Line) -> + reload_appls(Appls, Selected), + do_prepare(Actions, Selected, All, Config, File, Line). + +set_kill_timer(Config) -> + case init:get_argument(mnesia_test_timeout) of + {ok, _ } -> ok; + _ -> + Time0 = + case lookup_config(tc_timeout, Config) of + [] -> timer:minutes(5); + ConfigTime when is_integer(ConfigTime) -> ConfigTime + end, + Mul = try + test_server:timetrap_scale_factor() + catch _:_ -> 1 end, + (catch test_server:timetrap(Mul*Time0 + 1000)), + spawn_link(?MODULE, kill_tc, [self(),Time0*Mul]) + end. + +kill_tc(Pid, Time) -> + receive + after Time -> + case process_info(Pid) of + undefined -> ok; + _ -> + ?error("Watchdog in test case timed out " + "in ~p min~n", [Time div (1000*60)]), + Files = mnesia_lib:dist_coredump(), + ?log("Cores dumped to:~n ~p~n", [Files]), + %% Genarate erlang crashdumps. + %% GenDump = fun(Node) -> + %% File = "CRASH_" ++ atom_to_list(Node) ++ ".dump", + %% rpc:call(Node, os, putenv, ["ERL_CRASH_DUMP", File]), + %% rpc:cast(Node, erlang, halt, ["RemoteTimeTrap"]) + %% end, + %% [GenDump(Node) || Node <- nodes()], + + %% erlang:halt("DebugTimeTrap"), + exit(Pid, kill) + end + end. + + +append_unique([], List) -> List; +append_unique([H|R], List) -> + case lists:member(H, List) of + true -> append_unique(R, List); + false -> [H | append_unique(R, List)] + end. + +pick_nodes(all, Nodes, File, Line) -> + pick_nodes(length(Nodes), Nodes, File, Line); +pick_nodes(N, [H | T], File, Line) when N > 0 -> + [H | pick_nodes(N - 1, T, File, Line)]; +pick_nodes(0, _Nodes, _File, _Line) -> + []; +pick_nodes(N, [], File, Line) -> + ?skip("Test case (~p(~p)) ignored: ~p nodes missing~n", + [File, Line, N]). + +init_nodes([Node | Nodes], File, Line) -> + case net_adm:ping(Node) of + pong -> + [Node | init_nodes(Nodes, File, Line)]; + pang -> + [Name, Host] = node_to_name_and_host(Node), + case slave_start_link(Host, Name) of + {ok, Node1} -> + Path = code:get_path(), + true = rpc:call(Node1, code, set_path, [Path]), + [Node1 | init_nodes(Nodes, File, Line)]; + Other -> + ?skip("Test case (~p(~p)) ignored: cannot start node ~p: ~p~n", + [File, Line, Node, Other]) + end + end; +init_nodes([], _File, _Line) -> + []. + +%% Returns [Name, Host] +node_to_name_and_host(Node) -> + string:tokens(atom_to_list(Node), [$@]). + +lookup_config(Key,Config) -> + case lists:keysearch(Key,1,Config) of + {value,{Key,Val}} -> + Val; + _ -> + [] + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start_appls(Appls, Nodes) -> + start_appls(Appls, Nodes, [], [schema]). + +start_appls(Appls, Nodes, Config) -> + start_appls(Appls, Nodes, Config, [schema]). + +start_appls([Appl | Appls], Nodes, Config, Tabs) -> + {Started, BadStarters} = + rpc:multicall(Nodes, ?MODULE, remote_start, [Appl, Config, Nodes]), + BadS = [{Node, Appl, Res} || {Node, Res} <- Started, Res /= ok], + BadN = [{BadNode, Appl, bad_start} || BadNode <- BadStarters], + Bad = BadS ++ BadN, + case Appl of + mnesia when Bad == [] -> + sync_tables(Nodes, Tabs); + _ -> + ignore + end, + Bad ++ start_appls(Appls, Nodes, Config, Tabs); +start_appls([], _Nodes, _Config, _Tabs) -> + []. + +remote_start(mnesia, Config, Nodes) -> + case diskless(Config) of + true -> + application_controller:set_env(mnesia, + extra_db_nodes, + Nodes -- [node()]), + application_controller:set_env(mnesia, + schema_location, + ram); + false -> + application_controller:set_env(mnesia, + schema_location, + opt_disc), + ignore + end, + {node(), mnesia:start()}; +remote_start(Appl, _Config, _Nodes) -> + Res = + case application:start(Appl) of + {error, {already_started, Appl}} -> + ok; + Other -> + Other + end, + {node(), Res}. + +%% Start Mnesia on all given nodes and wait for specified +%% tables to be accessible on each node. The atom all means +%% that we should wait for all tables to be loaded +%% +%% Returns a list of error tuples {BadNode, mnesia, Reason} +start_mnesia(Nodes) -> + start_appls([mnesia], Nodes). +start_mnesia(Nodes, Tabs) when is_list(Nodes) -> + start_appls([mnesia], Nodes, [], Tabs). + +%% Wait for the tables to be accessible from all nodes in the list +%% and that all nodes are aware of that the other nodes also ... +sync_tables(Nodes, Tabs) -> + Res = send_wait(Nodes, Tabs, []), + if + Res == [] -> + mnesia:transaction(fun() -> mnesia:write_lock_table(schema) end), + Res; + true -> + Res + end. + +send_wait([Node | Nodes], Tabs, Pids) -> + Pid = spawn_link(Node, ?MODULE, start_wait, [self(), Tabs]), + send_wait(Nodes, Tabs, [Pid | Pids]); +send_wait([], _Tabs, Pids) -> + rec_wait(Pids, []). + +rec_wait([Pid | Pids], BadRes) -> + receive + {'EXIT', Pid, R} -> + rec_wait(Pids, [{node(Pid), bad_wait, R} | BadRes]); + {Pid, ok} -> + rec_wait(Pids, BadRes); + {Pid, {error, R}} -> + rec_wait(Pids, [{node(Pid), bad_wait, R} | BadRes]) + end; +rec_wait([], BadRes) -> + BadRes. + +start_wait(Coord, Tabs) -> + process_flag(trap_exit, true), + Mon = whereis(mnesia_monitor), + case catch link(Mon) of + {'EXIT', _} -> + unlink(Coord), + Coord ! {self(), {error, {node_not_running, node()}}}; + _ -> + Res = start_wait_loop(Tabs), + unlink(Mon), + unlink(Coord), + Coord ! {self(), Res} + end. + +start_wait_loop(Tabs) -> + receive + {'EXIT', Pid, Reason} -> + {error, {start_wait, Pid, Reason}} + after 0 -> + case mnesia:wait_for_tables(Tabs, timer:seconds(30)) of + ok -> + verify_nodes(Tabs); + {timeout, BadTabs} -> + log("<>WARNING<> Wait for tables ~p: ~p~n", [node(), Tabs]), + start_wait_loop(BadTabs); + {error, Reason} -> + {error, {start_wait, Reason}} + end + end. + +verify_nodes(Tabs) -> + verify_nodes(Tabs, 0). + +verify_nodes([], _) -> + ok; + +verify_nodes([Tab| Tabs], N) -> + ?match(X when is_atom(X), mnesia_lib:val({Tab, where_to_read})), + Nodes = mnesia:table_info(Tab, where_to_write), + Copies = + mnesia:table_info(Tab, disc_copies) ++ + mnesia:table_info(Tab, disc_only_copies) ++ + mnesia:table_info(Tab, ram_copies), + Local = mnesia:table_info(Tab, local_content), + case Copies -- Nodes of + [] -> + verify_nodes(Tabs, 0); + _Else when Local == true, Nodes /= [] -> + verify_nodes(Tabs, 0); + Else -> + N2 = + if + N > 20 -> + log("<>WARNING<> ~w Waiting for table: ~p on ~p ~n", + [node(), Tab, Else]), + 0; + true -> N+1 + end, + timer:sleep(500), + verify_nodes([Tab| Tabs], N2) + end. + + +%% Nicely stop Mnesia on all given nodes +%% +%% Returns a list of error tuples {BadNode, Reason} +stop_mnesia(Nodes) when is_list(Nodes) -> + stop_appls([mnesia], Nodes). + +stop_appls([Appl | Appls], Nodes) when is_list(Nodes) -> + {Stopped, BadNodes} = rpc:multicall(Nodes, ?MODULE, remote_stop, [Appl]), + BadS =[{Node, Appl, Res} || {Node, Res} <- Stopped, Res /= stopped], + BadN =[{BadNode, Appl, bad_node} || BadNode <- BadNodes], + BadS ++ BadN ++ stop_appls(Appls, Nodes); +stop_appls([], _Nodes) -> + []. + +remote_stop(mnesia) -> + {node(), mnesia:stop()}; +remote_stop(Appl) -> + {node(), application:stop(Appl)}. + +remote_kill([Appl | Appls]) -> + catch Appl:lkill(), + application:stop(Appl), + remote_kill(Appls); +remote_kill([]) -> + ok. + +%% Abruptly kill Mnesia on all given nodes +%% Returns [] +kill_appls(Appls, Nodes) when is_list(Nodes) -> + verbose("<>WARNING<> Intentionally killing ~p: ~w...~n", + [Appls, Nodes], ?FILE, ?LINE), + rpc:multicall(Nodes, ?MODULE, remote_kill, [Appls]), + []. + +kill_mnesia(Nodes) when is_list(Nodes) -> + kill_appls([mnesia], Nodes). + +reload_appls([Appl | Appls], Selected) -> + kill_appls([Appl], Selected), + timer:sleep(1000), + Ok = {[ok || _N <- Selected], []}, + {Ok2temp, Empty} = rpc:multicall(Selected, application, unload, [Appl]), + Conv = fun({error,{not_loaded,mnesia}}) -> ok; (Else) -> Else end, + Ok2 = {lists:map(Conv, Ok2temp), Empty}, + + Ok3 = rpc:multicall(Selected, application, load, [Appl]), + if + Ok /= Ok2 -> + ?fatal("Cannot unload appl ~p: ~p~n", [Appl, Ok2]); + Ok /= Ok3 -> + ?fatal("Cannot load appl ~p: ~p~n", [Appl, Ok3]); + true -> + ok + end, + reload_appls(Appls, Selected); +reload_appls([], _Selected) -> + ok. + +shutdown() -> + log("<>WARNING<> Intentionally shutting down all nodes... ~p~n", + [nodes() ++ [node()]]), + rpc:multicall(nodes(), erlang, halt, []), + erlang:halt(). + +verify_mnesia(Ups, Downs, File, Line) when is_list(Ups), is_list(Downs) -> + BadUps = + [N || N <- Ups, rpc:call(N, mnesia, system_info, [is_running]) /= yes], + BadDowns = + [N || N <- Downs, rpc:call(N, mnesia, system_info, [is_running]) == yes], + if + BadUps == [] -> + ignore; + true -> + error("Mnesia is not running as expected: ~p~n", + [BadUps], File, Line) + end, + if + BadDowns == [] -> + ignore; + true -> + error("Mnesia is not stopped as expected: ~p~n", + [BadDowns], File, Line) + end, + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +verify_replica_location(Tab, [], [], [], _) -> + ?match({'EXIT', _}, mnesia:table_info(Tab, ram_copies)), + ?match({'EXIT', _}, mnesia:table_info(Tab, disc_copies)), + ?match({'EXIT', _}, mnesia:table_info(Tab, disc_only_copies)), + ?match({'EXIT', _}, mnesia:table_info(Tab, where_to_write)), + ?match({'EXIT', _}, mnesia:table_info(Tab, where_to_read)), + []; + +verify_replica_location(Tab, DiscOnly0, Ram0, Disc0, AliveNodes0) -> +%% sync_tables(AliveNodes0, [Tab]), + AliveNodes = lists:sort(AliveNodes0), + DiscOnly = lists:sort(DiscOnly0), + Ram = lists:sort(Ram0), + Disc = lists:sort(Disc0), + Write = ignore_dead(DiscOnly ++ Ram ++ Disc, AliveNodes), + Read = ignore_dead(DiscOnly ++ Ram ++ Disc, AliveNodes), + This = node(), + + timer:sleep(100), + + S1 = ?match(AliveNodes, lists:sort(mnesia:system_info(running_db_nodes))), + S2 = ?match(DiscOnly, lists:sort(mnesia:table_info(Tab, disc_only_copies))), + S3 = ?match(Ram, lists:sort(mnesia:table_info(Tab, ram_copies))), + S4 = ?match(Disc, lists:sort(mnesia:table_info(Tab, disc_copies))), + S5 = ?match(Write, lists:sort(mnesia:table_info(Tab, where_to_write))), + S6 = case lists:member(This, Read) of + true -> + ?match(This, mnesia:table_info(Tab, where_to_read)); + false -> + ?match(true, lists:member(mnesia:table_info(Tab, where_to_read), Read)) + end, + lists:filter(fun({success,_}) -> false; (_) -> true end, [S1,S2,S3,S4,S5,S6]). + +ignore_dead(Nodes, AliveNodes) -> + Filter = fun(Node) -> lists:member(Node, AliveNodes) end, + lists:sort(lists:zf(Filter, Nodes)). + + +remote_activate_debug_fun(N, I, F, C, File, Line) -> + Pid = spawn_link(N, ?MODULE, do_remote_activate_debug_fun, [self(), I, F, C, File, Line]), + receive + {activated, Pid} -> ok; + {'EXIT', Pid, Reason} -> {error, Reason} + end. + +do_remote_activate_debug_fun(From, I, F, C, File, Line) -> + mnesia_lib:activate_debug_fun(I, F, C, File, Line), + From ! {activated, self()}, + timer:sleep(infinity). % Dies whenever the test process dies !! + + +sort(L) when is_list(L) -> + lists:sort(L); +sort({atomic, L}) when is_list(L) -> + {atomic, lists:sort(L)}; +sort({ok, L}) when is_list(L) -> + {ok, lists:sort(L)}; +sort(W) -> + W. diff --git a/lib/mnesia/test/mnesia_test_lib.hrl b/lib/mnesia/test/mnesia_test_lib.hrl new file mode 100644 index 0000000000..85f12200d4 --- /dev/null +++ b/lib/mnesia/test/mnesia_test_lib.hrl @@ -0,0 +1,132 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-define(log(Format,Args),mnesia_test_lib:log(Format,Args,?FILE,?LINE)). +-define(warning(Format,Args),?log("<>WARNING<>~n " ++ Format,Args)). +-define(error(Format,Args), + mnesia_test_lib:error(Format,Args,?FILE,?LINE)). +-define(verbose(Format,Args),mnesia_test_lib:verbose(Format,Args,?FILE,?LINE)). + +-define(fatal(Format,Args), + ?error(Format, Args), + exit({test_case_fatal, Format, Args, ?FILE, ?LINE})). + +-define(skip(Format,Args), + ?warning(Format, Args), + exit({skipped, ?flat_format(Format, Args)})). + +-define(flat_format(Format,Args), + lists:flatten(io_lib:format(Format, Args))). + +-define(sort(What), mnesia_test_lib:sort(What)). + +-define(ignore(Expr), + fun() -> + AcTuAlReS = (catch (Expr)), + ?verbose("ok, ~n Result as expected:~p~n",[AcTuAlReS]), + AcTuAlReS + end()). + +-define(match(ExpectedRes,Expr), + fun() -> + AcTuAlReS = (catch (Expr)), + case AcTuAlReS of + ExpectedRes -> + ?verbose("ok, ~n Result as expected:~p~n",[AcTuAlReS]), + {success,AcTuAlReS}; + _ -> + ?error("Not Matching Actual result was:~n ~p~n", + [AcTuAlReS]), + {fail,AcTuAlReS} + end + end()). + +-define(match_inverse(NotExpectedRes,Expr), + fun() -> + AcTuAlReS = (catch (Expr)), + case AcTuAlReS of + NotExpectedRes -> + ?error("Not matching Actual result was:~n ~p~n", + [AcTuAlReS]), + {fail,AcTuAlReS}; + _ -> + ?verbose("ok, ~n Result as expected: ~p~n",[AcTuAlReS]), + {success,AcTuAlReS} + end + end()). + +-define(match_receive(ExpectedMsg), + ?match(ExpectedMsg,mnesia_test_lib:pick_msg())). + +%% ExpectedMsgs must be completely bound +-define(match_multi_receive(ExpectedMsgs), + fun() -> + TmPeXpCtEdMsGs = lists:sort(ExpectedMsgs), + ?match(TmPeXpCtEdMsGs, + lists:sort(lists:map(fun(_) -> + mnesia_test_lib:pick_msg() + end, + TmPeXpCtEdMsGs))) + end()). + +-define(start_activities(Nodes), + mnesia_test_lib:start_activities(Nodes)). + +-define(start_transactions(Pids), + mnesia_test_lib:start_transactions(Pids)). + +-define(acquire_nodes(N, Config), + mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}, + delete_schema, + create_schema, + {start_appls, [mnesia]}], + N, Config, ?FILE, ?LINE)). + +-define(activate_debug_fun(I, F, C), + mnesia_lib:activate_debug_fun(I, F, C, ?FILE, ?LINE)). + +-define(remote_activate_debug_fun(N, I, F, C), + ?match(ok, mnesia_test_lib:remote_activate_debug_fun(N, I, F, C, + ?FILE, ?LINE))). + +-define(deactivate_debug_fun(I), + mnesia_lib:deactivate_debug_fun(I, ?FILE, ?LINE)). + +-define(remote_deactivate_debug_fun(N, I), + rpc:call(N, mnesia_lib, deactivate_debug_fun, [I, ?FILE, ?LINE])). + +-define(is_debug_compiled, + case mnesia_lib:is_debug_compiled() of + false -> + ?skip("Mnesia is not debug compiled, test case ignored.~n", []); + _OhTeR -> + ok + end). + +-define(needs_disc(Config), + case mnesia_test_lib:diskless(Config) of + false -> + ok; + true -> + ?skip("Must have disc, test case ignored.~n", []) + end). + +-define(verify_mnesia(Ups, Downs), + mnesia_test_lib:verify_mnesia(Ups, Downs, ?FILE, ?LINE)). diff --git a/lib/mnesia/test/mnesia_tpcb.erl b/lib/mnesia/test/mnesia_tpcb.erl new file mode 100644 index 0000000000..903c53a21c --- /dev/null +++ b/lib/mnesia/test/mnesia_tpcb.erl @@ -0,0 +1,1268 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% +%% MODULE +%% +%% mnesia_tpcb - TPC-B benchmarking of Mnesia +%% +%% DESCRIPTION +%% +%% The metrics used in the TPC-B benchmark are throughput as measured +%% in transactions per second (TPS). The benchmark uses a single, +%% simple update-intensive transaction to load the database system. +%% The single transaction type provides a simple, repeatable +%% unit of work, and is designed to exercise the basic components of +%% a database system. +%% +%% The definition of the TPC-B states lots of detailed rules and +%% conditions that must be fullfilled, e.g. how the ACID (atomicity, +%% consistency, isolation and durability) properties are verified, +%% how the random numbers must be distributed, minimum sizes of +%% the different types of records, minimum duration of the benchmark, +%% formulas to calculate prices (dollars per tps), disclosure issues +%% etc. Please, see http://www.tpc.org/ about the nitty gritty details. +%% +%% The TPC-B benchmark is stated in terms of a hypothetical bank. The +%% bank has one or more branches. Each branch has multiple tellers. The +%% bank has many customers, each with an account. The database represents +%% the cash position of each entity (branch, teller and account) and a +%% history of recent transactions run by the bank. The transaction +%% represents the work done when a customer makes a deposit or a +%% withdrawal against his account. The transaction is performed by a +%% teller at some branch. +%% +%% Each process that performs TPC-B transactions is called a driver. +%% Drivers generates teller_id, account_id and delta amount of +%% money randomly. An account, a teller and a branch are read, their +%% balances are adjusted and a history record is created. The driver +%% measures the time for 3 reads, 3 writes and 1 create. +%% +%% GETTING STARTED +%% +%% Generate tables and run with default configuration: +%% +%% mnesia_tpcb:start(). +%% +%% A little bit more advanced; +%% +%% spawn(mnesia_tpcb, start, [[[{n_drivers_per_node, 8}, {stop_after, infinity}]]), +%% mnesia_tpcb:stop(). +%% +%% Really advanced; +%% +%% mnesia_tpcb:init(([{n_branches, 8}, {replica_type, disc_only_copies}]), +%% mnesia_tpcb:run(([{n_drivers_per_node, 8}]), +%% mnesia_tpcb:run(([{n_drivers_per_node, 64}]). +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-module(mnesia_tpcb). +-author('[email protected]'). + +-export([ + config/2, + count_balance/0, + driver_init/2, + init/1, + reporter_init/2, + run/1, + start/0, + start/1, + start/2, + stop/0, + real_trans/5, + verify_tabs/0, + reply_gen_branch/3, + frag_add_delta/7, + + conflict_test/1, + dist_test/1, + replica_test/1, + sticky_replica_test/1, + remote_test/1, + remote_frag2_test/1 + ]). + +-define(SECOND, 1000000). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Account record, total size must be at least 100 bytes + +-define(ACCOUNT_FILLER, + {123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234}). + +-record(account, + { + id = 0, % Unique account id + branch_id = 0, % Branch where the account is held + balance = 0, % Account balance + filler = ?ACCOUNT_FILLER % Gap filler to ensure size >= 100 bytes + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Branch record, total size must be at least 100 bytes + +-define(BRANCH_FILLER, + {123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234567890}). + +-record(branch, + { + id = 0, % Unique branch id + balance = 0, % Total balance of whole branch + filler = ?BRANCH_FILLER % Gap filler to ensure size >= 100 bytes + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Teller record, total size must be at least 100 bytes + +-define(TELLER_FILLER, + {123456789012345678901234567890123456789012345678901234567890, + 123456789012345678901234567890123456789012345678901234567890, + 1234567890123456789012345678901234567890123456789012345678}). + +-record(teller, + { + id = 0, % Unique teller id + branch_id = 0, % Branch where the teller is located + balance = 0, % Teller balance + filler = ?TELLER_FILLER % Gap filler to ensure size >= 100 bytes + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% History record, total size must be at least 50 bytes + +-define(HISTORY_FILLER, 1234567890). + +-record(history, + { + history_id = {0, 0}, % {DriverId, DriverLocalHistoryid} + time_stamp = now(), % Time point during active transaction + branch_id = 0, % Branch associated with teller + teller_id = 0, % Teller invlolved in transaction + account_id = 0, % Account updated by transaction + amount = 0, % Amount (delta) specified by transaction + filler = ?HISTORY_FILLER % Gap filler to ensure size >= 50 bytes + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-record(tab_config, + { + db_nodes = [node()], + n_replicas = 1, % Ignored for non-fragmented tables + replica_nodes = [node()], + replica_type = ram_copies, + use_running_mnesia = false, + n_fragments = 0, + n_branches = 1, + n_tellers_per_branch = 10, % Must be 10 + n_accounts_per_branch = 100000, % Must be 100000 + branch_filler = ?BRANCH_FILLER, + account_filler = ?ACCOUNT_FILLER, + teller_filler = ?TELLER_FILLER + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-record(run_config, + { + driver_nodes = [node()], + n_drivers_per_node = 1, + use_running_mnesia = false, + stop_after = timer:minutes(15), % Minimum 15 min + report_interval = timer:minutes(1), + use_sticky_locks = false, + spawn_near_branch = false, + activity_type = transaction, + reuse_history_id = false + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-record(time, + { + n_trans = 0, + min_n = 0, + max_n = 0, + acc_time = 0, + max_time = 0 + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-record(driver_state, + { + driver_id, + driver_node, + seed, + n_local_branches, + local_branches, + tab_config, + run_config, + history_id, + time = #time{}, + acc_time = #time{}, + reuse_history_id + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-record(reporter_state, + { + driver_pids, + starter_pid, + n_iters = 0, + prev_tps = 0, + curr = #time{}, + acc = #time{}, + init_micros, + prev_micros, + run_config + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% One driver on each node, table not replicated + +config(frag_test, ReplicaType) -> + Remote = nodes(), + Local = node(), + Nodes = [Local | Remote], + [ + {n_branches, length(Nodes)}, + {n_fragments, length(Nodes)}, + {replica_nodes, Nodes}, + {db_nodes, Nodes}, + {driver_nodes, Nodes}, + {n_accounts_per_branch, 100}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(1)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true} + ]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% One driver on each node, table replicated to two nodes. + +config(frag2_test, ReplicaType) -> + Remote = nodes(), + Local = node(), + Nodes = [Local | Remote], + [ + {n_branches, length(Nodes)}, + {n_fragments, length(Nodes)}, + {n_replicas, 2}, + {replica_nodes, Nodes}, + {db_nodes, Nodes}, + {driver_nodes, Nodes}, + {n_accounts_per_branch, 100}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(1)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true} + ]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% One driver on this node, table replicated to all nodes. + +config(replica_test, ReplicaType) -> + Remote = nodes(), + Local = node(), + Nodes = [Local | Remote], + [ + {db_nodes, Nodes}, + {driver_nodes, [Local]}, + {replica_nodes, Nodes}, + {n_accounts_per_branch, 100}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(1)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true} + ]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% One driver on this node, table replicated to all nodes. + +config(sticky_replica_test, ReplicaType) -> + Remote = nodes(), + Local = node(), + Nodes = [Local | Remote], + [ + {db_nodes, Nodes}, + {driver_nodes, [node()]}, + {replica_nodes, Nodes}, + {n_accounts_per_branch, 100}, + {replica_type, ReplicaType}, + {use_sticky_locks, true}, + {stop_after, timer:minutes(1)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true} + ]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Ten drivers per node, tables replicated to all nodes, lots of branches + +config(dist_test, ReplicaType) -> + Remote = nodes(), + Local = node(), + Nodes = [Local | Remote], + [ + {db_nodes, Nodes}, + {driver_nodes, Nodes}, + {replica_nodes, Nodes}, + {n_drivers_per_node, 10}, + {n_branches, 10 * length(Nodes) * 100}, + {n_accounts_per_branch, 10}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(1)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true} + ]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Ten drivers per node, tables replicated to all nodes, single branch + +config(conflict_test, ReplicaType) -> + Remote = nodes(), + Local = node(), + Nodes = [Local | Remote], + [ + {db_nodes, Nodes}, + {driver_nodes, Nodes}, + {replica_nodes, Nodes}, + {n_drivers_per_node, 10}, + {n_branches, 1}, + {n_accounts_per_branch, 10}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(1)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true} + ]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% One driver on this node, table replicated to all other nodes. + +config(remote_test, ReplicaType) -> + Remote = nodes(), + Local = node(), + Nodes = [Local | Remote], + [ + {db_nodes, Nodes}, + {driver_nodes, [Local]}, + {replica_nodes, Remote}, + {n_accounts_per_branch, 100}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(1)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true} + ]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% One driver on this node, table replicated to two other nodes. + +config(remote_frag2_test, ReplicaType) -> + Remote = nodes(), + Local = node(), + Nodes = [Local | Remote], + [ + {n_branches, length(Remote)}, + {n_fragments, length(Remote)}, + {n_replicas, 2}, + {replica_nodes, Remote}, + {db_nodes, Nodes}, + {driver_nodes, [Local]}, + {n_accounts_per_branch, 100}, + {replica_type, ReplicaType}, + {stop_after, timer:minutes(1)}, + {report_interval, timer:seconds(10)}, + {reuse_history_id, true} + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start(What, ReplicaType) -> + spawn_link(?MODULE, start, [config(What, ReplicaType)]). + +replica_test(ReplicaType) -> + start(replica_test, ReplicaType). + +sticky_replica_test(ReplicaType) -> + start(sticky_replica_test, ReplicaType). + +dist_test(ReplicaType) -> + start(dist_test, ReplicaType). + +conflict_test(ReplicaType) -> + start(conflict_test, ReplicaType). + +remote_test(ReplicaType) -> + start(remote_test, ReplicaType). + +remote_frag2_test(ReplicaType) -> + start(remote_frag2_test, ReplicaType). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Args is a list of {Key, Val} tuples where Key is a field name +%% in either the record tab_config or run_config. Unknown keys are ignored. + +start() -> + start([]). +start(Args) -> + init(Args), + run(Args). + +list2rec(List, Fields, DefaultTuple) -> + [Name|Defaults] = tuple_to_list(DefaultTuple), + List2 = list2rec(List, Fields, Defaults, []), + list_to_tuple([Name] ++ List2). + +list2rec(_List, [], [], Acc) -> + Acc; +list2rec(List, [F|Fields], [D|Defaults], Acc) -> + {Val, List2} = + case lists:keysearch(F, 1, List) of + false -> + {D, List}; + {value, {F, NewVal}} -> + {NewVal, lists:keydelete(F, 1, List)} + end, + list2rec(List2, Fields, Defaults, Acc ++ [Val]). + +stop() -> + case whereis(mnesia_tpcb) of + undefined -> + {error, not_running}; + Pid -> + sync_stop(Pid) + end. + +sync_stop(Pid) -> + Pid ! {self(), stop}, + receive + {Pid, {stopped, Res}} -> Res + after timer:minutes(1) -> + exit(Pid, kill), + {error, brutal_kill} + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Initialization + +%% Args is a list of {Key, Val} tuples where Key is a field name +%% in the record tab_config, unknown keys are ignored. + +init(Args) -> + TabConfig0 = list2rec(Args, record_info(fields, tab_config), #tab_config{}), + TabConfig = + if + TabConfig0#tab_config.n_fragments =:= 0 -> + TabConfig0#tab_config{n_replicas = length(TabConfig0#tab_config.replica_nodes)}; + true -> + TabConfig0 + end, + Tags = record_info(fields, tab_config), + Fun = fun(F, Pos) -> {{F, element(Pos, TabConfig)}, Pos + 1} end, + {List, _} = lists:mapfoldl(Fun, 2, Tags), + io:format("TPC-B: Table config: ~p ~n", [List]), + + DbNodes = TabConfig#tab_config.db_nodes, + stop(), + if + TabConfig#tab_config.use_running_mnesia =:= true -> + ignore; + true -> + rpc:multicall(DbNodes, mnesia, lkill, []), + case mnesia:delete_schema(DbNodes) of + ok -> + case mnesia:create_schema(DbNodes) of + ok -> + {Replies, BadNodes} = + rpc:multicall(DbNodes, mnesia, start, []), + case [Res || Res <- Replies, Res =/= ok] of + [] when BadNodes =:= [] -> + ok; + BadRes -> + io:format("TPC-B: <ERROR> " + "Failed to start ~p: ~p~n", + [BadNodes, BadRes]), + exit({start_failed, BadRes, BadNodes}) + end; + {error, Reason} -> + io:format("TPC-B: <ERROR> " + "Failed to create schema on disc: ~p~n", + [Reason]), + exit({create_schema_failed, Reason}) + end; + {error, Reason} -> + io:format("TPC-B: <ERROR> " + "Failed to delete schema on disc: ~p~n", + [Reason]), + exit({delete_schema_failed, Reason}) + end + end, + gen_tabs(TabConfig). + +gen_tabs(TC) -> + create_tab(TC, branch, record_info(fields, branch), + undefined), + create_tab(TC, account, record_info(fields, account), + {branch, #account.branch_id}), + create_tab(TC, teller, record_info(fields, teller), + {branch, #teller.branch_id}), + create_tab(TC, history, record_info(fields, history), + {branch, #history.branch_id}), + + NB = TC#tab_config.n_branches, + NT = TC#tab_config.n_tellers_per_branch, + NA = TC#tab_config.n_accounts_per_branch, + io:format("TPC-B: Generating ~p branches a ~p bytes~n", + [NB, size(term_to_binary(default_branch(TC)))]), + io:format("TPC-B: Generating ~p * ~p tellers a ~p bytes~n", + [NB, NT, size(term_to_binary(default_teller(TC)))]), + io:format("TPC-B: Generating ~p * ~p accounts a ~p bytes~n", + [NB, NA, size(term_to_binary(default_account(TC)))]), + io:format("TPC-B: Generating 0 history records a ~p bytes~n", + [size(term_to_binary(default_history(TC)))]), + gen_branches(TC), + + case verify_tabs() of + ok -> + ignore; + {error, Reason} -> + io:format("TPC-B: <ERROR> Inconsistent tables: ~w~n", + [Reason]), + exit({inconsistent_tables, Reason}) + end. + +create_tab(TC, Name, Attrs, _ForeignKey) when TC#tab_config.n_fragments =:= 0 -> + Nodes = TC#tab_config.replica_nodes, + Type = TC#tab_config.replica_type, + Def = [{Type, Nodes}, {attributes, Attrs}], + create_tab(Name, Def); +create_tab(TC, Name, Attrs, ForeignKey) -> + NReplicas = TC#tab_config.n_replicas, + NodePool = TC#tab_config.replica_nodes, + Type = TC#tab_config.replica_type, + NF = TC#tab_config.n_fragments, + Props = [{n_fragments, NF}, + {node_pool, NodePool}, + {n_copies(Type), NReplicas}, + {foreign_key, ForeignKey}], + Def = [{frag_properties, Props}, + {attributes, Attrs}], + create_tab(Name, Def). + +create_tab(Name, Def) -> + mnesia:delete_table(Name), + case mnesia:create_table(Name, Def) of + {atomic, ok} -> + ok; + {aborted, Reason} -> + io:format("TPC-B: <ERROR> failed to create table ~w ~w: ~p~n", + [Name, Def, Reason]), + exit({create_table_failed, Reason}) + end. + +n_copies(Type) -> + case Type of + ram_copies -> n_ram_copies; + disc_copies -> n_disc_copies; + disc_only_copies -> n_disc_only_copies + end. + +gen_branches(TC) -> + First = 0, + Last = First + TC#tab_config.n_branches - 1, + GenPids = gen_branches(TC, First, Last, []), + wait_for_gen(GenPids). + +wait_for_gen([]) -> + ok; +wait_for_gen(Pids) -> + receive + {branch_generated, Pid} -> wait_for_gen(lists:delete(Pid, Pids)); + Exit -> + exit({tpcb_failed, Exit}) + end. + +gen_branches(TC, BranchId, Last, UsedNs) when BranchId =< Last -> + UsedNs2 = get_branch_nodes(BranchId, UsedNs), + Node = hd(UsedNs2), + Pid = spawn_link(Node, ?MODULE, reply_gen_branch, + [self(), TC, BranchId]), + [Pid | gen_branches(TC, BranchId + 1, Last, UsedNs2)]; +gen_branches(_, _, _, _) -> + []. + +reply_gen_branch(ReplyTo, TC, BranchId) -> + gen_branch(TC, BranchId), + ReplyTo ! {branch_generated, self()}, + unlink(ReplyTo). + +%% Returns a new list of nodes with the best node as head +get_branch_nodes(BranchId, UsedNs) -> + WriteNs = table_info({branch, BranchId}, where_to_write), + WeightedNs = [{n_duplicates(N, UsedNs, 0), N} || N <- WriteNs], + [{_, LeastUsed} | _ ] = lists:sort(WeightedNs), + [LeastUsed | UsedNs]. + +n_duplicates(_N, [], Count) -> + Count; +n_duplicates(N, [N | Tail], Count) -> + n_duplicates(N, Tail, Count + 1); +n_duplicates(N, [_ | Tail], Count) -> + n_duplicates(N, Tail, Count). + +gen_branch(TC, BranchId) -> + A = default_account(TC), + NA = TC#tab_config.n_accounts_per_branch, + FirstA = BranchId * NA, + ArgsA = [FirstA, FirstA + NA - 1, BranchId, A], + ok = mnesia:activity(async_dirty, fun gen_accounts/4, ArgsA, mnesia_frag), + + T = default_teller(TC), + NT = TC#tab_config.n_tellers_per_branch, + FirstT = BranchId * NT, + ArgsT = [FirstT, FirstT + NT - 1, BranchId, T], + ok = mnesia:activity(async_dirty, fun gen_tellers/4, ArgsT, mnesia_frag), + + B = default_branch(TC), + FunB = fun() -> mnesia:write(branch, B#branch{id = BranchId}, write) end, + ok = mnesia:activity(sync_dirty, FunB, [], mnesia_frag). + +gen_tellers(Id, Last, BranchId, T) when Id =< Last -> + mnesia:write(teller, T#teller{id = Id, branch_id=BranchId}, write), + gen_tellers(Id + 1, Last, BranchId, T); +gen_tellers(_, _, _, _) -> + ok. + +gen_accounts(Id, Last, BranchId, A) when Id =< Last -> + mnesia:write(account, A#account{id = Id, branch_id=BranchId}, write), + gen_accounts(Id + 1, Last, BranchId, A); +gen_accounts(_, _, _, _) -> + ok. + +default_branch(TC) -> #branch{filler = TC#tab_config.branch_filler}. +default_teller(TC) -> #teller{filler = TC#tab_config.teller_filler}. +default_account(TC) -> #account{filler = TC#tab_config.account_filler}. +default_history(_TC) -> #history{}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Run the benchmark + +%% Args is a list of {Key, Val} tuples where Key is a field name +%% in the record run_config, unknown keys are ignored. +run(Args) -> + RunConfig = list2rec(Args, record_info(fields, run_config), #run_config{}), + Tags = record_info(fields, run_config), + Fun = fun(F, Pos) -> {{F, element(Pos, RunConfig)}, Pos + 1} end, + {List, _} = lists:mapfoldl(Fun, 2, Tags), + io:format("TPC-B: Run config: ~p ~n", [List]), + + Pid = spawn_link(?MODULE, reporter_init, [self(), RunConfig]), + receive + {Pid, {stopped, Res}} -> + Res; % Stopped by other process + Else -> + {tpcb_got, Else} + after RunConfig#run_config.stop_after -> + sync_stop(Pid) + end. + +reporter_init(Starter, RC) -> + register(mnesia_tpcb, self()), + process_flag(trap_exit, true), + DbNodes = mnesia:system_info(db_nodes), + if + RC#run_config.use_running_mnesia =:= true -> + ignore; + true -> + {Replies, BadNodes} = + rpc:multicall(DbNodes, mnesia, start, []), + case [Res || Res <- Replies, Res =/= ok] of + [] when BadNodes =:= [] -> + ok; + BadRes -> + io:format("TPC-B: <ERROR> " + "Failed to start ~w: ~p~n", + [BadNodes, BadRes]), + exit({start_failed, BadRes, BadNodes}) + end, + verify_tabs() + end, + + N = table_info(branch, size), + NT = table_info(teller, size) div N, + NA = table_info(account, size) div N, + + {Type, NF, RepNodes} = table_storage(branch), + TC = #tab_config{n_fragments = NF, + n_branches = N, + n_tellers_per_branch = NT, + n_accounts_per_branch = NA, + db_nodes = DbNodes, + replica_nodes = RepNodes, + replica_type = Type + }, + Drivers = start_drivers(RC, TC), + Now = now_to_micros(erlang:now()), + State = #reporter_state{driver_pids = Drivers, + run_config = RC, + starter_pid = Starter, + init_micros = Now, + prev_micros = Now + }, + case catch reporter_loop(State) of + {'EXIT', Reason} -> + io:format("TPC-B: Abnormal termination: ~p~n", [Reason]), + if + RC#run_config.use_running_mnesia =:= true -> + ignore; + true -> + rpc:multicall(DbNodes, mnesia, lkill, []) + end, + unlink(Starter), + Starter ! {self(), {stopped, {error, Reason}}}, % To be sure + exit(shutdown); + {ok, Stopper, State2} -> + Time = State2#reporter_state.acc, + Res = + case verify_tabs() of + ok -> + {ok, Time}; + {error, Reason} -> + io:format("TPC-B: <ERROR> Inconsistent tables, ~p~n", + [{error, Reason}]), + {error, Reason} + end, + if + RC#run_config.use_running_mnesia =:= true -> + ignore; + true -> + rpc:multicall(DbNodes, mnesia, stop, []) + end, + unlink(Starter), + Starter ! {self(), {stopped, Res}}, + if + Stopper =/= Starter -> + Stopper ! {self(), {stopped, Res}}; + true -> + ignore + end, + exit(shutdown) + end. + +table_info(Tab, Item) -> + Fun = fun() -> mnesia:table_info(Tab, Item) end, + mnesia:activity(sync_dirty, Fun, mnesia_frag). + +%% Returns {Storage, NFragments, ReplicaNodes} +table_storage(Tab) -> + case mnesia:table_info(branch, frag_properties) of + [] -> + NFO = 0, + NR = length(mnesia:table_info(Tab, ram_copies)), + ND = length(mnesia:table_info(Tab, disc_copies)), + NDO = length(mnesia:table_info(Tab, disc_only_copies)), + if + NR =/= 0 -> {ram_copies, NFO, NR}; + ND =/= 0 -> {disc_copies, NFO, ND}; + NDO =/= 0 -> {disc_copies, NFO, NDO} + end; + Props -> + {value, NFO} = lists:keysearch(n_fragments, 1, Props), + NR = table_info(Tab, n_ram_copies), + ND = table_info(Tab, n_disc_copies), + NDO = table_info(Tab, n_disc_only_copies), + if + NR =/= 0 -> {ram_copies, NFO, NR}; + ND =/= 0 -> {disc_copies, NFO, ND}; + NDO =/= 0 -> {disc_copies, NFO, NDO} + end + end. + +reporter_loop(State) -> + RC = State#reporter_state.run_config, + receive + {From, stop} -> + {ok, From, call_drivers(State, stop)}; + {'EXIT', Pid, Reason} when Pid =:= State#reporter_state.starter_pid -> + %% call_drivers(State, stop), + exit({starter_died, Pid, Reason}) + after RC#run_config.report_interval -> + Iters = State#reporter_state.n_iters, + State2 = State#reporter_state{n_iters = Iters + 1}, + case call_drivers(State2, report) of + State3 when State3#reporter_state.driver_pids =/= [] -> + State4 = State3#reporter_state{curr = #time{}}, + reporter_loop(State4); + _ -> + exit(drivers_died) + end + end. + +call_drivers(State, Msg) -> + Drivers = State#reporter_state.driver_pids, + lists:foreach(fun(Pid) -> Pid ! {self(), Msg} end, Drivers), + State2 = show_report(calc_reports(Drivers, State)), + case Msg =:= stop of + true -> + Acc = State2#reporter_state.acc, + Init = State2#reporter_state.init_micros, + show_report(State2#reporter_state{n_iters = 0, + curr = Acc, + prev_micros = Init}); + false -> + ignore + end, + State2. + +calc_reports([], State) -> + State; +calc_reports([Pid|Drivers], State) -> + receive + {'EXIT', P, Reason} when P =:= State#reporter_state.starter_pid -> + exit({starter_died, P, Reason}); + {'EXIT', Pid, Reason} -> + exit({driver_died, Pid, Reason}); + {Pid, Time} when is_record(Time, time) -> + %% io:format("~w: ~w~n", [Pid, Time]), + A = add_time(State#reporter_state.acc, Time), + C = add_time(State#reporter_state.curr, Time), + State2 = State#reporter_state{acc = A, curr = C}, + calc_reports(Drivers, State2) + end. + +add_time(Acc, New) -> + Acc#time{n_trans = New#time.n_trans + Acc#time.n_trans, + min_n = lists:min([New#time.n_trans, Acc#time.min_n] -- [0]), + max_n = lists:max([New#time.n_trans, Acc#time.max_n]), + acc_time = New#time.acc_time + Acc#time.acc_time, + max_time = lists:max([New#time.max_time, Acc#time.max_time])}. + +-define(AVOID_DIV_ZERO(_What_), try (_What_) catch _:_ -> 0 end). + +show_report(State) -> + Now = now_to_micros(erlang:now()), + Iters = State#reporter_state.n_iters, + Time = State#reporter_state.curr, + Max = Time#time.max_time, + N = Time#time.n_trans, + Avg = ?AVOID_DIV_ZERO(Time#time.acc_time div N), + AliveN = length(State#reporter_state.driver_pids), + Tps = ?AVOID_DIV_ZERO((?SECOND * AliveN) div Avg), + PrevTps= State#reporter_state.prev_tps, + {DiffSign, DiffTps} = signed_diff(Iters, Tps, PrevTps), + Unfairness = ?AVOID_DIV_ZERO(Time#time.max_n / Time#time.min_n), + BruttoAvg = ?AVOID_DIV_ZERO((Now - State#reporter_state.prev_micros) div N), +%% io:format("n_iters=~p, n_trans=~p, n_drivers=~p, avg=~p, now=~p, prev=~p~n", +%% [Iters, N, AliveN, BruttoAvg, Now, State#reporter_state.prev_micros]), + BruttoTps = ?AVOID_DIV_ZERO(?SECOND div BruttoAvg), + case Iters > 0 of + true -> + io:format("TPC-B: ~p iter ~s~p diff ~p (~p) tps ~p avg micros ~p max micros ~p unfairness~n", + [Iters, DiffSign, DiffTps, Tps, BruttoTps, Avg, Max, Unfairness]); + false -> + io:format("TPC-B: ~p (~p) transactions per second, " + "duration of longest transaction was ~p milliseconds~n", + [Tps, BruttoTps, Max div 1000]) + end, + State#reporter_state{prev_tps = Tps, prev_micros = Now}. + +signed_diff(Iters, Curr, Prev) -> + case Iters > 1 of + true -> sign(Curr - Prev); + false -> sign(0) + end. + +sign(N) when N > 0 -> {"+", N}; +sign(N) -> {"", N}. + +now_to_micros({Mega, Secs, Micros}) -> + DT = calendar:now_to_datetime({Mega, Secs, 0}), + S = calendar:datetime_to_gregorian_seconds(DT), + (S * ?SECOND) + Micros. + +start_drivers(RC, TC) -> + LastHistoryId = table_info(history, size), + Reuse = RC#run_config.reuse_history_id, + DS = #driver_state{tab_config = TC, + run_config = RC, + n_local_branches = 0, + local_branches = [], + history_id = LastHistoryId, + reuse_history_id = Reuse}, + Nodes = RC#run_config.driver_nodes, + NB = TC#tab_config.n_branches, + First = 0, + AllBranches = lists:seq(First, First + NB - 1), + ND = RC#run_config.n_drivers_per_node, + Spawn = fun(Spec) -> + Node = Spec#driver_state.driver_node, + spawn_link(Node, ?MODULE, driver_init, [Spec, AllBranches]) + end, + Specs = [DS#driver_state{driver_id = Id, driver_node = N} + || N <- Nodes, + Id <- lists:seq(1, ND)], + Specs2 = lists:sort(lists:flatten(Specs)), + {Specs3, OrphanBranches} = alloc_local_branches(AllBranches, Specs2, []), + case length(OrphanBranches) of + N when N =< 10 -> + io:format("TPC-B: Orphan branches: ~p~n", [OrphanBranches]); + N -> + io:format("TPC-B: Orphan branches: ~p~n", [N]) + end, + [Spawn(Spec) || Spec <- Specs3]. + +alloc_local_branches([BranchId | Tail], Specs, OrphanBranches) -> + Nodes = table_info({branch, BranchId}, where_to_write), + LocalSpecs = [DS || DS <- Specs, + lists:member(DS#driver_state.driver_node, Nodes)], + case lists:keysort(#driver_state.n_local_branches, LocalSpecs) of + [] -> + alloc_local_branches(Tail, Specs, [BranchId | OrphanBranches]); + [DS | _] -> + LocalNB = DS#driver_state.n_local_branches + 1, + LocalBranches = [BranchId | DS#driver_state.local_branches], + DS2 = DS#driver_state{n_local_branches = LocalNB, + local_branches = LocalBranches}, + Specs2 = Specs -- [DS], + Specs3 = [DS2 | Specs2], + alloc_local_branches(Tail, Specs3, OrphanBranches) + end; +alloc_local_branches([], Specs, OrphanBranches) -> + {Specs, OrphanBranches}. + +driver_init(DS, AllBranches) -> + Seed = erlang:now(), + DS2 = + if + DS#driver_state.n_local_branches =:= 0 -> + DS#driver_state{seed = Seed, + n_local_branches = length(AllBranches), + local_branches = AllBranches}; + true -> + DS#driver_state{seed = Seed} + end, + io:format("TPC-B: Driver ~p started as ~p on node ~p with ~p local branches~n", + [DS2#driver_state.driver_id, self(), node(), DS2#driver_state.n_local_branches]), + driver_loop(DS2). + +driver_loop(DS) -> + receive + {From, report} -> + From ! {self(), DS#driver_state.time}, + Acc = add_time(DS#driver_state.time, DS#driver_state.acc_time), + DS2 = DS#driver_state{time=#time{}, acc_time = Acc}, % Reset timer + DS3 = calc_trans(DS2), + driver_loop(DS3); + {From, stop} -> + Acc = add_time(DS#driver_state.time, DS#driver_state.acc_time), + io:format("TPC-B: Driver ~p (~p) on node ~p stopped: ~w~n", + [DS#driver_state.driver_id, self(), node(self()), Acc]), + From ! {self(), DS#driver_state.time}, + unlink(From), + exit(stopped) + after 0 -> + DS2 = calc_trans(DS), + driver_loop(DS2) + end. + +calc_trans(DS) -> + {Micros, DS2} = time_trans(DS), + Time = DS2#driver_state.time, + Time2 = Time#time{n_trans = Time#time.n_trans + 1, + acc_time = Time#time.acc_time + Micros, + max_time = lists:max([Micros, Time#time.max_time]) + }, + case DS#driver_state.reuse_history_id of + false -> + HistoryId = DS#driver_state.history_id + 1, + DS2#driver_state{time=Time2, history_id = HistoryId}; + true -> + DS2#driver_state{time=Time2} + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Generate teller_id, account_id and delta +%% Time the TPC-B transaction +time_trans(DS) -> + OldSeed = get(random_seed), % Avoid interference with Mnesia + put(random_seed, DS#driver_state.seed), + Random = random:uniform(), + NewSeed = get(random_seed), + case OldSeed of + undefined -> erase(random_seed); + _ -> put(random_seed, OldSeed) + end, + + TC = DS#driver_state.tab_config, + RC = DS#driver_state.run_config, + {Branchid, Args} = random_to_args(Random, DS), + {Fun, Mod} = trans_type(TC, RC), + {Time, Res} = timer:tc(?MODULE, real_trans, [RC, Branchid, Fun, Args, Mod]), + + case Res of + AccountBal when is_integer(AccountBal) -> + {Time, DS#driver_state{seed = NewSeed}}; + Other -> + exit({crash, Other, Args, Random, DS}) + end. + +random_to_args(Random, DS) -> + DriverId = DS#driver_state.driver_id, + TC = DS#driver_state.tab_config, + HistoryId = DS#driver_state.history_id, + Delta = trunc(Random * 1999998) - 999999, % -999999 <= Delta <= +999999 + + Branches = DS#driver_state.local_branches, + NB = DS#driver_state.n_local_branches, + NT = TC#tab_config.n_tellers_per_branch, + NA = TC#tab_config.n_accounts_per_branch, + Tmp = trunc(Random * NB * NT), + BranchPos = (Tmp div NT) + 1, + BranchId = + case TC#tab_config.n_fragments of + 0 -> BranchPos - 1; + _ -> lists:nth(BranchPos, Branches) + end, + RelativeTellerId = Tmp div NT, + TellerId = (BranchId * NT) + RelativeTellerId, + {AccountBranchId, AccountId} = + if + Random >= 0.85, NB > 1 -> + %% Pick from a remote account + TmpAccountId= trunc(Random * (NB - 1) * NA), + TmpAccountBranchId = TmpAccountId div NA, + if + TmpAccountBranchId =:= BranchId -> + {TmpAccountBranchId + 1, TmpAccountId + NA}; + true -> + {TmpAccountBranchId, TmpAccountId} + end; + true -> + %% Pick from a local account + RelativeAccountId = trunc(Random * NA), + TmpAccountId = (BranchId * NA) + RelativeAccountId, + {BranchId, TmpAccountId} + end, + + {BranchId, [DriverId, BranchId, TellerId, AccountBranchId, AccountId, HistoryId, Delta]}. + +real_trans(RC, BranchId, Fun, Args, Mod) -> + Type = RC#run_config.activity_type, + case RC#run_config.spawn_near_branch of + false -> + mnesia:activity(Type, Fun, Args, Mod); + true -> + Node = table_info({branch, BranchId}, where_to_read), + case rpc:call(Node, mnesia, activity, [Type, Fun, Args, Mod]) of + {badrpc, Reason} -> exit(Reason); + Other -> Other + end + end. + +trans_type(TC, RC) -> + if + TC#tab_config.n_fragments =:= 0, + RC#run_config.use_sticky_locks =:= false -> + {fun add_delta/7, mnesia}; + TC#tab_config.n_fragments =:= 0, + RC#run_config.use_sticky_locks =:= true -> + {fun sticky_add_delta/7, mnesia}; + TC#tab_config.n_fragments > 0, + RC#run_config.use_sticky_locks =:= false -> + {fun frag_add_delta/7, mnesia_frag} + end. + +%% +%% Runs the TPC-B defined transaction and returns NewAccountBalance +%% + +add_delta(DriverId, BranchId, TellerId, _AccountBranchId, AccountId, HistoryId, Delta) -> + %% Grab write lock already when the record is read + + %% Add delta to branch balance + [B] = mnesia:read(branch, BranchId, write), + NewB = B#branch{balance = B#branch.balance + Delta}, + ok = mnesia:write(branch, NewB, write), + + %% Add delta to teller balance + [T] = mnesia:read(teller, TellerId, write), + NewT = T#teller{balance = T#teller.balance + Delta}, + ok = mnesia:write(teller, NewT, write), + + %% Add delta to account balance + [A] = mnesia:read(account, AccountId, write), + NewA = A#account{balance = A#account.balance + Delta}, + ok = mnesia:write(account, NewA, write), + + %% Append to history log + History = #history{history_id = {DriverId, HistoryId}, + account_id = AccountId, + teller_id = TellerId, + branch_id = BranchId, + amount = Delta + }, + ok = mnesia:write(history, History, write), + + %% Return account balance + NewA#account.balance. + +sticky_add_delta(DriverId, BranchId, TellerId, _AccountBranchId, AccountId, HistoryId, Delta) -> + %% Grab orinary read lock when the record is read + %% Grab sticky write lock when the record is written + %% This transaction would benefit of an early stick_write lock at read + + %% Add delta to branch balance + [B] = mnesia:read(branch, BranchId, read), + NewB = B#branch{balance = B#branch.balance + Delta}, + ok = mnesia:write(branch, NewB, sticky_write), + + %% Add delta to teller balance + [T] = mnesia:read(teller, TellerId, read), + NewT = T#teller{balance = T#teller.balance + Delta}, + ok = mnesia:write(teller, NewT, sticky_write), + + %% Add delta to account balance + [A] = mnesia:read(account, AccountId, read), + NewA = A#account{balance = A#account.balance + Delta}, + ok = mnesia:write(account, NewA, sticky_write), + + %% Append to history log + History = #history{history_id = {DriverId, HistoryId}, + account_id = AccountId, + teller_id = TellerId, + branch_id = BranchId, + amount = Delta + }, + ok = mnesia:write(history, History, sticky_write), + + %% Return account balance + NewA#account.balance. + +frag_add_delta(DriverId, BranchId, TellerId, AccountBranchId, AccountId, HistoryId, Delta) -> + %% Access fragmented table + %% Grab write lock already when the record is read + + %% Add delta to branch balance + [B] = mnesia:read(branch, BranchId, write), + NewB = B#branch{balance = B#branch.balance + Delta}, + ok = mnesia:write(NewB), + + %% Add delta to teller balance + [T] = mnesia:read({teller, BranchId}, TellerId, write), + NewT = T#teller{balance = T#teller.balance + Delta}, + ok = mnesia:write(NewT), + + %% Add delta to account balance + %%io:format("frag_add_delta(~p): ~p\n", [node(), {account, BranchId, AccountId}]), + [A] = mnesia:read({account, AccountBranchId}, AccountId, write), + NewA = A#account{balance = A#account.balance + Delta}, + ok = mnesia:write(NewA), + + %% Append to history log + History = #history{history_id = {DriverId, HistoryId}, + account_id = AccountId, + teller_id = TellerId, + branch_id = BranchId, + amount = Delta + }, + ok = mnesia:write(History), + + %% Return account balance + NewA#account.balance. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Verify table consistency + +verify_tabs() -> + Nodes = mnesia:system_info(running_db_nodes), + case lists:member(node(), Nodes) of + true -> + Tabs = [branch, teller, account, history], + io:format("TPC-B: Verifying tables: ~w~n", [Tabs]), + rpc:multicall(Nodes, mnesia, wait_for_tables, [Tabs, infinity]), + + Fun = fun() -> + mnesia:write_lock_table(branch), + mnesia:write_lock_table(teller), + mnesia:write_lock_table(account), + mnesia:write_lock_table(history), + {Res, BadNodes} = + rpc:multicall(Nodes, ?MODULE, count_balance, []), + check_balance(Res, BadNodes) + end, + case mnesia:transaction(Fun) of + {atomic, Res} -> Res; + {aborted, Reason} -> {error, Reason} + end; + false -> + {error, "Must be initiated from a running db_node"} + end. + +%% Returns a list of {Table, Node, Balance} tuples +%% Assumes that no updates are performed + +-record(summary, {table, node, balance, size}). + +count_balance() -> + [count_balance(branch, #branch.balance), + count_balance(teller, #teller.balance), + count_balance(account, #account.balance)]. + +count_balance(Tab, BalPos) -> + Frags = table_info(Tab, frag_names), + count_balance(Tab, Frags, 0, 0, BalPos). + +count_balance(Tab, [Frag | Frags], Bal, Size, BalPos) -> + First = mnesia:dirty_first(Frag), + {Bal2, Size2} = count_frag_balance(Frag, First, Bal, Size, BalPos), + count_balance(Tab, Frags, Bal2, Size2, BalPos); +count_balance(Tab, [], Bal, Size, _BalPos) -> + #summary{table = Tab, node = node(), balance = Bal, size = Size}. + +count_frag_balance(_Frag, '$end_of_table', Bal, Size, _BalPos) -> + {Bal, Size}; +count_frag_balance(Frag, Key, Bal, Size, BalPos) -> + [Record] = mnesia:dirty_read({Frag, Key}), + Bal2 = Bal + element(BalPos, Record), + Next = mnesia:dirty_next(Frag, Key), + count_frag_balance(Frag, Next, Bal2, Size + 1, BalPos). + +check_balance([], []) -> + mnesia:abort({"No balance"}); +check_balance(Summaries, []) -> + [One | Rest] = lists:flatten(Summaries), + Balance = One#summary.balance, + %% Size = One#summary.size, + case [S || S <- Rest, S#summary.balance =/= Balance] of + [] -> + ok; + BadSummaries -> + mnesia:abort({"Bad balance", One, BadSummaries}) + end; +check_balance(_, BadNodes) -> + mnesia:abort({"Bad nodes", BadNodes}). diff --git a/lib/mnesia/test/mnesia_trans_access_test.erl b/lib/mnesia/test/mnesia_trans_access_test.erl new file mode 100644 index 0000000000..c67382e694 --- /dev/null +++ b/lib/mnesia/test/mnesia_trans_access_test.erl @@ -0,0 +1,1254 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(mnesia_trans_access_test). +-author('[email protected]'). +-compile([export_all]). +-include("mnesia_test_lib.hrl"). + +init_per_testcase(Func, Conf) -> + mnesia_test_lib:init_per_testcase(Func, Conf). + +fin_per_testcase(Func, Conf) -> + mnesia_test_lib:fin_per_testcase(Func, Conf). + +-define(receive_messages(Msgs), mnesia_recovery_test:receive_messages(Msgs, ?FILE, ?LINE)). + +% First Some debug logging +-define(dgb, true). +-ifdef(dgb). +-define(dl(X, Y), ?verbose("**TRACING: " ++ X ++ "**~n", Y)). +-else. +-define(dl(X, Y), ok). +-endif. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +all(doc) -> + ["Evil access of records in the scope of transactions", + "Invoke all functions in the API and try to cover all legal uses", + "cases as well the illegal dito. This is a complement to the", + "other more explicit test cases."]; +all(suite) -> + [ + write, read, wread, delete, delete_object, + match_object, select, select14, all_keys, + transaction, nested_activities, + index_tabs, index_lifecycle + ]. + +%% Write records + +write(suite) -> []; +write(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = write, + Schema = [{name, Tab}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:write([]) end)), + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:write({Tab, 2}) end)), + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:write({foo, 2}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, 1, 2}) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:write({Tab, 1, 2})), + ?verify_mnesia(Nodes, []). + +%% Read records + +read(suite) -> []; +read(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = read, + Schema = [{name, Tab}, {type, bag}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + OneRec = {Tab, 1, 2}, + TwoRec = {Tab, 1, 3}, + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:read([]) end)), + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:read({Tab}) end)), + ?match({aborted, {bad_type, _}} + , mnesia:transaction(fun() -> mnesia:read(OneRec) end)), + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + ?match({atomic, [OneRec]}, + mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(TwoRec) end)), + ?match({atomic, [OneRec, TwoRec]}, + mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:read({Tab, 1})), + ?verify_mnesia(Nodes, []). + +%% Read records and set write lock + +wread(suite) -> []; +wread(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = wread, + Schema = [{name, Tab}, {type, set}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + OneRec = {Tab, 1, 2}, + TwoRec = {Tab, 1, 3}, + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:wread([]) end)), + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:wread({Tab}) end)), + ?match({aborted, {bad_type, _}} + , mnesia:transaction(fun() -> mnesia:wread(OneRec) end)), + + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:wread({Tab, 1}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + + ?match({atomic, [OneRec]}, + mnesia:transaction(fun() -> mnesia:wread({Tab, 1}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(TwoRec) end)), + ?match({atomic, [TwoRec]}, + mnesia:transaction(fun() -> mnesia:wread({Tab, 1}) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:wread({Tab, 1})), + ?verify_mnesia(Nodes, []). + +%% Delete record + +delete(suite) -> []; +delete(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = delete, + Schema = [{name, Tab}, {type, bag}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:delete([]) end)), + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:delete({Tab}) end)), + ?match({aborted, {bad_type, _}} + , mnesia:transaction(fun() -> mnesia:delete({Tab, 1, 2}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:delete({Tab, 1}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, 1, 2}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:delete({Tab, 1}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, 1, 2}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, 1, 2}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:delete({Tab, 1}) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:delete({Tab, 1})), + ?verify_mnesia(Nodes, []). + +%% Delete matching record + +delete_object(suite) -> []; +delete_object(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = delete_object, + Schema = [{name, Tab}, {type, bag}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + OneRec = {Tab, 1, 2}, + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:delete_object([]) end)), + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:delete_object({Tab}) end)), + ?match({aborted, {bad_type, _}}, + mnesia:transaction(fun() -> mnesia:delete_object({Tab, 1}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:delete_object(OneRec) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:delete_object(OneRec) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:delete_object(OneRec) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:delete_object(OneRec)), + + ?match({aborted, {bad_type, Tab, _}}, + mnesia:transaction(fun() -> mnesia:delete_object({Tab, {['_']}, 21}) end)), + ?match({aborted, {bad_type, Tab, _}}, + mnesia:transaction(fun() -> mnesia:delete_object({Tab, {['$5']}, 21}) end)), + + ?verify_mnesia(Nodes, []). + +%% Read matching records + +match_object(suite) -> []; +match_object(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = match, + Schema = [{name, Tab}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + OneRec = {Tab, 1, 2}, + OnePat = {Tab, '$1', 2}, + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:match_object(OnePat) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + ?match({atomic, [OneRec]}, + mnesia:transaction(fun() -> mnesia:match_object(OnePat) end)), + + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:match_object({foo, '$1', 2}) end)), + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:match_object({[], '$1', 2}) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:match_object(OnePat)), + ?verify_mnesia(Nodes, []). + +%% select +select(suite) -> []; +select(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = match, + Schema = [{name, Tab}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + OneRec = {Tab, 1, 2}, + TwoRec = {Tab, 2, 3}, + OnePat = [{{Tab, '$1', 2}, [], ['$_']}], + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:select(Tab, OnePat) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(TwoRec) end)), + ?match({atomic, [OneRec]}, + mnesia:transaction(fun() -> mnesia:select(Tab, OnePat) end)), + + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:select(Tab, {match, '$1', 2}) end)), + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:select(Tab, [{'_', [], '$1'}]) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:select(Tab, OnePat)), + ?verify_mnesia(Nodes, []). + + +%% more select +select14(suite) -> []; +select14(Config) when is_list(Config) -> + [Node1,Node2] = Nodes = ?acquire_nodes(2, Config), + Tab1 = select14_ets, + Tab2 = select14_dets, + Tab3 = select14_remote, + Tab4 = select14_remote_dets, + Schemas = [[{name, Tab1}, {attributes, [k, v]}, {ram_copies, [Node1]}], + [{name, Tab2}, {attributes, [k, v]}, {disc_only_copies, [Node1]}], + [{name, Tab3}, {attributes, [k, v]}, {ram_copies, [Node2]}], + [{name, Tab4}, {attributes, [k, v]}, {disc_only_copies, [Node2]}]], + [?match({atomic, ok}, mnesia:create_table(Schema)) || Schema <- Schemas], + + %% Some Helpers + Trans = fun(Fun) -> mnesia:transaction(Fun) end, + LoopHelp = fun('$end_of_table',_) -> []; + ({Recs,Cont},Fun) -> + Sel = mnesia:select(Cont), + Recs ++ Fun(Sel, Fun) + end, + Loop = fun(Table,Pattern) -> + Sel = mnesia:select(Table, Pattern, 1, read), + Res = LoopHelp(Sel,LoopHelp), + case mnesia:table_info(Table, type) of + ordered_set -> Res; + _ -> lists:sort(Res) + end + end, + Test = + fun(Tab) -> + OneRec = {Tab, 1, 2}, + TwoRec = {Tab, 2, 3}, + OnePat = [{{Tab, '$1', 2}, [], ['$_']}], + All = [OneRec,TwoRec], + AllPat = [{'_', [], ['$_']}], + + ?match({atomic, []}, Trans(fun() -> Loop(Tab, OnePat) end)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(TwoRec) end)), + ?match({atomic, [OneRec]}, Trans(fun() -> Loop(Tab, OnePat) end)), + ?match({atomic, All}, Trans(fun() -> Loop(Tab, AllPat) end)), + + {atomic,{_, Cont}} = Trans(fun() -> mnesia:select(Tab, OnePat, 1, read) end), + ?match({aborted, wrong_transaction}, Trans(fun() -> mnesia:select(Cont) end)), + + ?match({aborted, _}, Trans(fun() -> mnesia:select(Tab, {match, '$1', 2},1,read) end)), + ?match({aborted, _}, Trans(fun() -> mnesia:select(Tab, [{'_', [], '$1'}],1,read) end)), + ?match({aborted, _}, Trans(fun() -> mnesia:select(sune) end)), + ?match({'EXIT', {aborted, no_transaction}}, mnesia:select(Tab, OnePat,1,read)), + ?match({aborted, {badarg,sune}}, + Trans(fun() -> mnesia:select(sune) end)) + end, + Test(Tab1), + Test(Tab2), + Test(Tab3), + Test(Tab4), + ?verify_mnesia(Nodes, []). + + +%% Pick all keys from table + +all_keys(suite) ->[]; +all_keys(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = all_keys, + Schema = [{name, Tab}, {type, bag}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + Write = fun() -> mnesia:write({Tab, 14, 4}) end, + AllKeys = fun() -> mnesia:all_keys(Tab) end, + + ?match({atomic, []}, mnesia:transaction(AllKeys)), + + ?match({atomic, ok}, mnesia:transaction(Write)), + ?match({atomic, [14]}, mnesia:transaction(AllKeys)), + + ?match({atomic, ok}, mnesia:transaction(Write)), + ?match({atomic, [14]}, mnesia:transaction(AllKeys)), + + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:all_keys(foo) end)), + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:all_keys([]) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:all_keys(Tab)), + ?verify_mnesia(Nodes, []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Use and misuse transactions + +transaction(suite) -> []; +transaction(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + ?match({atomic, ali_baba}, mnesia:transaction(fun() -> ali_baba end)), + ?match({aborted, _}, mnesia:transaction(no_fun)), + ?match({aborted, _}, mnesia:transaction(?MODULE, no_fun, [foo])), + + {success, [A, B, C, D, E, F, G, H]} = + ?start_activities(lists:duplicate(8, Node1)), + ?start_transactions([A, B, C, D, E, F, G, H]), + + A ! fun() -> mnesia:abort(abort_bad_trans) end, + ?match_receive({A, {aborted, abort_bad_trans}}), + + B ! fun() -> erlang:error(exit_here) end, + ?match_receive({B, {aborted, _}}), + + C ! fun() -> throw(throw_bad_trans) end, + ?match_receive({C, {aborted, {throw, throw_bad_trans}}}), + + D ! fun() -> exit(exit_bad_trans) end, + ?match_receive({D, {aborted, exit_bad_trans}}), + + E ! fun() -> exit(normal) end, + ?match_receive({E, {aborted, normal}}), + + F ! fun() -> exit(abnormal) end, + ?match_receive({F, {aborted, abnormal}}), + + G ! fun() -> exit(G, abnormal) end, + ?match_receive({'EXIT', G, abnormal}), + + H ! fun() -> exit(H, kill) end, + ?match_receive({'EXIT', H, killed}), + + ?match({atomic, ali_baba}, + mnesia:transaction(fun() -> ali_baba end, infinity)), + ?match({atomic, ali_baba}, mnesia:transaction(fun() -> ali_baba end, 1)), + ?match({atomic, ali_baba}, mnesia:transaction(fun() -> ali_baba end, 0)), + ?match({aborted, Reason8} when element(1, Reason8) == badarg, mnesia:transaction(fun() -> ali_baba end, -1)), + ?match({aborted, Reason1} when element(1, Reason1) == badarg, mnesia:transaction(fun() -> ali_baba end, foo)), + Fun = fun() -> + ?match(true, mnesia:is_transaction()), + ?match({atomic, ok}, + mnesia:transaction(fun() -> ?match(true, mnesia:is_transaction()),ok end)), ok end, + ?match({atomic, ok}, mnesia:transaction(Fun)), + ?verify_mnesia(Nodes, []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +nested_activities(suite) -> + [ + basic_nested, + nested_transactions, + mix_of_nested_activities + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% ensure that nested transactions behave correctly +%% We create a particular table that is used by this test only +-record(ntab, {a, b}). +basic_nested(doc) -> ["Test the basic functionality of nested transactions"]; +basic_nested(suite) -> []; +basic_nested(Config) when is_list(Config) -> + Nodes = ?acquire_nodes(3, Config), + Args = [{ram_copies, Nodes}, + {attributes, record_info(fields, ntab)}], + ?match({atomic, ok}, mnesia:create_table(ntab, Args)), + do_nested(top), + case mnesia_test_lib:diskless(Config) of + false -> + lists:foreach(fun(N) -> + ?match({atomic, ok}, + mnesia:change_table_copy_type(ntab, N, disc_only_copies)) + end, Nodes), + do_nested(top); + true -> + skip + end, + ?verify_mnesia(Nodes, []). + +do_nested(How) -> + F1 = fun() -> + mnesia:write(#ntab{a= 1}), + mnesia:write(#ntab{a= 2}) + end, + F2 = fun() -> + mnesia:read({ntab, 1}) + end, + ?match({atomic, ok}, mnesia:transaction(F1)), + ?match({atomic, _}, mnesia:transaction(F2)), + + ?match({atomic, {aborted, _}}, + mnesia:transaction(fun() -> n_f1(), + mnesia:transaction(fun() -> n_f2() end) + end)), + + ?match({atomic, {aborted, _}}, + mnesia:transaction(fun() -> n_f1(), + mnesia:transaction(fun() -> n_f3() end) + end)), + ?match({atomic, {atomic, [#ntab{a = 5}]}}, + mnesia:transaction(fun() -> mnesia:write(#ntab{a = 5}), + mnesia:transaction(fun() -> n_f4() end) + end)), + Cyclic = fun() -> mnesia:abort({cyclic,a,a,a,a,a}) end, %% Ugly + NodeNotR = fun() -> mnesia:abort({node_not_running, testNode}) end, + + TestAbort = fun(Fun) -> + case get(restart_counter) of + undefined -> + put(restart_counter, 1), + Fun(); + _ -> + erase(restart_counter), + ok + end + end, + + ?match({atomic,{atomic,ok}}, + mnesia:transaction(fun()->mnesia:transaction(TestAbort, + [Cyclic])end)), + + ?match({atomic,{atomic,ok}}, + mnesia:transaction(fun()->mnesia:transaction(TestAbort, + [NodeNotR])end)), + + %% Now try the restart thingie + case How of + top -> + Pids = [spawn(?MODULE, do_nested, [{spawned, self()}]), + spawn(?MODULE, do_nested, [{spawned, self()}]), + spawn(?MODULE, do_nested, [{spawned, self()}]), + spawn(?MODULE, do_nested, [{spawned, self()}])], + ?match({info, _, _}, mnesia_tm:get_info(2000)), + lists:foreach(fun(P) -> receive + {P, ok} -> ok + end + end, Pids), + ?match([], [Tab || Tab <- ets:all(), mnesia_trans_store == ets:info(Tab, name)]); + + {spawned, Pid} -> + ?match({info, _, _}, mnesia_tm:get_info(2000)), + Pid ! {self(), ok}, + exit(normal) + end. + + +n_f1() -> + mnesia:read({ntab, 1}), + mnesia:write(#ntab{a = 3}). + +n_f2() -> + mnesia:write(#ntab{a = 4}), + erlang:error(exit_here). + +n_f3() -> + mnesia:write(#ntab{a = 4}), + throw(funky). + +n_f4() -> + mnesia:read({ntab, 5}). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +nested_transactions(doc) -> + ["Verify that nested_transactions are handled as expected"]; +nested_transactions(suite) -> + [nested_trans_both_ok, + nested_trans_child_dies, + nested_trans_parent_dies, + nested_trans_both_dies]. + +nested_trans_both_ok(suite) -> []; +nested_trans_both_ok(Config) when is_list(Config) -> + nested_transactions(Config, ok, ok). + +nested_trans_child_dies(suite) -> []; +nested_trans_child_dies(Config) when is_list(Config) -> + nested_transactions(Config, abort, ok). + +nested_trans_parent_dies(suite) -> []; +nested_trans_parent_dies(Config) when is_list(Config) -> + nested_transactions(Config, ok, abort). + +nested_trans_both_dies(suite) -> []; +nested_trans_both_dies(Config) when is_list(Config) -> + nested_transactions(Config, abort, abort). + +nested_transactions(Config, Child, Father) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Tab = nested_trans, + + Def = + case mnesia_test_lib:diskless(Config) of + true -> + [{name, Tab}, {ram_copies, Nodes}]; + false -> + [{name, Tab}, {ram_copies, [Node1]}, + {disc_copies, [Node2]}, {disc_only_copies, [Node3]}] + end, + + ?match({atomic, ok}, mnesia:create_table(Def)), + ?match(ok, mnesia:dirty_write({Tab, father, not_updated})), + ?match(ok, mnesia:dirty_write({Tab, child, not_updated})), + + ChildOk = fun() -> mnesia:write({Tab, child, updated}) end, + ChildAbort = fun() -> + mnesia:write({Tab, child, updated}), + erlang:error(exit_here) + end, + + Child_Fun = % Depending of test case + case Child of + ok -> ChildOk; + abort -> ChildAbort + end, + + FatherOk = fun() -> mnesia:transaction(Child_Fun), + mnesia:write({Tab, father, updated}) + end, + + FatherAbort = fun() -> mnesia:transaction(Child_Fun), + mnesia:write({Tab, father, updated}), + erlang:error(exit_here) + end, + + {FatherRes, ChildRes} = % Depending of test case + case Father of + ok -> ?match({atomic, ok}, mnesia:transaction(FatherOk)), + case Child of + ok -> {[{Tab, father, updated}], [{Tab, child, updated}]}; + _ -> {[{Tab, father, updated}], [{Tab, child, not_updated}]} + end; + abort -> ?match({aborted, _}, mnesia:transaction(FatherAbort)), + {[{Tab, father, not_updated}], [{Tab, child, not_updated}]} + end, + + %% Syncronize things!! + ?match({atomic, ok}, mnesia:sync_transaction(fun() -> mnesia:write({Tab, sync, sync}) end)), + + ?match(ChildRes, rpc:call(Node1, mnesia, dirty_read, [{Tab, child}])), + ?match(ChildRes, rpc:call(Node2, mnesia, dirty_read, [{Tab, child}])), + ?match(ChildRes, rpc:call(Node3, mnesia, dirty_read, [{Tab, child}])), + + ?match(FatherRes, rpc:call(Node1, mnesia, dirty_read, [{Tab, father}])), + ?match(FatherRes, rpc:call(Node2, mnesia, dirty_read, [{Tab, father}])), + ?match(FatherRes, rpc:call(Node3, mnesia, dirty_read, [{Tab, father}])), + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +mix_of_nested_activities(doc) -> + ["Verify that dirty operations in a transaction are handled like ", + "normal transactions"]; +mix_of_nested_activities(suite) -> []; +mix_of_nested_activities(Config) when is_list(Config) -> + [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config), + Tab = tab, + + Def = + case mnesia_test_lib:diskless(Config) of + true -> [{ram_copies, Nodes}]; + false -> + [{ram_copies, [Node1]}, + {disc_copies, [Node2]}, + {disc_only_copies, [Node3]}] + end, + + ?match({atomic, ok}, mnesia:create_table(Tab, [{type,bag}|Def])), + Activities = [transaction, sync_transaction, + ets, async_dirty, sync_dirty], + %% Make a test for all 3000 combinations + Tests = [[A,B,C,D,E] || + A <- Activities, + B <- Activities, + C <- Activities, + D <- Activities, + E <- Activities], + Foreach = + fun(Test,No) -> + Result = lists:reverse(Test), + ?match({No,Result},{No,catch apply_op({Tab,No},Test)}), + No+1 + end, + lists:foldl(Foreach, 0, Tests), + ?verify_mnesia(Nodes, []). + +apply_op(Oid,[Type]) -> + check_res(Type,mnesia:Type(fun() -> [Type|read_op(Oid)] end)); +apply_op(Oid = {Tab,Key},[Type|Next]) -> + check_res(Type,mnesia:Type(fun() -> + Prev = read_op(Oid), + mnesia:write({Tab,Key,[Type|Prev]}), + apply_op(Oid,Next) + end)). + +check_res(transaction, {atomic,Res}) -> + Res; +check_res(sync_transaction, {atomic,Res}) -> + Res; +check_res(async_dirty, Res) when is_list(Res) -> + Res; +check_res(sync_dirty, Res) when is_list(Res) -> + Res; +check_res(ets, Res) when is_list(Res) -> + Res; +check_res(Type,Res) -> + ?match(bug,{Type,Res}). + +read_op(Oid) -> + case lists:reverse(mnesia:read(Oid)) of + [] -> []; + [{_,_,Ops}|_] -> + Ops + end. + +index_tabs(suite) -> + [ + index_match_object, + index_read, + index_update, + index_write + ]. + +%% Read matching records by using an index + +index_match_object(suite) -> []; +index_match_object(Config) when is_list(Config) -> + [Node1, Node2] = Nodes = ?acquire_nodes(2, Config), + Tab = index_match_object, + Schema = [{name, Tab}, {attributes, [k, v, e]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ValPos = 3, + BadValPos = ValPos + 2, + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:index_match_object({Tab, '$1', 2}, ValPos) end)), + OneRec = {Tab, {1, 1}, 2, {1, 1}}, + OnePat = {Tab, '$1', 2, '_'}, + BadPat = {Tab, '$1', '$2', '_'}, %% See ref guide + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + + Imatch = fun(Patt, Pos) -> + mnesia:transaction(fun() -> lists:sort(mnesia:index_match_object(Patt, Pos)) end) + end, + ?match({atomic, [OneRec]}, Imatch(OnePat, ValPos)), + ?match({aborted, _}, Imatch(OnePat, BadValPos)), + ?match({aborted, _}, Imatch({foo, '$1', 2, '_'}, ValPos)), + ?match({aborted, _}, Imatch({[], '$1', 2, '_'}, ValPos)), + ?match({aborted, _}, Imatch(BadPat, ValPos)), + ?match({'EXIT', {aborted, no_transaction}}, mnesia:index_match_object(OnePat, ValPos)), + + Another = {Tab, {3,1}, 2, {4,4}}, + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Another) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, {4, 4}, 3, {4, 4}}) end)), + + ?match({atomic, [OneRec]}, Imatch({Tab, {1,1}, 2, {1,1}}, ValPos)), + ?match({atomic, [OneRec]}, Imatch({Tab, {1,1}, 2, '$1'}, ValPos)), + ?match({atomic, [OneRec]}, Imatch({Tab, '$1', 2, {1,1}}, ValPos)), + ?match({atomic, [OneRec]}, Imatch({Tab, '$1', 2, '$1'}, ValPos)), + ?match({atomic, [OneRec]}, Imatch({Tab, {1, '$1'}, 2, '_'}, ValPos)), + ?match({atomic, [OneRec]}, Imatch({Tab, {'$2', '$1'}, 2, {'_', '$1'}}, ValPos)), + ?match({atomic, [OneRec, Another]}, Imatch({Tab, '_', 2, '_'}, ValPos)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, 4, 5, {7, 4}}) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, 7, 5, {7, 5}}) end)), + + ?match({atomic, [{Tab, 4, 5, {7, 4}}]}, Imatch({Tab, '$1', 5, {'_', '$1'}}, ValPos)), + + ?match({atomic, [OneRec]}, rpc:call(Node2, mnesia, transaction, + [fun() -> + lists:sort(mnesia:index_match_object({Tab, {1,1}, 2, + {1,1}}, ValPos)) + end])), + ?verify_mnesia(Nodes, []). + +%% Read records by using an index + +index_read(suite) -> []; +index_read(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = index_read, + Schema = [{name, Tab}, {attributes, [k, v]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ValPos = 3, + BadValPos = ValPos + 1, + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + OneRec = {Tab, 1, 2}, + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(OneRec) end)), + ?match({atomic, [OneRec]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)), + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, BadValPos) end)), + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:index_read(foo, 2, ValPos) end)), + ?match({aborted, _}, + mnesia:transaction(fun() -> mnesia:index_read([], 2, ValPos) end)), + + ?match({'EXIT', {aborted, no_transaction}}, mnesia:index_read(Tab, 2, ValPos)), + ?verify_mnesia(Nodes, []). + +index_update(suite) -> [index_update_set, index_update_bag]; +index_update(doc) -> ["See Ticket OTP-2083, verifies that a table with a index is " + "update in the correct way i.e. the index finds the correct " + "records after a update"]. +index_update_set(suite) -> []; +index_update_set(Config)when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = index_test, + Schema = [{name, Tab}, {attributes, [k, v1, v2, v3]}, {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ValPos = v1, + ValPos2 = v3, + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + Pat1 = {Tab, '$1', 2, '$2', '$3'}, + Pat2 = {Tab, '$1', '$2', '$3', '$4'}, + + Rec1 = {Tab, 1, 2, 3, 4}, + Rec2 = {Tab, 2, 2, 13, 14}, + Rec3 = {Tab, 1, 12, 13, 14}, + Rec4 = {Tab, 4, 2, 13, 14}, + + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec1) end)), + ?match({atomic, [Rec1]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec2) end)), + {atomic, R1} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec2], lists:sort(R1)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec3) end)), + {atomic, R2} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec2], lists:sort(R2)), + ?match({atomic, [Rec2]}, + mnesia:transaction(fun() -> mnesia:index_match_object(Pat1, ValPos) end)), + + {atomic, R3} = mnesia:transaction(fun() -> mnesia:match_object(Pat2) end), + ?match([Rec3, Rec2], lists:sort(R3)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec4) end)), + {atomic, R4} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec2, Rec4], lists:sort(R4)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:delete({Tab, 4}) end)), + ?match({atomic, [Rec2]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)), + + ?match({atomic, ok}, mnesia:del_table_index(Tab, ValPos)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec4) end)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos2)), + + {atomic, R5} = mnesia:transaction(fun() -> mnesia:match_object(Pat2) end), + ?match([Rec3, Rec2, Rec4], lists:sort(R5)), + + {atomic, R6} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec2, Rec4], lists:sort(R6)), + + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end)), + {atomic, R7} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end), + ?match([Rec3, Rec2, Rec4], lists:sort(R7)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec1) end)), + {atomic, R8} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec2, Rec4], lists:sort(R8)), + ?match({atomic, [Rec1]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end)), + {atomic, R9} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end), + ?match([Rec2, Rec4], lists:sort(R9)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec2) end)), + {atomic, R10} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec4], lists:sort(R10)), + ?match({atomic, [Rec1]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end)), + ?match({atomic, [Rec4]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete({Tab, 4}) end)), + {atomic, R11} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1], lists:sort(R11)), + ?match({atomic, [Rec1]},mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end)), + ?match({atomic, []},mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end)), + + ?verify_mnesia(Nodes, []). + +index_update_bag(suite) -> []; +index_update_bag(Config)when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = index_test, + Schema = [{name, Tab}, + {type, bag}, + {attributes, [k, v1, v2, v3]}, + {ram_copies, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ValPos = v1, + ValPos2 = v3, + + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + + Pat1 = {Tab, '$1', 2, '$2', '$3'}, + Pat2 = {Tab, '$1', '$2', '$3', '$4'}, + + Rec1 = {Tab, 1, 2, 3, 4}, + Rec2 = {Tab, 2, 2, 13, 14}, + Rec3 = {Tab, 1, 12, 13, 14}, + Rec4 = {Tab, 4, 2, 13, 4}, + Rec5 = {Tab, 1, 2, 234, 14}, + + %% Simple Index + ?match({atomic, []}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)), + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec1) end)), + ?match({atomic, [Rec1]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec2) end)), + {atomic, R1} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec2], lists:sort(R1)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec3) end)), + {atomic, R2} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec2], lists:sort(R2)), + + {atomic, R3} = mnesia:transaction(fun() -> mnesia:index_match_object(Pat1, ValPos) end), + ?match([Rec1, Rec2], lists:sort(R3)), + + {atomic, R4} = mnesia:transaction(fun() -> mnesia:match_object(Pat2) end), + ?match([Rec1, Rec3, Rec2], lists:sort(R4)), + + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write(Rec4) end)), + {atomic, R5} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec2, Rec4], lists:sort(R5)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete({Tab, 4}) end)), + {atomic, R6} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec2], lists:sort(R6)), + + %% OTP-6587 Needs some whitebox testing to see that the index table is cleaned correctly + + [IPos] = mnesia_lib:val({Tab,index}), + ITab = mnesia_lib:val({index_test,{index, IPos}}), + io:format("~n Index ~p @ ~p => ~p ~n~n",[IPos,ITab, ets:tab2list(ITab)]), + ?match([{2,1},{2,2},{12,1}], ets:tab2list(ITab)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec5) end)), + {atomic, R60} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1,Rec5,Rec2], lists:sort(R60)), + + ?match([{2,1},{2,2},{12,1}], ets:tab2list(ITab)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec3) end)), + {atomic, R61} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1,Rec5,Rec2], lists:sort(R61)), + {atomic, R62} = mnesia:transaction(fun() -> mnesia:index_read(Tab,12, ValPos) end), + ?match([], lists:sort(R62)), + ?match([{2,1},{2,2}], ets:tab2list(ITab)), + + %% reset for rest of testcase + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec3) end)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec5) end)), + {atomic, R6} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec2], lists:sort(R6)), + %% OTP-6587 + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec1) end)), + ?match({atomic, [Rec2]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)), + {atomic, R7} = mnesia:transaction(fun() -> mnesia:match_object(Pat2) end), + ?match([Rec3, Rec2], lists:sort(R7)), + + %% Two indexies + ?match({atomic, ok}, mnesia:del_table_index(Tab, ValPos)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec1) end)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec4) end)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos2)), + + {atomic, R8} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec2, Rec4], lists:sort(R8)), + + {atomic, R9} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end), + ?match([Rec1, Rec4], lists:sort(R9)), + {atomic, R10} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end), + ?match([Rec3, Rec2], lists:sort(R10)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec5) end)), + {atomic, R11} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec1, Rec5, Rec2, Rec4], lists:sort(R11)), + {atomic, R12} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end), + ?match([Rec1, Rec4], lists:sort(R12)), + {atomic, R13} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end), + ?match([Rec5, Rec3, Rec2], lists:sort(R13)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec1) end)), + {atomic, R14} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec5, Rec2, Rec4], lists:sort(R14)), + ?match({atomic, [Rec4]}, + mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end)), + {atomic, R15} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end), + ?match([Rec5, Rec3, Rec2], lists:sort(R15)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec5) end)), + {atomic, R16} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec2, Rec4], lists:sort(R16)), + ?match({atomic, [Rec4]}, mnesia:transaction(fun()->mnesia:index_read(Tab, 4, ValPos2) end)), + {atomic, R17} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end), + ?match([Rec3, Rec2], lists:sort(R17)), + + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec1) end)), + ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete({Tab, 1}) end)), + {atomic, R18} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end), + ?match([Rec2, Rec4], lists:sort(R18)), + ?match({atomic, [Rec4]}, mnesia:transaction(fun()->mnesia:index_read(Tab, 4, ValPos2) end)), + {atomic, R19} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end), + ?match([Rec2], lists:sort(R19)), + + ?verify_mnesia(Nodes, []). + + +index_write(suite) -> []; +index_write(doc) -> ["See ticket OTP-8072"]; +index_write(Config)when is_list(Config) -> + Nodes = ?acquire_nodes(1, Config), + mnesia:create_table(a, [{index, [val]}]), + mnesia:create_table(counter, []), + + CreateIfNonExist = + fun(Index) -> + case mnesia:index_read(a, Index, 3) of + [] -> + Id = mnesia:dirty_update_counter(counter, id, 1), + New = {a, Id, Index}, + mnesia:write(New), + New; + [Found] -> + Found + end + end, + + Trans = fun(A) -> + mnesia:transaction(CreateIfNonExist, [A]) + %% This works better most of the time + %% And it is allowed to fail since it's dirty + %% mnesia:async_dirty(CreateIfNonExist, [A]) + end, + + Self = self(), + Update = fun() -> + Res = lists:map(Trans, lists:seq(1,10)), + Self ! {self(), Res} + end, + + Pids = [spawn(Update) || _ <- lists:seq(1,5)], + + Gather = fun(Pid, Acc) -> receive {Pid, Res} -> [Res|Acc] end end, + Results = lists:foldl(Gather, [], Pids), + Expected = hd(Results), + Check = fun(Res) -> ?match(Expected, Res) end, + lists:foreach(Check, Results), + ?verify_mnesia(Nodes, []). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Add and drop indecies + +index_lifecycle(suite) -> + [ + add_table_index_ram, + add_table_index_disc, + add_table_index_disc_only, + create_live_table_index_ram, + create_live_table_index_disc, + create_live_table_index_disc_only, + del_table_index_ram, + del_table_index_disc, + del_table_index_disc_only, + idx_schema_changes + ]. + +add_table_index_ram(suite) -> []; +add_table_index_ram(Config) when is_list(Config) -> + add_table_index(Config, ram_copies). + +add_table_index_disc(suite) -> []; +add_table_index_disc(Config) when is_list(Config) -> + add_table_index(Config, disc_copies). + +add_table_index_disc_only(suite) -> []; +add_table_index_disc_only(Config) when is_list(Config) -> + add_table_index(Config, disc_only_copies). + +%% Add table index + +add_table_index(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = add_table_index, + Schema = [{name, Tab}, {attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ValPos = 3, + BadValPos = ValPos + 1, + ?match({aborted, Reason41 } when element(1, Reason41) == bad_type, + mnesia:add_table_index(Tab, BadValPos)), + ?match({aborted,Reason42 } when element(1, Reason42) == bad_type, + mnesia:add_table_index(Tab, 2)), + ?match({aborted, Reason43 } when element(1, Reason43) == bad_type, + mnesia:add_table_index(Tab, 1)), + ?match({aborted, Reason44 } when element(1, Reason44) == bad_type, + mnesia:add_table_index(Tab, 0)), + ?match({aborted, Reason45 } when element(1, Reason45) == bad_type, + mnesia:add_table_index(Tab, -1)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + ?match({aborted, Reason46 } when element(1, Reason46) == already_exists, + mnesia:add_table_index(Tab, ValPos)), + + NestedFun = fun() -> + ?match({aborted, nested_transaction}, + mnesia:add_table_index(Tab, ValPos)), + ok + end, + ?match({atomic, ok}, mnesia:transaction(NestedFun)), + ?verify_mnesia(Nodes, []). + +create_live_table_index_ram(suite) -> []; +create_live_table_index_ram(Config) when is_list(Config) -> + create_live_table_index(Config, ram_copies). + +create_live_table_index_disc(suite) -> []; +create_live_table_index_disc(Config) when is_list(Config) -> + create_live_table_index(Config, disc_copies). + +create_live_table_index_disc_only(suite) -> []; +create_live_table_index_disc_only(Config) when is_list(Config) -> + create_live_table_index(Config, disc_only_copies). + +create_live_table_index(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = create_live_table_index, + Schema = [{name, Tab}, {attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ValPos = 3, + mnesia:dirty_write({Tab, 1, 2}), + + Fun = fun() -> + ?match(ok, mnesia:write({Tab, 2, 2})), + ok + end, + ?match({atomic, ok}, mnesia:transaction(Fun)), + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + ?match({atomic, [{Tab, 1, 2},{Tab, 2, 2}]}, + mnesia:transaction(fun() -> lists:sort(mnesia:index_read(Tab, 2, ValPos)) + end)), + ?verify_mnesia(Nodes, []). + +%% Drop table index + +del_table_index_ram(suite) ->[]; +del_table_index_ram(Config) when is_list(Config) -> + del_table_index(Config, ram_copies). + +del_table_index_disc(suite) ->[]; +del_table_index_disc(Config) when is_list(Config) -> + del_table_index(Config, disc_copies). + +del_table_index_disc_only(suite) ->[]; +del_table_index_disc_only(Config) when is_list(Config) -> + del_table_index(Config, disc_only_copies). + +del_table_index(Config, Storage) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = del_table_index, + Schema = [{name, Tab}, {attributes, [k, v]}, {Storage, [Node1]}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + ValPos = 3, + BadValPos = ValPos + 1, + ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), + ?match({aborted,Reason} when element(1, Reason) == no_exists, + mnesia:del_table_index(Tab, BadValPos)), + ?match({atomic, ok}, mnesia:del_table_index(Tab, ValPos)), + + ?match({aborted,Reason1} when element(1, Reason1) == no_exists, + mnesia:del_table_index(Tab, ValPos)), + NestedFun = + fun() -> + ?match({aborted, nested_transaction}, + mnesia:del_table_index(Tab, ValPos)), + ok + end, + ?match({atomic, ok}, mnesia:transaction(NestedFun)), + ?verify_mnesia(Nodes, []). + +idx_schema_changes(suite) -> [idx_schema_changes_ram, + idx_schema_changes_disc, + idx_schema_changes_disc_only]; +idx_schema_changes(doc) -> + ["Tests that index tables are handled correctly when schema changes.", + "For example when a replica is deleted or inserted", + "TICKET OTP-2XXX (ELVIRA)"]. + +idx_schema_changes_ram(suite) -> []; +idx_schema_changes_ram(Config) when is_list(Config) -> + idx_schema_changes(Config, ram_copies). +idx_schema_changes_disc(suite) -> []; +idx_schema_changes_disc(Config) when is_list(Config) -> + idx_schema_changes(Config, disc_copies). +idx_schema_changes_disc_only(suite) -> []; +idx_schema_changes_disc_only(Config) when is_list(Config) -> + idx_schema_changes(Config, disc_only_copies). + +idx_schema_changes(Config, Storage) -> + [N1, N2] = Nodes = ?acquire_nodes(2, Config), + Tab = index_schema_changes, + Idx = 3, + Schema = [{name, Tab}, {index, [Idx]}, {attributes, [k, v]}, {Storage, Nodes}], + ?match({atomic, ok}, mnesia:create_table(Schema)), + + {Storage1, Storage2} = + case Storage of + disc_only_copies -> + {ram_copies, disc_copies}; + disc_copies -> + {disc_only_copies, ram_copies}; + ram_copies -> + {disc_copies, disc_only_copies} + end, + + Write = fun(N) -> + mnesia:write({Tab, N, N+50}) + end, + + [mnesia:sync_transaction(Write, [N]) || N <- lists:seq(1, 10)], + ?match([{Tab, 1, 51}], rpc:call(N1, mnesia, dirty_index_read, [Tab, 51, Idx])), + ?match([{Tab, 1, 51}], rpc:call(N2, mnesia, dirty_index_read, [Tab, 51, Idx])), + + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, N1, Storage1)), + + ?match({atomic, ok}, rpc:call(N1, mnesia, sync_transaction, [Write, [17]])), + ?match({atomic, ok}, rpc:call(N2, mnesia, sync_transaction, [Write, [18]])), + + ?match([{Tab, 17, 67}], rpc:call(N2, mnesia, dirty_index_read, [Tab, 67, Idx])), + ?match([{Tab, 18, 68}], rpc:call(N1, mnesia, dirty_index_read, [Tab, 68, Idx])), + + ?match({atomic, ok}, mnesia:del_table_copy(Tab, N1)), + ?match({atomic, ok}, rpc:call(N1, mnesia, sync_transaction, [Write, [11]])), + ?match({atomic, ok}, rpc:call(N2, mnesia, sync_transaction, [Write, [12]])), + + ?match([{Tab, 11, 61}], rpc:call(N2, mnesia, dirty_index_read, [Tab, 61, Idx])), + ?match([{Tab, 12, 62}], rpc:call(N1, mnesia, dirty_index_read, [Tab, 62, Idx])), + + ?match({atomic, ok}, mnesia:move_table_copy(Tab, N2, N1)), + ?match({atomic, ok}, rpc:call(N1, mnesia, sync_transaction, [Write, [19]])), + ?match({atomic, ok}, rpc:call(N2, mnesia, sync_transaction, [Write, [20]])), + + ?match([{Tab, 19, 69}], rpc:call(N2, mnesia, dirty_index_read, [Tab, 69, Idx])), + ?match([{Tab, 20, 70}], rpc:call(N1, mnesia, dirty_index_read, [Tab, 70, Idx])), + + ?match({atomic, ok}, mnesia:add_table_copy(Tab, N2, Storage)), + ?match({atomic, ok}, rpc:call(N1, mnesia, sync_transaction, [Write, [13]])), + ?match({atomic, ok}, rpc:call(N2, mnesia, sync_transaction, [Write, [14]])), + + ?match([{Tab, 13, 63}], rpc:call(N2, mnesia, dirty_index_read, [Tab, 63, Idx])), + ?match([{Tab, 14, 64}], rpc:call(N1, mnesia, dirty_index_read, [Tab, 64, Idx])), + + ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, N2, Storage2)), + + ?match({atomic, ok}, rpc:call(N1, mnesia, sync_transaction, [Write, [15]])), + ?match({atomic, ok}, rpc:call(N2, mnesia, sync_transaction, [Write, [16]])), + + ?match([{Tab, 15, 65}], rpc:call(N2, mnesia, dirty_index_read, [Tab, 65, Idx])), + ?match([{Tab, 16, 66}], rpc:call(N1, mnesia, dirty_index_read, [Tab, 66, Idx])), + + ?verify_mnesia(Nodes, []). diff --git a/lib/mnesia/test/mt b/lib/mnesia/test/mt new file mode 100755 index 0000000000..25243f1149 --- /dev/null +++ b/lib/mnesia/test/mt @@ -0,0 +1,60 @@ +#! /bin/sh -f +# ``The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved via the world wide web at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# The Initial Developer of the Original Code is Ericsson Utvecklings AB. +# Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings +# AB. All Rights Reserved.'' +# +# $Id$ +# +# +# Author: Hakan Mattsson <[email protected]> +# Purpose: Simplified execution of the test suite +# +# Usage: mt <args to erlang startup script> + +#top=".." +top="$ERL_TOP/lib/mnesia" +h=`hostname` +p="-pa $top/examples -pa $top/ebin -pa $top/test -mnesia_test_verbose true" +log=test_log$$ +latest=test_log_latest +args=${1+"$@"} +erlcmd="erl -sname a $p $args -mnesia_test_timeout" +erlcmd1="erl -sname a1 $p $args" +erlcmd2="erl -sname a2 $p $args" + +xterm -geometry 70x20+0+550 -T a1 -e $erlcmd1 & +xterm -geometry 70x20+450+550 -T a2 -e $erlcmd2 & + +rm "$latest" 2>/dev/null +ln -s "$log" "$latest" +touch "$log" + +echo "$erlcmd1" +echo "" +echo "$erlcmd2" +echo "" +echo "$erlcmd" +echo "" +echo "Give the following command in order to see the outcome from node a@$h"":" +echo "" +echo " less test_log$$" + +ostype=`uname -s` +if [ "$ostype" = "SunOS" ] ; then + /usr/openwin/bin/xterm -geometry 145x40+0+0 -T a -l -lf "$log" -e $erlcmd & +else + xterm -geometry 145x40+0+0 -T a -e script -f -c "$erlcmd" "$log" & +fi +tail -f "$log" | egrep 'Eval|<>ERROR|NYI' + diff --git a/lib/mnesia/test/mt.erl b/lib/mnesia/test/mt.erl new file mode 100644 index 0000000000..f69c4a11fd --- /dev/null +++ b/lib/mnesia/test/mt.erl @@ -0,0 +1,262 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%% Author: Hakan Mattsson [email protected] +%%% Purpose: Nice shortcuts intended for testing of Mnesia +%%% +%%% See the mnesia_SUITE module about the structure of +%%% the test suite. +%%% +%%% See the mnesia_test_lib module about the test case execution. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-module(mt). +-author('[email protected]'). +-export([ + t/0, t/1, t/2, t/3, % Run test cases + loop/1, loop/2, loop/3, % loop test cases + doc/0, doc/1, % Generate test case doc + struct/0, struct/1, % View test suite struct + shutdown/0, ping/0, start_nodes/0, % Node admin + read_config/0, write_config/1 % Config admin + ]). + +-include("mnesia_test_lib.hrl"). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Aliases for the (sub) test suites +alias(all) -> mnesia_SUITE; +alias(atomicity) -> mnesia_atomicity_test; +alias(backup) -> mnesia_evil_backup; +alias(config) -> mnesia_config_test; +alias(consistency) -> mnesia_consistency_test; +alias(dirty) -> mnesia_dirty_access_test; +alias(durability) -> mnesia_durability_test; +alias(evil) -> mnesia_evil_coverage_test; +alias(qlc) -> mnesia_qlc_test; +alias(examples) -> mnesia_examples_test; +alias(frag) -> mnesia_frag_test; +alias(heavy) -> {mnesia_SUITE, heavy}; +alias(install) -> mnesia_install_test; +alias(isolation) -> mnesia_isolation_test; +alias(light) -> {mnesia_SUITE, light}; +alias(measure) -> mnesia_measure_test; +alias(medium) -> {mnesia_SUITE, medium}; +alias(nice) -> mnesia_nice_coverage_test; +alias(recover) -> mnesia_recover_test; +alias(recovery) -> mnesia_recovery_test; +alias(registry) -> mnesia_registry_test; +alias(suite) -> mnesia_SUITE; +alias(trans) -> mnesia_trans_access_test; +alias(Other) -> Other. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Resolves the name of test suites and test cases +%% according to the alias definitions. Single atoms +%% are assumed to be the name of a test suite. +resolve(Suite0) when is_atom(Suite0) -> + case alias(Suite0) of + Suite when is_atom(Suite) -> + {Suite, all}; + {Suite, Case} -> + {Suite, Case} + end; +resolve({Suite0, Case}) when is_atom(Suite0), is_atom(Case) -> + case alias(Suite0) of + Suite when is_atom(Suite) -> + {Suite, Case}; + {Suite, Case2} -> + {Suite, Case2} + end; +resolve(List) when is_list(List) -> + [resolve(Case) || Case <- List]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Run one or more test cases + +%% Run the default test case with default config +t() -> + t(read_test_case()). + +%% Resolve the test case name and run the test case +%% The test case is noted as default test case +%% and the outcome of the tests are written to +%% to a file. +t(silly) -> + mnesia_install_test:silly(); +t(diskless) -> + %% Run the default test case with default config, + %% but diskless + t(read_test_case(), diskless); +t(Case) -> + %% Use the default config + t(Case, read_config()). + +t(Case, Config) when Config == diskless -> + %% Run the test case with default config, but diskless + Config2 = [{diskless, true} | read_config()], + t(Case, Config2); +t(Mod, Fun) when is_atom(Mod), is_atom(Fun) -> + %% Run the test case with default config + t({Mod, Fun}, read_config()); +t(RawCase, Config) when is_list(Config) -> + %% Resolve the test case name and run the test case + Case = resolve(RawCase), + write_test_case(Case), + Res = mnesia_test_lib:test(Case, Config), + append_test_case_info(Case, Res). + +t(Mod, Fun, Config) when Config == diskless -> + t({Mod, Fun}, diskless). + +config_fname() -> + "mnesia_test_case_config". + +%% Read default config file +read_config() -> + Fname = config_fname(), + mnesia_test_lib:log("Consulting file ~s...~n", [Fname]), + case file:consult(Fname) of + {ok, Config} -> + mnesia_test_lib:log("Read config ~w~n", [Config]), + Config; + _Error -> + Config = mnesia_test_lib:default_config(), + mnesia_test_lib:log("<>WARNING<> Using default config: ~w~n", [Config]), + Config + end. + +%% Write new default config file +write_config(Config) when is_list(Config) -> + Fname = config_fname(), + {ok, Fd} = file:open(Fname, write), + write_list(Fd, Config), + file:close(Fd). + +write_list(Fd, [H | T]) -> + ok = io:format(Fd, "~p.~n",[H]), + write_list(Fd, T); +write_list(_, []) -> + ok. + +test_case_fname() -> + "mnesia_test_case_info". + +%% Read name of test case +read_test_case() -> + Fname = test_case_fname(), + case file:open(Fname, [read]) of + {ok, Fd} -> + Res = io:read(Fd, []), + file:close(Fd), + case Res of + {ok, TestCase} -> + mnesia_test_lib:log("Using test case ~w from file ~s~n", + [TestCase, Fname]), + TestCase; + {error, _} -> + default_test_case(Fname) + end; + {error, _} -> + default_test_case(Fname) + end. + +default_test_case(Fname) -> + TestCase = all, + mnesia_test_lib:log("<>WARNING<> Cannot read file ~s, " + "using default test case: ~w~n", + [Fname, TestCase]), + TestCase. + +write_test_case(TestCase) -> + Fname = test_case_fname(), + {ok, Fd} = file:open(Fname, write), + ok = io:format(Fd, "~p.~n",[TestCase]), + file:close(Fd). + +append_test_case_info(TestCase, TestCaseInfo) -> + Fname = test_case_fname(), + {ok, Fd} = file:open(Fname, [read, write]), + ok = io:format(Fd, "~p.~n",[TestCase]), + ok = io:format(Fd, "~p.~n",[TestCaseInfo]), + file:close(Fd), + TestCaseInfo. + +%% Generate HTML pages from the test case structure +doc() -> + doc(suite). + +doc(Case) -> + mnesia_test_lib:doc(resolve(Case)). + +%% Display out the test case structure +struct() -> + struct(suite). + +struct(Case) -> + mnesia_test_lib:struct([resolve(Case)]). + +%% Shutdown all nodes with erlang:halt/0 +shutdown() -> + mnesia_test_lib:shutdown(). + +%% Ping all nodes in config spec +ping() -> + Config = read_config(), + Nodes = mnesia_test_lib:select_nodes(all, Config, ?FILE, ?LINE), + [{N, net_adm:ping(N)} || N <- Nodes]. + +%% Slave start all nodes in config spec +start_nodes() -> + Config = read_config(), + Nodes = mnesia_test_lib:select_nodes(all, Config, ?FILE, ?LINE), + mnesia_test_lib:init_nodes(Nodes, ?FILE, ?LINE), + ping(). + +%% loop one testcase /suite until it fails + +loop(Case) -> + loop_1(Case,-1,read_config()). + +loop(M,F) when is_atom(F) -> + loop_1({M,F},-1,read_config()); +loop(Case,N) when is_integer(N) -> + loop_1(Case, N,read_config()). + +loop(M,F,N) when is_integer(N) -> + loop_1({M,F},N,read_config()). + +loop_1(Case,N,Config) when N /= 0 -> + io:format("Loop test ~p ~n", [abs(N)]), + case ok_result(Res = t(Case,Config)) of + true -> + loop_1(Case,N-1,Config); + error -> + Res + end; +loop_1(_,_,_) -> + ok. + +ok_result([{_T,{ok,_,_}}|R]) -> + ok_result(R); +ok_result([{_T,{TC,List}}|R]) when is_tuple(TC), is_list(List) -> + ok_result(List) andalso ok_result(R); +ok_result([]) -> true; +ok_result(_) -> error. diff --git a/lib/public_key/asn1/OTP-PKIX.asn1 b/lib/public_key/asn1/OTP-PKIX.asn1 index 2bcacc0990..c0cf440496 100644 --- a/lib/public_key/asn1/OTP-PKIX.asn1 +++ b/lib/public_key/asn1/OTP-PKIX.asn1 @@ -313,7 +313,7 @@ SupportedPublicKeyAlgorithms PUBLIC-KEY-ALGORITHM-CLASS ::= { dsa-with-sha1 SIGNATURE-ALGORITHM-CLASS ::= { ID id-dsa-with-sha1 - TYPE NULL } -- XXX Must be empty and not NULL + TYPE Dss-Parms } -- -- RSA Keys and Signatures diff --git a/lib/public_key/doc/src/notes.xml b/lib/public_key/doc/src/notes.xml index 33a424f432..13a9151869 100644 --- a/lib/public_key/doc/src/notes.xml +++ b/lib/public_key/doc/src/notes.xml @@ -33,6 +33,35 @@ <rev>A</rev> <file>notes.xml</file> </header> + +<section><title>Public_Key 0.7</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Certificates without any extensions could not be handled + by public_key.</p> + <p> + Own Id: OTP-8626</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Code cleanup and minor bugfixes.</p> + <p> + Own Id: OTP-8649</p> + </item> + </list> + </section> + +</section> + <section><title>Public_Key 0.6</title> <section><title>Improvements and New Features</title> diff --git a/lib/public_key/src/pubkey_cert.erl b/lib/public_key/src/pubkey_cert.erl index 799e3820d1..0651dcec29 100644 --- a/lib/public_key/src/pubkey_cert.erl +++ b/lib/public_key/src/pubkey_cert.erl @@ -29,7 +29,7 @@ validate_issuer/4, validate_names/6, validate_revoked_status/3, validate_extensions/4, validate_unknown_extensions/3, - normalize_general_name/1, digest_type/1, digest/2, is_self_signed/1, + normalize_general_name/1, digest_type/1, is_self_signed/1, is_issuer/2, issuer_id/2, is_fixed_dh_cert/1]). -define(NULL, 0). @@ -130,7 +130,7 @@ validate_signature(OtpCert, DerCert, Key, KeyParams, validate_names(OtpCert, Permit, Exclude, Last, AccErr, Verify) -> case is_self_signed(OtpCert) andalso (not Last) of true -> - ok; + AccErr; false -> TBSCert = OtpCert#'OTPCertificate'.tbsCertificate, Subject = TBSCert#'OTPTBSCertificate'.subject, @@ -197,7 +197,7 @@ normalize_general_name({rdnSequence, Issuer}) -> normalize_general_name(Issuer) -> Normalize = fun([{Description, Type, {printableString, Value}}]) -> NewValue = string:to_lower(strip_spaces(Value)), - {Description, Type, {printableString, NewValue}}; + [{Description, Type, {printableString, NewValue}}]; (Atter) -> Atter end, @@ -275,13 +275,6 @@ digest_type(?md5WithRSAEncryption) -> digest_type(?'id-dsa-with-sha1') -> sha. -digest(?sha1WithRSAEncryption, Msg) -> - crypto:sha(Msg); -digest(?md5WithRSAEncryption, Msg) -> - crypto:md5(Msg); -digest(?'id-dsa-with-sha1', Msg) -> - crypto:sha(Msg). - public_key_info(PublicKeyInfo, #path_validation_state{working_public_key_algorithm = WorkingAlgorithm, @@ -332,12 +325,6 @@ is_dir_name([[{'AttributeTypeAndValue', Type, What1}]|Rest1], true -> is_dir_name(Rest1,Rest2,Exact); false -> false end; -is_dir_name([{'AttributeTypeAndValue', Type, What1}|Rest1], - [{'AttributeTypeAndValue', Type, What2}|Rest2], Exact) -> - case is_dir_name2(What1,What2) of - true -> is_dir_name(Rest1,Rest2,Exact); - false -> false - end; is_dir_name(_,[],false) -> true; is_dir_name(_,_,_) -> diff --git a/lib/public_key/src/pubkey_cert_records.erl b/lib/public_key/src/pubkey_cert_records.erl index c7d4080adb..ac04e1c2cb 100644 --- a/lib/public_key/src/pubkey_cert_records.erl +++ b/lib/public_key/src/pubkey_cert_records.erl @@ -25,8 +25,6 @@ -export([decode_cert/2, encode_cert/1, encode_tbs_cert/1, transform/2]). --export([old_decode_cert/2, old_encode_cert/1]). %% Debugging and testing new code. - %%==================================================================== %% Internal application API %%==================================================================== @@ -35,77 +33,25 @@ decode_cert(DerCert, plain) -> 'OTP-PUB-KEY':decode('Certificate', DerCert); decode_cert(DerCert, otp) -> {ok, Cert} = 'OTP-PUB-KEY':decode('OTPCertificate', DerCert), - {ok, decode_all_otp(Cert)}. - -old_decode_cert(DerCert, otp) -> - {ok, Cert} = 'OTP-PUB-KEY':decode('Certificate', DerCert), - {ok, plain_to_otp(Cert)}. - -old_encode_cert(Cert) -> - PlainCert = otp_to_plain(Cert), - {ok, EncCert} = 'OTP-PUB-KEY':encode('Certificate', PlainCert), - list_to_binary(EncCert). - + #'OTPCertificate'{tbsCertificate = TBS} = Cert, + {ok, Cert#'OTPCertificate'{tbsCertificate = decode_tbs(TBS)}}. encode_cert(Cert = #'Certificate'{}) -> {ok, EncCert} = 'OTP-PUB-KEY':encode('Certificate', Cert), list_to_binary(EncCert); -encode_cert(C = #'OTPCertificate'{tbsCertificate = TBS = - #'OTPTBSCertificate'{ - issuer=Issuer0, - subject=Subject0, - subjectPublicKeyInfo=Spki0, - extensions=Exts0} - }) -> - Issuer = transform(Issuer0,encode), - Subject = transform(Subject0,encode), - Spki = encode_supportedPublicKey(Spki0), - Exts = encode_extensions(Exts0), - %% io:format("Extensions ~p~n",[Exts]), - Cert = C#'OTPCertificate'{tbsCertificate= - TBS#'OTPTBSCertificate'{ - issuer=Issuer, subject=Subject, - subjectPublicKeyInfo=Spki, - extensions=Exts}}, +encode_cert(C = #'OTPCertificate'{tbsCertificate = TBS}) -> + Cert = C#'OTPCertificate'{tbsCertificate=encode_tbs(TBS)}, {ok, EncCert} = 'OTP-PUB-KEY':encode('OTPCertificate', Cert), list_to_binary(EncCert). -encode_tbs_cert(TBS = #'OTPTBSCertificate'{ - issuer=Issuer0, - subject=Subject0, - subjectPublicKeyInfo=Spki0, - extensions=Exts0}) -> - Issuer = transform(Issuer0,encode), - Subject = transform(Subject0,encode), - Spki = encode_supportedPublicKey(Spki0), - Exts = encode_extensions(Exts0), - TBSCert = TBS#'OTPTBSCertificate'{issuer=Issuer,subject=Subject, - subjectPublicKeyInfo=Spki,extensions=Exts}, - {ok, EncTBSCert} = 'OTP-PUB-KEY':encode('OTPTBSCertificate', TBSCert), +encode_tbs_cert(TBS) -> + {ok, EncTBSCert} = 'OTP-PUB-KEY':encode('OTPTBSCertificate', encode_tbs(TBS)), list_to_binary(EncTBSCert). %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -decode_all_otp(C = #'OTPCertificate'{tbsCertificate = TBS = - #'OTPTBSCertificate'{ - issuer=Issuer0, - subject=Subject0, - subjectPublicKeyInfo=Spki0, - extensions=Exts0} - }) -> - Issuer = transform(Issuer0,decode), - Subject = transform(Subject0,decode), - Spki = decode_supportedPublicKey(Spki0), - Exts = decode_extensions(Exts0), - %% io:format("Extensions ~p~n",[Exts]), - C#'OTPCertificate'{tbsCertificate= - TBS#'OTPTBSCertificate'{ - issuer=Issuer, subject=Subject, - subjectPublicKeyInfo=Spki,extensions=Exts}}. - - %%% SubjectPublicKey supportedPublicKeyAlgorithms(?'rsaEncryption') -> 'RSAPublicKey'; supportedPublicKeyAlgorithms(?'id-dsa') -> 'DSAPublicKey'; @@ -204,15 +150,35 @@ transform({rdnSequence, SeqList},Func) when is_list(SeqList) -> lists:map(fun(Seq) -> lists:map(fun(Element) -> transform(Element,Func) end, Seq) end, SeqList)}; -%% transform(List = [{rdnSequence, _}|_],Func) -> -%% lists:map(fun(Element) -> transform(Element,Func) end, List); transform(#'NameConstraints'{permittedSubtrees=Permitted, excludedSubtrees=Excluded}, Func) -> - Res = #'NameConstraints'{permittedSubtrees=transform_sub_tree(Permitted,Func), - excludedSubtrees=transform_sub_tree(Excluded,Func)}, -%% io:format("~p~n",[Res]), - Res; + #'NameConstraints'{permittedSubtrees=transform_sub_tree(Permitted,Func), + excludedSubtrees=transform_sub_tree(Excluded,Func)}; + transform(Other,_) -> Other. + +encode_tbs(TBS=#'OTPTBSCertificate'{issuer=Issuer0, + subject=Subject0, + subjectPublicKeyInfo=Spki0, + extensions=Exts0}) -> + Issuer = transform(Issuer0,encode), + Subject = transform(Subject0,encode), + Spki = encode_supportedPublicKey(Spki0), + Exts = encode_extensions(Exts0), + TBS#'OTPTBSCertificate'{issuer=Issuer, subject=Subject, + subjectPublicKeyInfo=Spki,extensions=Exts}. + +decode_tbs(TBS = #'OTPTBSCertificate'{issuer=Issuer0, + subject=Subject0, + subjectPublicKeyInfo=Spki0, + extensions=Exts0}) -> + Issuer = transform(Issuer0,decode), + Subject = transform(Subject0,decode), + Spki = decode_supportedPublicKey(Spki0), + Exts = decode_extensions(Exts0), + TBS#'OTPTBSCertificate'{issuer=Issuer, subject=Subject, + subjectPublicKeyInfo=Spki,extensions=Exts}. + transform_sub_tree(asn1_NOVALUE,_) -> asn1_NOVALUE; transform_sub_tree(TreeList,Func) -> [Tree#'GeneralSubtree'{base=transform(Name,Func)} || @@ -236,303 +202,3 @@ attribute_type(?'id-at-pseudonym') -> 'X520Pseudonym'; attribute_type(?'id-domainComponent') -> 'DomainComponent'; attribute_type(?'id-emailAddress') -> 'EmailAddress'; attribute_type(Type) -> Type. - -%%% Old code transforms - -plain_to_otp(#'Certificate'{tbsCertificate = TBSCert, - signatureAlgorithm = SigAlg, - signature = Signature} = Cert) -> - Cert#'Certificate'{tbsCertificate = plain_to_otp(TBSCert), - signatureAlgorithm = plain_to_otp(SigAlg), - signature = plain_to_otp(Signature)}; - -plain_to_otp(#'TBSCertificate'{signature = Signature, - issuer = Issuer, - subject = Subject, - subjectPublicKeyInfo = SPubKeyInfo, - extensions = Extensions} = TBSCert) -> - - TBSCert#'TBSCertificate'{signature = plain_to_otp(Signature), - issuer = plain_to_otp(Issuer), - subject = - plain_to_otp(Subject), - subjectPublicKeyInfo = - plain_to_otp(SPubKeyInfo), - extensions = - plain_to_otp_extensions(Extensions) - }; - -plain_to_otp(#'AlgorithmIdentifier'{algorithm = Algorithm, - parameters = Params}) -> - SignAlgAny = - #'SignatureAlgorithm-Any'{algorithm = Algorithm, - parameters = Params}, - {ok, AnyEnc} = 'OTP-PUB-KEY':encode('SignatureAlgorithm-Any', - SignAlgAny), - {ok, SignAlg} = 'OTP-PUB-KEY':decode('SignatureAlgorithm', - list_to_binary(AnyEnc)), - SignAlg; - -plain_to_otp({rdnSequence, SeqList}) when is_list(SeqList) -> - {rdnSequence, - lists:map(fun(Seq) -> - lists:map(fun(Element) -> - plain_to_otp(Element) - end, - Seq) - end, SeqList)}; - -plain_to_otp(#'AttributeTypeAndValue'{} = ATAV) -> - {ok, ATAVEnc} = - 'OTP-PUB-KEY':encode('AttributeTypeAndValue', ATAV), - {ok, ATAVDec} = 'OTP-PUB-KEY':decode('OTPAttributeTypeAndValue', - list_to_binary(ATAVEnc)), - #'AttributeTypeAndValue'{type = ATAVDec#'OTPAttributeTypeAndValue'.type, - value = - ATAVDec#'OTPAttributeTypeAndValue'.value}; - -plain_to_otp(#'SubjectPublicKeyInfo'{algorithm = - #'AlgorithmIdentifier'{algorithm - = Algo, - parameters = - Params}, - subjectPublicKey = PublicKey}) -> - - AnyAlgo = #'PublicKeyAlgorithm'{algorithm = Algo, - parameters = Params}, - {0, AnyKey} = PublicKey, - AnyDec = #'OTPSubjectPublicKeyInfo-Any'{algorithm = AnyAlgo, - subjectPublicKey = AnyKey}, - {ok, AnyEnc} = - 'OTP-PUB-KEY':encode('OTPSubjectPublicKeyInfo-Any', AnyDec), - {ok, InfoDec} = 'OTP-PUB-KEY':decode('OTPOLDSubjectPublicKeyInfo', - list_to_binary(AnyEnc)), - - AlgorithmDec = InfoDec#'OTPOLDSubjectPublicKeyInfo'.algorithm, - AlgoDec = AlgorithmDec#'OTPOLDSubjectPublicKeyInfo_algorithm'.algo, - NewParams = AlgorithmDec#'OTPOLDSubjectPublicKeyInfo_algorithm'.parameters, - PublicKeyDec = InfoDec#'OTPOLDSubjectPublicKeyInfo'.subjectPublicKey, - NewAlgorithmDec = - #'SubjectPublicKeyInfoAlgorithm'{algorithm = AlgoDec, - parameters = NewParams}, - #'SubjectPublicKeyInfo'{algorithm = NewAlgorithmDec, - subjectPublicKey = PublicKeyDec - }; - -plain_to_otp(#'Extension'{extnID = ExtID, - critical = Critical, - extnValue = Value}) - when ExtID == ?'id-ce-authorityKeyIdentifier'; - ExtID == ?'id-ce-subjectKeyIdentifier'; - ExtID == ?'id-ce-keyUsage'; - ExtID == ?'id-ce-privateKeyUsagePeriod'; - ExtID == ?'id-ce-certificatePolicies'; - ExtID == ?'id-ce-policyMappings'; - ExtID == ?'id-ce-subjectAltName'; - ExtID == ?'id-ce-issuerAltName'; - ExtID == ?'id-ce-subjectDirectoryAttributes'; - ExtID == ?'id-ce-basicConstraints'; - ExtID == ?'id-ce-nameConstraints'; - ExtID == ?'id-ce-policyConstraints'; - ExtID == ?'id-ce-extKeyUsage'; - ExtID == ?'id-ce-cRLDistributionPoints'; - ExtID == ?'id-ce-inhibitAnyPolicy'; - ExtID == ?'id-ce-freshestCRL' -> - ExtAny = #'Extension-Any'{extnID = ExtID, - critical = Critical, - extnValue = Value}, - {ok, AnyEnc} = 'OTP-PUB-KEY':encode('Extension-Any', ExtAny), - {ok, ExtDec} = 'OTP-PUB-KEY':decode('OTPExtension', - list_to_binary(AnyEnc)), - - ExtValue = plain_to_otp_extension_value(ExtID, - ExtDec#'OTPExtension'.extnValue), - #'Extension'{extnID = ExtID, - critical = ExtDec#'OTPExtension'.critical, - extnValue = ExtValue}; - -plain_to_otp(#'Extension'{} = Ext) -> - Ext; - -plain_to_otp(#'AuthorityKeyIdentifier'{} = Ext) -> - CertIssuer = Ext#'AuthorityKeyIdentifier'.authorityCertIssuer, - Ext#'AuthorityKeyIdentifier'{authorityCertIssuer = - plain_to_otp(CertIssuer)}; - - -plain_to_otp([{directoryName, Value}]) -> - [{directoryName, plain_to_otp(Value)}]; - -plain_to_otp(Value) -> - Value. - -otp_to_plain(#'Certificate'{tbsCertificate = TBSCert, - signatureAlgorithm = SigAlg, - signature = Signature} = Cert) -> - Cert#'Certificate'{tbsCertificate = otp_to_plain(TBSCert), - signatureAlgorithm = - otp_to_plain(SigAlg), - signature = otp_to_plain(Signature)}; - -otp_to_plain(#'TBSCertificate'{signature = Signature, - issuer = Issuer, - subject = Subject, - subjectPublicKeyInfo = SPubKeyInfo, - extensions = Extensions} = TBSCert) -> - - TBSCert#'TBSCertificate'{signature = otp_to_plain(Signature), - issuer = otp_to_plain(Issuer), - subject = - otp_to_plain(Subject), - subjectPublicKeyInfo = - otp_to_plain(SPubKeyInfo), - extensions = otp_to_plain_extensions(Extensions) - }; - -otp_to_plain(#'SignatureAlgorithm'{} = SignAlg) -> - {ok, EncSignAlg} = 'OTP-PUB-KEY':encode('SignatureAlgorithm', SignAlg), - {ok, #'SignatureAlgorithm-Any'{algorithm = Algorithm, - parameters = Params}} = - 'OTP-PUB-KEY':decode('SignatureAlgorithm-Any', - list_to_binary(EncSignAlg)), - #'AlgorithmIdentifier'{algorithm = Algorithm, - parameters = Params}; - -otp_to_plain({rdnSequence, SeqList}) when is_list(SeqList) -> - {rdnSequence, - lists:map(fun(Seq) -> - lists:map(fun(Element) -> - otp_to_plain(Element) - end, - Seq) - end, SeqList)}; - -otp_to_plain(#'AttributeTypeAndValue'{type = Type, value = Value}) -> - {ok, ATAVEnc} = - 'OTP-PUB-KEY':encode('OTPAttributeTypeAndValue', - #'OTPAttributeTypeAndValue'{type = Type, - value = Value}), - {ok, ATAVDec} = 'OTP-PUB-KEY':decode('AttributeTypeAndValue', - list_to_binary(ATAVEnc)), - ATAVDec; - -otp_to_plain(#'SubjectPublicKeyInfo'{algorithm = - #'SubjectPublicKeyInfoAlgorithm'{ - algorithm = Algo, - parameters = - Params}, - subjectPublicKey = PublicKey}) -> - - OtpAlgo = #'OTPOLDSubjectPublicKeyInfo_algorithm'{algo = Algo, - parameters = Params}, - OtpDec = #'OTPOLDSubjectPublicKeyInfo'{algorithm = OtpAlgo, - subjectPublicKey = PublicKey}, - {ok, OtpEnc} = - 'OTP-PUB-KEY':encode('OTPOLDSubjectPublicKeyInfo', OtpDec), - - {ok, AnyDec} = 'OTP-PUB-KEY':decode('OTPSubjectPublicKeyInfo-Any', - list_to_binary(OtpEnc)), - - #'OTPSubjectPublicKeyInfo-Any'{algorithm = #'PublicKeyAlgorithm'{ - algorithm = NewAlgo, - parameters = NewParams}, - subjectPublicKey = Bin} = AnyDec, - - #'SubjectPublicKeyInfo'{algorithm = - #'AlgorithmIdentifier'{ - algorithm = NewAlgo, - parameters = plain_key_params(NewParams)}, - subjectPublicKey = - {0, Bin} - }; - -otp_to_plain(#'Extension'{extnID = ExtID, - extnValue = Value} = Ext) -> - ExtValue = - otp_to_plain_extension_value(ExtID, Value), - - Ext#'Extension'{extnValue = ExtValue}; - -otp_to_plain(#'AuthorityKeyIdentifier'{} = Ext) -> - CertIssuer = Ext#'AuthorityKeyIdentifier'.authorityCertIssuer, - Ext#'AuthorityKeyIdentifier'{authorityCertIssuer = - otp_to_plain(CertIssuer)}; - -otp_to_plain([{directoryName, Value}]) -> - [{directoryName, otp_to_plain(Value)}]; - -otp_to_plain(Value) -> - Value. - -plain_key_params('NULL') -> - <<5,0>>; -plain_key_params(Value) -> - Value. - -plain_to_otp_extension_value(?'id-ce-authorityKeyIdentifier', Value) -> - plain_to_otp(Value); -plain_to_otp_extension_value(_, Value) -> - Value. - -plain_to_otp_extensions(Exts) when is_list(Exts) -> - lists:map(fun(Ext) -> plain_to_otp(Ext) end, Exts). - -otp_to_plain_extension_value(?'id-ce-authorityKeyIdentifier', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('AuthorityKeyIdentifier', - otp_to_plain(Value)), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-subjectKeyIdentifier', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('SubjectKeyIdentifier', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-keyUsage', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('KeyUsage', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-privateKeyUsagePeriod', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('PrivateKeyUsagePeriod', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-certificatePolicies', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('CertificatePolicies', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-policyMappings', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('PolicyMappings', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-subjectAltName', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('SubjectAltName', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-issuerAltName', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('IssuerAltName', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-subjectDirectoryAttributes', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('SubjectDirectoryAttributes', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-basicConstraints', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('BasicConstraints', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-nameConstraints', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('NameConstraints', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-policyConstraints', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('PolicyConstraints', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-extKeyUsage', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('ExtKeyUsage', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-cRLDistributionPoints', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('CRLDistributionPoints', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-inhibitAnyPolicy', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('InhibitAnyPolicy', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-freshestCRL', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('FreshestCRL', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(_Id, Value) -> - Value. - -otp_to_plain_extension_value_format(Value) -> - list_to_binary(Value). - -otp_to_plain_extensions(Exts) when is_list(Exts) -> - lists:map(fun(Ext) -> - otp_to_plain(Ext) - end, Exts). diff --git a/lib/public_key/src/pubkey_pem.erl b/lib/public_key/src/pubkey_pem.erl index 9fc17b6f73..65879f1bbe 100644 --- a/lib/public_key/src/pubkey_pem.erl +++ b/lib/public_key/src/pubkey_pem.erl @@ -124,25 +124,31 @@ decode_file2([L|Rest], RLs, Ens, Tag, Info0) -> decode_file2([], _, Ens, _, _) -> {ok, lists:reverse(Ens)}. -%% TODO Support same as decode_file +%% Support same as decode_file encode_file(Ds) -> lists:map( - fun({cert, Bin}) -> + fun({cert, Bin, not_encrypted}) -> %% PKIX (X.509) ["-----BEGIN CERTIFICATE-----\n", b64encode_and_split(Bin), "-----END CERTIFICATE-----\n\n"]; - ({cert_req, Bin}) -> + ({cert_req, Bin, not_encrypted}) -> %% PKCS#10 ["-----BEGIN CERTIFICATE REQUEST-----\n", b64encode_and_split(Bin), "-----END CERTIFICATE REQUEST-----\n\n"]; - ({rsa_private_key, Bin}) -> + ({rsa_private_key, Bin, not_encrypted}) -> %% PKCS#? ["XXX Following key assumed not encrypted\n", "-----BEGIN RSA PRIVATE KEY-----\n", b64encode_and_split(Bin), - "-----END RSA PRIVATE KEY-----\n\n"] + "-----END RSA PRIVATE KEY-----\n\n"]; + ({dsa_private_key, Bin, not_encrypted}) -> + %% PKCS#? + ["XXX Following key assumed not encrypted\n", + "-----BEGIN DSA PRIVATE KEY-----\n", + b64encode_and_split(Bin), + "-----END DSA PRIVATE KEY-----\n\n"] end, Ds). dek_info(Line0, Info) -> diff --git a/lib/public_key/src/public_key.appup.src b/lib/public_key/src/public_key.appup.src index 46e5ecca33..2eb5750923 100644 --- a/lib/public_key/src/public_key.appup.src +++ b/lib/public_key/src/public_key.appup.src @@ -1,39 +1,43 @@ %% -*- erlang -*- {"%VSN%", [ - {"0.5", + {"0.6", [ + {update, 'OTP-PUB-KEY', soft, soft_purge, soft_purge, []}, {update, public_key, soft, soft_purge, soft_purge, []}, - {update, pubkey_crypto, soft, soft_purge, soft_purge, []}, {update, pubkey_pem, soft, soft_purge, soft_purge, []}, + {update, pubkey_cert_records, soft, soft_purge, soft_purge, []} {update, pubkey_cert, soft, soft_purge, soft_purge, []} ] }, - {"0.4", + {"0.5", [ + {update, 'OTP-PUB-KEY', soft, soft_purge, soft_purge, []}, {update, public_key, soft, soft_purge, soft_purge, []}, - {update, pubkey_cert_records, soft, soft_purge, soft_purge, []}, {update, pubkey_crypto, soft, soft_purge, soft_purge, []}, {update, pubkey_pem, soft, soft_purge, soft_purge, []}, + {update, pubkey_cert_records, soft, soft_purge, soft_purge, []}, {update, pubkey_cert, soft, soft_purge, soft_purge, []} - ] + ] } ], [ - {"0.5", + {"0.6", [ + {update, 'OTP-PUB-KEY', soft, soft_purge, soft_purge, []}, {update, public_key, soft, soft_purge, soft_purge, []}, - {update, pubkey_crypto, soft, soft_purge, soft_purge, []}, {update, pubkey_pem, soft, soft_purge, soft_purge, []}, + {update, pubkey_cert_records, soft, soft_purge, soft_purge, []} {update, pubkey_cert, soft, soft_purge, soft_purge, []} ] }, - {"0.4", + {"0.5", [ + {update, 'OTP-PUB-KEY', soft, soft_purge, soft_purge, []}, {update, public_key, soft, soft_purge, soft_purge, []}, - {update, pubkey_cert_records, soft, soft_purge, soft_purge, []}, {update, pubkey_crypto, soft, soft_purge, soft_purge, []}, {update, pubkey_pem, soft, soft_purge, soft_purge, []}, + {update, pubkey_cert_records, soft, soft_purge, soft_purge, []}, {update, pubkey_cert, soft, soft_purge, soft_purge, []} ] } diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl index 157e76bb21..d1d45f21a0 100644 --- a/lib/public_key/src/public_key.erl +++ b/lib/public_key/src/public_key.erl @@ -113,13 +113,13 @@ decrypt_public(CipherText, Key, Options) -> encrypt_public(PlainText, Key) -> encrypt_public(PlainText, Key, []). encrypt_public(PlainText, Key, Options) -> - Padding = proplists:get_value(rsa_pad, Options, rsa_pkcs1_oaep_padding), + Padding = proplists:get_value(rsa_pad, Options, rsa_pkcs1_padding), pubkey_crypto:encrypt_public(PlainText, Key, Padding). encrypt_private(PlainText, Key) -> encrypt_private(PlainText, Key, []). encrypt_private(PlainText, Key, Options) -> - Padding = proplists:get_value(rsa_pad, Options, rsa_pkcs1_oaep_padding), + Padding = proplists:get_value(rsa_pad, Options, rsa_pkcs1_padding), pubkey_crypto:encrypt_private(PlainText, Key, Padding). %%-------------------------------------------------------------------- diff --git a/lib/public_key/test/Makefile b/lib/public_key/test/Makefile index c7215020c7..5544339ff2 100644 --- a/lib/public_key/test/Makefile +++ b/lib/public_key/test/Makefile @@ -28,6 +28,7 @@ INCLUDES= -I. -I ../include # ---------------------------------------------------- MODULES= \ + pkey_test \ public_key_SUITE \ pkits_SUITE @@ -40,6 +41,9 @@ TARGET_FILES= \ SPEC_FILES = public_key.spec +COVER_FILE = public_key.cover + + # ---------------------------------------------------- # Release directory specification # ---------------------------------------------------- @@ -74,7 +78,7 @@ release_spec: opt release_tests_spec: opt $(INSTALL_DIR) $(RELSYSDIR) - $(INSTALL_DATA) $(SPEC_FILES) $(ERL_FILES) $(HRL_FILES)$(RELSYSDIR) + $(INSTALL_DATA) $(SPEC_FILES) $(ERL_FILES) $(COVER_FILE) $(HRL_FILES) $(RELSYSDIR) $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR) chmod -f -R u+w $(RELSYSDIR) @tar cf - *_SUITE_data | (cd $(RELSYSDIR); tar xf -) diff --git a/lib/public_key/test/pkey_test.erl b/lib/public_key/test/pkey_test.erl new file mode 100644 index 0000000000..4cf20f0174 --- /dev/null +++ b/lib/public_key/test/pkey_test.erl @@ -0,0 +1,412 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% Create test certificates + +-module(pkey_test). +-include_lib("public_key/include/public_key.hrl"). + +-export([make_cert/1, gen_rsa/1, verify_signature/3, write_pem/3]). +-compile(export_all). + +%%-------------------------------------------------------------------- +%% @doc Create and return a der encoded certificate +%% Option Default +%% ------------------------------------------------------- +%% digest sha1 +%% validity {date(), date() + week()} +%% version 3 +%% subject [] list of the following content +%% {name, Name} +%% {email, Email} +%% {city, City} +%% {state, State} +%% {org, Org} +%% {org_unit, OrgUnit} +%% {country, Country} +%% {serial, Serial} +%% {title, Title} +%% {dnQualifer, DnQ} +%% issuer = {Issuer, IssuerKey} true (i.e. a ca cert is created) +%% (obs IssuerKey migth be {Key, Password} +%% key = KeyFile|KeyBin|rsa|dsa Subject PublicKey rsa or dsa generates key +%% +%% +%% (OBS: The generated keys are for testing only) +%% @spec ([{::atom(), ::term()}]) -> {Cert::binary(), Key::binary()} +%% @end +%%-------------------------------------------------------------------- + +make_cert(Opts) -> + SubjectPrivateKey = get_key(Opts), + {TBSCert, IssuerKey} = make_tbs(SubjectPrivateKey, Opts), + Cert = public_key:sign(TBSCert, IssuerKey), + true = verify_signature(Cert, IssuerKey, undef), %% verify that the keys where ok + {Cert, encode_key(SubjectPrivateKey)}. + +%%-------------------------------------------------------------------- +%% @doc Writes pem files in Dir with FileName ++ ".pem" and FileName ++ "_key.pem" +%% @spec (::string(), ::string(), {Cert,Key}) -> ok +%% @end +%%-------------------------------------------------------------------- +write_pem(Dir, FileName, {Cert, Key = {_,_,not_encrypted}}) when is_binary(Cert) -> + ok = public_key:der_to_pem(filename:join(Dir, FileName ++ ".pem"), [{cert, Cert, not_encrypted}]), + ok = public_key:der_to_pem(filename:join(Dir, FileName ++ "_key.pem"), [Key]). + +%%-------------------------------------------------------------------- +%% @doc Creates a rsa key (OBS: for testing only) +%% the size are in bytes +%% @spec (::integer()) -> {::atom(), ::binary(), ::opaque()} +%% @end +%%-------------------------------------------------------------------- +gen_rsa(Size) when is_integer(Size) -> + Key = gen_rsa2(Size), + {Key, encode_key(Key)}. + +%%-------------------------------------------------------------------- +%% @doc Creates a dsa key (OBS: for testing only) +%% the sizes are in bytes +%% @spec (::integer()) -> {::atom(), ::binary(), ::opaque()} +%% @end +%%-------------------------------------------------------------------- +gen_dsa(LSize,NSize) when is_integer(LSize), is_integer(NSize) -> + Key = gen_dsa2(LSize, NSize), + {Key, encode_key(Key)}. + +%%-------------------------------------------------------------------- +%% @doc Verifies cert signatures +%% @spec (::binary(), ::tuple()) -> ::boolean() +%% @end +%%-------------------------------------------------------------------- +verify_signature(DerEncodedCert, DerKey, KeyParams) -> + Key = decode_key(DerKey), + case Key of + #'RSAPrivateKey'{modulus=Mod, publicExponent=Exp} -> + public_key:verify_signature(DerEncodedCert, + #'RSAPublicKey'{modulus=Mod, publicExponent=Exp}, + 'NULL'); + #'DSAPrivateKey'{p=P, q=Q, g=G, y=Y} -> + public_key:verify_signature(DerEncodedCert, Y, #'Dss-Parms'{p=P, q=Q, g=G}); + + _ -> + public_key:verify_signature(DerEncodedCert, Key, KeyParams) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%% Implementation %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +get_key(Opts) -> + case proplists:get_value(key, Opts) of + undefined -> make_key(rsa, Opts); + rsa -> make_key(rsa, Opts); + dsa -> make_key(dsa, Opts); + Key -> + Password = proplists:get_value(password, Opts, no_passwd), + decode_key(Key, Password) + end. + +decode_key({Key, Pw}) -> + decode_key(Key, Pw); +decode_key(Key) -> + decode_key(Key, no_passwd). + + +decode_key(#'RSAPublicKey'{} = Key,_) -> + Key; +decode_key(#'RSAPrivateKey'{} = Key,_) -> + Key; +decode_key(#'DSAPrivateKey'{} = Key,_) -> + Key; +decode_key(Der = {_,_,_}, Pw) -> + {ok, Key} = public_key:decode_private_key(Der, Pw), + Key; +decode_key(FileOrDer, Pw) -> + {ok, [KeyInfo]} = public_key:pem_to_der(FileOrDer), + decode_key(KeyInfo, Pw). + +encode_key(Key = #'RSAPrivateKey'{}) -> + {ok, Der} = 'OTP-PUB-KEY':encode('RSAPrivateKey', Key), + {rsa_private_key, list_to_binary(Der), not_encrypted}; +encode_key(Key = #'DSAPrivateKey'{}) -> + {ok, Der} = 'OTP-PUB-KEY':encode('DSAPrivateKey', Key), + {dsa_private_key, list_to_binary(Der), not_encrypted}. + +make_tbs(SubjectKey, Opts) -> + Version = list_to_atom("v"++integer_to_list(proplists:get_value(version, Opts, 3))), + {Issuer, IssuerKey} = issuer(Opts, SubjectKey), + + {Algo, Parameters} = sign_algorithm(IssuerKey, Opts), + + SignAlgo = #'SignatureAlgorithm'{algorithm = Algo, + parameters = Parameters}, + + {#'OTPTBSCertificate'{serialNumber = trunc(random:uniform()*100000000)*10000 + 1, + signature = SignAlgo, + issuer = Issuer, + validity = validity(Opts), + subject = subject(proplists:get_value(subject, Opts),false), + subjectPublicKeyInfo = publickey(SubjectKey), + version = Version, + extensions = extensions(Opts) + }, IssuerKey}. + +issuer(Opts, SubjectKey) -> + IssuerProp = proplists:get_value(issuer, Opts, true), + case IssuerProp of + true -> %% Self signed + {subject(proplists:get_value(subject, Opts), true), SubjectKey}; + {Issuer, IssuerKey} when is_binary(Issuer) -> + {issuer_der(Issuer), decode_key(IssuerKey)}; + {File, IssuerKey} when is_list(File) -> + {ok, [{cert, Cert, _}|_]} = public_key:pem_to_der(File), + {issuer_der(Cert), decode_key(IssuerKey)} + end. + +issuer_der(Issuer) -> + {ok, Decoded} = public_key:pkix_decode_cert(Issuer, otp), + #'OTPCertificate'{tbsCertificate=Tbs} = Decoded, + #'OTPTBSCertificate'{subject=Subject} = Tbs, + Subject. + +subject(undefined, IsCA) -> + User = if IsCA -> "CA"; true -> os:getenv("USER") end, + Opts = [{email, User ++ "@erlang.org"}, + {name, User}, + {city, "Stockholm"}, + {country, "SE"}, + {org, "erlang"}, + {org_unit, "testing dep"}], + subject(Opts); +subject(Opts, _) -> + subject(Opts). + +subject(SubjectOpts) when is_list(SubjectOpts) -> + Encode = fun(Opt) -> + {Type,Value} = subject_enc(Opt), + [#'AttributeTypeAndValue'{type=Type, value=Value}] + end, + {rdnSequence, [Encode(Opt) || Opt <- SubjectOpts]}. + +%% Fill in the blanks +subject_enc({name, Name}) -> {?'id-at-commonName', {printableString, Name}}; +subject_enc({email, Email}) -> {?'id-emailAddress', Email}; +subject_enc({city, City}) -> {?'id-at-localityName', {printableString, City}}; +subject_enc({state, State}) -> {?'id-at-stateOrProvinceName', {printableString, State}}; +subject_enc({org, Org}) -> {?'id-at-organizationName', {printableString, Org}}; +subject_enc({org_unit, OrgUnit}) -> {?'id-at-organizationalUnitName', {printableString, OrgUnit}}; +subject_enc({country, Country}) -> {?'id-at-countryName', Country}; +subject_enc({serial, Serial}) -> {?'id-at-serialNumber', Serial}; +subject_enc({title, Title}) -> {?'id-at-title', {printableString, Title}}; +subject_enc({dnQualifer, DnQ}) -> {?'id-at-dnQualifier', DnQ}; +subject_enc(Other) -> Other. + + +extensions(Opts) -> + case proplists:get_value(extensions, Opts, []) of + false -> + asn1_NOVALUE; + Exts -> + lists:flatten([extension(Ext) || Ext <- default_extensions(Exts)]) + end. + +default_extensions(Exts) -> + Def = [{key_usage,undefined}, + {subject_altname, undefined}, + {issuer_altname, undefined}, + {basic_constraints, default}, + {name_constraints, undefined}, + {policy_constraints, undefined}, + {ext_key_usage, undefined}, + {inhibit_any, undefined}, + {auth_key_id, undefined}, + {subject_key_id, undefined}, + {policy_mapping, undefined}], + Filter = fun({Key, _}, D) -> lists:keydelete(Key, 1, D) end, + Exts ++ lists:foldl(Filter, Def, Exts). + +extension({_, undefined}) -> []; +extension({basic_constraints, Data}) -> + case Data of + default -> + #'Extension'{extnID = ?'id-ce-basicConstraints', + extnValue = #'BasicConstraints'{cA=true}, + critical=true}; + false -> + []; + Len when is_integer(Len) -> + #'Extension'{extnID = ?'id-ce-basicConstraints', + extnValue = #'BasicConstraints'{cA=true, pathLenConstraint=Len}, + critical=true}; + _ -> + #'Extension'{extnID = ?'id-ce-basicConstraints', + extnValue = Data} + end; +extension({Id, Data, Critical}) -> + #'Extension'{extnID = Id, extnValue = Data, critical = Critical}. + + +publickey(#'RSAPrivateKey'{modulus=N, publicExponent=E}) -> + Public = #'RSAPublicKey'{modulus=N, publicExponent=E}, + Algo = #'PublicKeyAlgorithm'{algorithm= ?rsaEncryption, parameters='NULL'}, + #'OTPSubjectPublicKeyInfo'{algorithm = Algo, + subjectPublicKey = Public}; +publickey(#'DSAPrivateKey'{p=P, q=Q, g=G, y=Y}) -> + Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-dsa', + parameters=#'Dss-Parms'{p=P, q=Q, g=G}}, + #'OTPSubjectPublicKeyInfo'{algorithm = Algo, subjectPublicKey = Y}. + +validity(Opts) -> + DefFrom0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())-1), + DefTo0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())+7), + {DefFrom, DefTo} = proplists:get_value(validity, Opts, {DefFrom0, DefTo0}), + Format = fun({Y,M,D}) -> lists:flatten(io_lib:format("~w~2..0w~2..0w000000Z",[Y,M,D])) end, + #'Validity'{notBefore={generalTime, Format(DefFrom)}, + notAfter ={generalTime, Format(DefTo)}}. + +sign_algorithm(#'RSAPrivateKey'{}, Opts) -> + Type = case proplists:get_value(digest, Opts, sha1) of + sha1 -> ?'sha1WithRSAEncryption'; + sha512 -> ?'sha512WithRSAEncryption'; + sha384 -> ?'sha384WithRSAEncryption'; + sha256 -> ?'sha256WithRSAEncryption'; + md5 -> ?'md5WithRSAEncryption'; + md2 -> ?'md2WithRSAEncryption' + end, + {Type, 'NULL'}; +sign_algorithm(#'DSAPrivateKey'{p=P, q=Q, g=G}, _Opts) -> + {?'id-dsa-with-sha1', #'Dss-Parms'{p=P, q=Q, g=G}}. + +make_key(rsa, _Opts) -> + %% (OBS: for testing only) + gen_rsa2(64); +make_key(dsa, _Opts) -> + gen_dsa2(128, 20). %% Bytes i.e. {1024, 160} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% RSA key generation (OBS: for testing only) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-define(SMALL_PRIMES, [65537,97,89,83,79,73,71,67,61,59,53, + 47,43,41,37,31,29,23,19,17,13,11,7,5,3]). + +gen_rsa2(Size) -> + P = prime(Size), + Q = prime(Size), + N = P*Q, + Tot = (P - 1) * (Q - 1), + [E|_] = lists:dropwhile(fun(Candidate) -> (Tot rem Candidate) == 0 end, ?SMALL_PRIMES), + {D1,D2} = extended_gcd(E, Tot), + D = erlang:max(D1,D2), + case D < E of + true -> + gen_rsa2(Size); + false -> + {Co1,Co2} = extended_gcd(Q, P), + Co = erlang:max(Co1,Co2), + #'RSAPrivateKey'{version = 'two-prime', + modulus = N, + publicExponent = E, + privateExponent = D, + prime1 = P, + prime2 = Q, + exponent1 = D rem (P-1), + exponent2 = D rem (Q-1), + coefficient = Co + } + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% DSA key generation (OBS: for testing only) +%% See http://en.wikipedia.org/wiki/Digital_Signature_Algorithm +%% and the fips_186-3.pdf +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +gen_dsa2(LSize, NSize) -> + Q = prime(NSize), %% Choose N-bit prime Q + X0 = prime(LSize), + P0 = prime((LSize div 2) +1), + + %% Choose L-bit prime modulus P such that p–1 is a multiple of q. + case dsa_search(X0 div (2*Q*P0), P0, Q, 1000) of + error -> + gen_dsa2(LSize, NSize); + P -> + G = crypto:mod_exp(2, (P-1) div Q, P), % Choose G a number whose multiplicative order modulo p is q. + %% such that This may be done by setting g = h^(p–1)/q mod p, commonly h=2 is used. + + X = prime(20), %% Choose x by some random method, where 0 < x < q. + Y = crypto:mod_exp(G, X, P), %% Calculate y = g^x mod p. + + #'DSAPrivateKey'{version=0, p=P, q=Q, g=G, y=Y, x=X} + end. + +%% See fips_186-3.pdf +dsa_search(T, P0, Q, Iter) when Iter > 0 -> + P = 2*T*Q*P0 + 1, + case is_prime(crypto:mpint(P), 50) of + true -> P; + false -> dsa_search(T+1, P0, Q, Iter-1) + end; +dsa_search(_,_,_,_) -> + error. + + +%%%%%%% Crypto Math %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +prime(ByteSize) -> + Rand = odd_rand(ByteSize), + crypto:erlint(prime_odd(Rand, 0)). + +prime_odd(Rand, N) -> + case is_prime(Rand, 50) of + true -> + Rand; + false -> + NotPrime = crypto:erlint(Rand), + prime_odd(crypto:mpint(NotPrime+2), N+1) + end. + +%% see http://en.wikipedia.org/wiki/Fermat_primality_test +is_prime(_, 0) -> true; +is_prime(Candidate, Test) -> + CoPrime = odd_rand(<<0,0,0,4, 10000:32>>, Candidate), + case crypto:mod_exp(CoPrime, Candidate, Candidate) of + CoPrime -> is_prime(Candidate, Test-1); + _ -> false + end. + +odd_rand(Size) -> + Min = 1 bsl (Size*8-1), + Max = (1 bsl (Size*8))-1, + odd_rand(crypto:mpint(Min), crypto:mpint(Max)). + +odd_rand(Min,Max) -> + Rand = <<Sz:32, _/binary>> = crypto:rand_uniform(Min,Max), + BitSkip = (Sz+4)*8-1, + case Rand of + Odd = <<_:BitSkip, 1:1>> -> Odd; + Even = <<_:BitSkip, 0:1>> -> + crypto:mpint(crypto:erlint(Even)+1) + end. + +extended_gcd(A, B) -> + case A rem B of + 0 -> + {0, 1}; + N -> + {X, Y} = extended_gcd(B, N), + {Y, X-Y*(A div B)} + end. diff --git a/lib/public_key/test/public_key.cover b/lib/public_key/test/public_key.cover new file mode 100644 index 0000000000..8477c76ef6 --- /dev/null +++ b/lib/public_key/test/public_key.cover @@ -0,0 +1,2 @@ + +{exclude, ['OTP-PUB-KEY']}.
\ No newline at end of file diff --git a/lib/public_key/test/public_key_SUITE.erl b/lib/public_key/test/public_key_SUITE.erl index 8cc36e490d..dc1015969a 100644 --- a/lib/public_key/test/public_key_SUITE.erl +++ b/lib/public_key/test/public_key_SUITE.erl @@ -101,14 +101,13 @@ all(doc) -> all(suite) -> [app, + dh, pem_to_der, - decode_private_key -%% encrypt_decrypt, -%% rsa_verify -%% dsa_verify_sign, -%% pkix_encode_decode, -%% pkix_verify_sign, -%% pkix_path_validation + decode_private_key, + encrypt_decrypt, + sign_verify, + pkix, + pkix_path_validation ]. %% Test cases starts here. @@ -118,20 +117,35 @@ app(doc) -> "Test that the public_key app file is ok"; app(suite) -> []; -app(Config) when list(Config) -> +app(Config) when is_list(Config) -> ok = test_server:app_test(public_key). +dh(doc) -> + "Test diffie-hellman functions file is ok"; +dh(suite) -> + []; +dh(Config) when is_list(Config) -> + Datadir = ?config(data_dir, Config), + {ok,[DerDHparams = {dh_params, _, _}]} = + public_key:pem_to_der(filename:join(Datadir, "dh.pem")), + {ok, DHps = #'DHParameter'{prime=P,base=G}} = public_key:decode_dhparams(DerDHparams), + DHKeys = {Private,_Public} = public_key:gen_key(DHps), + test_server:format("DHparams = ~p~nDH Keys~p~n", [DHps, DHKeys]), + {_Private,_Public2} = pubkey_crypto:gen_key(diffie_hellman, [crypto:erlint(Private), P, G]), + ok. + + pem_to_der(doc) -> ["Check that supported PEM files are decoded into the expected entry type"]; pem_to_der(suite) -> []; pem_to_der(Config) when is_list(Config) -> Datadir = ?config(data_dir, Config), - {ok,[{dsa_private_key, _, not_encrypted}]} = + {ok,DSAKey =[{dsa_private_key, _, not_encrypted}]} = public_key:pem_to_der(filename:join(Datadir, "dsa.pem")), {ok,[{rsa_private_key, _, _}]} = public_key:pem_to_der(filename:join(Datadir, "client_key.pem")), - {ok,[{rsa_private_key, _, _}]} = + {ok, [{rsa_private_key, _, _}]} = public_key:pem_to_der(filename:join(Datadir, "rsa.pem")), {ok,[{rsa_private_key, _, _}]} = public_key:pem_to_der(filename:join(Datadir, "rsa.pem"), "abcd1234"), @@ -144,12 +158,18 @@ pem_to_der(Config) when is_list(Config) -> public_key:pem_to_der(filename:join(Datadir, "client_cert.pem")), {ok,[{cert_req, _, _}]} = public_key:pem_to_der(filename:join(Datadir, "req.pem")), - {ok,[{cert, _, _}, {cert, _, _}]} = + {ok, Certs = [{cert, _, _}, {cert, _, _}]} = public_key:pem_to_der(filename:join(Datadir, "cacerts.pem")), - {ok, Bin1} = file:read_file(filename:join(Datadir, "cacerts.pem")), + {ok, Bin1} = file:read_file(filename:join(Datadir, "cacerts.pem")), {ok, [{cert, _, _}, {cert, _, _}]} = public_key:pem_to_der(Bin1), + + ok = public_key:der_to_pem(filename:join(Datadir, "wcacerts.pem"), Certs), + ok = public_key:der_to_pem(filename:join(Datadir, "wdsa.pem"), DSAKey), + {ok, Certs} = public_key:pem_to_der(filename:join(Datadir, "wcacerts.pem")), + {ok, DSAKey} = public_key:pem_to_der(filename:join(Datadir, "wdsa.pem")), + ok. %%-------------------------------------------------------------------- decode_private_key(doc) -> @@ -178,84 +198,148 @@ encrypt_decrypt(doc) -> encrypt_decrypt(suite) -> []; encrypt_decrypt(Config) when is_list(Config) -> - RSAPrivateKey = #'RSAPrivateKey'{publicExponent = 17, - modulus = 3233, - privateExponent = 2753, - prime1 = 61, - prime2 = 53, - version = 'two-prime'}, - Msg = <<0,123>>, - {ok, Encrypted} = public_key:encrypt(Msg, RSAPrivateKey, [{block_type, 2}]), - test_server:format("Expected 855, Encrypted ~p ~n", [Encrypted]), + {PrivateKey, _DerKey} = pkey_test:gen_rsa(64), + #'RSAPrivateKey'{modulus=Mod, publicExponent=Exp} = PrivateKey, + PublicKey = #'RSAPublicKey'{modulus=Mod, publicExponent=Exp}, + Msg = list_to_binary(lists:duplicate(5, "Foo bar 100")), + RsaEncrypted = public_key:encrypt_private(Msg, PrivateKey), + Msg = public_key:decrypt_public(RsaEncrypted, PublicKey), + Msg = public_key:decrypt_public(RsaEncrypted, PrivateKey), + RsaEncrypted2 = public_key:encrypt_public(Msg, PublicKey), + RsaEncrypted3 = public_key:encrypt_public(Msg, PrivateKey), + Msg = public_key:decrypt_private(RsaEncrypted2, PrivateKey), + Msg = public_key:decrypt_private(RsaEncrypted3, PrivateKey), + ok. + +%%-------------------------------------------------------------------- +sign_verify(doc) -> + ["Checks that we can sign and verify signatures."]; +sign_verify(suite) -> + []; +sign_verify(Config) when is_list(Config) -> + %% Make cert signs and validates the signature using RSA and DSA + Ca = {_, CaKey} = pkey_test:make_cert([]), + {ok, PrivateRSA = #'RSAPrivateKey'{modulus=Mod, publicExponent=Exp}} = + public_key:decode_private_key(CaKey), + + CertInfo = {Cert1,CertKey1} = pkey_test:make_cert([{key, dsa}, {issuer, Ca}]), + + PublicRSA = #'RSAPublicKey'{modulus=Mod, publicExponent=Exp}, + true = public_key:verify_signature(Cert1, PublicRSA, undefined), + + {Cert2,_CertKey} = pkey_test:make_cert([{issuer, CertInfo}]), + + {ok, #'DSAPrivateKey'{p=P, q=Q, g=G, y=Y, x=_X}} = + public_key:decode_private_key(CertKey1), + true = public_key:verify_signature(Cert2, Y, #'Dss-Parms'{p=P, q=Q, g=G}), + %% RSA sign + Msg0 = lists:duplicate(5, "Foo bar 100"), + Msg = list_to_binary(Msg0), + RSASign = public_key:sign(sha, Msg0, PrivateRSA), + RSASign = public_key:sign(Msg, PrivateRSA), + true = public_key:verify_signature(Msg, sha, RSASign, PublicRSA), + false = public_key:verify_signature(<<1:8, Msg/binary>>, sha, RSASign, PublicRSA), + false = public_key:verify_signature(Msg, sha, <<1:8, RSASign/binary>>, PublicRSA), + RSASign = public_key:sign(sha, Msg, PrivateRSA), + RSASign1 = public_key:sign(md5, Msg, PrivateRSA), + true = public_key:verify_signature(Msg, md5, RSASign1, PublicRSA), + + %% DSA sign + Datadir = ?config(data_dir, Config), + {ok,[DsaKey = {dsa_private_key, _, _}]} = + public_key:pem_to_der(filename:join(Datadir, "dsa.pem")), + {ok, DSAPrivateKey} = public_key:decode_private_key(DsaKey), + #'DSAPrivateKey'{p=P1, q=Q1, g=G1, y=Y1, x=_X1} = DSAPrivateKey, + DSASign = public_key:sign(Msg, DSAPrivateKey), + DSAPublicKey = Y1, + DSAParams = #'Dss-Parms'{p=P1, q=Q1, g=G1}, + true = public_key:verify_signature(Msg, sha, DSASign, DSAPublicKey, DSAParams), + false = public_key:verify_signature(<<1:8, Msg/binary>>, sha, DSASign, DSAPublicKey, DSAParams), + false = public_key:verify_signature(Msg, sha, <<1:8, DSASign/binary>>, DSAPublicKey, DSAParams), + + ok. +pkix(doc) -> + "Misc pkix tests not covered elsewhere"; +pkix(suite) -> + []; +pkix(Config) when is_list(Config) -> + Datadir = ?config(data_dir, Config), + {ok,Certs0} = public_key:pem_to_der(filename:join(Datadir, "cacerts.pem")), + {ok,Certs1} = public_key:pem_to_der(filename:join(Datadir, "client_cert.pem")), + TestTransform = fun({cert, CertDer, not_encrypted}) -> + {ok, PlainCert} = public_key:pkix_decode_cert(CertDer, plain), + {ok, OtpCert} = public_key:pkix_decode_cert(CertDer, otp), + CertDer = public_key:pkix_encode_cert(OtpCert), + CertDer = public_key:pkix_encode_cert(PlainCert), + OTPSubj = (OtpCert#'OTPCertificate'.tbsCertificate)#'OTPTBSCertificate'.subject, + Subj = public_key:pkix_transform(OTPSubj, encode), + {ok, DNEncoded} = 'OTP-PUB-KEY':encode('Name', Subj), + Subj2 = (PlainCert#'Certificate'.tbsCertificate)#'TBSCertificate'.subject, + {ok, DNEncoded} = 'OTP-PUB-KEY':encode('Name', Subj2), + OTPSubj = public_key:pkix_transform(Subj2, decode), + false = public_key:pkix_is_fixed_dh_cert(CertDer) + end, + [TestTransform(Cert) || Cert <- Certs0 ++ Certs1], + true = public_key:pkix_is_self_signed(element(2,hd(Certs0))), + false = public_key:pkix_is_self_signed(element(2,hd(Certs1))), + CaIds = [element(2, public_key:pkix_issuer_id(Cert, self)) || {cert, Cert, _} <- Certs0], + {ok, IssuerId = {_, IssuerName}} = public_key:pkix_issuer_id(element(2,hd(Certs1)), other), + true = lists:member(IssuerId, CaIds), + %% Should be normalized allready + TestStr = {rdnSequence, [[{'AttributeTypeAndValue', {2,5,4,3},{printableString,"ERLANGCA"}}], + [{'AttributeTypeAndValue', {2,5,4,3},{printableString," erlang ca "}}]]}, + VerifyStr = {rdnSequence, [[{'AttributeTypeAndValue', {2,5,4,3},{printableString,"erlang ca"}}], + [{'AttributeTypeAndValue', {2,5,4,3},{printableString,"erlangca"}}]]}, + VerifyStr = public_key:pkix_normalize_general_name(TestStr), -%% Datadir = ?config(data_dir, Config), -%% {ok,[{rsa_private_key, EncKey}]} = -%% public_key:pem_to_der(filename:join(Datadir, "server_key.pem")), -%% {ok, Key} = public_key:decode_private_key(EncKey, rsa), -%% RSAPublicKey = #'RSAPublicKey'{publicExponent = -%% Key#'RSAPrivateKey'.publicExponent, -%% modulus = Key#'RSAPrivateKey'.modulus}, -%% {ok, Msg} = file:read_file(filename:join(Datadir, "msg.txt")), -%% Hash = crypto:sha(Msg), -%% {ok, Encrypted} = public_key:encrypt(Hash, Key, [{block_type, 2}]), -%% test_server:format("Encrypted ~p", [Encrypted]), -%% {ok, Decrypted} = public_key:decrypt(Encrypted, -%% RSAPublicKey, [{block_type, 1}]), -%% test_server:format("Encrypted ~p", [Decrypted]), -%% true = Encrypted == Decrypted. - -%%-------------------------------------------------------------------- -rsa_verify(doc) -> - ["Cheks that we can verify an rsa signature."]; -rsa_verify(suite) -> + ok. + +pkix_path_validation(doc) -> + "Misc pkix tests not covered elsewhere"; +pkix_path_validation(suite) -> []; -rsa_verify(Config) when is_list(Config) -> - Datadir = ?config(data_dir, Config), +pkix_path_validation(Config) when is_list(Config) -> + CaK = {Trusted,_} = + pkey_test:make_cert([{key, dsa}, + {subject, [ + {name, "Public Key"}, + {?'id-at-name', {printableString, "public_key"}}, + {?'id-at-pseudonym', {printableString, "pubkey"}}, + {city, "Stockholm"}, + {country, "SE"}, + {org, "erlang"}, + {org_unit, "testing dep"} + ]} + ]), + ok = pkey_test:write_pem("./", "public_key_cacert", CaK), + + CertK1 = {Cert1, _} = pkey_test:make_cert([{issuer, CaK}]), + CertK2 = {Cert2,_} = pkey_test:make_cert([{issuer, CertK1}, {digest, md5}, {extensions, false}]), + ok = pkey_test:write_pem("./", "public_key_cert", CertK2), - {ok,[{cert, DerCert}]} = - public_key:pem_to_der(filename:join(Datadir, "server_cert.pem")), + {ok, _} = public_key:pkix_path_validation(Trusted, [Cert1], []), - {ok, OTPCert} = public_key:pkix_decode_cert(DerCert, otp), + {error, {bad_cert,invalid_issuer}} = public_key:pkix_path_validation(Trusted, [Cert2], []), + %%{error, {bad_cert,invalid_issuer}} = public_key:pkix_path_validation(Trusted, [Cert2], [{verify,false}]), - {0, Signature} = OTPCert#'Certificate'.signature, - TBSCert = OTPCert#'Certificate'.tbsCertificate, + {ok, _} = public_key:pkix_path_validation(Trusted, [Cert1, Cert2], []), + {error, issuer_not_found} = public_key:pkix_issuer_id(Cert2, other), - #'TBSCertificate'{subjectPublicKeyInfo = Info} = TBSCert, - - #'SubjectPublicKeyInfo'{subjectPublicKey = RSAPublicKey} = Info, + CertK3 = {Cert3,_} = pkey_test:make_cert([{issuer, CertK1}, {extensions, [{basic_constraints, false}]}]), + {Cert4,_} = pkey_test:make_cert([{issuer, CertK3}]), + {error, E={bad_cert,missing_basic_constraint}} = + public_key:pkix_path_validation(Trusted, [Cert1, Cert3,Cert4], []), - EncTBSCert = encoded_tbs_cert(DerCert), - Digest = crypto:sha(EncTBSCert), - - public_key:verify_signature(Digest, Signature, RSAPublicKey). - - -%% Signature is generated in the following way (in datadir): -%% openssl dgst -sha1 -binary -out rsa_signature -sign server_key.pem msg.txt -%%{ok, Signature} = file:read_file(filename:join(Datadir, "rsa_signature")), -%%{ok, Signature} = file:read_file(filename:join(Datadir, "rsa_signature")), -%% {ok, Msg} = file:read_file(filename:join(Datadir, "msg.txt")), -%% Digest = crypto:sha(Msg), -%% {ok,[{rsa_private_key, EncKey}]} = -%% public_key:pem_to_der(filename:join(Datadir, "server_key.pem")), -%% {ok, Key} = public_key:decode_private_key(EncKey, rsa), -%% RSAPublicKey = #'RSAPublicKey'{publicExponent = -%% Key#'RSAPrivateKey'.publicExponent, -%% modulus = Key#'RSAPrivateKey'.modulus}, - -encoded_tbs_cert(Cert) -> - {ok, PKIXCert} = - 'OTP-PUB-KEY':decode_TBSCert_exclusive(Cert), - {'Certificate', - {'Certificate_tbsCertificate', EncodedTBSCert}, _, _} = PKIXCert, - EncodedTBSCert. + {ok, {_,_,[E]}} = public_key:pkix_path_validation(Trusted, [Cert1, Cert3,Cert4], [{verify,false}]), + % test_server:format("PV ~p ~n", [Result]), + ok. diff --git a/lib/public_key/vsn.mk b/lib/public_key/vsn.mk index 8c4e4127b2..4b3071a85b 100644 --- a/lib/public_key/vsn.mk +++ b/lib/public_key/vsn.mk @@ -1,6 +1,6 @@ PUBLIC_KEY_VSN = 0.7 -TICKETS = OTP-8626 +TICKETS = OTP-8626 OTP-8649 #TICKETS_0.6 = OTP-7046 \ # OTP-8553 diff --git a/lib/snmp/doc/src/notes.xml b/lib/snmp/doc/src/notes.xml index 45e1549de7..3f4954cfbd 100644 --- a/lib/snmp/doc/src/notes.xml +++ b/lib/snmp/doc/src/notes.xml @@ -33,6 +33,61 @@ </header> <section> + <title>SNMP Development Toolkit 4.17</title> + <p>Version 4.17 supports code replacement in runtime from/to + version 4.16.2, 4.16.1, 4.16, 4.15, 4.14 and 4.13.5.</p> + + <section> + <title>Improvements and new features</title> + <!-- + <p>-</p> + --> + <list type="bulleted"> + <item> + <p>[agent] Added very basic support for multiple SNMPv3 + EngineIDs in a single agent. See + <seealso marker="snmpa#send_notification">send_notification/7</seealso>, + <seealso marker="snmpa_mpd#process_packet">process_packet/7</seealso>, + <seealso marker="snmpa_mpd#generate_response_msg">generate_response_msg/6</seealso> or + <seealso marker="snmpa_mpd#generate_msg">generate_msg/6</seealso> + for more info. </p> + + <p>Own Id: OTP-8478</p> + </item> + + </list> + + </section> + + <section> + <title>Reported Fixed Bugs and Malfunctions</title> + <p>-</p> + + <!-- + <list type="bulleted"> + <item> + <p>The config utility + (<seealso marker="snmp#config">snmp:config/0</seealso>) + generated a default notify.conf + with a bad name for the starndard trap entry (was "stadard trap", + but should have been "standard trap"). This has been corrected. </p> + <p>Kenji Rikitake</p> + <p>Own Id: OTP-8433</p> + </item> + + </list> + --> + + </section> + + <section> + <title>Incompatibilities</title> + <p>-</p> + </section> + </section> <!-- 4.17 --> + + + <section> <title>SNMP Development Toolkit 4.16.2</title> <p>Version 4.16.2 supports code replacement in runtime from/to version 4.16.1, 4.16, 4.15, 4.14 and 4.13.5.</p> diff --git a/lib/snmp/doc/src/snmpa.xml b/lib/snmp/doc/src/snmpa.xml index 69fe6d62f4..1be6abe6dd 100644 --- a/lib/snmp/doc/src/snmpa.xml +++ b/lib/snmp/doc/src/snmpa.xml @@ -881,6 +881,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2). <name>send_notification(Agent, Notification, Receiver, Varbinds)</name> <name>send_notification(Agent, Notification, Receiver, NotifyName, Varbinds)</name> <name>send_notification(Agent, Notification, Receiver, NotifyName, ContextName, Varbinds) -> void() </name> + <name>send_notification(Agent, Notification, Receiver, NotifyName, ContextName, Varbinds, LocalEngineID) -> void() </name> <fsummary>Send a notification</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -902,6 +903,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2). <v>OID = oid()</v> <v>Value = term()</v> <v>RowIndex = [int()]</v> + <v>LocalEngineID = string()</v> </type> <desc> <p>Sends the notification <c>Notification</c> to the @@ -1041,6 +1043,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2). <item><c>{?sysLocation_instance, "upstairs"}</c> (provided that the generated <c>.hrl</c> file is included)</item> </list> + <p>If a variable in the notification is a table element, the <c>RowIndex</c> for the element must be given in the <c>Varbinds</c> list. In this case, the OBJECT IDENTIFIER sent @@ -1048,15 +1051,27 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2). element. This OBJECT IDENTIFIER could be used in a get operation later. </p> + <p>This function is asynchronous, and does not return any information. If an error occurs, <c>user_err/2</c> of the error report module is called and the notification is discarded. </p> + <note> + <p>Note that the use of the LocalEngineID argument is only intended + for special cases, if the agent is to "emulate" multiple EngineIDs! + By default, the agent uses the value of <c>SnmpEngineID</c> + (see SNMP-FRAMEWORK-MIB). </p> + </note> + +<!-- <marker id="send_trap"></marker> +--> + <marker id="discovery"></marker> </desc> </func> +<!-- <func> <name>send_trap(Agent,Trap,Community)</name> <name>send_trap(Agent,Trap,Community,Varbinds) -> void()</name> @@ -1128,6 +1143,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2). <marker id="discovery"></marker> </desc> </func> +--> <func> <name>discovery(TargetName, Notification) -> {ok, ManagerEngineID} | {error, Reason}</name> diff --git a/lib/snmp/doc/src/snmpa_mpd.xml b/lib/snmp/doc/src/snmpa_mpd.xml index ea5bde8956..202e6b5661 100644 --- a/lib/snmp/doc/src/snmpa_mpd.xml +++ b/lib/snmp/doc/src/snmpa_mpd.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>1999</year><year>2009</year> + <year>1999</year><year>2010</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -13,12 +13,12 @@ compliance with the License. You should have received a copy of the Erlang Public License along with this software. If not, it can be retrieved online at http://www.erlang.org/. - + Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - + </legalnotice> <title>snmpa_mpd</title> @@ -63,15 +63,19 @@ </func> <func> - <name>process_packet(Packet, TDomain, TAddress, State) -> {ok, Vsn, Pdu, PduMS, ACMData} | {discarded, Reason} | {discovery, DiscoPacket}</name> + <name>process_packet(Packet, TDomain, TAddress, State, NoteStore, Log) -> {ok, Vsn, Pdu, PduMS, ACMData} | {discarded, Reason} | {discovery, DiscoPacket}</name> + <name>process_packet(Packet, TDomain, TAddress, LocalEngineID, State, NoteStore, Log) -> {ok, Vsn, Pdu, PduMS, ACMData} | {discarded, Reason} | {discovery, DiscoPacket}</name> <fsummary>Process a packet received from the network</fsummary> <type> <v>Packet = binary()</v> <v>TDomain = snmpUDPDomain</v> <v>TAddress = {Ip, Udp}</v> + <v>LocalEngineID = string()</v> <v>Ip = {integer(), integer(), integer(), integer()}</v> <v>Udp = integer()</v> <v>State = mpd_state()</v> + <v>NoteStore = pid()</v> + <v>Log = snmp_log()</v> <v>Vsn = 'version-1' | 'version-2' | 'version-3'</v> <v>Pdu = #pdu</v> <v>PduMs = integer()</v> @@ -84,18 +88,27 @@ decryption as necessary. The return values should be passed the agent.</p> + <note> + <p>Note that the use of the LocalEngineID argument is only intended + for special cases, if the agent is to "emulate" multiple EngineIDs! + By default, the agent uses the value of <c>SnmpEngineID</c> + (see SNMP-FRAMEWORK-MIB). </p> + </note> + <marker id="generate_response_msg"></marker> </desc> </func> <func> - <name>generate_response_msg(Vsn, RePdu, Type, ACMData) -> {ok, Packet} | {discarded, Reason}</name> + <name>generate_response_msg(Vsn, RePdu, Type, ACMData, Log) -> {ok, Packet} | {discarded, Reason}</name> + <name>generate_response_msg(Vsn, RePdu, Type, ACMData, LocalEngineID, Log) -> {ok, Packet} | {discarded, Reason}</name> <fsummary>Generate a response packet to be sent to the network</fsummary> <type> <v>Vsn = 'version-1' | 'version-2' | 'version-3'</v> <v>RePdu = #pdu</v> <v>Type = atom()</v> <v>ACMData = acm_data()</v> + <v>LocalEngineID = string()</v> <v>Packet = binary()</v> </type> <desc> @@ -103,17 +116,27 @@ network. <c>Type</c> is the <c>#pdu.type</c> of the original request.</p> + <note> + <p>Note that the use of the LocalEngineID argument is only intended + for special cases, if the agent is to "emulate" multiple EngineIDs! + By default, the agent uses the value of <c>SnmpEngineID</c> + (see SNMP-FRAMEWORK-MIB). </p> + </note> + <marker id="generate_msg"></marker> </desc> </func> <func> - <name>generate_msg(Vsn, Pdu, MsgData, To) -> {ok, PacketsAndAddresses} | {discarded, Reason}</name> + <name>generate_msg(Vsn, NoteStore, Pdu, MsgData, To) -> {ok, PacketsAndAddresses} | {discarded, Reason}</name> + <name>generate_msg(Vsn, NoteStore, Pdu, MsgData, LocalEngineID, To) -> {ok, PacketsAndAddresses} | {discarded, Reason}</name> <fsummary>Generate a request message to be sent to the network</fsummary> <type> <v>Vsn = 'version-1' | 'version-2' | 'version-3'</v> + <v>NoteStore = pid()</v> <v>Pdu = #pdu</v> <v>MsgData = msg_data()</v> + <v>LocalEngineID = string()</v> <v>To = [dest_addrs()]</v> <v>PacketsAndAddresses = [{TDomain, TAddress, Packet}]</v> <v>TDomain = snmpUDPDomain</v> @@ -136,6 +159,13 @@ also received from the requests mentioned above. </p> + <note> + <p>Note that the use of the LocalEngineID argument is only intended + for special cases, if the agent is to "emulate" multiple EngineIDs! + By default, the agent uses the value of <c>SnmpEngineID</c> + (see SNMP-FRAMEWORK-MIB). </p> + </note> + <marker id="discarded_pdu"></marker> </desc> </func> diff --git a/lib/snmp/src/agent/snmpa.erl b/lib/snmp/src/agent/snmpa.erl index 1c37d76074..87b191caed 100644 --- a/lib/snmp/src/agent/snmpa.erl +++ b/lib/snmp/src/agent/snmpa.erl @@ -61,7 +61,7 @@ register_subagent/3, unregister_subagent/2, send_notification/3, send_notification/4, send_notification/5, - send_notification/6, + send_notification/6, send_notification/7, send_trap/3, send_trap/4, discovery/2, discovery/3, discovery/4, discovery/5, discovery/6, @@ -423,14 +423,23 @@ send_notification(Agent, Notification, Recv, Varbinds) -> send_notification(Agent, Notification, Recv, NotifyName, Varbinds) -> send_notification(Agent, Notification, Recv, NotifyName, "", Varbinds). -send_notification(Agent, Notification, Recv, - NotifyName, ContextName, Varbinds) +send_notification(Agent, Notification, Recv, NotifyName, + ContextName, Varbinds) when (is_list(NotifyName) andalso is_list(ContextName) andalso is_list(Varbinds)) -> snmpa_agent:send_trap(Agent, Notification, NotifyName, ContextName, Recv, Varbinds). +send_notification(Agent, Notification, Recv, + NotifyName, ContextName, Varbinds, LocalEngineID) + when (is_list(NotifyName) andalso + is_list(ContextName) andalso + is_list(Varbinds) andalso + is_list(LocalEngineID)) -> + snmpa_agent:send_trap(Agent, Notification, NotifyName, + ContextName, Recv, Varbinds, LocalEngineID). + %% Kept for backwards compatibility send_trap(Agent, Trap, Community) -> send_notification(Agent, Trap, no_receiver, Community, "", []). diff --git a/lib/snmp/src/agent/snmpa_agent.erl b/lib/snmp/src/agent/snmpa_agent.erl index 648f5b53fa..f70885b2ec 100644 --- a/lib/snmp/src/agent/snmpa_agent.erl +++ b/lib/snmp/src/agent/snmpa_agent.erl @@ -30,7 +30,7 @@ -export([subagent_set/2, load_mibs/2, unload_mibs/2, which_mibs/1, whereis_mib/2, info/1, register_subagent/3, unregister_subagent/2, - send_trap/6, + send_trap/6, send_trap/7, register_notification_filter/5, unregister_notification_filter/2, which_notification_filter/1, @@ -65,7 +65,7 @@ %% Internal exports -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3, tr_var/2, tr_varbind/1, - handle_pdu/7, worker/2, worker_loop/1, do_send_trap/6]). + handle_pdu/7, worker/2, worker_loop/1, do_send_trap/7]). -ifndef(default_verbosity). -define(default_verbosity,silence). @@ -529,14 +529,15 @@ which_notification_filter(Agent) -> send_trap(Agent, Trap, NotifyName, CtxName, Recv, Varbinds) -> ?d("send_trap -> entry with" - "~n self(): ~p" - "~n Agent: ~p [~p]" - "~n Trap: ~p" - "~n NotifyName: ~p" - "~n CtxName: ~p" - "~n Recv: ~p" - "~n Varbinds: ~p", - [self(), Agent, wis(Agent), Trap, NotifyName, CtxName, Recv, Varbinds]), + "~n self(): ~p" + "~n Agent: ~p [~p]" + "~n Trap: ~p" + "~n NotifyName: ~p" + "~n CtxName: ~p" + "~n Recv: ~p" + "~n Varbinds: ~p", + [self(), Agent, wis(Agent), + Trap, NotifyName, CtxName, Recv, Varbinds]), Msg = {send_trap, Trap, NotifyName, CtxName, Recv, Varbinds}, case (wis(Agent) =:= self()) of false -> @@ -545,6 +546,27 @@ send_trap(Agent, Trap, NotifyName, CtxName, Recv, Varbinds) -> Agent ! Msg end. +send_trap(Agent, Trap, NotifyName, CtxName, Recv, Varbinds, LocalEngineID) -> + ?d("send_trap -> entry with" + "~n self(): ~p" + "~n Agent: ~p [~p]" + "~n Trap: ~p" + "~n NotifyName: ~p" + "~n CtxName: ~p" + "~n Recv: ~p" + "~n Varbinds: ~p" + "~n LocalEngineID: ~p", + [self(), Agent, wis(Agent), + Trap, NotifyName, CtxName, Recv, Varbinds, LocalEngineID]), + Msg = + {send_trap, Trap, NotifyName, CtxName, Recv, Varbinds, LocalEngineID}, + case (wis(Agent) =:= self()) of + false -> + call(Agent, Msg); + true -> + Agent ! Msg + end. + %% -- Discovery functions -- @@ -631,6 +653,7 @@ wis(Pid) when is_pid(Pid) -> wis(Atom) when is_atom(Atom) -> whereis(Atom). + forward_trap(Agent, TrapRecord, NotifyName, CtxName, Recv, Varbinds) -> Agent ! {forward_trap, TrapRecord, NotifyName, CtxName, Recv, Varbinds}. @@ -724,14 +747,36 @@ handle_info(worker_available, S) -> handle_info({send_trap, Trap, NotifyName, ContextName, Recv, Varbinds}, S) -> ?vlog("[handle_info] send trap request:" - "~n Trap: ~p" - "~n NotifyName: ~p" - "~n ContextName: ~p" - "~n Recv: ~p" - "~n Varbinds: ~p", - [Trap,NotifyName,ContextName,Recv,Varbinds]), + "~n Trap: ~p" + "~n NotifyName: ~p" + "~n ContextName: ~p" + "~n Recv: ~p" + "~n Varbinds: ~p", + [Trap, NotifyName, ContextName, Recv, Varbinds]), + LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID, + case catch handle_send_trap(S, Trap, NotifyName, ContextName, + Recv, Varbinds, LocalEngineID) of + {ok, NewS} -> + {noreply, NewS}; + {'EXIT', R} -> + ?vinfo("Trap not sent:~n ~p", [R]), + {noreply, S}; + _ -> + {noreply, S} + end; + +handle_info({send_trap, Trap, NotifyName, ContextName, Recv, Varbinds, + LocalEngineID}, S) -> + ?vlog("[handle_info] send trap request:" + "~n Trap: ~p" + "~n NotifyName: ~p" + "~n ContextName: ~p" + "~n Recv: ~p" + "~n Varbinds: ~p" + "~n LocalEngineID: ~p", + [Trap, NotifyName, ContextName, Recv, Varbinds, LocalEngineID]), case catch handle_send_trap(S, Trap, NotifyName, ContextName, - Recv, Varbinds) of + Recv, Varbinds, LocalEngineID) of {ok, NewS} -> {noreply, NewS}; {'EXIT', R} -> @@ -741,17 +786,18 @@ handle_info({send_trap, Trap, NotifyName, ContextName, Recv, Varbinds}, S) -> {noreply, S} end; -handle_info({forward_trap, TrapRecord, NotifyName, ContextName, - Recv, Varbinds},S) -> +handle_info({forward_trap, TrapRecord, NotifyName, ContextName, + Recv, Varbinds}, S) -> ?vlog("[handle_info] forward trap request:" - "~n TrapRecord: ~p" - "~n NotifyName: ~p" - "~n ContextName: ~p" - "~n Recv: ~p" - "~n Varbinds: ~p", - [TrapRecord,NotifyName,ContextName,Recv,Varbinds]), + "~n TrapRecord: ~p" + "~n NotifyName: ~p" + "~n ContextName: ~p" + "~n Recv: ~p" + "~n Varbinds: ~p", + [TrapRecord, NotifyName, ContextName, Recv, Varbinds]), + LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID, case (catch maybe_send_trap(S, TrapRecord, NotifyName, ContextName, - Recv, Varbinds)) of + Recv, Varbinds, LocalEngineID)) of {ok, NewS} -> {noreply, NewS}; {'EXIT', R} -> @@ -861,17 +907,29 @@ handle_call(restart_set_worker, _From, #state{set_worker = Pid} = S) -> ok end, {reply, ok, S}; + handle_call({send_trap, Trap, NotifyName, ContextName, Recv, Varbinds}, _From, S) -> ?vlog("[handle_call] send trap request:" - "~n Trap: ~p" - "~n NotifyName: ~p" - "~n ContextName: ~p" - "~n Recv: ~p" - "~n Varbinds: ~p", - [Trap,NotifyName,ContextName,Recv,Varbinds]), + "~n Trap: ~p" + "~n NotifyName: ~p" + "~n ContextName: ~p" + "~n Recv: ~p" + "~n Varbinds: ~p", + [Trap, NotifyName, ContextName, Recv, Varbinds]), + LocalEngineID = + case S#state.type of + master_agent -> + ?DEFAULT_LOCAL_ENGINE_ID; + _ -> + %% subagent - + %% we don't need this, eventually the trap sent request + %% will reach the master-agent and then it will look up + %% the proper engine id. + ignore + end, case (catch handle_send_trap(S, Trap, NotifyName, ContextName, - Recv, Varbinds)) of + Recv, Varbinds, LocalEngineID)) of {ok, NewS} -> {reply, ok, NewS}; {'EXIT', Reason} -> @@ -881,8 +939,33 @@ handle_call({send_trap, Trap, NotifyName, ContextName, Recv, Varbinds}, ?vinfo("Trap not sent", []), {reply, {error, send_failed}, S} end; + +handle_call({send_trap, Trap, NotifyName, + ContextName, Recv, Varbinds, LocalEngineID}, + _From, S) -> + ?vlog("[handle_call] send trap request:" + "~n Trap: ~p" + "~n NotifyName: ~p" + "~n ContextName: ~p" + "~n Recv: ~p" + "~n Varbinds: ~p" + "~n LocalEngineID: ~p", + [Trap, NotifyName, ContextName, Recv, Varbinds, LocalEngineID]), + case (catch handle_send_trap(S, Trap, NotifyName, ContextName, + Recv, Varbinds, LocalEngineID)) of + {ok, NewS} -> + {reply, ok, NewS}; + {'EXIT', Reason} -> + ?vinfo("Trap not sent:~n ~p", [Reason]), + {reply, {error, {send_failed, Reason}}, S}; + _ -> + ?vinfo("Trap not sent", []), + {reply, {error, send_failed}, S} + end; + handle_call({discovery, - TargetName, Notification, ContextName, Vbs, DiscoHandler, ExtraInfo}, + TargetName, Notification, ContextName, Vbs, DiscoHandler, + ExtraInfo}, From, #state{disco = undefined} = S) -> ?vlog("[handle_call] initiate discovery process:" @@ -1439,17 +1522,20 @@ spawn_thread(Vsn, Pdu, PduMS, ACMData, Address, Extra) -> Args = [Vsn, Pdu, PduMS, ACMData, Address, Extra, Dict], proc_lib:spawn_link(?MODULE, handle_pdu, Args). -spawn_trap_thread(TrapRec, NotifyName, ContextName, Recv, V) -> +spawn_trap_thread(TrapRec, NotifyName, ContextName, Recv, Vbs, + LocalEngineID) -> Dict = get(), proc_lib:spawn_link(?MODULE, do_send_trap, - [TrapRec, NotifyName, ContextName, Recv, V, Dict]). + [TrapRec, NotifyName, ContextName, + Recv, Vbs, LocalEngineID, Dict]). -do_send_trap(TrapRec, NotifyName, ContextName, Recv, V, Dict) -> +do_send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, + LocalEngineID, Dict) -> lists:foreach(fun({Key, Val}) -> put(Key, Val) end, Dict), put(sname,trap_sender_short_name(get(sname))), ?vlog("starting",[]), - snmpa_trap:send_trap(TrapRec, NotifyName, ContextName, Recv, V, - get(net_if)). + snmpa_trap:send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, + LocalEngineID, get(net_if)). worker(Master, Dict) -> lists:foreach(fun({Key, Val}) -> put(Key, Val) end, Dict), @@ -1464,17 +1550,22 @@ worker_loop(Master) -> handle_pdu(Vsn, Pdu, PduMS, ACMData, Address, Extra), Master ! worker_available; - %% Old style message - {MibView, Vsn, Pdu, PduMS, ACMData, AgentData, Extra} -> - ?vtrace("worker_loop -> received (old) request", []), - do_handle_pdu(MibView, Vsn, Pdu, PduMS, ACMData, AgentData, Extra), + %% We don't trap exits! + {TrapRec, NotifyName, ContextName, Recv, Vbs} -> + ?vtrace("worker_loop -> send trap:" + "~n ~p", [TrapRec]), + snmpa_trap:send_trap(TrapRec, NotifyName, + ContextName, Recv, Vbs, get(net_if)), Master ! worker_available; - {TrapRec, NotifyName, ContextName, Recv, V} -> % We don't trap exits! + %% We don't trap exits! + {send_trap, + TrapRec, NotifyName, ContextName, Recv, Vbs, LocalEngineID} -> ?vtrace("worker_loop -> send trap:" "~n ~p", [TrapRec]), snmpa_trap:send_trap(TrapRec, NotifyName, - ContextName, Recv, V, get(net_if)), + ContextName, Recv, Vbs, LocalEngineID, + get(net_if)), Master ! worker_available; {verbosity, Verbosity} -> @@ -1623,13 +1714,15 @@ handle_acm_error(Vsn, Reason, Pdu, ACMData, Address, Extra) -> end. -handle_send_trap(S, TrapName, NotifyName, ContextName, Recv, Varbinds) -> +handle_send_trap(S, TrapName, NotifyName, ContextName, Recv, Varbinds, + LocalEngineID) -> ?vtrace("handle_send_trap -> entry with" - "~n S#state.type: ~p" - "~n TrapName: ~p" - "~n NotifyName: ~p" - "~n ContextName: ~p", - [S#state.type, TrapName, NotifyName, ContextName]), + "~n S#state.type: ~p" + "~n TrapName: ~p" + "~n NotifyName: ~p" + "~n ContextName: ~p" + "~n LocalEngineID: ~p", + [S#state.type, TrapName, NotifyName, ContextName, LocalEngineID]), case snmpa_trap:construct_trap(TrapName, Varbinds) of {ok, TrapRecord, VarList} -> ?vtrace("handle_send_trap -> construction complete: " @@ -1646,7 +1739,8 @@ handle_send_trap(S, TrapName, NotifyName, ContextName, Recv, Varbinds) -> ?vtrace("handle_send_trap -> " "[master] handle send trap",[]), maybe_send_trap(S, TrapRecord, NotifyName, - ContextName, Recv, VarList) + ContextName, Recv, VarList, + LocalEngineID) end; error -> error @@ -1683,7 +1777,8 @@ maybe_forward_trap(#state{parent = Parent, nfilters = NFs} = S, maybe_send_trap(#state{nfilters = NFs} = S, - TrapRec, NotifyName, ContextName, Recv, Varbinds) -> + TrapRec, NotifyName, ContextName, Recv, Varbinds, + LocalEngineID) -> ?vtrace("maybe_send_trap -> entry with" "~n NFs: ~p", [NFs]), case filter_notification(NFs, [], TrapRec) of @@ -1700,39 +1795,45 @@ maybe_send_trap(#state{nfilters = NFs} = S, ?vtrace("maybe_send_trap -> send trap:" "~n ~p", [TrapRec2]), do_handle_send_trap(S, TrapRec2, - NotifyName, ContextName, Recv, Varbinds); + NotifyName, ContextName, Recv, Varbinds, + LocalEngineID); {send, Removed, TrapRec2} -> ?vtrace("maybe_send_trap -> send trap:" "~n ~p", [TrapRec2]), NFs2 = del_notification_filter(Removed, NFs), do_handle_send_trap(S#state{nfilters = NFs2}, TrapRec2, - NotifyName, ContextName, Recv, Varbinds) + NotifyName, ContextName, Recv, Varbinds, + LocalEngineID) end. -do_handle_send_trap(S, TrapRec, NotifyName, ContextName, Recv, Varbinds) -> - V = snmpa_trap:try_initialise_vars(get(mibserver), Varbinds), +do_handle_send_trap(S, TrapRec, NotifyName, ContextName, Recv, Varbinds, + LocalEngineID) -> + Vbs = snmpa_trap:try_initialise_vars(get(mibserver), Varbinds), case S#state.type of subagent -> forward_trap(S#state.parent, TrapRec, NotifyName, ContextName, - Recv, V), + Recv, Vbs), {ok, S}; master_agent when S#state.multi_threaded =:= false -> ?vtrace("do_handle_send_trap -> send trap:" "~n ~p", [TrapRec]), snmpa_trap:send_trap(TrapRec, NotifyName, ContextName, - Recv, V, get(net_if)), + Recv, Vbs, LocalEngineID, get(net_if)), {ok, S}; master_agent when S#state.worker_state =:= busy -> %% Main worker busy => create new worker ?vtrace("do_handle_send_trap -> main worker busy: " "spawn a trap sender", []), - spawn_trap_thread(TrapRec, NotifyName, ContextName, Recv, V), + spawn_trap_thread(TrapRec, NotifyName, ContextName, Recv, Vbs, + LocalEngineID), {ok, S}; master_agent -> %% Send to main worker ?vtrace("do_handle_send_trap -> send to main worker",[]), - S#state.worker ! {TrapRec, NotifyName, ContextName, Recv, V}, + S#state.worker ! {send_trap, + TrapRec, NotifyName, ContextName, Recv, Vbs, + LocalEngineID}, {ok, S#state{worker_state = busy}} end. diff --git a/lib/snmp/src/agent/snmpa_internal.hrl b/lib/snmp/src/agent/snmpa_internal.hrl index a33a6809dc..9fa874f119 100644 --- a/lib/snmp/src/agent/snmpa_internal.hrl +++ b/lib/snmp/src/agent/snmpa_internal.hrl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2006-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2006-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -22,6 +22,8 @@ -include_lib("snmp/src/app/snmp_internal.hrl"). +-define(DEFAULT_LOCAL_ENGINE_ID, snmp_framework_mib:get_engine_id()). + -define(snmpa_info(F, A), ?snmp_info("agent", F, A)). -define(snmpa_warning(F, A), ?snmp_warning("agent", F, A)). -define(snmpa_error(F, A), ?snmp_error("agent", F, A)). diff --git a/lib/snmp/src/agent/snmpa_mpd.erl b/lib/snmp/src/agent/snmpa_mpd.erl index 2e09286b87..fd75b98f84 100644 --- a/lib/snmp/src/agent/snmpa_mpd.erl +++ b/lib/snmp/src/agent/snmpa_mpd.erl @@ -1,27 +1,28 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1997-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% -module(snmpa_mpd). -export([init/1, reset/0, inc/1, counters/0, discarded_pdu/1, - process_packet/6, - generate_response_msg/5, generate_msg/5, + process_packet/6, process_packet/7, + generate_response_msg/5, generate_response_msg/6, + generate_msg/5, generate_msg/6, generate_discovery_msg/4, process_taddrs/1, generate_req_id/0]). @@ -34,6 +35,7 @@ -define(VMODULE,"MPD"). -include("snmp_verbosity.hrl"). +-include("snmpa_internal.hrl"). -define(empty_msg_size, 24). @@ -120,6 +122,12 @@ reset() -> %% section 4.2.1 in rfc2272) %%----------------------------------------------------------------- process_packet(Packet, TDomain, TAddress, State, NoteStore, Log) -> + LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID, + process_packet(Packet, TDomain, TAddress, LocalEngineID, + State, NoteStore, Log). + +process_packet(Packet, TDomain, TAddress, LocalEngineID, + State, NoteStore, Log) -> inc(snmpInPkts), case catch snmp_pdus:dec_message_only(binary_to_list(Packet)) of @@ -127,15 +135,17 @@ process_packet(Packet, TDomain, TAddress, State, NoteStore, Log) -> when State#state.v1 =:= true -> ?vlog("v1, community: ~s", [Community]), HS = ?empty_msg_size + length(Community), - v1_v2c_proc('version-1', NoteStore, Community, TDomain, TAddress, - Data, HS, Log, Packet); + v1_v2c_proc('version-1', NoteStore, Community, + TDomain, TAddress, + LocalEngineID, Data, HS, Log, Packet); #message{version = 'version-2', vsn_hdr = Community, data = Data} when State#state.v2c =:= true -> ?vlog("v2c, community: ~s", [Community]), HS = ?empty_msg_size + length(Community), - v1_v2c_proc('version-2', NoteStore, Community, TDomain, TAddress, - Data, HS, Log, Packet); + v1_v2c_proc('version-2', NoteStore, Community, + TDomain, TAddress, + LocalEngineID, Data, HS, Log, Packet); #message{version = 'version-3', vsn_hdr = V3Hdr, data = Data} when State#state.v3 =:= true -> @@ -143,7 +153,9 @@ process_packet(Packet, TDomain, TAddress, State, NoteStore, Log) -> [V3Hdr#v3_hdr.msgID, V3Hdr#v3_hdr.msgFlags, V3Hdr#v3_hdr.msgSecurityModel]), - v3_proc(NoteStore, Packet, TDomain, TAddress, V3Hdr, Data, Log); + v3_proc(NoteStore, Packet, + TDomain, TAddress, + LocalEngineID, V3Hdr, Data, Log); {'EXIT', {bad_version, Vsn}} -> ?vtrace("exit: bad version: ~p",[Vsn]), @@ -170,10 +182,11 @@ discarded_pdu(Variable) -> inc(Variable). %%----------------------------------------------------------------- %% Handles a Community based message (v1 or v2c). %%----------------------------------------------------------------- -v1_v2c_proc(Vsn, NoteStore, Community, snmpUDPDomain, {Ip, Udp}, +v1_v2c_proc(Vsn, NoteStore, Community, snmpUDPDomain, + {Ip, Udp}, LocalEngineID, Data, HS, Log, Packet) -> TAddress = tuple_to_list(Ip) ++ [Udp div 256, Udp rem 256], - AgentMS = snmp_framework_mib:get_engine_max_message_size(), + AgentMS = get_engine_max_message_size(LocalEngineID), MgrMS = snmp_community_mib:get_target_addr_ext_mms(?snmpUDPDomain, TAddress), PduMS = case MgrMS of @@ -220,10 +233,10 @@ v1_v2c_proc(Vsn, NoteStore, Community, snmpUDPDomain, {Ip, Udp}, {discarded, trap_pdu} end; v1_v2c_proc(_Vsn, _NoteStore, _Community, snmpUDPDomain, TAddress, - _Data, _HS, _Log, _Packet) -> + _LocalEngineID, _Data, _HS, _Log, _Packet) -> {discarded, {badarg, TAddress}}; v1_v2c_proc(_Vsn, _NoteStore, _Community, TDomain, _TAddress, - _Data, _HS, _Log, _Packet) -> + _LocalEngineID, _Data, _HS, _Log, _Packet) -> {discarded, {badarg, TDomain}}. sec_model('version-1') -> ?SEC_V1; @@ -234,15 +247,19 @@ sec_model('version-2') -> ?SEC_V2C. %% Handles a SNMPv3 Message, following the procedures in rfc2272, %% section 4.2 and 7.2 %%----------------------------------------------------------------- -v3_proc(NoteStore, Packet, _TDomain, _TAddress, V3Hdr, Data, Log) -> - case (catch v3_proc(NoteStore, Packet, V3Hdr, Data, Log)) of +v3_proc(NoteStore, Packet, _TDomain, _TAddress, LocalEngineID, + V3Hdr, Data, Log) -> + case (catch v3_proc(NoteStore, Packet, LocalEngineID, V3Hdr, Data, Log)) of {'EXIT', Reason} -> exit(Reason); Result -> Result end. -v3_proc(NoteStore, Packet, V3Hdr, Data, Log) -> +v3_proc(NoteStore, Packet, LocalEngineID, V3Hdr, Data, Log) -> + ?vtrace("v3_proc -> entry with" + "~n LocalEngineID: ~p", + [LocalEngineID]), %% 7.2.3 #v3_hdr{msgID = MsgID, msgMaxSize = MMS, @@ -250,7 +267,7 @@ v3_proc(NoteStore, Packet, V3Hdr, Data, Log) -> msgSecurityModel = MsgSecurityModel, msgSecurityParameters = SecParams, hdr_size = HdrSize} = V3Hdr, - ?vdebug("v3_proc -> version 3 message header:" + ?vdebug("v3_proc -> version 3 message header [7.2.3]:" "~n msgID = ~p" "~n msgMaxSize = ~p" "~n msgFlags = ~p" @@ -263,17 +280,19 @@ v3_proc(NoteStore, Packet, V3Hdr, Data, Log) -> SecLevel = check_sec_level(MsgFlags), IsReportable = snmp_misc:is_reportable(MsgFlags), %% 7.2.6 - ?vtrace("v3_proc -> " + ?vtrace("v3_proc -> [7.2.6]" "~n SecModule = ~p" "~n SecLevel = ~p" "~n IsReportable = ~p", - [SecModule,SecLevel,IsReportable]), + [SecModule, SecLevel, IsReportable]), SecRes = (catch SecModule:process_incoming_msg(Packet, Data, - SecParams, SecLevel)), + SecParams, SecLevel, + LocalEngineID)), ?vtrace("v3_proc -> message processing result: " "~n SecRes: ~p", [SecRes]), {SecEngineID, SecName, ScopedPDUBytes, SecData, DiscoOrPlain} = - check_sec_module_result(SecRes, V3Hdr, Data, IsReportable, Log), + check_sec_module_result(SecRes, V3Hdr, Data, + LocalEngineID, IsReportable, Log), ?vtrace("v3_proc -> " "~n DiscoOrPlain: ~w" "~n SecEngineID: ~w" @@ -311,7 +330,7 @@ v3_proc(NoteStore, Packet, V3Hdr, Data, Log) -> Log(PDU#pdu.type, Packet) end, %% Make sure a get_bulk doesn't get too big. - AgentMS = snmp_framework_mib:get_engine_max_message_size(), + AgentMS = get_engine_max_message_size(LocalEngineID), %% PduMMS is supposed to be the maximum total length of the response %% PDU we can send. From the MMS, we need to subtract everything before %% the PDU, i.e. Message and ScopedPDU. @@ -415,8 +434,8 @@ v3_proc(NoteStore, Packet, V3Hdr, Data, Log) -> throw({discarded, received_v2_trap}); Type -> %% 7.2.13 - SnmpEngineID = snmp_framework_mib:get_engine_id(), - ?vtrace("v3_proc -> SnmpEngineID = ~w", [SnmpEngineID]), + SnmpEngineID = LocalEngineID, + ?vtrace("v3_proc -> 7.2.13", []), case SecEngineID of SnmpEngineID when (DiscoOrPlain =:= discovery) -> %% This is a discovery step 2 message! @@ -429,6 +448,7 @@ v3_proc(NoteStore, Packet, V3Hdr, Data, Log) -> ContextName, SecData, PDU, + LocalEngineID, Log); SnmpEngineID when (DiscoOrPlain =:= plain) -> @@ -444,17 +464,18 @@ v3_proc(NoteStore, Packet, V3Hdr, Data, Log) -> %% 4.2.2.1.2 NIsReportable = snmp_misc:is_reportable_pdu(Type), Val = inc(snmpUnknownPDUHandlers), - ErrorInfo = {#varbind{oid = ?snmpUnknownPDUHandlers, - variabletype = 'Counter32', - value = Val}, - SecName, - [{securityLevel, SecLevel}, - {contextEngineID, ContextEngineID}, - {contextName, ContextName}]}, + ErrorInfo = + {#varbind{oid = ?snmpUnknownPDUHandlers, + variabletype = 'Counter32', + value = Val}, + SecName, + [{securityLevel, SecLevel}, + {contextEngineID, ContextEngineID}, + {contextName, ContextName}]}, case generate_v3_report_msg(MsgID, MsgSecurityModel, - Data, ErrorInfo, - Log) of + Data, LocalEngineID, + ErrorInfo, Log) of {ok, Report} when NIsReportable =:= true -> {discarded, snmpUnknownPDUHandlers, Report}; _ -> @@ -473,6 +494,7 @@ v3_proc(NoteStore, Packet, V3Hdr, Data, Log) -> ContextName, SecData, PDU, + LocalEngineID, Log); _ -> @@ -501,7 +523,7 @@ check_sec_level(Unknown) -> inc(snmpInvalidMsgs), throw({discarded, snmpInvalidMsgs}). -check_sec_module_result(Res, V3Hdr, Data, IsReportable, Log) -> +check_sec_module_result(Res, V3Hdr, Data, LocalEngineID, IsReportable, Log) -> case Res of {ok, X} -> X; @@ -516,7 +538,7 @@ check_sec_module_result(Res, V3Hdr, Data, IsReportable, Log) -> #v3_hdr{msgID = MsgID, msgSecurityModel = MsgSecModel} = V3Hdr, Pdu = get_scoped_pdu(Data), case generate_v3_report_msg(MsgID, MsgSecModel, Pdu, - ErrorInfo, Log) of + LocalEngineID, ErrorInfo, Log) of {ok, Report} -> throw({discarded, {securityError, Reason}, Report}); {discarded, _SomeOtherReason} -> @@ -545,8 +567,15 @@ get_scoped_pdu(D) -> generate_response_msg(Vsn, RePdu, Type, ACMData, Log) -> generate_response_msg(Vsn, RePdu, Type, ACMData, Log, 1). +generate_response_msg(Vsn, RePdu, Type, ACMData, Log, N) when is_integer(N) -> + LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID, + generate_response_msg(Vsn, RePdu, Type, ACMData, LocalEngineID, Log, N); +generate_response_msg(Vsn, RePdu, Type, ACMData, LocalEngineID, Log) -> + generate_response_msg(Vsn, RePdu, Type, ACMData, LocalEngineID, Log, 1). + generate_response_msg(Vsn, RePdu, Type, {community, _SecModel, Community, _IpUdp}, + LocalEngineID, Log, _) -> case catch snmp_pdus:enc_pdu(RePdu) of {'EXIT', Reason} -> @@ -555,8 +584,9 @@ generate_response_msg(Vsn, RePdu, Type, [RePdu, Community, Reason]), {discarded, Reason}; PduBytes -> - Message = #message{version = Vsn, vsn_hdr = Community, - data = PduBytes}, + Message = #message{version = Vsn, + vsn_hdr = Community, + data = PduBytes}, case catch list_to_binary( snmp_pdus:enc_message_only(Message)) of {'EXIT', Reason} -> @@ -565,7 +595,7 @@ generate_response_msg(Vsn, RePdu, Type, [RePdu, Community, Reason]), {discarded, Reason}; Packet -> - MMS = snmp_framework_mib:get_engine_max_message_size(), + MMS = get_engine_max_message_size(LocalEngineID), case size(Packet) of Len when Len =< MMS -> Log(Type, Packet), @@ -584,6 +614,7 @@ generate_response_msg(Vsn, RePdu, Type, generate_response_msg(Vsn, RePdu, Type, {v3, MsgID, MsgSecurityModel, SecName, SecLevel, ContextEngineID, ContextName, SecData}, + LocalEngineID, Log, N) -> %% rfc2272: 7.1 steps 6-8 ScopedPDU = #scopedPdu{contextEngineID = ContextEngineID, @@ -596,7 +627,7 @@ generate_response_msg(Vsn, RePdu, Type, [RePdu, ContextName, Reason]), {discarded, Reason}; ScopedPDUBytes -> - AgentMS = snmp_framework_mib:get_engine_max_message_size(), + AgentMS = get_engine_max_message_size(LocalEngineID), V3Hdr = #v3_hdr{msgID = MsgID, msgMaxSize = AgentMS, msgFlags = snmp_misc:mk_msg_flags(Type, SecLevel), @@ -611,13 +642,14 @@ generate_response_msg(Vsn, RePdu, Type, ?SEC_USM -> snmpa_usm end, - SecEngineID = snmp_framework_mib:get_engine_id(), + SecEngineID = LocalEngineID, ?vtrace("generate_response_msg -> SecEngineID: ~w", [SecEngineID]), case (catch SecModule:generate_outgoing_msg(Message, SecEngineID, SecName, SecData, - SecLevel)) of + SecLevel, + LocalEngineID)) of {'EXIT', Reason} -> config_err("~p (message: ~p)", [Reason, Message]), {discarded, Reason}; @@ -668,12 +700,14 @@ generate_response_msg(Vsn, RePdu, Type, SecName, SecLevel, ContextEngineID, ContextName, - SecData}, Log, N+1) + SecData}, + LocalEngineID, Log, N+1) end end end. -generate_v3_report_msg(MsgID, MsgSecurityModel, Data, ErrorInfo, Log) -> +generate_v3_report_msg(MsgID, MsgSecurityModel, Data, LocalEngineID, + ErrorInfo, Log) -> {Varbind, SecName, Opts} = ErrorInfo, ReqId = if @@ -689,7 +723,7 @@ generate_v3_report_msg(MsgID, MsgSecurityModel, Data, ErrorInfo, Log) -> error_index = 0, varbinds = [Varbind]}, SecLevel = snmp_misc:get_option(securityLevel, Opts, 0), - SnmpEngineID = snmp_framework_mib:get_engine_id(), + SnmpEngineID = LocalEngineID, ContextEngineID = snmp_misc:get_option(contextEngineID, Opts, SnmpEngineID), ContextName = snmp_misc:get_option(contextName, Opts, ""), @@ -697,7 +731,8 @@ generate_v3_report_msg(MsgID, MsgSecurityModel, Data, ErrorInfo, Log) -> generate_response_msg('version-3', Pdu, report, {v3, MsgID, MsgSecurityModel, SecName, SecLevel, - ContextEngineID, ContextName, SecData}, Log). + ContextEngineID, ContextName, SecData}, + LocalEngineID, Log). %% req_id(#scopedPdu{data = #pdu{request_id = ReqId}}) -> %% ?vtrace("Report ReqId: ~p",[ReqId]), @@ -719,7 +754,8 @@ generate_discovery1_report_msg(MsgID, MsgSecurityModel, SecName, SecLevel, ContextEngineID, ContextName, {SecData, Oid, Value}, - #pdu{request_id = ReqId}, Log) -> + #pdu{request_id = ReqId}, + LocalEngineID, Log) -> ?vtrace("generate_discovery1_report_msg -> entry with" "~n ReqId: ~p" "~n Value: ~p", [ReqId, Value]), @@ -734,7 +770,8 @@ generate_discovery1_report_msg(MsgID, MsgSecurityModel, varbinds = [Varbind]}, case generate_response_msg('version-3', PduOut, report, {v3, MsgID, MsgSecurityModel, SecName, SecLevel, - ContextEngineID, ContextName, SecData}, Log) of + ContextEngineID, ContextName, SecData}, + LocalEngineID, Log) of {ok, Packet} -> {discovery, Packet}; Error -> @@ -745,7 +782,8 @@ generate_discovery1_report_msg(MsgID, MsgSecurityModel, generate_discovery2_report_msg(MsgID, MsgSecurityModel, SecName, SecLevel, ContextEngineID, ContextName, - SecData, #pdu{request_id = ReqId}, Log) -> + SecData, #pdu{request_id = ReqId}, + LocalEngineID, Log) -> ?vtrace("generate_discovery2_report_msg -> entry with" "~n ReqId: ~p", [ReqId]), SecModule = get_security_module(MsgSecurityModel), @@ -757,7 +795,8 @@ generate_discovery2_report_msg(MsgID, MsgSecurityModel, varbinds = [Vb]}, case generate_response_msg('version-3', PduOut, report, {v3, MsgID, MsgSecurityModel, SecName, SecLevel, - ContextEngineID, ContextName, SecData}, Log) of + ContextEngineID, ContextName, SecData}, + LocalEngineID, Log) of {ok, Packet} -> {discovery, Packet}; Error -> @@ -816,7 +855,11 @@ set_vb_null([]) -> %% Executed when a message that isn't a response is generated, i.e. %% a trap or an inform. %%----------------------------------------------------------------- -generate_msg(Vsn, _NoteStore, Pdu, {community, Community}, To) -> +generate_msg(Vsn, NoteStore, Pdu, ACMData, To) -> + LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID, + generate_msg(Vsn, NoteStore, Pdu, ACMData, LocalEngineID, To). + +generate_msg(Vsn, _NoteStore, Pdu, {community, Community}, LocalEngineID, To) -> Message = #message{version = Vsn, vsn_hdr = Community, data = Pdu}, case catch list_to_binary(snmp_pdus:enc_message(Message)) of {'EXIT', Reason} -> @@ -825,7 +868,7 @@ generate_msg(Vsn, _NoteStore, Pdu, {community, Community}, To) -> [Pdu, Community, Reason]), {discarded, Reason}; Packet -> - AgentMax = snmp_framework_mib:get_engine_max_message_size(), + AgentMax = get_engine_max_message_size(LocalEngineID), case size(Packet) of Len when Len =< AgentMax -> {ok, mk_v1_v2_packet_list(To, Packet, Len, Pdu)}; @@ -838,9 +881,9 @@ generate_msg(Vsn, _NoteStore, Pdu, {community, Community}, To) -> end end; generate_msg('version-3', NoteStore, Pdu, - {v3, ContextEngineID, ContextName}, To) -> - %% rfc2272: 7.1.6 - ScopedPDU = #scopedPdu{contextEngineID = ContextEngineID, + {v3, ContextEngineID, ContextName}, LocalEngineID, To) -> + %% rfc2272: 7.1 step 6 + ScopedPDU = #scopedPdu{contextEngineID = LocalEngineID, contextName = ContextName, data = Pdu}, case (catch snmp_pdus:enc_scoped_pdu(ScopedPDU)) of @@ -851,7 +894,8 @@ generate_msg('version-3', NoteStore, Pdu, {discarded, Reason}; ScopedPDUBytes -> {ok, mk_v3_packet_list(NoteStore, To, ScopedPDUBytes, Pdu, - ContextEngineID, ContextName)} + ContextEngineID, ContextName, + LocalEngineID)} end. @@ -1094,17 +1138,21 @@ mk_msg_flags(PduType, SecLevel) -> mk_v3_packet_entry(NoteStore, Domain, Addr, {SecModel, SecName, SecLevel, TargetAddrName}, - ScopedPDUBytes, Pdu, ContextEngineID, ContextName) -> - %% 7.1.7 - ?vtrace("mk_v3_packet_entry -> entry - 7.1.7", []), - MsgID = generate_msg_id(), - PduType = Pdu#pdu.type, - MsgFlags = mk_msg_flags(PduType, SecLevel), + ScopedPDUBytes, Pdu, _ContextEngineID, ContextName, + LocalEngineID) -> + %% rfc2272 7.1 step 77 + ?vtrace("mk_v3_packet_entry -> entry - RFC2272-7.1:7", []), + MsgVersion = 'version-3', % 7.1:7a + MsgID = generate_msg_id(), % 7.1:7b + MaxMsgSz = get_max_message_size(), % 7.1:7c + PduType = Pdu#pdu.type, + MsgFlags = mk_msg_flags(PduType, SecLevel), % 7.1:7d + MsgSecModel = SecModel, % 7.1:7e V3Hdr = #v3_hdr{msgID = MsgID, - msgMaxSize = get_max_message_size(), + msgMaxSize = MaxMsgSz, msgFlags = MsgFlags, - msgSecurityModel = SecModel}, - Message = #message{version = 'version-3', + msgSecurityModel = MsgSecModel}, + Message = #message{version = MsgVersion, vsn_hdr = V3Hdr, data = ScopedPDUBytes}, SecModule = @@ -1113,12 +1161,21 @@ mk_v3_packet_entry(NoteStore, Domain, Addr, snmpa_usm end, + %% + %% 7.1:8 - If the PDU is from the Response Class or the Internal Class + %% securityEngineID = snmpEngineID (local/source) + %% 7.1:9 - If the PDU is from the Unconfirmed Class + %% securityEngineID = snmpEngineID (local/source) + %% else + %% securityEngineID = targetEngineID (remote/destination) + %% + %% 7.1.9a ?vtrace("mk_v3_packet_entry -> sec engine id - 7.1.9a", []), SecEngineID = case PduType of 'snmpv2-trap' -> - snmp_framework_mib:get_engine_id(); + LocalEngineID; _ -> %% This is the implementation dependent target engine id %% procedure. @@ -1141,8 +1198,9 @@ mk_v3_packet_entry(NoteStore, Domain, Addr, ?vdebug("mk_v3_packet_entry -> secEngineID: ~p", [SecEngineID]), %% 7.1.9b - case catch SecModule:generate_outgoing_msg(Message, SecEngineID, - SecName, [], SecLevel) of + case (catch SecModule:generate_outgoing_msg(Message, SecEngineID, + SecName, [], SecLevel, + LocalEngineID)) of {'EXIT', Reason} -> config_err("~p (message: ~p)", [Reason, Message]), skip; @@ -1169,7 +1227,7 @@ mk_v3_packet_entry(NoteStore, Domain, Addr, sec_model = SecModel, sec_name = SecName, sec_level = SecLevel, - ctx_engine_id = ContextEngineID, + ctx_engine_id = LocalEngineID, ctx_name = ContextName, disco = false, req_id = Pdu#pdu.request_id}, @@ -1180,15 +1238,16 @@ mk_v3_packet_entry(NoteStore, Domain, Addr, mk_v3_packet_list(NoteStore, To, - ScopedPDUBytes, Pdu, ContextEngineID, ContextName) -> + ScopedPDUBytes, Pdu, ContextEngineID, ContextName, + LocalEngineID) -> mk_v3_packet_list(NoteStore, To, ScopedPDUBytes, Pdu, - ContextEngineID, ContextName, []). + ContextEngineID, ContextName, LocalEngineID, []). mk_v3_packet_list(_, [], _ScopedPDUBytes, _Pdu, _ContextEngineID, _ContextName, - Acc) -> + _LocalEngineID, Acc) -> lists:reverse(Acc); %% This clause is for backward compatibillity reasons @@ -1196,20 +1255,21 @@ mk_v3_packet_list(_, [], mk_v3_packet_list(NoteStore, [{{?snmpUDPDomain, [A,B,C,D,U1,U2]}, SecData} | T], ScopedPDUBytes, Pdu, ContextEngineID, ContextName, - Acc) -> + LocalEngineID, Acc) -> case mk_v3_packet_entry(NoteStore, snmpUDPDomain, {{A,B,C,D}, U1 bsl 8 + U2}, SecData, ScopedPDUBytes, Pdu, - ContextEngineID, ContextName) of + ContextEngineID, ContextName, LocalEngineID) of skip -> mk_v3_packet_list(NoteStore, T, ScopedPDUBytes, Pdu, - ContextEngineID, ContextName, + ContextEngineID, ContextName, LocalEngineID, Acc); {ok, Entry} -> mk_v3_packet_list(NoteStore, T, ScopedPDUBytes, Pdu, - ContextEngineID, ContextName, [Entry | Acc]) + ContextEngineID, ContextName, LocalEngineID, + [Entry | Acc]) end; %% This is the new clause @@ -1218,11 +1278,11 @@ mk_v3_packet_list(NoteStore, mk_v3_packet_list(NoteStore, [{{Domain, Addr}, SecData} | T], ScopedPDUBytes, Pdu, ContextEngineID, ContextName, - Acc) -> + LocalEngineID, Acc) -> case mk_v3_packet_entry(NoteStore, Domain, Addr, SecData, ScopedPDUBytes, Pdu, - ContextEngineID, ContextName) of + ContextEngineID, ContextName, LocalEngineID) of skip -> mk_v3_packet_list(NoteStore, T, ScopedPDUBytes, Pdu, @@ -1230,7 +1290,8 @@ mk_v3_packet_list(NoteStore, {ok, Entry} -> mk_v3_packet_list(NoteStore, T, ScopedPDUBytes, Pdu, - ContextEngineID, ContextName, [Entry | Acc]) + ContextEngineID, ContextName, + LocalEngineID, [Entry | Acc]) end. @@ -1253,6 +1314,9 @@ gen(Id) -> get_target_engine_id(TargetAddrName) -> snmp_target_mib:get_target_engine_id(TargetAddrName). +get_engine_max_message_size(_LocalEngineID) -> + snmp_framework_mib:get_engine_max_message_size(). + sec_module(?SEC_USM) -> snmpa_usm. diff --git a/lib/snmp/src/agent/snmpa_trap.erl b/lib/snmp/src/agent/snmpa_trap.erl index b1096b1135..450cb2e9f4 100644 --- a/lib/snmp/src/agent/snmpa_trap.erl +++ b/lib/snmp/src/agent/snmpa_trap.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1996-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% -module(snmpa_trap). @@ -23,14 +23,18 @@ %%%----------------------------------------------------------------- %% External exports -export([construct_trap/2, - try_initialise_vars/2, send_trap/6]). + try_initialise_vars/2, + send_trap/6, send_trap/7]). -export([send_discovery/5]). %% Internal exports --export([init_v2_inform/9, init_v3_inform/9, send_inform/6]). +-export([init_v2_inform/9, + init_v3_inform/9, init_v3_inform/10, + send_inform/6]). -export([init_discovery_inform/12, send_discovery_inform/5]). -include("snmp_types.hrl"). +-include("snmpa_internal.hrl"). -include("SNMPv2-MIB.hrl"). -include("SNMPv2-TM.hrl"). -include("SNMPv2-TC.hrl"). @@ -331,13 +335,20 @@ make_varbind_list(Varbinds) -> %% SnmpTargetAddrTable (using the Tag). %%----------------------------------------------------------------- send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, NetIf) -> - (catch do_send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, NetIf)). + LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID, + send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, + LocalEngineID, NetIf). + +send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, LocalEngineID, NetIf) -> + (catch do_send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, + LocalEngineID, NetIf)). -do_send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, NetIf) -> +do_send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, + LocalEngineID, NetIf) -> VarbindList = make_varbind_list(Vbs), Dests = find_dests(NotifyName), send_trap_pdus(Dests, ContextName, {TrapRec, VarbindList}, [], [], [], - Recv, NetIf). + Recv, LocalEngineID, NetIf). send_discovery(TargetName, Record, ContextName, Vbs, NetIf) -> case find_dest(TargetName) of @@ -619,7 +630,9 @@ send_discovery_inform(Parent, Timeout, Retry, Msg, NetIf) -> %%----------------------------------------------------------------- send_trap_pdus([{DestAddr, TargetName, {MpModel, SecModel, SecName, SecLevel}, Type} | T], - ContextName,{TrapRec, Vbs}, V1Res, V2Res, V3Res, Recv, NetIf) -> + ContextName, + {TrapRec, Vbs}, V1Res, V2Res, V3Res, Recv, + LocalEngineID, NetIf) -> ?vdebug("send trap pdus: " "~n Destination address: ~p" "~n Target name: ~p" @@ -634,7 +647,7 @@ send_trap_pdus([{DestAddr, TargetName, {MpModel, SecModel, SecName, SecLevel}, case check_all_varbinds(TrapRec, Vbs, MibView) of true when MpModel =:= ?MP_V1 -> ?vtrace("send_trap_pdus -> v1 mp model",[]), - ContextEngineId = snmp_framework_mib:get_engine_id(), + ContextEngineId = LocalEngineID, case snmp_community_mib:vacm2community({SecName, ContextEngineId, ContextName}, @@ -644,16 +657,18 @@ send_trap_pdus([{DestAddr, TargetName, {MpModel, SecModel, SecName, SecLevel}, [element(2, DestAddr)]), send_trap_pdus(T, ContextName, {TrapRec, Vbs}, [{DestAddr, Community} | V1Res], - V2Res, V3Res, Recv, NetIf); + V2Res, V3Res, Recv, + LocalEngineID, NetIf); undefined -> ?vdebug("No community found for v1 dest: ~p", [element(2, DestAddr)]), send_trap_pdus(T, ContextName, {TrapRec, Vbs}, - V1Res, V2Res, V3Res, Recv, NetIf) + V1Res, V2Res, V3Res, Recv, + LocalEngineID, NetIf) end; true when MpModel =:= ?MP_V2C -> ?vtrace("send_trap_pdus -> v2c mp model",[]), - ContextEngineId = snmp_framework_mib:get_engine_id(), + ContextEngineId = LocalEngineID, case snmp_community_mib:vacm2community({SecName, ContextEngineId, ContextName}, @@ -664,12 +679,13 @@ send_trap_pdus([{DestAddr, TargetName, {MpModel, SecModel, SecName, SecLevel}, send_trap_pdus(T, ContextName, {TrapRec, Vbs}, V1Res, [{DestAddr, Community, Type}|V2Res], - V3Res, Recv, NetIf); + V3Res, Recv, LocalEngineID, NetIf); undefined -> ?vdebug("No community found for v2c dest: ~p", [element(2, DestAddr)]), send_trap_pdus(T, ContextName, {TrapRec, Vbs}, - V1Res, V2Res, V3Res, Recv, NetIf) + V1Res, V2Res, V3Res, Recv, + LocalEngineID, NetIf) end; true when MpModel =:= ?MP_V3 -> ?vtrace("send_trap_pdus -> v3 mp model",[]), @@ -678,18 +694,20 @@ send_trap_pdus([{DestAddr, TargetName, {MpModel, SecModel, SecName, SecLevel}, send_trap_pdus(T, ContextName, {TrapRec, Vbs}, V1Res, V2Res, [{DestAddr, MsgData, Type} | V3Res], - Recv, NetIf); + Recv, LocalEngineID, NetIf); true -> ?vlog("bad MpModel ~p for dest ~p", [MpModel, element(2, DestAddr)]), send_trap_pdus(T, ContextName, {TrapRec, Vbs}, - V1Res, V2Res, V3Res, Recv, NetIf); + V1Res, V2Res, V3Res, Recv, + LocalEngineID, NetIf); _ -> ?vlog("no access for dest: " "~n ~p in target ~p", [element(2, DestAddr), TargetName]), send_trap_pdus(T, ContextName, {TrapRec, Vbs}, - V1Res, V2Res, V3Res, Recv, NetIf) + V1Res, V2Res, V3Res, Recv, + LocalEngineID, NetIf) end; {discarded, Reason} -> ?vlog("mib view error ~p for" @@ -697,10 +715,10 @@ send_trap_pdus([{DestAddr, TargetName, {MpModel, SecModel, SecName, SecLevel}, "~n SecName: ~w", [Reason, element(2, DestAddr), SecName]), send_trap_pdus(T, ContextName, {TrapRec, Vbs}, - V1Res, V2Res, V3Res, Recv, NetIf) + V1Res, V2Res, V3Res, Recv, LocalEngineID, NetIf) end; send_trap_pdus([], ContextName, {TrapRec, Vbs}, V1Res, V2Res, V3Res, - Recv, NetIf) -> + Recv, LocalEngineID, NetIf) -> SysUpTime = snmp_standard_mib:sys_up_time(), ?vdebug("send trap pdus with sysUpTime ~p", [SysUpTime]), InformRecvs = get_inform_recvs(V2Res ++ V3Res), @@ -708,7 +726,8 @@ send_trap_pdus([], ContextName, {TrapRec, Vbs}, V1Res, V2Res, V3Res, deliver_recv(Recv, snmp_targets, InformTargets), send_v1_trap(TrapRec, V1Res, Vbs, NetIf, SysUpTime), send_v2_trap(TrapRec, V2Res, Vbs, Recv, NetIf, SysUpTime), - send_v3_trap(TrapRec, V3Res, Vbs, Recv, NetIf, SysUpTime, ContextName). + send_v3_trap(TrapRec, V3Res, Vbs, Recv, LocalEngineID, NetIf, + SysUpTime, ContextName). send_v1_trap(_TrapRec, [], _Vbs, _NetIf, _SysUpTime) -> ok; @@ -762,21 +781,25 @@ send_v2_trap(TrapRec, V2Res, Vbs, Recv, NetIf, SysUpTime) -> do_send_v2_trap(TrapRecvs, IVbs, NetIf), do_send_v2_inform(InformRecvs, IVbs, Recv, NetIf). -send_v3_trap(_TrapRec, [], _Vbs, _Recv, _NetIf, _SysUpTime, _ContextName) -> +send_v3_trap(_TrapRec, [], _Vbs, _Recv, _LocalEngineID, + _NetIf, _SysUpTime, _ContextName) -> ok; -send_v3_trap(TrapRec, V3Res, Vbs, Recv, NetIf, SysUpTime, ContextName) -> +send_v3_trap(TrapRec, V3Res, Vbs, Recv, LocalEngineID, + NetIf, SysUpTime, ContextName) -> ?vdebug("prepare to send v3 trap",[]), {_Oid, IVbs} = mk_v2_trap(TrapRec, Vbs, SysUpTime), % v2 refers to SMIv2; - TrapRecvs = get_trap_recvs(V3Res), % same SMI for v3 + TrapRecvs = get_trap_recvs(V3Res), % same SMI for v3 InformRecvs = get_inform_recvs(V3Res), do_send_v3_trap(TrapRecvs, ContextName, IVbs, NetIf), - do_send_v3_inform(InformRecvs, ContextName, IVbs, Recv, NetIf). + do_send_v3_inform(InformRecvs, ContextName, IVbs, Recv, + LocalEngineID, NetIf). mk_v2_trap(#notification{oid = Oid}, Vbs, SysUpTime) -> ?vtrace("make v2 notification '~p'",[Oid]), mk_v2_notif(Oid, Vbs, SysUpTime); -mk_v2_trap(#trap{enterpriseoid = Enter, specificcode = Spec}, Vbs, SysUpTime) -> +mk_v2_trap(#trap{enterpriseoid = Enter, specificcode = Spec}, + Vbs, SysUpTime) -> %% Use alg. in rfc1908 to map a v1 trap to a v2 trap ?vtrace("make v2 trap for '~p' with ~p",[Enter,Spec]), {Oid,Enterp} = @@ -845,16 +868,16 @@ do_send_v3_trap(Recvs, ContextName, Vbs, NetIf) -> end, Recvs), ok. -do_send_v3_inform([], _ContextName, _Vbs, _Recv, _NetIf) -> +do_send_v3_inform([], _ContextName, _Vbs, _Recv, _LocalEngineID, _NetIf) -> ok; -do_send_v3_inform(Recvs, ContextName, Vbs, Recv, NetIf) -> +do_send_v3_inform(Recvs, ContextName, Vbs, Recv, LocalEngineID, NetIf) -> lists:foreach( fun({Addr, MsgData, Timeout, Retry}) -> ?vtrace("~n start inform sender to send v3 inform to ~p", [Addr]), proc_lib:spawn_link(?MODULE, init_v3_inform, [{Addr, MsgData}, Timeout, Retry, Vbs, - Recv, NetIf, ContextName, + Recv, LocalEngineID, NetIf, ContextName, get(verbosity), get(sname)]) end, Recvs). @@ -874,7 +897,13 @@ init_v2_inform(Addr, Timeout, Retry, Vbs, Recv, NetIf, Community,V,S) -> %% New process -init_v3_inform(Addr, Timeout, Retry, Vbs, Recv, NetIf, ContextName,V,S) -> +init_v3_inform(Addr, Timeout, Retry, Vbs, Recv, NetIf, ContextName, V, S) -> + LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID, + init_v3_inform(Addr, Timeout, Retry, Vbs, Recv, LocalEngineID, + NetIf, ContextName, V, S). + +init_v3_inform(Addr, Timeout, Retry, Vbs, Recv, LocalEngineID, + NetIf, ContextName, V, S) -> %% Make a new Inform for each recipient; they need unique %% request-ids! put(verbosity,V), @@ -882,7 +911,7 @@ init_v3_inform(Addr, Timeout, Retry, Vbs, Recv, NetIf, ContextName,V,S) -> ?vdebug("~n starting with timeout = ~p and retry = ~p", [Timeout,Retry]), InformPdu = make_v2_notif_pdu(Vbs, 'inform-request'), % Yes, v2 - ContextEngineId = snmp_framework_mib:get_engine_id(), + ContextEngineId = LocalEngineID, Msg = {send_pdu_req, 'version-3', InformPdu, {v3, ContextEngineId, ContextName}, [Addr], self()}, ?MODULE:send_inform(Addr, Timeout*10, Retry, Msg, Recv, NetIf). diff --git a/lib/snmp/src/agent/snmpa_usm.erl b/lib/snmp/src/agent/snmpa_usm.erl index b94294844b..ae584bb3c1 100644 --- a/lib/snmp/src/agent/snmpa_usm.erl +++ b/lib/snmp/src/agent/snmpa_usm.erl @@ -19,8 +19,8 @@ -module(snmpa_usm). -export([ - process_incoming_msg/4, - generate_outgoing_msg/5, + process_incoming_msg/4, process_incoming_msg/5, + generate_outgoing_msg/5, generate_outgoing_msg/6, generate_discovery_msg/4, generate_discovery_msg/5, current_statsNotInTimeWindows_vb/0 ]). @@ -33,6 +33,7 @@ -define(VMODULE,"A-USM"). -include("snmp_verbosity.hrl"). +-include("snmpa_internal.hrl"). %%----------------------------------------------------------------- @@ -58,7 +59,11 @@ %%----------------------------------------------------------------- process_incoming_msg(Packet, Data, SecParams, SecLevel) -> - TermDiscoEnabled = is_terminating_discovery_enabled(), + LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID, + process_incoming_msg(Packet, Data, SecParams, SecLevel, LocalEngineID). + +process_incoming_msg(Packet, Data, SecParams, SecLevel, LocalEngineID) -> + TermDiscoEnabled = is_terminating_discovery_enabled(), TermTriggerUsername = terminating_trigger_username(), %% 3.2.1 ?vtrace("process_incoming_msg -> check security parms: 3.2.1",[]), @@ -124,7 +129,7 @@ process_incoming_msg(Packet, Data, SecParams, SecLevel) -> "~n ~p",[UsmUser]), DiscoOrPlain = authenticate_incoming(Packet, UsmSecParams, UsmUser, - SecLevel), + SecLevel, LocalEngineID), %% 3.2.8 ?vtrace("process_incoming_msg -> " "decrypt scoped data: 3.2.8",[]), @@ -166,7 +171,8 @@ process_discovery_msg(MsgAuthEngineID, Data, SecLevel) -> end. -authenticate_incoming(Packet, UsmSecParams, UsmUser, SecLevel) -> +authenticate_incoming(Packet, UsmSecParams, UsmUser, SecLevel, + LocalEngineID) -> %% 3.2.6 ?vtrace("authenticate_incoming -> 3.2.6", []), AuthProtocol = element(?usmUserAuthProtocol, UsmUser), @@ -190,7 +196,8 @@ authenticate_incoming(Packet, UsmSecParams, UsmUser, SecLevel) -> SecName, MsgAuthEngineID, MsgAuthEngineBoots, - MsgAuthEngineTime) of + MsgAuthEngineTime, + LocalEngineID) of discovery -> discovery; true -> @@ -205,15 +212,15 @@ authenticate_incoming(Packet, UsmSecParams, UsmUser, SecLevel) -> plain end. -authoritative(SecName, MsgAuthEngineBoots, MsgAuthEngineTime) -> +authoritative(SecName, MsgAuthEngineBoots, MsgAuthEngineTime, LocalEngineID) -> ?vtrace("authoritative -> entry with" "~n SecName: ~p" "~n MsgAuthEngineBoots: ~p" "~n MsgAuthEngineTime: ~p", [SecName, MsgAuthEngineBoots, MsgAuthEngineTime]), - SnmpEngineBoots = snmp_framework_mib:get_engine_boots(), + SnmpEngineBoots = get_local_engine_boots(LocalEngineID), ?vtrace("authoritative -> SnmpEngineBoots: ~p", [SnmpEngineBoots]), - SnmpEngineTime = snmp_framework_mib:get_engine_time(), + SnmpEngineTime = get_local_engine_time(LocalEngineID), ?vtrace("authoritative -> SnmpEngineTime: ~p", [SnmpEngineTime]), InTimeWindow = if @@ -320,11 +327,12 @@ non_authoritative(SecName, end. -is_auth(?usmNoAuthProtocol, _, _, _, SecName, _, _, _) -> % 3.2.5 +is_auth(?usmNoAuthProtocol, _, _, _, SecName, _, _, _, _) -> % 3.2.5 error(usmStatsUnsupportedSecLevels, ?usmStatsUnsupportedSecLevels_instance, SecName); % OTP-5464 is_auth(AuthProtocol, AuthKey, AuthParams, Packet, SecName, - MsgAuthEngineID, MsgAuthEngineBoots, MsgAuthEngineTime) -> + MsgAuthEngineID, MsgAuthEngineBoots, MsgAuthEngineTime, + LocalEngineID) -> TermDiscoEnabled = is_terminating_discovery_enabled(), TermDiscoStage2 = terminating_discovery_stage2(), IsAuth = auth_in(AuthProtocol, AuthKey, AuthParams, Packet), @@ -334,7 +342,7 @@ is_auth(AuthProtocol, AuthKey, AuthParams, Packet, SecName, %% 3.2.7 ?vtrace("is_auth -> " "retrieve EngineBoots and EngineTime: 3.2.7",[]), - SnmpEngineID = snmp_framework_mib:get_engine_id(), + SnmpEngineID = LocalEngineID, ?vtrace("is_auth -> SnmpEngineID: ~p", [SnmpEngineID]), case MsgAuthEngineID of SnmpEngineID when ((MsgAuthEngineBoots =:= 0) andalso @@ -351,12 +359,14 @@ is_auth(AuthProtocol, AuthKey, AuthParams, Packet, SecName, %% This will *always* result in the manager *not* %% beeing in timewindow authoritative(SecName, - MsgAuthEngineBoots, MsgAuthEngineTime); + MsgAuthEngineBoots, MsgAuthEngineTime, + LocalEngineID); SnmpEngineID -> %% 3.2.7a ?vtrace("is_auth -> we are authoritative: 3.2.7a", []), authoritative(SecName, - MsgAuthEngineBoots, MsgAuthEngineTime); + MsgAuthEngineBoots, MsgAuthEngineTime, + LocalEngineID); _ -> %% 3.2.7b - we're non-authoritative ?vtrace("is_auth -> we are non-authoritative: 3.2.7b",[]), @@ -418,12 +428,19 @@ try_decrypt(?usmAesCfb128Protocol, generate_outgoing_msg(Message, SecEngineID, SecName, SecData, SecLevel) -> + LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID, + generate_outgoing_msg(Message, SecEngineID, SecName, SecData, SecLevel, + LocalEngineID). + +generate_outgoing_msg(Message, SecEngineID, SecName, SecData, SecLevel, + LocalEngineID) -> %% 3.1.1 ?vtrace("generate_outgoing_msg -> [3.1.1] entry with" - "~n SecEngineID: ~p" - "~n SecName: ~p" - "~n SecLevel: ~w", - [SecEngineID, SecName, SecLevel]), + "~n SecEngineID: ~p" + "~n SecName: ~p" + "~n SecLevel: ~w" + "~n LocalEngineID: ~p", + [SecEngineID, SecName, SecLevel, LocalEngineID]), {UserName, AuthProtocol, PrivProtocol, AuthKey, PrivKey} = case SecData of [] -> % 3.1.1b @@ -439,7 +456,7 @@ generate_outgoing_msg(Message, SecEngineID, SecName, SecData, SecLevel) -> element(?usmUserPrivKey, User)}; {_, Name,_,_,_,_,_,_,_,_,_,_,_, RowStatus,_,_} -> ?vdebug("generate_outgoing_msg -> " - "found user ~p with wrong row status: ~p", + "found not active user ~p: ~p", [Name, RowStatus]), error(unknownSecurityName); _ -> @@ -460,7 +477,7 @@ generate_outgoing_msg(Message, SecEngineID, SecName, SecData, SecLevel) -> ScopedPduBytes = Message#message.data, {ScopedPduData, MsgPrivParams} = encrypt(ScopedPduBytes, PrivProtocol, PrivKey, SecLevel), - SnmpEngineID = snmp_framework_mib:get_engine_id(), + SnmpEngineID = LocalEngineID, ?vtrace("generate_outgoing_msg -> SnmpEngineID: ~p [3.1.6]", [SnmpEngineID]), %% 3.1.6 @@ -474,8 +491,8 @@ generate_outgoing_msg(Message, SecEngineID, SecName, SecData, SecLevel) -> {get_engine_boots(SecEngineID), get_engine_time(SecEngineID)}; _ -> - {snmp_framework_mib:get_engine_boots(), - snmp_framework_mib:get_engine_time()} + {get_local_engine_boots(SnmpEngineID), + get_local_engine_time(SnmpEngineID)} end, %% 3.1.5 - 3.1.7 ?vtrace("generate_outgoing_msg -> [3.1.5 - 3.1.7]",[]), @@ -681,6 +698,19 @@ current_statsNotInTimeWindows_vb() -> value = get_counter(usmStatsNotInTimeWindows)}. + +%%----------------------------------------------------------------- +%% Future profing... +%%----------------------------------------------------------------- + +get_local_engine_boots(_LocalEngineID) -> + snmp_framework_mib:get_engine_boots(). + +get_local_engine_time(_LocalEngineID) -> + snmp_framework_mib:get_engine_time(). + + + %%----------------------------------------------------------------- %% We cache the local values of all non-auth engines we know. %% Keep the values in the snmp_agent_table. diff --git a/lib/snmp/src/app/snmp.appup.src b/lib/snmp/src/app/snmp.appup.src index a138a2dfd1..9ad16ffad2 100644 --- a/lib/snmp/src/app/snmp.appup.src +++ b/lib/snmp/src/app/snmp.appup.src @@ -22,44 +22,68 @@ %% ----- U p g r a d e ------------------------------------------------------- [ + {"4.16.2", + [ + {load_module, snmp_log, soft_purge, soft_purge, []}, + {load_module, snmpa, soft_purge, soft_purge, [snmpa_agent]}, + {load_module, snmpa_mpd, soft_purge, soft_purge, [snmpa_usm]}, + {load_module, snmpa_usm, soft_purge, soft_purge, []}, + {update, snmpa_agent, soft, soft_purge, soft_purge, []}, + + {load_module, snmpm_mpd, soft_purge, soft_purge, []} + ] + }, {"4.16.1", [ + {load_module, snmp_log, soft_purge, soft_purge, []}, + {load_module, snmp_pdus, soft_purge, soft_purge, []}, + {load_module, snmp_usm, soft_purge, soft_purge, []}, + {load_module, snmpa, soft_purge, soft_purge, [snmpa_agent]}, + {load_module, snmpa_mpd, soft_purge, soft_purge, [snmpa_usm]}, {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]}, - {load_module, snmp_usm, soft_purge, soft_purge, []}, - {load_module, snmp_pdus, soft_purge, soft_purge, []}, - {update, snmpm_server, soft, soft_purge, soft_purge, []}, {update, snmpa_mib, soft, soft_purge, soft_purge, []}, - {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mib]} + {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mib]}, + + {load_module, snmpm_mpd, soft_purge, soft_purge, []}, + {update, snmpm_server, soft, soft_purge, soft_purge, []} ] }, {"4.16", [ - {load_module, snmpa, soft_purge, soft_purge, [snmpa_agent]}, - {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]}, - {load_module, snmp_usm, soft_purge, soft_purge, []}, + {load_module, snmp_log, soft_purge, soft_purge, []}, {load_module, snmp_pdus, soft_purge, soft_purge, []}, + {load_module, snmp_usm, soft_purge, soft_purge, []}, + + {load_module, snmpa, soft_purge, soft_purge, [snmpa_agent]}, {load_module, snmpa_general_db, soft_purge, soft_purge, []}, - {update, snmpm_net_if, soft, soft_purge, soft_purge, []}, - {update, snmpm_server, soft, soft_purge, soft_purge, []}, + {load_module, snmpa_mpd, soft_purge, soft_purge, [snmpa_usm]}, + {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]}, {update, snmpa_mib, soft, soft_purge, soft_purge, []}, - {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mib]} + {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mib]}, + + {load_module, snmpm_mpd, soft_purge, soft_purge, []}, + {update, snmpm_net_if, soft, soft_purge, soft_purge, []}, + {update, snmpm_server, soft, soft_purge, soft_purge, []} ] }, {"4.15", [ - {load_module, snmp_pdus, soft_purge, soft_purge, []}, - {load_module, snmpa, soft_purge, soft_purge, [snmpa_agent, snmp_log]}, {load_module, snmp_config, soft_purge, soft_purge, []}, {load_module, snmp_log, soft_purge, soft_purge, []}, + {load_module, snmp_pdus, soft_purge, soft_purge, []}, + {load_module, snmp_usm, soft_purge, soft_purge, []}, + + {load_module, snmpa, soft_purge, soft_purge, [snmp_log, snmpa_agent]}, {load_module, snmpa_general_db, soft_purge, soft_purge, []}, + {load_module, snmpa_mpd, soft_purge, soft_purge, [snmpa_usm]}, + {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]}, {update, snmpa_net_if, {advanced, upgrade_from_pre_4_16}, soft_purge, soft_purge, [snmpa_agent, snmp_log]}, {update, snmpa_mib, soft, soft_purge, soft_purge, []}, {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mib]}, - {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]}, - {load_module, snmp_usm, soft_purge, soft_purge, []}, + {load_module, snmpm_mpd, soft_purge, soft_purge, []}, {update, snmpm_net_if, {advanced, upgrade_from_pre_4_16}, soft_purge, soft_purge, [snmpm_config, snmp_log]}, {update, snmpm_config, soft, soft_purge, soft_purge, []}, @@ -68,18 +92,21 @@ }, {"4.14", [ - {load_module, snmp_pdus, soft_purge, soft_purge, []}, - {load_module, snmpa, soft_purge, soft_purge, [snmpa_agent, snmp_log]}, {load_module, snmp_config, soft_purge, soft_purge, []}, {load_module, snmp_log, soft_purge, soft_purge, []}, + {load_module, snmp_pdus, soft_purge, soft_purge, []}, + {load_module, snmp_usm, soft_purge, soft_purge, []}, + + {load_module, snmpa, soft_purge, soft_purge, [snmp_log, snmpa_agent]}, {load_module, snmpa_general_db, soft_purge, soft_purge, []}, + {load_module, snmpa_mpd, soft_purge, soft_purge, [snmpa_usm]}, + {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]}, {update, snmpa_net_if, {advanced, upgrade_from_pre_4_16}, - soft_purge, soft_purge, [snmpa_agent, snmp_log]}, + soft_purge, soft_purge, [snmp_log, snmpa_agent]}, {update, snmpa_mib, soft, soft_purge, soft_purge, []}, {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mib]}, - {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]}, - {load_module, snmp_usm, soft_purge, soft_purge, []}, + {load_module, snmpm_mpd, soft_purge, soft_purge, []}, {load_module, snmpm_user, soft_purge, soft_purge, []}, {load_module, snmpm_user_default, soft_purge, soft_purge, [snmpm_user]}, {update, snmpm_net_if, {advanced, upgrade_from_pre_4_16}, @@ -91,19 +118,22 @@ }, {"4.13.5", [ - {load_module, snmp_pdus, soft_purge, soft_purge, []}, - {load_module, snmpa_mib_data, soft_purge, soft_purge, []}, - {load_module, snmpa, soft_purge, soft_purge, [snmpa_agent, snmp_log]}, {load_module, snmp_config, soft_purge, soft_purge, []}, {load_module, snmp_log, soft_purge, soft_purge, []}, + {load_module, snmp_pdus, soft_purge, soft_purge, []}, + {load_module, snmp_usm, soft_purge, soft_purge, []}, + + {load_module, snmpa, soft_purge, soft_purge, [snmp_log, snmpa_agent]}, {load_module, snmpa_general_db, soft_purge, soft_purge, []}, + {load_module, snmpa_mib_data, soft_purge, soft_purge, []}, + {load_module, snmpa_mpd, soft_purge, soft_purge, [snmpa_usm]}, + {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]}, {update, snmpa_net_if, {advanced, upgrade_from_pre_4_16}, soft_purge, soft_purge, [snmpa_agent, snmp_log]}, {update, snmpa_mib, soft, soft_purge, soft_purge, [snmpa_mib_data]}, {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mib]}, - {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]}, - {load_module, snmp_usm, soft_purge, soft_purge, []}, + {load_module, snmpm_mpd, soft_purge, soft_purge, []}, {load_module, snmpm_user, soft_purge, soft_purge, []}, {load_module, snmpm_user_default, soft_purge, soft_purge, [snmpm_user]}, {update, snmpm_net_if, {advanced, upgrade_from_pre_4_14}, @@ -119,45 +149,69 @@ %% ------D o w n g r a d e --------------------------------------------------- [ + {"4.16.2", + [ + {load_module, snmp_log, soft_purge, soft_purge, []}, + + {load_module, snmpa, soft_purge, soft_purge, [snmpa_agent]}, + {load_module, snmpa_mpd, soft_purge, soft_purge, [snmpa_usm]}, + {load_module, snmpa_usm, soft_purge, soft_purge, []}, + {update, snmpa_agent, soft, soft_purge, soft_purge, []}, + + {load_module, snmpm_mpd, soft_purge, soft_purge, []} + ] + }, {"4.16.1", [ + {load_module, snmp_log, soft_purge, soft_purge, []}, + {load_module, snmp_pdus, soft_purge, soft_purge, []}, + {load_module, snmp_usm, soft_purge, soft_purge, []}, + {load_module, snmpa, soft_purge, soft_purge, [snmpa_agent]}, + {load_module, snmpa_mpd, soft_purge, soft_purge, [snmpa_usm]}, {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]}, - {load_module, snmp_usm, soft_purge, soft_purge, []}, - {load_module, snmp_pdus, soft_purge, soft_purge, []}, - {update, snmpm_server, soft, soft_purge, soft_purge, []}, {update, snmpa_mib, soft, soft_purge, soft_purge, []}, - {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mib]} + {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mib]}, + + {load_module, snmpm_mpd, soft_purge, soft_purge, []}, + {update, snmpm_server, soft, soft_purge, soft_purge, []} ] }, {"4.16", [ - {load_module, snmpa, soft_purge, soft_purge, [snmpa_agent]}, - {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]}, - {load_module, snmp_usm, soft_purge, soft_purge, []}, + {load_module, snmp_log, soft_purge, soft_purge, []}, {load_module, snmp_pdus, soft_purge, soft_purge, []}, + {load_module, snmp_usm, soft_purge, soft_purge, []}, + + {load_module, snmpa, soft_purge, soft_purge, [snmpa_agent]}, {load_module, snmpa_general_db, soft_purge, soft_purge, []}, - {update, snmpm_net_if, soft, soft_purge, soft_purge, []}, - {update, snmpm_server, soft, soft_purge, soft_purge, []}, + {load_module, snmpa_mpd, soft_purge, soft_purge, [snmpa_usm]}, + {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]}, {update, snmpa_mib, soft, soft_purge, soft_purge, []}, - {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mib]} + {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mib]}, + + {load_module, snmpm_mpd, soft_purge, soft_purge, []}, + {update, snmpm_net_if, soft, soft_purge, soft_purge, []}, + {update, snmpm_server, soft, soft_purge, soft_purge, []} ] }, {"4.15", [ - {load_module, snmp_pdus, soft_purge, soft_purge, []}, - {load_module, snmpa, soft_purge, soft_purge, [snmpa_agent, snmp_log]}, {load_module, snmp_config, soft_purge, soft_purge, []}, {load_module, snmp_log, soft_purge, soft_purge, []}, + {load_module, snmp_pdus, soft_purge, soft_purge, []}, + {load_module, snmp_usm, soft_purge, soft_purge, []}, + + {load_module, snmpa, soft_purge, soft_purge, [snmpa_agent, snmp_log]}, {load_module, snmpa_general_db, soft_purge, soft_purge, []}, + {load_module, snmpa_mpd, soft_purge, soft_purge, [snmpa_usm]}, + {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]}, {update, snmpa_net_if, {advanced, downgrade_to_pre_4_16}, soft_purge, soft_purge, [snmpa_agent, snmp_log]}, {update, snmpa_mib, soft, soft_purge, soft_purge, []}, {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mib]}, - {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]}, - {load_module, snmp_usm, soft_purge, soft_purge, []}, - {load_module, snmpa_general_db, soft_purge, soft_purge, []}, + {load_module, snmpm_mpd, soft_purge, soft_purge, []}, {update, snmpm_net_if, {advanced, downgrade_to_pre_4_16}, soft_purge, soft_purge, [snmpm_config, snmp_log]}, {update, snmpm_config, soft, soft_purge, soft_purge, []}, @@ -166,18 +220,21 @@ }, {"4.14", [ - {load_module, snmp_pdus, soft_purge, soft_purge, []}, - {load_module, snmpa, soft_purge, soft_purge, [snmpa_agent, snmp_log]}, {load_module, snmp_config, soft_purge, soft_purge, []}, {load_module, snmp_log, soft_purge, soft_purge, []}, + {load_module, snmp_pdus, soft_purge, soft_purge, []}, + {load_module, snmp_usm, soft_purge, soft_purge, []}, + + {load_module, snmpa, soft_purge, soft_purge, [snmpa_agent, snmp_log]}, {load_module, snmpa_general_db, soft_purge, soft_purge, []}, + {load_module, snmpa_mpd, soft_purge, soft_purge, [snmpa_usm]}, + {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]}, {update, snmpa_net_if, {advanced, downgrade_to_pre_4_16}, soft_purge, soft_purge, [snmpa_agent, snmp_log]}, {update, snmpa_mib, soft, soft_purge, soft_purge, []}, {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mib]}, - {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]}, - {load_module, snmp_usm, soft_purge, soft_purge, []}, + {load_module, snmpm_mpd, soft_purge, soft_purge, []}, {load_module, snmpm_user, soft_purge, soft_purge, []}, {load_module, snmpm_user_default, soft_purge, soft_purge, [snmpm_user]}, {update, snmpm_net_if, {advanced, downgrade_to_pre_4_16}, @@ -189,19 +246,22 @@ }, {"4.13.5", [ - {load_module, snmp_pdus, soft_purge, soft_purge, []}, - {load_module, snmpa_mib_data, soft_purge, soft_purge, []}, {load_module, snmp_config, soft_purge, soft_purge, []}, - {load_module, snmpa, soft_purge, soft_purge, [snmpa_agent, snmp_log]}, {load_module, snmp_log, soft_purge, soft_purge, []}, + {load_module, snmp_pdus, soft_purge, soft_purge, []}, + {load_module, snmp_usm, soft_purge, soft_purge, []}, + + {load_module, snmpa, soft_purge, soft_purge, [snmp_log, snmpa_agent]}, {load_module, snmpa_general_db, soft_purge, soft_purge, []}, + {load_module, snmpa_mib_data, soft_purge, soft_purge, []}, + {load_module, snmpa_mpd, soft_purge, soft_purge, [snmpa_usm]}, + {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]}, {update, snmpa_net_if, {advanced, downgrade_to_pre_4_16}, soft_purge, soft_purge, [snmpa_agent, snmp_log]}, {update, snmpa_mib, soft, soft_purge, soft_purge, [snmpa_mib_data]}, {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mib]}, - {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]}, - {load_module, snmp_usm, soft_purge, soft_purge, []}, + {load_module, snmpm_mpd, soft_purge, soft_purge, []}, {load_module, snmpm_user, soft_purge, soft_purge, []}, {load_module, snmpm_user_default, soft_purge, soft_purge, [snmpm_user]}, {update, snmpm_net_if, {advanced, downgrade_to_pre_4_14}, diff --git a/lib/snmp/src/manager/snmpm_mpd.erl b/lib/snmp/src/manager/snmpm_mpd.erl index d76ad20051..7712370d28 100644 --- a/lib/snmp/src/manager/snmpm_mpd.erl +++ b/lib/snmp/src/manager/snmpm_mpd.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2004-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2004-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -257,11 +257,11 @@ process_v3_msg(NoteStore, Msg, Hdr, Data, Addr, Port, Log) -> end, ?vlog("7.2.7" - "~n ContextEngineID: \"~s\" " + "~n ContextEngineID: ~p " "~n context: \"~s\" ", [CtxEngineID, CtxName]), if - SecLevel == 3 -> % encrypted message - log decrypted pdu + SecLevel =:= 3 -> % encrypted message - log decrypted pdu Log({Hdr, ScopedPDUBytes}); true -> % otherwise, log binary Log(Msg) @@ -338,7 +338,8 @@ process_v3_msg(NoteStore, Msg, Hdr, Data, Addr, Port, Log) -> SnmpEngineID = get_engine_id(), case SecEngineID of SnmpEngineID -> % 7.2.13.b - ?vtrace("valid securityEngineID: ~p", [SecEngineID]), + ?vtrace("7.2.13d - valid securityEngineID: ~p", + [SecEngineID]), %% 4.2.2.1.1 - we don't handle proxys yet => we only %% handle CtxEngineID to ourselves %% Check that we actually know of an agent with this @@ -353,7 +354,9 @@ process_v3_msg(NoteStore, Msg, Hdr, Data, Addr, Port, Log) -> {MsgID, MsgSecModel, SecName, SecLevel, CtxEngineID, CtxName, SecData}, {ok, 'version-3', PDU, PduMMS, ACMData}; - _ -> + UnknownEngineID -> + ?vtrace("4.2.2.1.2 - UnknownEngineId: ~p", + [UnknownEngineID]), %% 4.2.2.1.2 NIsReportable = snmp_misc:is_reportable_pdu(Type), Val = inc(snmpUnknownPDUHandlers), @@ -377,7 +380,8 @@ process_v3_msg(NoteStore, Msg, Hdr, Data, Addr, Port, Log) -> end end; _ -> % 7.2.13.a - ?vinfo("invalid securityEngineID: ~p",[SecEngineID]), + ?vinfo("7.2.13a - invalid securityEngineID: ~p", + [SecEngineID]), discard({badSecurityEngineID, SecEngineID}) end; diff --git a/lib/snmp/test/snmp_agent_test.erl b/lib/snmp/test/snmp_agent_test.erl index af0581150a..2534147769 100644 --- a/lib/snmp/test/snmp_agent_test.erl +++ b/lib/snmp/test/snmp_agent_test.erl @@ -1046,7 +1046,7 @@ v1_cases() -> sparse_table, cnt_64, opaque, - + change_target_addr_config ]. @@ -1977,7 +1977,8 @@ inform_i(Config) -> ?P1("unload TestTrap & TestTrapv2..."), ?line unload_master("TestTrap"), - ?line unload_master("TestTrapv2"). + ?line unload_master("TestTrapv2"), + ok. v3_inform_i(X) -> %% <CONDITIONAL-SKIP> @@ -3446,7 +3447,7 @@ do_mul_set_err() -> ?line ?v1_2(expect(2, noSuchName, 1, any), expect(2, [{[friendsEntry, [2,3]], noSuchInstance}])), g([NewKeyc4]), - ?line ?v1_2(expect(3, noSuchName, 1, any), + ?line ?v1_2(expect(3, noSuchName, 1, any), expect(3, [{NewKeyc4, noSuchInstance}])). %% Req. SA-MIB @@ -3457,10 +3458,10 @@ sa_mib() -> ?line expect(2, [{[sa, [1,0]], "sa_test"}]). ma_trap1(MA) -> - snmpa:send_trap(MA, testTrap2, "standard trap"), + ok = snmpa:send_trap(MA, testTrap2, "standard trap"), ?line expect(1, trap, [system], 6, 1, [{[system, [4,0]], "{mbj,eklas}@erlang.ericsson.se"}]), - snmpa:send_trap(MA, testTrap1, "standard trap"), + ok = snmpa:send_trap(MA, testTrap1, "standard trap"), ?line expect(2, trap, [1,2,3] , 1, 0, [{[system, [4,0]], "{mbj,eklas}@erlang.ericsson.se"}]). @@ -3509,7 +3510,8 @@ ma_v2_trap1(MA) -> ?DBG("ma_v2_traps -> send standard trap: testTrapv21",[]), snmpa:send_trap(MA, testTrapv21, "standard trap"), ?line expect(2, v2trap, [{[sysUpTime, 0], any}, - {[snmpTrapOID, 0], ?snmp ++ [1]}]). + {[snmpTrapOID, 0], ?snmp ++ [1]}]), + ok. ma_v2_trap2(MA) -> snmpa:send_trap(MA,testTrapv22,"standard trap",[{sysContact,"pelle"}]), @@ -3517,7 +3519,7 @@ ma_v2_trap2(MA) -> {[snmpTrapOID, 0], ?system ++ [0,1]}, {[system, [4,0]], "pelle"}]). -%% Note: This test case takes a while... actually a couple of minutes. +%% Note: This test case takes a while... actually a couple of minutes. ma_v2_inform1(MA) -> ?DBG("ma_v2_inform1 -> entry with" "~n MA = ~p => " @@ -6219,12 +6221,15 @@ verify_old_info([Key|Keys], Info) -> is(S) -> [length(S) | S]. try_test(Func) -> + ?P2("try test ~w...", [Func]), snmp_agent_test_lib:try_test(?MODULE, Func). try_test(Func, A) -> + ?P2("try test ~w...", [Func]), snmp_agent_test_lib:try_test(?MODULE, Func, A). try_test(Func, A, Opts) -> + ?P2("try test ~w...", [Func]), snmp_agent_test_lib:try_test(?MODULE, Func, A, Opts). diff --git a/lib/snmp/test/snmp_agent_test_lib.erl b/lib/snmp/test/snmp_agent_test_lib.erl index 31b375efa9..9e89aa889c 100644 --- a/lib/snmp/test/snmp_agent_test_lib.erl +++ b/lib/snmp/test/snmp_agent_test_lib.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2005-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2005-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -421,7 +421,7 @@ start_agent(Config, Vsns, Opts) -> ?LOG("start_agent -> entry (~p) with" "~n Config: ~p" "~n Vsns: ~p" - "~n Opts: ~p",[node(), Config, Vsns, Opts]), + "~n Opts: ~p", [node(), Config, Vsns, Opts]), ?line AgentDir = ?config(agent_dir, Config), ?line SaNode = ?config(snmp_sa, Config), diff --git a/lib/snmp/vsn.mk b/lib/snmp/vsn.mk index c3704bf6c9..4ca1fb7901 100644 --- a/lib/snmp/vsn.mk +++ b/lib/snmp/vsn.mk @@ -17,11 +17,19 @@ # # %CopyrightEnd% -SNMP_VSN = 4.16.2 +SNMP_VSN = 4.17 PRE_VSN = APP_VSN = "snmp-$(SNMP_VSN)$(PRE_VSN)" -TICKETS = OTP-8563 OTP-8574 OTP-8594 OTP-8595 OTP-8646 OTP-8648 +TICKETS = OTP-8478 + +TICKETS_4_16_2 = \ + OTP-8563 \ + OTP-8574 \ + OTP-8594 \ + OTP-8595 \ + OTP-8646 \ + OTP-8648 TICKETS_4_16_1 = \ OTP-8480 \ @@ -32,149 +40,18 @@ TICKETS_4_16 = \ OTP-8433 \ OTP-8442 -TICKETS_4_15 = OTP-8229 OTP-8249 - -TICKETS_4_14 = OTP-8223 OTP-8228 OTP-8237 - -TICKETS_4_13_5 = OTP-8116 OTP-8120 OTP-8181 OTP-8182 - -TICKETS_4_13_4 = OTP-8044 OTP-8062 OTP-8098 - -TICKETS_4_13_3 = OTP-8015 OTP-8020 - -TICKETS_4_13_2 = OTP-7961 OTP-7977 OTP-7983 OTP-7989 - -TICKETS_4_13_1 = OTP-7902 - -TICKETS_4_13 = OTP-7571 OTP-7735 OTP-7836 OTP-7851 - -TICKETS_4_12_2 = OTP-7868 - -TICKETS_4_12_1 = OTP-7695 OTP-7698 - -TICKETS_4_12 = OTP-7346 OTP-7525 - -TICKETS_4_11_2 = OTP-7570 OTP-7575 - -TICKETS_4_11_1 = OTP-7390 OTP-7412 OTP-7426 OTP-7432 - -TICKETS_4_11 = OTP-7201 OTP-7287 OTP-7319 OTP-7369 OTP-7371 OTP-7377 OTP-7381 - -TICKETS_4_10_3 = OTP-7219 - -TICKETS_4_10_2 = OTP-7152 OTP-7153 OTP-7157 OTP-7158 OTP-7159 OTP-7160 - -TICKETS_4_10_1 = OTP-7083 OTP-7109 OTP-7110 OTP-7119 OTP-7121 OTP-7123 - -TICKETS_4_10 = OTP-6649 OTP-6841 OTP-6898 OTP-6945 - -TICKETS_4_9_6 = OTP-6840 OTP-6843 - -TICKETS_4_9_5 = OTP-6805 OTP-6815 - -TICKETS_4_9_4 = OTP-6784 OTP-6771 - -TICKETS_4_9_3 = OTP-6605 OTP-6712 OTP-6713 - -TICKETS_4_9_2 = OTP-6571 - -TICKETS_4_9_1 = OTP-6566 OTP-6569 - -TICKETS_4_9 = \ - OTP-6317 \ - OTP-6318 \ - OTP-6383 \ - OTP-6487 \ - OTP-6515 \ - OTP-6518 \ - OTP-6529 \ - OTP-6532 \ - OTP-6533 \ - OTP-6540 - -TICKETS_4_8_4 = OTP-6408 - -TICKETS_4_8_3 = OTP-6337 OTP-6340 - -TICKETS_4_8_2 = OTP-6214 OTP-6247 OTP-6293 - -TICKETS_4_8_1 = OTP-6176 OTP-6177 - -TICKETS_4_8 = OTP-6137 OTP-6149 OTP-6150 OTP-6164 - -TICKETS_4_7_4 = \ - OTP-6042 \ - OTP-6044 \ - OTP-6049 \ - OTP-6062 \ - OTP-6068 \ - OTP-6074 \ - OTP-6077 \ - OTP-6081 - -TICKETS_4_7_3 = \ - OTP-6031 \ - OTP-6032 - -TICKETS_4_7_2 = \ - OTP-5992 \ - OTP-6024 - -TICKETS_4_7_1 = \ - OTP-5963 \ - OTP-5968 \ - OTP-5969 - -TICKETS_4_7 = \ - OTP-5870 \ - OTP-5934 \ - OTP-5935 \ - OTP-5937 - -TICKETS_4_6_1 = \ - OTP-5834 \ - OTP-5838 - -TICKETS_4_6 = \ - OTP-5763 \ - OTP-5771 \ - OTP-5787 \ - OTP-5797 \ - OTP-5829 - -TICKETS_4_5 = \ - OTP-5581 \ - OTP-5726 \ - OTP-5727 \ - OTP-5732 \ - OTP-5733 \ - OTP-5740 \ - OTP-5742 - -TICKETS_4_4_1 = \ - OTP-5719 \ - OTP-5720 - -TICKETS_4_4 = \ - OTP-5666 \ - OTP-5668 \ - OTP-5669 \ - OTP-5675 \ - OTP-5676 \ - OTP-5678 \ - OTP-5703 +TICKETS_4_15 = \ + OTP-8229 \ + OTP-8249 -TICKETS_4_3 = \ - OTP-5636 \ - OTP-5637 \ - OTP-5490 +TICKETS_4_14 = \ + OTP-8223 \ + OTP-8228 \ + OTP-8237 -TICKETS_4_2 = \ - OTP-5574 \ - OTP-5578 \ - OTP-5579 \ - OTP-5580 \ - OTP-5590 \ - OTP-5591 \ - OTP-5592 +TICKETS_4_13_5 = \ + OTP-8116 \ + OTP-8120 \ + OTP-8181 \ + OTP-8182 diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index 2764ea2e43..e3b6ffa125 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -327,7 +327,7 @@ window_change(Tty, OldTty, Buf) {[], Buf}; window_change(Tty, OldTty, {Buf, BufTail, Col}) -> M1 = move_cursor(Col, 0, OldTty), - N = max(Tty#ssh_pty.width - OldTty#ssh_pty.width, 0) * 2, + N = erlang:max(Tty#ssh_pty.width - OldTty#ssh_pty.width, 0) * 2, S = lists:reverse(Buf, [BufTail | lists:duplicate(N, $ )]), M2 = move_cursor(length(Buf) + length(BufTail) + N, Col, Tty), {[M1, S | M2], {Buf, BufTail, Col}}. @@ -398,10 +398,6 @@ nthtail(0, A) -> A; nthtail(N, [_ | A]) when N > 0 -> nthtail(N-1, A); nthtail(_, _) -> []. -%%% utils -max(A, B) when A > B -> A; -max(_A, B) -> B. - ifelse(Cond, A, B) -> case Cond of true -> A; diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 822ef8f8f9..d46002c494 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -527,7 +527,7 @@ handle_info({Protocol, Socket, Data}, Statename, %% Implementations SHOULD decrypt the length after receiving the %% first 8 (or cipher block size, whichever is larger) bytes of a %% packet. (RFC 4253: Section 6 - Binary Packet Protocol) - case size(EncData0) + size(Data) >= max(8, BlockSize) of + case size(EncData0) + size(Data) >= erlang:max(8, BlockSize) of true -> {Ssh, SshPacketLen, DecData, EncData} = @@ -758,11 +758,6 @@ after_new_keys(#state{renegotiate = false, ssh_params = #ssh{role = server}} = State) -> {userauth, State}. -max(N, M) when N > M -> - N; -max(_, M) -> - M. - handle_ssh_packet_data(RemainingSshPacketLen, DecData, EncData, StateName, State) -> EncSize = size(EncData), diff --git a/lib/ssl/doc/src/new_ssl.xml b/lib/ssl/doc/src/new_ssl.xml index 4ffaa9d96a..69298759bd 100644 --- a/lib/ssl/doc/src/new_ssl.xml +++ b/lib/ssl/doc/src/new_ssl.xml @@ -22,7 +22,6 @@ The Initial Developer of the Original Code is Ericsson AB. </legalnotice> - <title>ssl</title> <prepared>Ingela Anderton Andin</prepared> <responsible>Ingela Anderton Andin</responsible> @@ -83,7 +82,7 @@ meaningless pid.</item> <item>New API functions are ssl:shutdown/2, ssl:cipher_suites/[0,1] and - ssl:versions/0</item> + ssl:versions/0, ssl:renegotiate/1</item> <item>CRL and policy certificate extensions are not supported yet. </item> <item>Supported SSL/TLS-versions are SSL-3.0 and TLS-1.0 </item> @@ -408,6 +407,17 @@ end </desc> </func> + <func> + <name>format_error(Reason) -> string()</name> + <fsummary>Return an error string.</fsummary> + <type> + <v>Reason = term()</v> + </type> + <desc> + <p>Presents the error returned by an ssl function as a printable string.</p> + </desc> + </func> + <func> <name>getopts(Socket) -> </name> <name>getopts(Socket, OptionNames) -> diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml index 9d13427677..8028e94484 100644 --- a/lib/ssl/doc/src/notes.xml +++ b/lib/ssl/doc/src/notes.xml @@ -30,6 +30,73 @@ </header> <p>This document describes the changes made to the SSL application. </p> + + <section><title>SSL 3.11.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fixed handling of several ssl/tls packets arriving at the + same time. This was broken during a refactoring of the + code.</p> + <p> + Own Id: OTP-8679</p> + </item> + </list> + </section> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Added missing checks for padding and Mac value. Removed + code for export ciphers and DH certificates as we decided + not to support them.</p> + <p> + Own Id: OTP-7047</p> + </item> + <item> + <p> + New ssl will no longer return esslerrssl to be backwards + compatible with old ssl as this hids infomation from the + user. format_error/1 has been updated to support new ssl.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-7049</p> + </item> + <item> + <p> + New ssl now supports secure renegotiation as described by + RFC 5746.</p> + <p> + Own Id: OTP-8568</p> + </item> + <item> + <p> + New ssl now support client/server-certificates signed by + dsa keys.</p> + <p> + Own Id: OTP-8587</p> + </item> + <item> + <p> + Alert handling has been improved to better handle + unexpected but valid messages and the implementation is + also changed to avoid timing related issues that could + cause different error messages depending on network + latency. Packet handling was sort of broken but would + mostly work as expected when socket was in binary mode. + This has now been fixed.</p> + <p> + Own Id: OTP-8588</p> + </item> + </list> + </section> + +</section> + <section><title>SSL 3.11</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl index 37d5646673..9aa31ae8a4 100644 --- a/lib/ssl/src/ssl_certificate.erl +++ b/lib/ssl/src/ssl_certificate.erl @@ -34,7 +34,13 @@ -export([trusted_cert_and_path/3, certificate_chain/2, file_to_certificats/1, - validate_extensions/6]). + validate_extensions/6, + is_valid_extkey_usage/2, + is_valid_key_usage/2, + select_extension/2, + extensions_list/1, + signature_type/1 + ]). %%==================================================================== %% Internal application API @@ -112,7 +118,28 @@ validate_extensions([Extension | Rest], ValidationState, UnknownExtensions, Verify, AccErr, Role) -> validate_extensions(Rest, ValidationState, [Extension | UnknownExtensions], Verify, AccErr, Role). - + +is_valid_key_usage(KeyUse, Use) -> + lists:member(Use, KeyUse). + + select_extension(_, []) -> + undefined; +select_extension(Id, [#'Extension'{extnID = Id} = Extension | _]) -> + Extension; +select_extension(Id, [_ | Extensions]) -> + select_extension(Id, Extensions). + +extensions_list(asn1_NOVALUE) -> + []; +extensions_list(Extensions) -> + Extensions. + +signature_type(RSA) when RSA == ?sha1WithRSAEncryption; + RSA == ?md5WithRSAEncryption -> + rsa; +signature_type(?'id-dsa-with-sha1') -> + dsa. + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- @@ -188,9 +215,6 @@ is_valid_extkey_usage(KeyUse, server) -> %% Server wants to verify client is_valid_key_usage(KeyUse, ?'id-kp-clientAuth'). -is_valid_key_usage(KeyUse, Use) -> - lists:member(Use, KeyUse). - not_valid_extension(Error, true, _) -> throw(Error); not_valid_extension(Error, false, AccErrors) -> diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index f425886ce5..2a71df8ee1 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -30,11 +30,12 @@ -include("ssl_cipher.hrl"). -include("ssl_alert.hrl"). -include("ssl_debug.hrl"). +-include_lib("public_key/include/public_key.hrl"). -export([security_parameters/2, suite_definition/1, decipher/5, cipher/4, suite/1, suites/1, - openssl_suite/1, openssl_suite_name/1]). + openssl_suite/1, openssl_suite_name/1, filter/2]). -compile(inline). @@ -240,7 +241,7 @@ suite_definition(?TLS_RSA_WITH_3DES_EDE_CBC_SHA) -> suite_definition(?TLS_DHE_DSS_WITH_DES_CBC_SHA) -> {dhe_dss, des_cbc, sha}; suite_definition(?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA) -> - {dhe_dss, '3des_ede_cbc'}; + {dhe_dss, '3des_ede_cbc', sha}; suite_definition(?TLS_DHE_RSA_WITH_DES_CBC_SHA) -> {dhe_rsa, des_cbc, sha}; suite_definition(?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA) -> @@ -260,25 +261,6 @@ suite_definition(?TLS_DHE_DSS_WITH_AES_256_CBC_SHA) -> suite_definition(?TLS_DHE_RSA_WITH_AES_256_CBC_SHA) -> {dhe_rsa, aes_256_cbc, sha}. -%% TODO: support kerbos key exchange? -%% TSL V1.1 KRB SUITES -%% suite_definition(?TLS_KRB5_WITH_DES_CBC_SHA) -> -%% {krb5, des_cbc, sha}; -%% suite_definition(?TLS_KRB5_WITH_3DES_EDE_CBC_SHA) -> -%% {krb5, '3des_ede_cbc', sha}; -%% suite_definition(?TLS_KRB5_WITH_RC4_128_SHA) -> -%% {krb5, rc4_128, sha}; -%% suite_definition(?TLS_KRB5_WITH_IDEA_CBC_SHA) -> -%% {krb5, idea_cbc, sha}; -%% suite_definition(?TLS_KRB5_WITH_DES_CBC_MD5) -> -%% {krb5, des_cbc, md5}; -%% suite_definition(?TLS_KRB5_WITH_3DES_EDE_CBC_MD5) -> -%% {krb5, '3des_ede_cbc', md5}; -%% suite_definition(?TLS_KRB5_WITH_RC4_128_MD5) -> -%% {krb5, rc4_128, md5}; -%% suite_definition(?TLS_KRB5_WITH_IDEA_CBC_MD5) -> -%% {krb5, idea_cbc, md5}; - %% TLS v1.1 suites %%suite({rsa, null, md5}) -> %% ?TLS_RSA_WITH_NULL_MD5; @@ -312,8 +294,8 @@ suite({dhe_rsa, '3des_ede_cbc', sha}) -> %%% TSL V1.1 AES suites suite({rsa, aes_128_cbc, sha}) -> ?TLS_RSA_WITH_AES_128_CBC_SHA; -%% suite({dhe_dss, aes_128_cbc, sha}) -> -%% ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA; +suite({dhe_dss, aes_128_cbc, sha}) -> + ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA; suite({dhe_rsa, aes_128_cbc, sha}) -> ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA; %% suite({dh_anon, aes_128_cbc, sha}) -> @@ -327,29 +309,8 @@ suite({dhe_rsa, aes_256_cbc, sha}) -> %% suite({dh_anon, aes_256_cbc, sha}) -> %% ?TLS_DH_anon_WITH_AES_256_CBC_SHA. -%% TODO: support kerbos key exchange? -%% TSL V1.1 KRB SUITES -%% suite({krb5, des_cbc, sha}) -> -%% ?TLS_KRB5_WITH_DES_CBC_SHA; -%% suite({krb5_cbc, '3des_ede_cbc', sha}) -> -%% ?TLS_KRB5_WITH_3DES_EDE_CBC_SHA; -%% suite({krb5, rc4_128, sha}) -> -%% ?TLS_KRB5_WITH_RC4_128_SHA; -%% suite({krb5_cbc, idea_cbc, sha}) -> -%% ?TLS_KRB5_WITH_IDEA_CBC_SHA; -%% suite({krb5_cbc, md5}) -> -%% ?TLS_KRB5_WITH_DES_CBC_MD5; -%% suite({krb5_ede_cbc, des_cbc, md5}) -> -%% ?TLS_KRB5_WITH_3DES_EDE_CBC_MD5; -%% suite({krb5_128, rc4_128, md5}) -> -%% ?TLS_KRB5_WITH_RC4_128_MD5; -%% suite({krb5, idea_cbc, md5}) -> -%% ?TLS_KRB5_WITH_IDEA_CBC_MD5; %% translate constants <-> openssl-strings -%% TODO: Is there a pattern in the nameing -%% that is useable to make a nicer function defention? - openssl_suite("DHE-RSA-AES256-SHA") -> ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA; openssl_suite("DHE-DSS-AES256-SHA") -> @@ -368,17 +329,12 @@ openssl_suite("DHE-DSS-AES128-SHA") -> ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA; openssl_suite("AES128-SHA") -> ?TLS_RSA_WITH_AES_128_CBC_SHA; -%% TODO: Do we want to support this? -%% openssl_suite("DHE-DSS-RC4-SHA") -> -%% ?TLS_DHE_DSS_WITH_RC4_128_SHA; %%openssl_suite("IDEA-CBC-SHA") -> %% ?TLS_RSA_WITH_IDEA_CBC_SHA; openssl_suite("RC4-SHA") -> ?TLS_RSA_WITH_RC4_128_SHA; openssl_suite("RC4-MD5") -> ?TLS_RSA_WITH_RC4_128_MD5; -%% openssl_suite("DHE-DSS-RC4-SHA") -> -%% ?TLS_DHE_DSS_WITH_RC4_128_SHA; openssl_suite("EDH-RSA-DES-CBC-SHA") -> ?TLS_DHE_RSA_WITH_DES_CBC_SHA; openssl_suite("DES-CBC-SHA") -> @@ -412,14 +368,22 @@ openssl_suite_name(?TLS_DHE_RSA_WITH_DES_CBC_SHA) -> "EDH-RSA-DES-CBC-SHA"; openssl_suite_name(?TLS_RSA_WITH_DES_CBC_SHA) -> "DES-CBC-SHA"; - -%% openssl_suite_name(?TLS_DHE_DSS_WITH_RC4_128_SHA) -> -%% "DHE-DSS-RC4-SHA"; - %% No oppenssl name openssl_suite_name(Cipher) -> suite_definition(Cipher). +filter(undefined, Ciphers) -> + Ciphers; +filter(DerCert, Ciphers) -> + {ok, OtpCert} = public_key:pkix_decode_cert(DerCert, otp), + SigAlg = OtpCert#'OTPCertificate'.signatureAlgorithm, + case ssl_certificate:signature_type(SigAlg#'SignatureAlgorithm'.algorithm) of + rsa -> + filter_rsa(OtpCert, Ciphers -- dsa_signed_suites()); + dsa -> + Ciphers -- rsa_signed_suites() + end. + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- @@ -567,3 +531,53 @@ next_iv(Bin, IV) -> <<_:FirstPart/binary, NextIV:IVSz/binary>> = Bin, NextIV. +rsa_signed_suites() -> + dhe_rsa_suites() ++ rsa_suites(). + +dhe_rsa_suites() -> + [?TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, + ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + ?TLS_DHE_RSA_WITH_DES_CBC_SHA]. + +rsa_suites() -> + [?TLS_RSA_WITH_AES_256_CBC_SHA, + ?TLS_RSA_WITH_3DES_EDE_CBC_SHA, + ?TLS_RSA_WITH_AES_128_CBC_SHA, + %%?TLS_RSA_WITH_IDEA_CBC_SHA, + ?TLS_RSA_WITH_RC4_128_SHA, + ?TLS_RSA_WITH_RC4_128_MD5, + ?TLS_RSA_WITH_DES_CBC_SHA]. + +dsa_signed_suites() -> + dhe_dss_suites(). + +dhe_dss_suites() -> + [?TLS_DHE_DSS_WITH_AES_256_CBC_SHA, + ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, + ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, + ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA]. + +filter_rsa(OtpCert, RsaCiphers) -> + TBSCert = OtpCert#'OTPCertificate'.tbsCertificate, + TBSExtensions = TBSCert#'OTPTBSCertificate'.extensions, + Extensions = ssl_certificate:extensions_list(TBSExtensions), + case ssl_certificate:select_extension(?'id-ce-keyUsage', Extensions) of + undefined -> + RsaCiphers; + #'Extension'{extnValue = KeyUse} -> + Result = filter_rsa_suites(keyEncipherment, + KeyUse, RsaCiphers, rsa_suites()), + filter_rsa_suites(digitalSignature, + KeyUse, Result, dhe_rsa_suites()) + end. + +filter_rsa_suites(Use, KeyUse, CipherSuits, RsaSuites) -> + case ssl_certificate:is_valid_key_usage(KeyUse, Use) of + true -> + CipherSuits; + false -> + CipherSuits -- RsaSuites + end. + + diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 644c2772b2..abd1b59011 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -65,13 +65,14 @@ ssl_options, % #ssl_options{} socket_options, % #socket_options{} connection_states, % #connection_states{} from ssl_record.hrl + tls_packets = [], % Not yet handled decode ssl/tls packets. tls_record_buffer, % binary() buffer of incomplete records tls_handshake_buffer, % binary() buffer of incomplete handshakes %% {{md5_hash, sha_hash}, {prev_md5, prev_sha}} (binary()) tls_handshake_hashes, % see above tls_cipher_texts, % list() received but not deciphered yet own_cert, % binary() - session, % #session{} from ssl_handshake.erl + session, % #session{} from ssl_handshake.hrl session_cache, % session_cache_cb, % negotiated_version, % #protocol_version{} @@ -280,12 +281,12 @@ start_link(Role, Host, Port, Socket, Options, User, CbInfo) -> %% gen_fsm:start_link/3,4, this function is called by the new process to %% initialize. %%-------------------------------------------------------------------- -init([Role, Host, Port, Socket, {SSLOpts, _} = Options, +init([Role, Host, Port, Socket, {SSLOpts0, _} = Options, User, CbInfo]) -> State0 = initial_state(Role, Host, Port, Socket, Options, User, CbInfo), Hashes0 = ssl_handshake:init_hashes(), - try ssl_init(SSLOpts, Role) of + try ssl_init(SSLOpts0, Role) of {ok, Ref, CacheRef, OwnCert, Key, DHParams} -> State = State0#state{tls_handshake_hashes = Hashes0, own_cert = OwnCert, @@ -317,10 +318,14 @@ hello(start, #state{host = Host, port = Port, role = client, ssl_options = SslOpts, transport_cb = Transport, socket = Socket, connection_states = ConnectionStates, + own_cert = Cert, renegotiation = {Renegotiation, _}} = State0) -> + Hello = ssl_handshake:client_hello(Host, Port, - ConnectionStates, SslOpts, Renegotiation), + ConnectionStates, + SslOpts, Cert, + Renegotiation), Version = Hello#client_hello.client_version, Hashes0 = ssl_handshake:init_hashes(), @@ -401,10 +406,11 @@ hello(Hello = #client_hello{client_version = ClientVersion}, renegotiation = {Renegotiation, _}, session_cache = Cache, session_cache_cb = CacheCb, - ssl_options = SslOpts}) -> + ssl_options = SslOpts, + own_cert = Cert}) -> case ssl_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb, - ConnectionStates0}, Renegotiation) of + ConnectionStates0, Cert}, Renegotiation) of {Version, {Type, Session}, ConnectionStates} -> do_server_hello(Type, State#state{connection_states = ConnectionStates, @@ -700,13 +706,14 @@ connection(#hello_request{}, #state{host = Host, port = Port, socket = Socket, ssl_options = SslOpts, negotiated_version = Version, + own_cert = Cert, transport_cb = Transport, connection_states = ConnectionStates0, renegotiation = {Renegotiation, _}, tls_handshake_hashes = Hashes0} = State0) -> Hello = ssl_handshake:client_hello(Host, Port, - ConnectionStates0, SslOpts, Renegotiation), + ConnectionStates0, SslOpts, Cert, Renegotiation), {BinMsg, ConnectionStates1, Hashes1} = encode_handshake(Hello, Version, ConnectionStates0, Hashes0), @@ -1485,15 +1492,15 @@ handle_server_key( SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, - Hash = ssl_handshake:server_key_exchange_hash(KeyAlgo, - <<ClientRandom/binary, + Plain = ssl_handshake:server_key_exchange_plain(KeyAlgo, + <<ClientRandom/binary, ServerRandom/binary, - ?UINT16(PLen), P/binary, - ?UINT16(GLen), G/binary, - ?UINT16(YLen), + ?UINT16(PLen), P/binary, + ?UINT16(GLen), G/binary, + ?UINT16(YLen), ServerPublicDhKey/binary>>), - - case verify_dh_params(Signed, Hash, PubKeyInfo) of + + case verify_dh_params(Signed, Plain, PubKeyInfo) of true -> PMpint = mpint_binary(P), GMpint = mpint_binary(G), @@ -1517,14 +1524,18 @@ handle_server_key( ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE) end. -verify_dh_params(Signed, Hash, {?rsaEncryption, PubKey, _PubKeyparams}) -> + +verify_dh_params(Signed, Hashes, {?rsaEncryption, PubKey, _PubKeyParams}) -> case public_key:decrypt_public(Signed, PubKey, [{rsa_pad, rsa_pkcs1_padding}]) of - Hash -> + Hashes -> true; _ -> false - end. + end; +verify_dh_params(Signed, Plain, {?'id-dsa', PublicKey, PublicKeyParams}) -> + public_key:verify_signature(Plain, sha, Signed, PublicKey, PublicKeyParams). + encode_alert(#alert{} = Alert, Version, ConnectionStates) -> ?DBG_TERM(Alert), @@ -1727,9 +1738,23 @@ opposite_role(server) -> send_user(Pid, Msg) -> Pid ! Msg. +handle_tls_handshake(Handle, StateName, #state{tls_packets = [Packet]} = State) -> + FsmReturn = {next_state, StateName, State#state{tls_packets = []}}, + Handle(Packet, FsmReturn); + +handle_tls_handshake(Handle, StateName, #state{tls_packets = [Packet | Packets]} = State0) -> + FsmReturn = {next_state, StateName, State0#state{tls_packets = Packets}}, + case Handle(Packet, FsmReturn) of + {next_state, NextStateName, State} -> + handle_tls_handshake(Handle, NextStateName, State); + {stop, _,_} = Stop -> + Stop + end. + next_state(_, #alert{} = Alert, #state{negotiated_version = Version} = State) -> handle_own_alert(Alert, Version, decipher_error, State), {stop, normal, State}; + next_state(Next, no_record, State) -> {next_state, Next, State}; @@ -1764,8 +1789,8 @@ next_state(StateName, #ssl_tls{type = ?HANDSHAKE, fragment = Data}, end, try {Packets, Buf} = ssl_handshake:get_tls_handshake(Data,Buf0, KeyAlg,Version), - Start = {next_state, StateName, State0#state{tls_handshake_buffer = Buf}}, - lists:foldl(Handle, Start, Packets) + State = State0#state{tls_packets = Packets, tls_handshake_buffer = Buf}, + handle_tls_handshake(Handle, StateName, State) catch throw:#alert{} = Alert -> handle_own_alert(Alert, Version, StateName, State0), {stop, normal, State0} @@ -1802,17 +1827,19 @@ next_tls_record(Data, #state{tls_record_buffer = Buf0, Alert end. -next_record(#state{tls_cipher_texts = [], socket = Socket} = State) -> +next_record(#state{tls_packets = [], tls_cipher_texts = [], socket = Socket} = State) -> inet:setopts(Socket, [{active,once}]), {no_record, State}; -next_record(#state{tls_cipher_texts = [CT | Rest], +next_record(#state{tls_packets = [], tls_cipher_texts = [CT | Rest], connection_states = ConnStates0} = State) -> case ssl_record:decode_cipher_text(CT, ConnStates0) of {Plain, ConnStates} -> {Plain, State#state{tls_cipher_texts = Rest, connection_states = ConnStates}}; #alert{} = Alert -> {Alert, State} - end. + end; +next_record(State) -> + {no_record, State}. next_record_if_active(State = #state{socket_options = diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 454d726f0d..c8245e2fb4 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -31,11 +31,11 @@ -include("ssl_debug.hrl"). -include_lib("public_key/include/public_key.hrl"). --export([master_secret/4, client_hello/5, server_hello/4, hello/4, +-export([master_secret/4, client_hello/6, server_hello/4, hello/4, hello_request/0, certify/7, certificate/3, client_certificate_verify/6, certificate_verify/6, certificate_request/2, - key_exchange/2, server_key_exchange_hash/2, finished/4, + key_exchange/2, server_key_exchange_plain/2, finished/4, verify_connection/5, get_tls_handshake/4, server_hello_done/0, sig_alg/1, @@ -46,7 +46,7 @@ %% Internal application API %%==================================================================== %%-------------------------------------------------------------------- -%% Function: client_hello(Host, Port, ConnectionStates, SslOpts) -> +%% Function: client_hello(Host, Port, ConnectionStates, SslOpts, Cert, Renegotiation) -> %% #client_hello{} %% Host %% Port @@ -56,8 +56,8 @@ %% Description: Creates a client hello message. %%-------------------------------------------------------------------- client_hello(Host, Port, ConnectionStates, #ssl_options{versions = Versions, - ciphers = Ciphers} - = SslOpts, Renegotiation) -> + ciphers = UserSuites} + = SslOpts, Cert, Renegotiation) -> Fun = fun(Version) -> ssl_record:protocol_version(Version) @@ -65,7 +65,8 @@ client_hello(Host, Port, ConnectionStates, #ssl_options{versions = Versions, Version = ssl_record:highest_protocol_version(lists:map(Fun, Versions)), Pending = ssl_record:pending_connection_state(ConnectionStates, read), SecParams = Pending#connection_state.security_parameters, - + Ciphers = available_suites(Cert, UserSuites, Version), + Id = ssl_manager:client_session_id(Host, Port, SslOpts), #client_hello{session_id = Id, @@ -150,14 +151,14 @@ hello(#client_hello{client_version = ClientVersion, random = Random, renegotiation_info = Info} = Hello, #ssl_options{versions = Versions, secure_renegotiate = SecureRenegotation} = SslOpts, - {Port, Session0, Cache, CacheCb, ConnectionStates0}, Renegotiation) -> + {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert}, Renegotiation) -> Version = select_version(ClientVersion, Versions), case ssl_record:is_acceptable_version(Version) of true -> {Type, #session{cipher_suite = CipherSuite, compression_method = Compression} = Session} = select_session(Hello, Port, Session0, Version, - SslOpts, Cache, CacheCb), + SslOpts, Cache, CacheCb, Cert), case CipherSuite of no_suite -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY); @@ -316,8 +317,12 @@ certificate_verify(Signature, {_, PublicKey, _}, Version, valid; _ -> ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE) - end. -%% TODO dsa clause + end; +certificate_verify(Signature, {_, PublicKey, PublicKeyParams}, Version, + MasterSecret, dhe_dss = Algorithm, {_, Hashes0}) -> + Hashes = calc_certificate_verify(Version, MasterSecret, + Algorithm, Hashes0), + public_key:verify_signature(Hashes, sha, Signature, PublicKey, PublicKeyParams). %%-------------------------------------------------------------------- %% Function: certificate_request(ConnectionStates, CertDbRef) -> @@ -356,7 +361,7 @@ key_exchange(client, {dh, <<?UINT32(Len), PublicKey:Len/binary>>}) -> dh_public = PublicKey} }; -key_exchange(server, {dh, {<<?UINT32(_), PublicKey/binary>>, _}, +key_exchange(server, {dh, {<<?UINT32(Len), PublicKey:Len/binary>>, _}, #'DHParameter'{prime = P, base = G}, KeyAlgo, ClientRandom, ServerRandom, PrivateKey}) -> <<?UINT32(_), PBin/binary>> = crypto:mpint(P), @@ -365,15 +370,14 @@ key_exchange(server, {dh, {<<?UINT32(_), PublicKey/binary>>, _}, GLen = byte_size(GBin), YLen = byte_size(PublicKey), ServerDHParams = #server_dh_params{dh_p = PBin, - dh_g = GBin, dh_y = PublicKey}, - - Hash = - server_key_exchange_hash(KeyAlgo, <<ClientRandom/binary, - ServerRandom/binary, - ?UINT16(PLen), PBin/binary, - ?UINT16(GLen), GBin/binary, - ?UINT16(YLen), PublicKey/binary>>), - Signed = digitally_signed(Hash, PrivateKey), + dh_g = GBin, dh_y = PublicKey}, + Plain = + server_key_exchange_plain(KeyAlgo, <<ClientRandom/binary, + ServerRandom/binary, + ?UINT16(PLen), PBin/binary, + ?UINT16(GLen), GBin/binary, + ?UINT16(YLen), PublicKey/binary>>), + Signed = digitally_signed(Plain, PrivateKey), #server_key_exchange{params = ServerDHParams, signed_params = Signed}. @@ -524,18 +528,12 @@ path_validation_alert(_, _) -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE). select_session(Hello, Port, Session, Version, - #ssl_options{ciphers = UserSuites} = SslOpts, Cache, CacheCb) -> + #ssl_options{ciphers = UserSuites} = SslOpts, Cache, CacheCb, Cert) -> SuggestedSessionId = Hello#client_hello.session_id, SessionId = ssl_manager:server_session_id(Port, SuggestedSessionId, SslOpts), - Suites = case UserSuites of - [] -> - ssl_cipher:suites(Version); - _ -> - UserSuites - end, - + Suites = available_suites(Cert, UserSuites, Version), case ssl_session:is_new(SuggestedSessionId, SessionId) of true -> CipherSuite = @@ -549,7 +547,14 @@ select_session(Hello, Port, Session, Version, {resumed, CacheCb:lookup(Cache, {Port, SessionId})} end. - +available_suites(Cert, UserSuites, Version) -> + case UserSuites of + [] -> + ssl_cipher:filter(Cert, ssl_cipher:suites(Version)); + _ -> + ssl_cipher:filter(Cert, UserSuites) + end. + cipher_suites(Suites, false) -> [?TLS_EMPTY_RENEGOTIATION_INFO_SCSV | Suites]; cipher_suites(Suites, true) -> @@ -812,7 +817,7 @@ dec_hs(?CERTIFICATE, <<?UINT24(ACLen), ASN1Certs:ACLen/binary>>, _, _) -> dec_hs(?SERVER_KEY_EXCHANGE, <<?UINT16(PLen), P:PLen/binary, ?UINT16(GLen), G:GLen/binary, ?UINT16(YLen), Y:YLen/binary, - ?UINT16(_), Sig/binary>>, + ?UINT16(Len), Sig:Len/binary>>, ?KEY_EXCHANGE_DIFFIE_HELLMAN, _) -> #server_key_exchange{params = #server_dh_params{dh_p = P,dh_g = G, dh_y = Y}, @@ -820,7 +825,6 @@ dec_hs(?SERVER_KEY_EXCHANGE, <<?UINT16(PLen), P:PLen/binary, dec_hs(?CERTIFICATE_REQUEST, <<?BYTE(CertTypesLen), CertTypes:CertTypesLen/binary, ?UINT16(CertAuthsLen), CertAuths:CertAuthsLen/binary>>, _, _) -> - %% TODO: maybe we should chop up CertAuths into a list? #certificate_request{certificate_types = CertTypes, certificate_authorities = CertAuths}; dec_hs(?SERVER_HELLO_DONE, <<>>, _, _) -> @@ -1086,9 +1090,8 @@ certificate_authorities_from_db(CertDbRef, PrevKey, Acc) -> digitally_signed(Hashes, #'RSAPrivateKey'{} = Key) -> public_key:encrypt_private(Hashes, Key, [{rsa_pad, rsa_pkcs1_padding}]); -digitally_signed(Hashes, #'DSAPrivateKey'{} = Key) -> - public_key:sign(Hashes, Key). - +digitally_signed(Plain, #'DSAPrivateKey'{} = Key) -> + public_key:sign(Plain, Key). calc_master_secret({3,0}, PremasterSecret, ClientRandom, ServerRandom) -> ssl_ssl3:master_secret(PremasterSecret, ClientRandom, ServerRandom); @@ -1119,23 +1122,15 @@ calc_certificate_verify({3, N}, _, Algorithm, Hashes) when N == 1; N == 2 -> ssl_tls1:certificate_verify(Algorithm, Hashes). -server_key_exchange_hash(Algorithm, Value) when Algorithm == rsa; +server_key_exchange_plain(Algorithm, Value) when Algorithm == rsa; Algorithm == dhe_rsa -> - MD5Context = crypto:md5_init(), - NewMD5Context = crypto:md5_update(MD5Context, Value), - MD5 = crypto:md5_final(NewMD5Context), - - SHAContext = crypto:sha_init(), - NewSHAContext = crypto:sha_update(SHAContext, Value), - SHA = crypto:sha_final(NewSHAContext), - + MD5 = crypto:md5(Value), + SHA = crypto:sha(Value), <<MD5/binary, SHA/binary>>; -server_key_exchange_hash(dhe_dss, Value) -> - SHAContext = crypto:sha_init(), - NewSHAContext = crypto:sha_update(SHAContext, Value), - crypto:sha_final(NewSHAContext). - +server_key_exchange_plain(dhe_dss, Value) -> + %% Hash will be done by crypto. + Value. sig_alg(dh_anon) -> ?SIGNATURE_ANONYMOUS; diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl index 7c4b0ee959..6b7cffaa7d 100644 --- a/lib/ssl/src/ssl_record.erl +++ b/lib/ssl/src/ssl_record.erl @@ -705,7 +705,6 @@ hash_and_bump_seqno(#connection_state{sequence_number = SeqNo, is_correct_mac(Mac, Mac) -> true; is_correct_mac(_M,_H) -> - io:format("Mac ~p ~n Hash: ~p~n",[_M, _H]), false. mac_hash({_,_}, ?NULL, _MacSecret, _SeqNo, _Type, diff --git a/lib/ssl/src/ssl_ssl3.erl b/lib/ssl/src/ssl_ssl3.erl index 1bf8c2b458..1cecd10e81 100644 --- a/lib/ssl/src/ssl_ssl3.erl +++ b/lib/ssl/src/ssl_ssl3.erl @@ -138,21 +138,18 @@ setup_keys(MasterSecret, ServerRandom, ClientRandom, HS, KML, _EKML, IVS) -> suites() -> [ - %% TODO: uncomment when supported ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA, - %% ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA, + ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA, ?TLS_RSA_WITH_AES_256_CBC_SHA, ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, - %% ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, + ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, ?TLS_RSA_WITH_3DES_EDE_CBC_SHA, ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, - %% ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, + ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, ?TLS_RSA_WITH_AES_128_CBC_SHA, - %%?TLS_DHE_DSS_WITH_RC4_128_SHA, - %% ?TLS_RSA_WITH_IDEA_CBC_SHA, Not supported: in later openssl version than OTP requires + %% ?TLS_RSA_WITH_IDEA_CBC_SHA, ?TLS_RSA_WITH_RC4_128_SHA, ?TLS_RSA_WITH_RC4_128_MD5, - %%?TLS_DHE_DSS_WITH_RC4_128_SHA, ?TLS_RSA_WITH_DES_CBC_SHA ]. diff --git a/lib/ssl/src/ssl_tls1.erl b/lib/ssl/src/ssl_tls1.erl index 900b8e166d..70db632835 100644 --- a/lib/ssl/src/ssl_tls1.erl +++ b/lib/ssl/src/ssl_tls1.erl @@ -134,22 +134,19 @@ mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor}, suites() -> [ - %% TODO: uncomment when supported ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA, - %%?TLS_DHE_DSS_WITH_AES_256_CBC_SHA, + ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA, ?TLS_RSA_WITH_AES_256_CBC_SHA, ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, - %%?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, + ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, ?TLS_RSA_WITH_3DES_EDE_CBC_SHA, ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, - %%?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, + ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, ?TLS_RSA_WITH_AES_128_CBC_SHA, - %%?TLS_DHE_DSS_WITH_RC4_128_SHA, %%?TLS_RSA_WITH_IDEA_CBC_SHA, ?TLS_RSA_WITH_RC4_128_SHA, ?TLS_RSA_WITH_RC4_128_MD5, ?TLS_DHE_RSA_WITH_DES_CBC_SHA, - %%TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA ?TLS_RSA_WITH_DES_CBC_SHA ]. diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile index bd86120c98..d35cafc47b 100644 --- a/lib/ssl/test/Makefile +++ b/lib/ssl/test/Makefile @@ -1,19 +1,19 @@ # # %CopyrightBegin% -# -# Copyright Ericsson AB 1999-2009. All Rights Reserved. -# +# +# Copyright Ericsson AB 1999-2010. All Rights Reserved. +# # The contents of this file are subject to the Erlang Public License, # Version 1.1, (the "License"); you may not use this file except in # compliance with the License. You should have received a copy of the # Erlang Public License along with this software. If not, it can be # retrieved online at http://www.erlang.org/. -# +# # Software distributed under the License is distributed on an "AS IS" # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See # the License for the specific language governing rights and limitations # under the License. -# +# # %CopyrightEnd% # @@ -50,7 +50,8 @@ MODULES = \ old_ssl_protocol_SUITE \ old_transport_accept_SUITE \ old_ssl_dist_SUITE \ - make_certs + make_certs\ + erl_make_certs ERL_FILES = $(MODULES:%=%.erl) diff --git a/lib/ssl/test/erl_make_certs.erl b/lib/ssl/test/erl_make_certs.erl new file mode 100644 index 0000000000..1d2cea6c72 --- /dev/null +++ b/lib/ssl/test/erl_make_certs.erl @@ -0,0 +1,412 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% Create test certificates + +-module(erl_make_certs). +-include_lib("public_key/include/public_key.hrl"). + +-export([make_cert/1, gen_rsa/1, verify_signature/3, write_pem/3]). +-compile(export_all). + +%%-------------------------------------------------------------------- +%% @doc Create and return a der encoded certificate +%% Option Default +%% ------------------------------------------------------- +%% digest sha1 +%% validity {date(), date() + week()} +%% version 3 +%% subject [] list of the following content +%% {name, Name} +%% {email, Email} +%% {city, City} +%% {state, State} +%% {org, Org} +%% {org_unit, OrgUnit} +%% {country, Country} +%% {serial, Serial} +%% {title, Title} +%% {dnQualifer, DnQ} +%% issuer = {Issuer, IssuerKey} true (i.e. a ca cert is created) +%% (obs IssuerKey migth be {Key, Password} +%% key = KeyFile|KeyBin|rsa|dsa Subject PublicKey rsa or dsa generates key +%% +%% +%% (OBS: The generated keys are for testing only) +%% @spec ([{::atom(), ::term()}]) -> {Cert::binary(), Key::binary()} +%% @end +%%-------------------------------------------------------------------- + +make_cert(Opts) -> + SubjectPrivateKey = get_key(Opts), + {TBSCert, IssuerKey} = make_tbs(SubjectPrivateKey, Opts), + Cert = public_key:sign(TBSCert, IssuerKey), + true = verify_signature(Cert, IssuerKey, undef), %% verify that the keys where ok + {Cert, encode_key(SubjectPrivateKey)}. + +%%-------------------------------------------------------------------- +%% @doc Writes pem files in Dir with FileName ++ ".pem" and FileName ++ "_key.pem" +%% @spec (::string(), ::string(), {Cert,Key}) -> ok +%% @end +%%-------------------------------------------------------------------- +write_pem(Dir, FileName, {Cert, Key = {_,_,not_encrypted}}) when is_binary(Cert) -> + ok = public_key:der_to_pem(filename:join(Dir, FileName ++ ".pem"), [{cert, Cert, not_encrypted}]), + ok = public_key:der_to_pem(filename:join(Dir, FileName ++ "_key.pem"), [Key]). + +%%-------------------------------------------------------------------- +%% @doc Creates a rsa key (OBS: for testing only) +%% the size are in bytes +%% @spec (::integer()) -> {::atom(), ::binary(), ::opaque()} +%% @end +%%-------------------------------------------------------------------- +gen_rsa(Size) when is_integer(Size) -> + Key = gen_rsa2(Size), + {Key, encode_key(Key)}. + +%%-------------------------------------------------------------------- +%% @doc Creates a dsa key (OBS: for testing only) +%% the sizes are in bytes +%% @spec (::integer()) -> {::atom(), ::binary(), ::opaque()} +%% @end +%%-------------------------------------------------------------------- +gen_dsa(LSize,NSize) when is_integer(LSize), is_integer(NSize) -> + Key = gen_dsa2(LSize, NSize), + {Key, encode_key(Key)}. + +%%-------------------------------------------------------------------- +%% @doc Verifies cert signatures +%% @spec (::binary(), ::tuple()) -> ::boolean() +%% @end +%%-------------------------------------------------------------------- +verify_signature(DerEncodedCert, DerKey, KeyParams) -> + Key = decode_key(DerKey), + case Key of + #'RSAPrivateKey'{modulus=Mod, publicExponent=Exp} -> + public_key:verify_signature(DerEncodedCert, + #'RSAPublicKey'{modulus=Mod, publicExponent=Exp}, + 'NULL'); + #'DSAPrivateKey'{p=P, q=Q, g=G, y=Y} -> + public_key:verify_signature(DerEncodedCert, Y, #'Dss-Parms'{p=P, q=Q, g=G}); + + _ -> + public_key:verify_signature(DerEncodedCert, Key, KeyParams) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%% Implementation %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +get_key(Opts) -> + case proplists:get_value(key, Opts) of + undefined -> make_key(rsa, Opts); + rsa -> make_key(rsa, Opts); + dsa -> make_key(dsa, Opts); + Key -> + Password = proplists:get_value(password, Opts, no_passwd), + decode_key(Key, Password) + end. + +decode_key({Key, Pw}) -> + decode_key(Key, Pw); +decode_key(Key) -> + decode_key(Key, no_passwd). + + +decode_key(#'RSAPublicKey'{} = Key,_) -> + Key; +decode_key(#'RSAPrivateKey'{} = Key,_) -> + Key; +decode_key(#'DSAPrivateKey'{} = Key,_) -> + Key; +decode_key(Der = {_,_,_}, Pw) -> + {ok, Key} = public_key:decode_private_key(Der, Pw), + Key; +decode_key(FileOrDer, Pw) -> + {ok, [KeyInfo]} = public_key:pem_to_der(FileOrDer), + decode_key(KeyInfo, Pw). + +encode_key(Key = #'RSAPrivateKey'{}) -> + {ok, Der} = 'OTP-PUB-KEY':encode('RSAPrivateKey', Key), + {rsa_private_key, list_to_binary(Der), not_encrypted}; +encode_key(Key = #'DSAPrivateKey'{}) -> + {ok, Der} = 'OTP-PUB-KEY':encode('DSAPrivateKey', Key), + {dsa_private_key, list_to_binary(Der), not_encrypted}. + +make_tbs(SubjectKey, Opts) -> + Version = list_to_atom("v"++integer_to_list(proplists:get_value(version, Opts, 3))), + {Issuer, IssuerKey} = issuer(Opts, SubjectKey), + + {Algo, Parameters} = sign_algorithm(IssuerKey, Opts), + + SignAlgo = #'SignatureAlgorithm'{algorithm = Algo, + parameters = Parameters}, + + {#'OTPTBSCertificate'{serialNumber = trunc(random:uniform()*100000000)*10000 + 1, + signature = SignAlgo, + issuer = Issuer, + validity = validity(Opts), + subject = subject(proplists:get_value(subject, Opts),false), + subjectPublicKeyInfo = publickey(SubjectKey), + version = Version, + extensions = extensions(Opts) + }, IssuerKey}. + +issuer(Opts, SubjectKey) -> + IssuerProp = proplists:get_value(issuer, Opts, true), + case IssuerProp of + true -> %% Self signed + {subject(proplists:get_value(subject, Opts), true), SubjectKey}; + {Issuer, IssuerKey} when is_binary(Issuer) -> + {issuer_der(Issuer), decode_key(IssuerKey)}; + {File, IssuerKey} when is_list(File) -> + {ok, [{cert, Cert, _}|_]} = public_key:pem_to_der(File), + {issuer_der(Cert), decode_key(IssuerKey)} + end. + +issuer_der(Issuer) -> + {ok, Decoded} = public_key:pkix_decode_cert(Issuer, otp), + #'OTPCertificate'{tbsCertificate=Tbs} = Decoded, + #'OTPTBSCertificate'{subject=Subject} = Tbs, + Subject. + +subject(undefined, IsCA) -> + User = if IsCA -> "CA"; true -> os:getenv("USER") end, + Opts = [{email, User ++ "@erlang.org"}, + {name, User}, + {city, "Stockholm"}, + {country, "SE"}, + {org, "erlang"}, + {org_unit, "testing dep"}], + subject(Opts); +subject(Opts, _) -> + subject(Opts). + +subject(SubjectOpts) when is_list(SubjectOpts) -> + Encode = fun(Opt) -> + {Type,Value} = subject_enc(Opt), + [#'AttributeTypeAndValue'{type=Type, value=Value}] + end, + {rdnSequence, [Encode(Opt) || Opt <- SubjectOpts]}. + +%% Fill in the blanks +subject_enc({name, Name}) -> {?'id-at-commonName', {printableString, Name}}; +subject_enc({email, Email}) -> {?'id-emailAddress', Email}; +subject_enc({city, City}) -> {?'id-at-localityName', {printableString, City}}; +subject_enc({state, State}) -> {?'id-at-stateOrProvinceName', {printableString, State}}; +subject_enc({org, Org}) -> {?'id-at-organizationName', {printableString, Org}}; +subject_enc({org_unit, OrgUnit}) -> {?'id-at-organizationalUnitName', {printableString, OrgUnit}}; +subject_enc({country, Country}) -> {?'id-at-countryName', Country}; +subject_enc({serial, Serial}) -> {?'id-at-serialNumber', Serial}; +subject_enc({title, Title}) -> {?'id-at-title', {printableString, Title}}; +subject_enc({dnQualifer, DnQ}) -> {?'id-at-dnQualifier', DnQ}; +subject_enc(Other) -> Other. + + +extensions(Opts) -> + case proplists:get_value(extensions, Opts, []) of + false -> + asn1_NOVALUE; + Exts -> + lists:flatten([extension(Ext) || Ext <- default_extensions(Exts)]) + end. + +default_extensions(Exts) -> + Def = [{key_usage,undefined}, + {subject_altname, undefined}, + {issuer_altname, undefined}, + {basic_constraints, default}, + {name_constraints, undefined}, + {policy_constraints, undefined}, + {ext_key_usage, undefined}, + {inhibit_any, undefined}, + {auth_key_id, undefined}, + {subject_key_id, undefined}, + {policy_mapping, undefined}], + Filter = fun({Key, _}, D) -> lists:keydelete(Key, 1, D) end, + Exts ++ lists:foldl(Filter, Def, Exts). + +extension({_, undefined}) -> []; +extension({basic_constraints, Data}) -> + case Data of + default -> + #'Extension'{extnID = ?'id-ce-basicConstraints', + extnValue = #'BasicConstraints'{cA=true}, + critical=true}; + false -> + []; + Len when is_integer(Len) -> + #'Extension'{extnID = ?'id-ce-basicConstraints', + extnValue = #'BasicConstraints'{cA=true, pathLenConstraint=Len}, + critical=true}; + _ -> + #'Extension'{extnID = ?'id-ce-basicConstraints', + extnValue = Data} + end; +extension({Id, Data, Critical}) -> + #'Extension'{extnID = Id, extnValue = Data, critical = Critical}. + + +publickey(#'RSAPrivateKey'{modulus=N, publicExponent=E}) -> + Public = #'RSAPublicKey'{modulus=N, publicExponent=E}, + Algo = #'PublicKeyAlgorithm'{algorithm= ?rsaEncryption, parameters='NULL'}, + #'OTPSubjectPublicKeyInfo'{algorithm = Algo, + subjectPublicKey = Public}; +publickey(#'DSAPrivateKey'{p=P, q=Q, g=G, y=Y}) -> + Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-dsa', + parameters=#'Dss-Parms'{p=P, q=Q, g=G}}, + #'OTPSubjectPublicKeyInfo'{algorithm = Algo, subjectPublicKey = Y}. + +validity(Opts) -> + DefFrom0 = date(), + DefTo0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())+7), + {DefFrom, DefTo} = proplists:get_value(validity, Opts, {DefFrom0, DefTo0}), + Format = fun({Y,M,D}) -> lists:flatten(io_lib:format("~w~2..0w~2..0w000000Z",[Y,M,D])) end, + #'Validity'{notBefore={generalTime, Format(DefFrom)}, + notAfter ={generalTime, Format(DefTo)}}. + +sign_algorithm(#'RSAPrivateKey'{}, Opts) -> + Type = case proplists:get_value(digest, Opts, sha1) of + sha1 -> ?'sha1WithRSAEncryption'; + sha512 -> ?'sha512WithRSAEncryption'; + sha384 -> ?'sha384WithRSAEncryption'; + sha256 -> ?'sha256WithRSAEncryption'; + md5 -> ?'md5WithRSAEncryption'; + md2 -> ?'md2WithRSAEncryption' + end, + {Type, 'NULL'}; +sign_algorithm(#'DSAPrivateKey'{p=P, q=Q, g=G}, _Opts) -> + {?'id-dsa-with-sha1', #'Dss-Parms'{p=P, q=Q, g=G}}. + +make_key(rsa, _Opts) -> + %% (OBS: for testing only) + gen_rsa2(64); +make_key(dsa, _Opts) -> + gen_dsa2(128, 20). %% Bytes i.e. {1024, 160} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% RSA key generation (OBS: for testing only) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-define(SMALL_PRIMES, [65537,97,89,83,79,73,71,67,61,59,53, + 47,43,41,37,31,29,23,19,17,13,11,7,5,3]). + +gen_rsa2(Size) -> + P = prime(Size), + Q = prime(Size), + N = P*Q, + Tot = (P - 1) * (Q - 1), + [E|_] = lists:dropwhile(fun(Candidate) -> (Tot rem Candidate) == 0 end, ?SMALL_PRIMES), + {D1,D2} = extended_gcd(E, Tot), + D = erlang:max(D1,D2), + case D < E of + true -> + gen_rsa2(Size); + false -> + {Co1,Co2} = extended_gcd(Q, P), + Co = erlang:max(Co1,Co2), + #'RSAPrivateKey'{version = 'two-prime', + modulus = N, + publicExponent = E, + privateExponent = D, + prime1 = P, + prime2 = Q, + exponent1 = D rem (P-1), + exponent2 = D rem (Q-1), + coefficient = Co + } + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% DSA key generation (OBS: for testing only) +%% See http://en.wikipedia.org/wiki/Digital_Signature_Algorithm +%% and the fips_186-3.pdf +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +gen_dsa2(LSize, NSize) -> + Q = prime(NSize), %% Choose N-bit prime Q + X0 = prime(LSize), + P0 = prime((LSize div 2) +1), + + %% Choose L-bit prime modulus P such that p–1 is a multiple of q. + case dsa_search(X0 div (2*Q*P0), P0, Q, 1000) of + error -> + gen_dsa2(LSize, NSize); + P -> + G = crypto:mod_exp(2, (P-1) div Q, P), % Choose G a number whose multiplicative order modulo p is q. + %% such that This may be done by setting g = h^(p–1)/q mod p, commonly h=2 is used. + + X = prime(20), %% Choose x by some random method, where 0 < x < q. + Y = crypto:mod_exp(G, X, P), %% Calculate y = g^x mod p. + + #'DSAPrivateKey'{version=0, p=P, q=Q, g=G, y=Y, x=X} + end. + +%% See fips_186-3.pdf +dsa_search(T, P0, Q, Iter) when Iter > 0 -> + P = 2*T*Q*P0 + 1, + case is_prime(crypto:mpint(P), 50) of + true -> P; + false -> dsa_search(T+1, P0, Q, Iter-1) + end; +dsa_search(_,_,_,_) -> + error. + + +%%%%%%% Crypto Math %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +prime(ByteSize) -> + Rand = odd_rand(ByteSize), + crypto:erlint(prime_odd(Rand, 0)). + +prime_odd(Rand, N) -> + case is_prime(Rand, 50) of + true -> + Rand; + false -> + NotPrime = crypto:erlint(Rand), + prime_odd(crypto:mpint(NotPrime+2), N+1) + end. + +%% see http://en.wikipedia.org/wiki/Fermat_primality_test +is_prime(_, 0) -> true; +is_prime(Candidate, Test) -> + CoPrime = odd_rand(<<0,0,0,4, 10000:32>>, Candidate), + case crypto:mod_exp(CoPrime, Candidate, Candidate) of + CoPrime -> is_prime(Candidate, Test-1); + _ -> false + end. + +odd_rand(Size) -> + Min = 1 bsl (Size*8-1), + Max = (1 bsl (Size*8))-1, + odd_rand(crypto:mpint(Min), crypto:mpint(Max)). + +odd_rand(Min,Max) -> + Rand = <<Sz:32, _/binary>> = crypto:rand_uniform(Min,Max), + BitSkip = (Sz+4)*8-1, + case Rand of + Odd = <<_:BitSkip, 1:1>> -> Odd; + Even = <<_:BitSkip, 0:1>> -> + crypto:mpint(crypto:erlint(Even)+1) + end. + +extended_gcd(A, B) -> + case A rem B of + 0 -> + {0, 1}; + N -> + {X, Y} = extended_gcd(B, N), + {Y, X-Y*(A div B)} + end. diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 522eda54d6..0d9a912e30 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -53,11 +53,15 @@ init_per_suite(Config) -> crypto:start(), ssl:start(), + + %% make rsa certs using oppenssl Result = (catch make_certs:all(?config(data_dir, Config), ?config(priv_dir, Config))), test_server:format("Make certs ~p~n", [Result]), - ssl_test_lib:cert_options(Config). + + NewConfig = ssl_test_lib:make_dsa_cert(Config), + ssl_test_lib:cert_options(NewConfig). %%-------------------------------------------------------------------- %% Function: end_per_suite(Config) -> _ @@ -105,8 +109,10 @@ init_per_testcase(no_authority_key_identifier, Config) -> ssl:start(), Config; -init_per_testcase(TestCase, Config) when TestCase == ciphers_ssl3; - TestCase == ciphers_ssl3_openssl_names -> +init_per_testcase(TestCase, Config) when TestCase == ciphers_rsa_signed_certs_ssl3; + TestCase == ciphers_rsa_signed_certs_openssl_names_ssl3; + TestCase == ciphers_dsa_signed_certs_ssl3; + TestCase == ciphers_dsa_signed_certs_openssl_names_ssl3 -> ssl:stop(), application:load(ssl), application:set_env(ssl, protocol_version, sslv3), @@ -124,7 +130,6 @@ init_per_testcase(protocol_versions, Config) -> init_per_testcase(empty_protocol_versions, Config) -> ssl:stop(), application:load(ssl), - %% For backwards compatibility sslv2 should be filtered out. application:set_env(ssl, protocol_version, []), ssl:start(), Config; @@ -165,8 +170,10 @@ end_per_testcase(session_cache_process_mnesia, Config) -> end_per_testcase(reuse_session_expired, Config) -> application:unset_env(ssl, session_lifetime), end_per_testcase(default_action, Config); -end_per_testcase(TestCase, Config) when TestCase == ciphers_ssl3; - TestCase == ciphers_ssl3_openssl_names; +end_per_testcase(TestCase, Config) when TestCase == ciphers_rsa_signed_certs_ssl3; + TestCase == ciphers_rsa_signed_certs_openssl_names_ssl3; + TestCase == ciphers_dsa_signed_certs_ssl3; + TestCase == ciphers_dsa_signed_certs_openssl_names_ssl3; TestCase == protocol_versions; TestCase == empty_protocol_versions-> application:unset_env(ssl, protocol_version), @@ -193,30 +200,37 @@ all(doc) -> all(suite) -> [app, alerts, connection_info, protocol_versions, - empty_protocol_versions, controlling_process, controller_dies, - client_closes_socket, peercert, connect_dist, peername, sockname, - socket_options, misc_ssl_options, versions, cipher_suites, - upgrade, upgrade_with_timeout, tcp_connect, ipv6, ekeyfile, - ecertfile, ecacertfile, eoptions, shutdown, shutdown_write, - shutdown_both, shutdown_error, ciphers, ciphers_ssl3, - ciphers_openssl_names, ciphers_ssl3_openssl_names, send_close, - close_transport_accept, dh_params, server_verify_peer_passive, - server_verify_peer_active, server_verify_peer_active_once, - server_verify_none_passive, server_verify_none_active, - server_verify_none_active_once, server_verify_no_cacerts, - server_require_peer_cert_ok, server_require_peer_cert_fail, - server_verify_client_once_passive, - server_verify_client_once_active, - server_verify_client_once_active_once, client_verify_none_passive, - client_verify_none_active, client_verify_none_active_once, - session_cache_process_list, session_cache_process_mnesia, - reuse_session, reuse_session_expired, - server_does_not_want_to_reuse_session, client_renegotiate, - server_renegotiate, client_renegotiate_reused_session, - server_renegotiate_reused_session, client_no_wrap_sequence_number, - server_no_wrap_sequence_number, extended_key_usage, - validate_extensions_fun, no_authority_key_identifier, - invalid_signature_client, invalid_signature_server, cert_expired + empty_protocol_versions, controlling_process, controller_dies, + client_closes_socket, peercert, connect_dist, peername, sockname, + socket_options, misc_ssl_options, versions, cipher_suites, + upgrade, upgrade_with_timeout, tcp_connect, ipv6, ekeyfile, + ecertfile, ecacertfile, eoptions, shutdown, shutdown_write, + shutdown_both, shutdown_error, + ciphers_rsa_signed_certs, ciphers_rsa_signed_certs_ssl3, + ciphers_rsa_signed_certs_openssl_names, + ciphers_rsa_signed_certs_openssl_names_ssl3, + ciphers_dsa_signed_certs, + ciphers_dsa_signed_certs_ssl3, + ciphers_dsa_signed_certs_openssl_names, + ciphers_dsa_signed_certs_openssl_names_ssl3, + send_close, + close_transport_accept, dh_params, server_verify_peer_passive, + server_verify_peer_active, server_verify_peer_active_once, + server_verify_none_passive, server_verify_none_active, + server_verify_none_active_once, server_verify_no_cacerts, + server_require_peer_cert_ok, server_require_peer_cert_fail, + server_verify_client_once_passive, + server_verify_client_once_active, + server_verify_client_once_active_once, client_verify_none_passive, + client_verify_none_active, client_verify_none_active_once, + session_cache_process_list, session_cache_process_mnesia, + reuse_session, reuse_session_expired, + server_does_not_want_to_reuse_session, client_renegotiate, + server_renegotiate, client_renegotiate_reused_session, + server_renegotiate_reused_session, client_no_wrap_sequence_number, + server_no_wrap_sequence_number, extended_key_usage, + validate_extensions_fun, no_authority_key_identifier, + invalid_signature_client, invalid_signature_server, cert_expired ]. %% Test cases starts here. @@ -1394,87 +1408,129 @@ shutdown_error(Config) when is_list(Config) -> {error, closed} = ssl:shutdown(Listen, read_write). %%------------------------------------------------------------------- -ciphers(doc) -> - ["Test all ssl cipher suites in highest support ssl/tls version"]; +ciphers_rsa_signed_certs(doc) -> + ["Test all rsa ssl cipher suites in highest support ssl/tls version"]; -ciphers(suite) -> +ciphers_rsa_signed_certs(suite) -> []; -ciphers(Config) when is_list(Config) -> +ciphers_rsa_signed_certs(Config) when is_list(Config) -> Version = ssl_record:protocol_version(ssl_record:highest_protocol_version([])), - Ciphers = ssl:cipher_suites(), + Ciphers = ssl_test_lib:rsa_suites(), test_server:format("tls1 erlang cipher suites ~p~n", [Ciphers]), - Result = lists:map(fun(Cipher) -> - cipher(Cipher, Version, Config) end, - Ciphers), - case lists:flatten(Result) of - [] -> - ok; - Error -> - test_server:format("Cipher suite errors: ~p~n", [Error]), - test_server:fail(cipher_suite_failed_see_test_case_log) - end. + run_suites(Ciphers, Version, Config, rsa). -ciphers_ssl3(doc) -> - ["Test all ssl cipher suites in ssl3"]; +ciphers_rsa_signed_certs_ssl3(doc) -> + ["Test all rsa ssl cipher suites in ssl3"]; -ciphers_ssl3(suite) -> +ciphers_rsa_signed_certs_ssl3(suite) -> []; -ciphers_ssl3(Config) when is_list(Config) -> +ciphers_rsa_signed_certs_ssl3(Config) when is_list(Config) -> Version = ssl_record:protocol_version({3,0}), - Ciphers = ssl:cipher_suites(), + Ciphers = ssl_test_lib:rsa_suites(), test_server:format("ssl3 erlang cipher suites ~p~n", [Ciphers]), - Result = lists:map(fun(Cipher) -> - cipher(Cipher, Version, Config) end, - Ciphers), - case lists:flatten(Result) of - [] -> - ok; - Error -> - test_server:format("Cipher suite errors: ~p~n", [Error]), - test_server:fail(cipher_suite_failed_see_test_case_log) - end. + run_suites(Ciphers, Version, Config, rsa). + +ciphers_rsa_signed_certs_openssl_names(doc) -> + ["Test all rsa ssl cipher suites in highest support ssl/tls version"]; + +ciphers_rsa_signed_certs_openssl_names(suite) -> + []; + +ciphers_rsa_signed_certs_openssl_names(Config) when is_list(Config) -> + Version = + ssl_record:protocol_version(ssl_record:highest_protocol_version([])), + Ciphers = ssl_test_lib:openssl_rsa_suites(), + test_server:format("tls1 openssl cipher suites ~p~n", [Ciphers]), + run_suites(Ciphers, Version, Config, rsa). + -ciphers_openssl_names(doc) -> - ["Test all ssl cipher suites in highest support ssl/tls version"]; +ciphers_rsa_signed_certs_openssl_names_ssl3(doc) -> + ["Test all dsa ssl cipher suites in ssl3"]; -ciphers_openssl_names(suite) -> +ciphers_rsa_signed_certs_openssl_names_ssl3(suite) -> []; -ciphers_openssl_names(Config) when is_list(Config) -> +ciphers_rsa_signed_certs_openssl_names_ssl3(Config) when is_list(Config) -> + Version = ssl_record:protocol_version({3,0}), + Ciphers = ssl_test_lib:openssl_rsa_suites(), + run_suites(Ciphers, Version, Config, rsa). + + +ciphers_dsa_signed_certs(doc) -> + ["Test all dsa ssl cipher suites in highest support ssl/tls version"]; + +ciphers_dsa_signed_certs(suite) -> + []; + +ciphers_dsa_signed_certs(Config) when is_list(Config) -> Version = ssl_record:protocol_version(ssl_record:highest_protocol_version([])), - Ciphers = ssl:cipher_suites(openssl), + Ciphers = ssl_test_lib:dsa_suites(), + test_server:format("tls1 erlang cipher suites ~p~n", [Ciphers]), + run_suites(Ciphers, Version, Config, dsa). + +ciphers_dsa_signed_certs_ssl3(doc) -> + ["Test all dsa ssl cipher suites in ssl3"]; + +ciphers_dsa_signed_certs_ssl3(suite) -> + []; + +ciphers_dsa_signed_certs_ssl3(Config) when is_list(Config) -> + Version = + ssl_record:protocol_version({3,0}), + + Ciphers = ssl_test_lib:dsa_suites(), + test_server:format("ssl3 erlang cipher suites ~p~n", [Ciphers]), + run_suites(Ciphers, Version, Config, dsa). + + +ciphers_dsa_signed_certs_openssl_names(doc) -> + ["Test all dsa ssl cipher suites in highest support ssl/tls version"]; + +ciphers_dsa_signed_certs_openssl_names(suite) -> + []; + +ciphers_dsa_signed_certs_openssl_names(Config) when is_list(Config) -> + Version = + ssl_record:protocol_version(ssl_record:highest_protocol_version([])), + + Ciphers = ssl_test_lib:openssl_dsa_suites(), test_server:format("tls1 openssl cipher suites ~p~n", [Ciphers]), - Result = lists:map(fun(Cipher) -> - cipher(Cipher, Version, Config) end, - Ciphers), - case lists:flatten(Result) of - [] -> - ok; - Error -> - test_server:format("Cipher suite errors: ~p~n", [Error]), - test_server:fail(cipher_suite_failed_see_test_case_log) - end. + run_suites(Ciphers, Version, Config, dsa). -ciphers_ssl3_openssl_names(doc) -> - ["Test all ssl cipher suites in ssl3"]; +ciphers_dsa_signed_certs_openssl_names_ssl3(doc) -> + ["Test all dsa ssl cipher suites in ssl3"]; -ciphers_ssl3_openssl_names(suite) -> +ciphers_dsa_signed_certs_openssl_names_ssl3(suite) -> []; -ciphers_ssl3_openssl_names(Config) when is_list(Config) -> +ciphers_dsa_signed_certs_openssl_names_ssl3(Config) when is_list(Config) -> Version = ssl_record:protocol_version({3,0}), - Ciphers = ssl:cipher_suites(openssl), + Ciphers = ssl_test_lib:openssl_dsa_suites(), + run_suites(Ciphers, Version, Config, dsa). + + +run_suites(Ciphers, Version, Config, Type) -> + {ClientOpts, ServerOpts} = + case Type of + rsa -> + {?config(client_opts, Config), + ?config(server_opts, Config)}; + dsa -> + {?config(client_opts, Config), + ?config(server_dsa_opts, Config)} + end, + Result = lists:map(fun(Cipher) -> - cipher(Cipher, Version, Config) end, + cipher(Cipher, Version, Config, ClientOpts, ServerOpts) end, Ciphers), case lists:flatten(Result) of [] -> @@ -1484,11 +1540,14 @@ ciphers_ssl3_openssl_names(Config) when is_list(Config) -> test_server:fail(cipher_suite_failed_see_test_case_log) end. -cipher(CipherSuite, Version, Config) -> +erlang_cipher_suite(Suite) when is_list(Suite)-> + ssl_cipher:suite_definition(ssl_cipher:openssl_suite(Suite)); +erlang_cipher_suite(Suite) -> + Suite. + +cipher(CipherSuite, Version, Config, ClientOpts, ServerOpts) -> process_flag(trap_exit, true), test_server:format("Testing CipherSuite ~p~n", [CipherSuite]), - 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()}, @@ -1527,11 +1586,6 @@ cipher(CipherSuite, Version, Config) -> [{ErlangCipherSuite, Error}] end. -erlang_cipher_suite(Suite) when is_list(Suite)-> - ssl_cipher:suite_definition(ssl_cipher:openssl_suite(Suite)); -erlang_cipher_suite(Suite) -> - Suite. - %%-------------------------------------------------------------------- reuse_session(doc) -> ["Test reuse of sessions (short handshake)"]; @@ -2681,13 +2735,51 @@ invalid_signature_client(Config) when is_list(Config) -> {options, [{verify, verify_peer} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, NewClientOpts}]), - - ssl_test_lib:check_result(Server, {error, "bad certificate"}, - Client, {error,"bad certificate"}). + {host, Hostname}, + {from, self()}, + {options, NewClientOpts}]), + tcp_delivery_workaround(Server, {error, "bad certificate"}, + Client, {error,"bad certificate"}). + +tcp_delivery_workaround(Server, ServMsg, Client, ClientMsg) -> + receive + {Server, ServerMsg} -> + receive + {Client, ClientMsg} -> + ok; + {Client, {error,closed}} -> + test_server:format("client got close"); + Unexpected -> + test_server:fail(Unexpected) + end; + {Client, ClientMsg} -> + receive + {Server, ServerMsg} -> + ok; + Unexpected -> + test_server:fail(Unexpected) + end; + {Client, {error,closed}} -> + receive + {Server, ServerMsg} -> + ok; + Unexpected -> + test_server:fail(Unexpected) + end; + {Server, {error,closed}} -> + receive + {Client, ClientMsg} -> + ok; + {Client, {error,closed}} -> + test_server:format("client got close"), + ok; + Unexpected -> + test_server:fail(Unexpected) + end; + Unexpected -> + test_server:fail(Unexpected) + end. %%-------------------------------------------------------------------- cert_expired(doc) -> ["Test server with invalid signature"]; @@ -2953,4 +3045,3 @@ erlang_ssl_receive(Socket, Data) -> after ?SLEEP * 3 -> test_server:fail({did_not_get, Data}) end. - diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index 46b6eb401d..d11acc8130 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -318,6 +318,25 @@ cert_options(Config) -> | Config]. +make_dsa_cert(Config) -> + ServerCaInfo = {ServerCaCert, _} = erl_make_certs:make_cert([{key, dsa}]), + {ServerCert, ServerCertKey} = erl_make_certs:make_cert([{key, dsa}, {issuer, ServerCaInfo}]), + ServerCaCertFile = filename:join([?config(priv_dir, Config), + "server", "dsa_cacerts.pem"]), + ServerCertFile = filename:join([?config(priv_dir, Config), + "server", "dsa_cert.pem"]), + ServerKeyFile = filename:join([?config(priv_dir, Config), + "server", "dsa_key.pem"]), + + public_key:der_to_pem(ServerCaCertFile, [{cert, ServerCaCert, not_encrypted}]), + public_key:der_to_pem(ServerCertFile, [{cert, ServerCert, not_encrypted}]), + public_key:der_to_pem(ServerKeyFile, [ServerCertKey]), + + [{server_dsa_opts, [{ssl_imp, new},{reuseaddr, true}, + {cacertfile, ServerCaCertFile}, + {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]} | Config]. + + start_upgrade_server(Args) -> Result = spawn_link(?MODULE, run_upgrade_server, [Args]), receive @@ -529,3 +548,42 @@ send_selected_port(Pid, 0, Socket) -> Pid ! {self(), {port, NewPort}}; send_selected_port(_,_,_) -> ok. + +rsa_suites() -> + lists:filter(fun({dhe_dss, _, _}) -> + false; + (_) -> + true + end, + ssl:cipher_suites()). + +dsa_suites() -> + lists:filter(fun({dhe_dss, _, _}) -> + true; + (_) -> + false + end, + ssl:cipher_suites()). + + +openssl_rsa_suites() -> + Ciphers = ssl:cipher_suites(openssl), + lists:filter(fun(Str) -> + case re:run(Str,"DSS",[]) of + nomatch -> + true; + _ -> + false + end + end, Ciphers). + +openssl_dsa_suites() -> + Ciphers = ssl:cipher_suites(openssl), + lists:filter(fun(Str) -> + case re:run(Str,"DSS",[]) of + nomatch -> + false; + _ -> + true + end + end, Ciphers). diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl index 1c18f10038..e4c77b2fb4 100644 --- a/lib/ssl/test/ssl_to_openssl_SUITE.erl +++ b/lib/ssl/test/ssl_to_openssl_SUITE.erl @@ -56,7 +56,8 @@ init_per_suite(Config) -> (catch make_certs:all(?config(data_dir, Config), ?config(priv_dir, Config))), test_server:format("Make certs ~p~n", [Result]), - ssl_test_lib:cert_options(Config) + NewConfig = ssl_test_lib:make_dsa_cert(Config), + ssl_test_lib:cert_options(NewConfig) end. %%-------------------------------------------------------------------- @@ -142,6 +143,7 @@ all(doc) -> all(suite) -> [erlang_client_openssl_server, erlang_server_openssl_client, + erlang_server_openssl_client_dsa_cert, erlang_server_openssl_client_reuse_session, erlang_client_openssl_server_renegotiate, erlang_client_openssl_server_no_wrap_sequence_number, @@ -157,7 +159,8 @@ all(suite) -> tls1_erlang_client_openssl_server_client_cert, tls1_erlang_server_openssl_client_client_cert, tls1_erlang_server_erlang_client_client_cert, - ciphers, + ciphers_rsa_signed_certs, + ciphers_dsa_signed_certs, erlang_client_bad_openssl_server, expired_session, ssl2_erlang_server_openssl_client @@ -247,6 +250,43 @@ erlang_server_openssl_client(Config) when is_list(Config) -> %%-------------------------------------------------------------------- +erlang_server_openssl_client_dsa_cert(doc) -> + ["Test erlang server with openssl client"]; +erlang_server_openssl_client_dsa_cert(suite) -> + []; +erlang_server_openssl_client_dsa_cert(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ?config(server_dsa_opts, Config), + + {_, ServerNode, _} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Cmd = "openssl s_client -port " ++ integer_to_list(Port) ++ + " -host localhost -tls1 -msg", + + test_server:format("openssl cmd: ~p~n", [Cmd]), + + OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + port_command(OpenSslPort, Data), + + ssl_test_lib:check_result(Server, ok), + + ssl_test_lib:close(Server), + + close_port(OpenSslPort), + process_flag(trap_exit, false), + ok. + + +%%-------------------------------------------------------------------- + erlang_server_openssl_client_reuse_session(doc) -> ["Test erlang server with openssl client that reconnects with the" "same session id, to test reusing of sessions."]; @@ -881,19 +921,46 @@ tls1_erlang_server_erlang_client_client_cert(Config) when is_list(Config) -> ok. %%-------------------------------------------------------------------- -ciphers(doc) -> - [""]; +ciphers_rsa_signed_certs(doc) -> + ["Test cipher suites that uses rsa certs"]; + +ciphers_rsa_signed_certs(suite) -> + []; + +ciphers_rsa_signed_certs(Config) when is_list(Config) -> + Version = + ssl_record:protocol_version(ssl_record:highest_protocol_version([])), + + Ciphers = ssl_test_lib:rsa_suites(), + run_suites(Ciphers, Version, Config, rsa). + + +ciphers_dsa_signed_certs(doc) -> + ["Test cipher suites that uses dsa certs"]; -ciphers(suite) -> +ciphers_dsa_signed_certs(suite) -> []; -ciphers(Config) when is_list(Config) -> +ciphers_dsa_signed_certs(Config) when is_list(Config) -> Version = ssl_record:protocol_version(ssl_record:highest_protocol_version([])), - Ciphers = ssl:cipher_suites(), + Ciphers = ssl_test_lib:dsa_suites(), + run_suites(Ciphers, Version, Config, dsa). + +run_suites(Ciphers, Version, Config, Type) -> + {ClientOpts, ServerOpts} = + case Type of + rsa -> + {?config(client_opts, Config), + ?config(server_opts, Config)}; + dsa -> + {?config(client_opts, Config), + ?config(server_dsa_opts, Config)} + end, + Result = lists:map(fun(Cipher) -> - cipher(Cipher, Version, Config) end, + cipher(Cipher, Version, Config, ClientOpts, ServerOpts) end, Ciphers), case lists:flatten(Result) of [] -> @@ -902,12 +969,12 @@ ciphers(Config) when is_list(Config) -> test_server:format("Cipher suite errors: ~p~n", [Error]), test_server:fail(cipher_suite_failed_see_test_case_log) end. - -cipher(CipherSuite, Version, Config) -> + + + +cipher(CipherSuite, Version, Config, ClientOpts, ServerOpts) -> process_flag(trap_exit, true), test_server:format("Testing CipherSuite ~p~n", [CipherSuite]), - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), {ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config), Port = ssl_test_lib:inet_port(node()), diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk index e3db7008e3..5d8be1cd0b 100644 --- a/lib/ssl/vsn.mk +++ b/lib/ssl/vsn.mk @@ -19,9 +19,12 @@ SSL_VSN = 3.11.1 -TICKETS = OTP-8588 \ +TICKETS = OTP-8679 \ + OTP-7047 \ + OTP-7049 \ OTP-8568 \ - OTP-7049 + OTP-8587 \ + OTP-8588 #TICKETS_3.11 = OTP-8517 \ # OTP-7046 \ diff --git a/lib/stdlib/doc/src/re.xml b/lib/stdlib/doc/src/re.xml index 4d2a0e0995..80adc3e347 100644 --- a/lib/stdlib/doc/src/re.xml +++ b/lib/stdlib/doc/src/re.xml @@ -80,7 +80,11 @@ - a unicode_binary is allowed as the tail of the list</code> <code type="none"> - mp() = Opaque datatype containing a compiled regular expression.</code> + mp() = Opaque datatype containing a compiled regular expression. + - The mp() is guaranteed to be a tuple() having the atom + 're_pattern' as it's first element, to allow for matching in + guards. The arity of the tuple() or the content of the other fields + is however not to be trusted.</code> </section> <funcs> <func> diff --git a/lib/stdlib/src/beam_lib.erl b/lib/stdlib/src/beam_lib.erl index c71dad6163..91ff2438c6 100644 --- a/lib/stdlib/src/beam_lib.erl +++ b/lib/stdlib/src/beam_lib.erl @@ -41,6 +41,8 @@ terminate/2,code_change/3]). -export([make_crypto_key/2, get_crypto_key/1]). %Utilities used by compiler +-export_type([attrib_entry/0, compinfo_entry/0, labeled_entry/0]). + -import(lists, [append/1, delete/2, foreach/2, keysort/2, member/2, reverse/1, sort/1, splitwith/2]). diff --git a/lib/stdlib/src/dets.erl b/lib/stdlib/src/dets.erl index 7f1c13770b..4584b8184f 100644 --- a/lib/stdlib/src/dets.erl +++ b/lib/stdlib/src/dets.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1996-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% -module(dets). @@ -88,6 +88,7 @@ %% Not documented, or not ready for publication. -export([lookup_keys/2]). +-export_type([tab_name/0]). -compile({inline, [{einval,2},{badarg,2},{undefined,1}, {badarg_exit,2},{lookup_reply,2}]}). diff --git a/lib/stdlib/src/digraph.erl b/lib/stdlib/src/digraph.erl index 9bdea671a9..b5f52da921 100644 --- a/lib/stdlib/src/digraph.erl +++ b/lib/stdlib/src/digraph.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1996-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% -module(digraph). @@ -36,6 +36,8 @@ -export([get_short_path/3, get_short_cycle/2]). +-export_type([d_type/0, vertex/0]). + -record(digraph, {vtab = notable :: ets:tab(), etab = notable :: ets:tab(), ntab = notable :: ets:tab(), diff --git a/lib/stdlib/src/epp.erl b/lib/stdlib/src/epp.erl index f144cbb938..81b2431f40 100644 --- a/lib/stdlib/src/epp.erl +++ b/lib/stdlib/src/epp.erl @@ -111,6 +111,8 @@ format_error({bad,W}) -> io_lib:format("badly formed '~s'", [W]); format_error(missing_parenthesis) -> io_lib:format("badly formed define: missing closing right parenthesis",[]); +format_error(premature_end) -> + "premature end"; format_error({call,What}) -> io_lib:format("illegal macro call '~s'",[What]); format_error({undefined,M,none}) -> @@ -163,7 +165,7 @@ parse_file(Epp) -> case normalize_typed_record_fields(Fields) of {typed, NewFields} -> [{attribute, La, record, {Record, NewFields}}, - {attribute, La, type, + {attribute, La, type, {{record, Record}, Fields, []}} |parse_file(Epp)]; not_typed -> @@ -188,7 +190,7 @@ normalize_typed_record_fields([], NewFields, Typed) -> true -> {typed, lists:reverse(NewFields)}; false -> not_typed end; -normalize_typed_record_fields([{typed_record_field,Field,_}|Rest], +normalize_typed_record_fields([{typed_record_field,Field,_}|Rest], NewFields, _Typed) -> normalize_typed_record_fields(Rest, [Field|NewFields], true); normalize_typed_record_fields([Field|Rest], NewFields, Typed) -> @@ -324,7 +326,7 @@ wait_req_scan(St) -> wait_req_skip(St, Sis) -> From = wait_request(St), skip_toks(From, St, Sis). - + %% enter_file(Path, FileName, IncludeToken, From, EppState) %% leave_file(From, EppState) %% Handle entering and leaving included files. Notify caller when the @@ -380,16 +382,16 @@ file_name(N) when is_atom(N) -> leave_file(From, St) -> case St#epp.istk of - [I|Cis] -> + [I|Cis] -> epp_reply(From, - {error,{St#epp.location,epp, + {error,{St#epp.location,epp, {illegal,"unterminated",I}}}), leave_file(wait_request(St),St#epp{istk=Cis}); [] -> case St#epp.sstk of [OldSt|Sts] -> close_file(St), - enter_file_reply(From, OldSt#epp.name, + enter_file_reply(From, OldSt#epp.name, OldSt#epp.location, OldSt#epp.location), Ms = dict:store({atom,'FILE'}, {none, @@ -491,9 +493,9 @@ scan_extends(_Ts, _As, Ms) -> Ms. %% scan_define(Tokens, DefineToken, From, EppState) -scan_define([{'(',_Lp},{Type,_Lm,M}=Mac,{',',_Lc}|Toks], _Def, From, St) +scan_define([{'(',_Lp},{Type,_Lm,M}=Mac,{',',Lc}|Toks], _Def, From, St) when Type =:= atom; Type =:= var -> - case catch macro_expansion(Toks) of + case catch macro_expansion(Toks, Lc) of Expansion when is_list(Expansion) -> case dict:find({atom,M}, St#epp.macs) of {ok, Defs} when is_list(Defs) -> @@ -608,7 +610,7 @@ scan_undef(_Toks, Undef, From, St) -> %% scan_include(Tokens, IncludeToken, From, St) -scan_include([{'(',_Llp},{string,_Lf,NewName0},{')',_Lrp},{dot,_Ld}], Inc, +scan_include([{'(',_Llp},{string,_Lf,NewName0},{')',_Lrp},{dot,_Ld}], Inc, From, St) -> NewName = expand_var(NewName0), enter_file(St#epp.path, NewName, Inc, From, St); @@ -644,7 +646,7 @@ scan_include_lib([{'(',_Llp},{string,_Lf,NewName0},{')',_Lrp},{dot,_Ld}], case file:open(LibName, [read]) of {ok,NewF} -> ExtraPath = [filename:dirname(LibName)], - wait_req_scan(enter_file2(NewF, LibName, From, + wait_req_scan(enter_file2(NewF, LibName, From, St, Loc, ExtraPath)); {error,_E2} -> epp_reply(From, @@ -773,7 +775,7 @@ scan_file(_Toks, Tf, From, St) -> new_location(Ln, Le, Lf) when is_integer(Lf) -> Ln+(Le-Lf); -new_location(Ln, {Le,_}, {Lf,_}) -> +new_location(Ln, {Le,_}, {Lf,_}) -> {Ln+(Le-Lf),1}. %% skip_toks(From, EppState, SkipIstack) @@ -814,22 +816,23 @@ skip_else(_Else, From, St, Sis) -> skip_toks(From, St, Sis). %% macro_pars(Tokens, ArgStack) -%% macro_expansion(Tokens) +%% macro_expansion(Tokens, Line) %% Extract the macro parameters and the expansion from a macro definition. -macro_pars([{')',_Lp}, {',',_Ld}|Ex], Args) -> - {ok, {lists:reverse(Args), macro_expansion(Ex)}}; -macro_pars([{var,_,Name}, {')',_Lp}, {',',_Ld}|Ex], Args) -> +macro_pars([{')',_Lp}, {',',Ld}|Ex], Args) -> + {ok, {lists:reverse(Args), macro_expansion(Ex, Ld)}}; +macro_pars([{var,_,Name}, {')',_Lp}, {',',Ld}|Ex], Args) -> false = lists:member(Name, Args), %Prolog is nice - {ok, {lists:reverse([Name|Args]), macro_expansion(Ex)}}; + {ok, {lists:reverse([Name|Args]), macro_expansion(Ex, Ld)}}; macro_pars([{var,_L,Name}, {',',_}|Ts], Args) -> - false = lists:member(Name, Args), + false = lists:member(Name, Args), macro_pars(Ts, [Name|Args]). -macro_expansion([{')',_Lp},{dot,_Ld}]) -> []; -macro_expansion([{dot,Ld}]) -> throw({error,Ld,missing_parenthesis}); -macro_expansion([T|Ts]) -> - [T|macro_expansion(Ts)]. +macro_expansion([{')',_Lp},{dot,_Ld}], _L0) -> []; +macro_expansion([{dot,Ld}], _L0) -> throw({error,Ld,missing_parenthesis}); +macro_expansion([T|Ts], _L0) -> + [T|macro_expansion(Ts, element(2, T))]; +macro_expansion([], L0) -> throw({error,L0,premature_end}). %% expand_macros(Tokens, Macros) %% expand_macro(Tokens, MacroToken, RestTokens) @@ -1084,11 +1087,11 @@ epp_reply(From, Rep) -> wait_epp_reply(Epp, Mref) -> receive - {epp_reply,Epp,Rep} -> + {epp_reply,Epp,Rep} -> erlang:demonitor(Mref), receive {'DOWN',Mref,_,_,_} -> ok after 0 -> ok end, Rep; - {'DOWN',Mref,_,_,E} -> + {'DOWN',Mref,_,_,E} -> receive {epp_reply,Epp,Rep} -> Rep after 0 -> exit(E) end @@ -1145,7 +1148,7 @@ get_line({Line,_Column}) -> %% mainly aimed at yecc, the parser generator, which uses the -file %% attribute to get correct lines in messages referring to code %% supplied by the user (actions etc in .yrl files). -%% +%% %% In a perfect world (read: perfectly implemented applications such %% as Xref, Cover, Debugger, etc.) it would not be necessary to %% distinguish -file attributes from epp and the input file. The @@ -1165,7 +1168,7 @@ get_line({Line,_Column}) -> %% have been output by epp (corresponding to -include and %% -include_lib) are kept, but the user's -file attributes are %% removed. This seems sufficient for now. -%% +%% %% It turns out to be difficult to distinguish -file attributes in the %% input file from the ones added by epp unless some action is taken. %% The (less than perfect) solution employed is to let epp assign @@ -1177,7 +1180,7 @@ get_line({Line,_Column}) -> interpret_file_attribute(Forms) -> interpret_file_attr(Forms, 0, []). -interpret_file_attr([{attribute,Loc,file,{File,Line}}=Form | Forms], +interpret_file_attr([{attribute,Loc,file,{File,Line}}=Form | Forms], Delta, Fs) -> {line, L} = erl_scan:attributes_info(Loc, line), if diff --git a/lib/stdlib/src/erl_compile.erl b/lib/stdlib/src/erl_compile.erl index d9d15e05f8..abff37e4bc 100644 --- a/lib/stdlib/src/erl_compile.erl +++ b/lib/stdlib/src/erl_compile.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1997-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% -module(erl_compile). @@ -23,6 +23,8 @@ -export([compile_cmdline/1]). +-export_type([cmd_line_arg/0]). + %% Mapping from extension to {M,F} to run the correct compiler. compiler(".erl") -> {compile, compile}; diff --git a/lib/stdlib/src/erl_expand_records.erl b/lib/stdlib/src/erl_expand_records.erl index a38b7639d8..61ce41f714 100644 --- a/lib/stdlib/src/erl_expand_records.erl +++ b/lib/stdlib/src/erl_expand_records.erl @@ -95,8 +95,9 @@ forms([F | Fs0], St0) -> forms([], St) -> {[],St}. clauses([{clause,Line,H0,G0,B0} | Cs0], St0) -> - {H,St1} = head(H0, St0), - {G,St2} = guard(G0, St1), + {H1,St1} = head(H0, St0), + {G1,St2} = guard(G0, St1), + {H,G} = optimize_is_record(H1, G1, St2), {B,St3} = exprs(B0, St2), {Cs,St4} = clauses(Cs0, St3), {[{clause,Line,H,G,B} | Cs],St4}; @@ -800,5 +801,137 @@ imported(F, A, St) -> error -> no end. +%%% +%%% Replace is_record/3 in guards with matching if possible. +%%% + +optimize_is_record(H0, G0, #exprec{compile=Opts}) -> + case opt_rec_vars(G0) of + [] -> + {H0,G0}; + Rs0 -> + case lists:member(no_is_record_optimization, Opts) of + true -> + {H0,G0}; + false -> + {H,Rs} = opt_pattern_list(H0, Rs0), + G = opt_remove(G0, Rs), + {H,G} + end + end. + + +%% opt_rec_vars(Guards) -> Vars. +%% Search through the guard expression, looking for +%% variables referenced in those is_record/3 calls that +%% will fail the entire guard if they evaluate to 'false' +%% +%% In the following code +%% +%% f(X, Y, Z) when is_record(X, r1) andalso +%% (is_record(Y, r2) orelse is_record(Z, r3)) +%% +%% the entire guard will be false if the record test for +%% X fails, and the clause can be rewritten to: +%% +%% f({r1,...}=X, Y, Z) when true andalso +%% (is_record(Y, r2) or is_record(Z, r3)) +%% +opt_rec_vars([G|Gs]) -> + Rs = opt_rec_vars_1(G, orddict:new()), + opt_rec_vars(Gs, Rs); +opt_rec_vars([]) -> orddict:new(). + +opt_rec_vars([G|Gs], Rs0) -> + Rs1 = opt_rec_vars_1(G, orddict:new()), + Rs = ordsets:intersection(Rs0, Rs1), + opt_rec_vars(Gs, Rs); +opt_rec_vars([], Rs) -> Rs. + +opt_rec_vars_1([T|Ts], Rs0) -> + Rs = opt_rec_vars_2(T, Rs0), + opt_rec_vars_1(Ts, Rs); +opt_rec_vars_1([], Rs) -> Rs. + +opt_rec_vars_2({op,_,'and',A1,A2}, Rs) -> + opt_rec_vars_1([A1,A2], Rs); +opt_rec_vars_2({op,_,'andalso',A1,A2}, Rs) -> + opt_rec_vars_1([A1,A2], Rs); +opt_rec_vars_2({op,_,'orelse',Arg,{atom,_,fail}}, Rs) -> + %% Since the second argument guarantees failure, + %% it is safe to inspect the first argument. + opt_rec_vars_2(Arg, Rs); +opt_rec_vars_2({call,_,{remote,_,{atom,_,erlang},{atom,_,is_record}}, + [{var,_,V},{atom,_,Tag},{integer,_,Sz}]}, Rs) -> + orddict:store(V, {Tag,Sz}, Rs); +opt_rec_vars_2({call,_,{atom,_,is_record}, + [{var,_,V},{atom,_,Tag},{integer,_,Sz}]}, Rs) -> + orddict:store(V, {Tag,Sz}, Rs); +opt_rec_vars_2(_, Rs) -> Rs. + +opt_pattern_list(Ps, Rs) -> + opt_pattern_list(Ps, Rs, []). + +opt_pattern_list([P0|Ps], Rs0, Acc) -> + {P,Rs} = opt_pattern(P0, Rs0), + opt_pattern_list(Ps, Rs, [P|Acc]); +opt_pattern_list([], Rs, Acc) -> + {reverse(Acc),Rs}. + +opt_pattern({var,_,V}=Var, Rs0) -> + case orddict:find(V, Rs0) of + {ok,{Tag,Sz}} -> + Rs = orddict:store(V, {remove,Tag,Sz}, Rs0), + {opt_var(Var, Tag, Sz),Rs}; + _ -> + {Var,Rs0} + end; +opt_pattern({cons,Line,H0,T0}, Rs0) -> + {H,Rs1} = opt_pattern(H0, Rs0), + {T,Rs} = opt_pattern(T0, Rs1), + {{cons,Line,H,T},Rs}; +opt_pattern({tuple,Line,Es0}, Rs0) -> + {Es,Rs} = opt_pattern_list(Es0, Rs0), + {{tuple,Line,Es},Rs}; +opt_pattern({match,Line,Pa0,Pb0}, Rs0) -> + {Pa,Rs1} = opt_pattern(Pa0, Rs0), + {Pb,Rs} = opt_pattern(Pb0, Rs1), + {{match,Line,Pa,Pb},Rs}; +opt_pattern(P, Rs) -> {P,Rs}. + +opt_var({var,Line,_}=Var, Tag, Sz) -> + Rp = record_pattern(2, -1, ignore, Sz, Line, [{atom,Line,Tag}]), + {match,Line,{tuple,Line,Rp},Var}. + +opt_remove(Gs, Rs) -> + [opt_remove_1(G, Rs) || G <- Gs]. + +opt_remove_1(Ts, Rs) -> + [opt_remove_2(T, Rs) || T <- Ts]. + +opt_remove_2({op,L,'and'=Op,A1,A2}, Rs) -> + {op,L,Op,opt_remove_2(A1, Rs),opt_remove_2(A2, Rs)}; +opt_remove_2({op,L,'andalso'=Op,A1,A2}, Rs) -> + {op,L,Op,opt_remove_2(A1, Rs),opt_remove_2(A2, Rs)}; +opt_remove_2({op,L,'orelse',A1,A2}, Rs) -> + {op,L,'orelse',opt_remove_2(A1, Rs),A2}; +opt_remove_2({call,Line,{remote,_,{atom,_,erlang},{atom,_,is_record}}, + [{var,_,V},{atom,_,Tag},{integer,_,Sz}]}=A, Rs) -> + case orddict:find(V, Rs) of + {ok,{remove,Tag,Sz}} -> + {atom,Line,true}; + _ -> + A + end; +opt_remove_2({call,Line,{atom,_,is_record}, + [{var,_,V},{atom,_,Tag},{integer,_,Sz}]}=A, Rs) -> + case orddict:find(V, Rs) of + {ok,{remove,Tag,Sz}} -> + {atom,Line,true}; + _ -> + A + end; +opt_remove_2(A, _) -> A. + neg_line(L) -> erl_parse:set_line(L, fun(Line) -> -abs(Line) end). diff --git a/lib/stdlib/src/erl_internal.erl b/lib/stdlib/src/erl_internal.erl index 2471c545dd..bf6e5bc5ca 100644 --- a/lib/stdlib/src/erl_internal.erl +++ b/lib/stdlib/src/erl_internal.erl @@ -48,7 +48,7 @@ %% -export([bif/2,bif/3,guard_bif/2, - type_test/2,new_type_test/2,old_type_test/2]). + type_test/2,new_type_test/2,old_type_test/2,old_bif/2]). -export([arith_op/2,bool_op/2,comp_op/2,list_op/2,send_op/2,op_type/2]). %%--------------------------------------------------------------------------- @@ -238,6 +238,7 @@ bif(binary_to_existing_atom, 2) -> true; bif(binary_to_list, 1) -> true; bif(binary_to_list, 3) -> true; bif(binary_to_term, 1) -> true; +bif(binary_to_term, 2) -> true; bif(bitsize, 1) -> true; bif(bit_size, 1) -> true; bif(bitstring_to_list, 1) -> true; @@ -298,6 +299,8 @@ bif(list_to_pid, 1) -> true; bif(list_to_tuple, 1) -> true; bif(load_module, 2) -> true; bif(make_ref, 0) -> true; +bif(max,2) -> true; +bif(min,2) -> true; bif(module_loaded, 1) -> true; bif(monitor_node, 2) -> true; bif(node, 0) -> true; @@ -309,6 +312,7 @@ bif(open_port, 2) -> true; bif(pid_to_list, 1) -> true; bif(port_close, 1) -> true; bif(port_command, 2) -> true; +bif(port_command, 3) -> true; bif(port_connect, 2) -> true; bif(port_control, 3) -> true; bif(pre_loaded, 0) -> true; @@ -353,3 +357,134 @@ bif(unlink, 1) -> true; bif(unregister, 1) -> true; bif(whereis, 1) -> true; bif(Name, A) when is_atom(Name), is_integer(A) -> false. + +-spec old_bif(Name::atom(), Arity::arity()) -> boolean(). +%% Returns true if erlang:Name/Arity is an old (pre R14) auto-imported BIF, false otherwise. +%% Use erlang:is_bultin(Mod, Name, Arity) to find whether a function is a BIF +%% (meaning implemented in C) or not. + +old_bif(abs, 1) -> true; +old_bif(apply, 2) -> true; +old_bif(apply, 3) -> true; +old_bif(atom_to_binary, 2) -> true; +old_bif(atom_to_list, 1) -> true; +old_bif(binary_to_atom, 2) -> true; +old_bif(binary_to_existing_atom, 2) -> true; +old_bif(binary_to_list, 1) -> true; +old_bif(binary_to_list, 3) -> true; +old_bif(binary_to_term, 1) -> true; +old_bif(bitsize, 1) -> true; +old_bif(bit_size, 1) -> true; +old_bif(bitstring_to_list, 1) -> true; +old_bif(byte_size, 1) -> true; +old_bif(check_process_code, 2) -> true; +old_bif(concat_binary, 1) -> true; +old_bif(date, 0) -> true; +old_bif(delete_module, 1) -> true; +old_bif(disconnect_node, 1) -> true; +old_bif(element, 2) -> true; +old_bif(erase, 0) -> true; +old_bif(erase, 1) -> true; +old_bif(exit, 1) -> true; +old_bif(exit, 2) -> true; +old_bif(float, 1) -> true; +old_bif(float_to_list, 1) -> true; +old_bif(garbage_collect, 0) -> true; +old_bif(garbage_collect, 1) -> true; +old_bif(get, 0) -> true; +old_bif(get, 1) -> true; +old_bif(get_keys, 1) -> true; +old_bif(group_leader, 0) -> true; +old_bif(group_leader, 2) -> true; +old_bif(halt, 0) -> true; +old_bif(halt, 1) -> true; +old_bif(hd, 1) -> true; +old_bif(integer_to_list, 1) -> true; +old_bif(iolist_size, 1) -> true; +old_bif(iolist_to_binary, 1) -> true; +old_bif(is_alive, 0) -> true; +old_bif(is_process_alive, 1) -> true; +old_bif(is_atom, 1) -> true; +old_bif(is_boolean, 1) -> true; +old_bif(is_binary, 1) -> true; +old_bif(is_bitstr, 1) -> true; +old_bif(is_bitstring, 1) -> true; +old_bif(is_float, 1) -> true; +old_bif(is_function, 1) -> true; +old_bif(is_function, 2) -> true; +old_bif(is_integer, 1) -> true; +old_bif(is_list, 1) -> true; +old_bif(is_number, 1) -> true; +old_bif(is_pid, 1) -> true; +old_bif(is_port, 1) -> true; +old_bif(is_reference, 1) -> true; +old_bif(is_tuple, 1) -> true; +old_bif(is_record, 2) -> true; +old_bif(is_record, 3) -> true; +old_bif(length, 1) -> true; +old_bif(link, 1) -> true; +old_bif(list_to_atom, 1) -> true; +old_bif(list_to_binary, 1) -> true; +old_bif(list_to_bitstring, 1) -> true; +old_bif(list_to_existing_atom, 1) -> true; +old_bif(list_to_float, 1) -> true; +old_bif(list_to_integer, 1) -> true; +old_bif(list_to_pid, 1) -> true; +old_bif(list_to_tuple, 1) -> true; +old_bif(load_module, 2) -> true; +old_bif(make_ref, 0) -> true; +old_bif(module_loaded, 1) -> true; +old_bif(monitor_node, 2) -> true; +old_bif(node, 0) -> true; +old_bif(node, 1) -> true; +old_bif(nodes, 0) -> true; +old_bif(nodes, 1) -> true; +old_bif(now, 0) -> true; +old_bif(open_port, 2) -> true; +old_bif(pid_to_list, 1) -> true; +old_bif(port_close, 1) -> true; +old_bif(port_command, 2) -> true; +old_bif(port_connect, 2) -> true; +old_bif(port_control, 3) -> true; +old_bif(pre_loaded, 0) -> true; +old_bif(process_flag, 2) -> true; +old_bif(process_flag, 3) -> true; +old_bif(process_info, 1) -> true; +old_bif(process_info, 2) -> true; +old_bif(processes, 0) -> true; +old_bif(purge_module, 1) -> true; +old_bif(put, 2) -> true; +old_bif(register, 2) -> true; +old_bif(registered, 0) -> true; +old_bif(round, 1) -> true; +old_bif(self, 0) -> true; +old_bif(setelement, 3) -> true; +old_bif(size, 1) -> true; +old_bif(spawn, 1) -> true; +old_bif(spawn, 2) -> true; +old_bif(spawn, 3) -> true; +old_bif(spawn, 4) -> true; +old_bif(spawn_link, 1) -> true; +old_bif(spawn_link, 2) -> true; +old_bif(spawn_link, 3) -> true; +old_bif(spawn_link, 4) -> true; +old_bif(spawn_monitor, 1) -> true; +old_bif(spawn_monitor, 3) -> true; +old_bif(spawn_opt, 2) -> true; +old_bif(spawn_opt, 3) -> true; +old_bif(spawn_opt, 4) -> true; +old_bif(spawn_opt, 5) -> true; +old_bif(split_binary, 2) -> true; +old_bif(statistics, 1) -> true; +old_bif(term_to_binary, 1) -> true; +old_bif(term_to_binary, 2) -> true; +old_bif(throw, 1) -> true; +old_bif(time, 0) -> true; +old_bif(tl, 1) -> true; +old_bif(trunc, 1) -> true; +old_bif(tuple_size, 1) -> true; +old_bif(tuple_to_list, 1) -> true; +old_bif(unlink, 1) -> true; +old_bif(unregister, 1) -> true; +old_bif(whereis, 1) -> true; +old_bif(Name, A) when is_atom(Name), is_integer(A) -> false. diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl index 94ad560549..c8bbb04e9a 100644 --- a/lib/stdlib/src/erl_lint.erl +++ b/lib/stdlib/src/erl_lint.erl @@ -40,7 +40,7 @@ %% Value. %% The option handling functions. --spec bool_option(atom(), atom(), boolean(), [_]) -> boolean(). +-spec bool_option(atom(), atom(), boolean(), [compile:option()]) -> boolean(). bool_option(On, Off, Default, Opts) -> foldl(fun (Opt, _Def) when Opt =:= On -> true; @@ -72,6 +72,10 @@ value_option(Flag, Default, On, OnVal, Off, OffVal, Opts) -> %%-define(DEBUGF(X,Y), io:format(X, Y)). -define(DEBUGF(X,Y), void). +-type line() :: erl_scan:line(). % a convenient alias +-type fa() :: {atom(), arity()}. % function+arity +-type ta() :: {atom(), arity()}. % type+arity + %% Usage of records, functions, and imports. The variable table, which %% is passed on as an argument, holds the usage of variables. -record(usage, { @@ -94,9 +98,11 @@ value_option(Flag, Default, On, OnVal, Off, OffVal, Opts) -> mod_imports=dict:new() :: dict(), %Module Imports compile=[], %Compile flags records=dict:new() :: dict(), %Record definitions + locals=gb_sets:empty() :: gb_set(), %All defined functions (prescanned) + no_auto=gb_sets:empty() :: gb_set(), %Functions explicitly not autoimported defined=gb_sets:empty() :: gb_set(), %Defined fuctions - on_load=[] :: [{atom(),integer()}], %On-load function - on_load_line=0 :: integer(), %Line for on_load + on_load=[] :: [fa()], %On-load function + on_load_line=0 :: line(), %Line for on_load clashes=[], %Exported functions named as BIFs not_deprecated=[], %Not considered deprecated func=[], %Current function @@ -110,10 +116,11 @@ value_option(Flag, Default, On, OnVal, Off, OffVal, Opts) -> %outside any fun or lc xqlc= false :: boolean(), %true if qlc.hrl included new = false :: boolean(), %Has user-defined 'new/N' - called= [], %Called functions + called= [] :: [{fa(),line()}], %Called functions usage = #usage{} :: #usage{}, specs = dict:new() :: dict(), %Type specifications - types = dict:new() :: dict() %Type definitions + types = dict:new() :: dict(), %Type definitions + exp_types=gb_sets:empty():: gb_set() %Exported types }). -type lint_state() :: #lint{}. @@ -161,6 +168,9 @@ format_error({bad_nowarn_unused_function,{F,A}}) -> io_lib:format("function ~w/~w undefined", [F,A]); format_error({bad_nowarn_bif_clash,{F,A}}) -> io_lib:format("function ~w/~w undefined", [F,A]); +format_error(disallowed_nowarn_bif_clash) -> + io_lib:format("compile directive nowarn_bif_clash is no longer allowed,~n" + " - use explicit module names or -compile({no_auto_import, [F/A]})", []); format_error({bad_nowarn_deprecated_function,{M,F,A}}) -> io_lib:format("~w:~w/~w is not a deprecated function", [M,F,A]); format_error({bad_on_load,Term}) -> @@ -186,13 +196,21 @@ format_error({define_import,{F,A}}) -> io_lib:format("defining imported function ~w/~w", [F,A]); format_error({unused_function,{F,A}}) -> io_lib:format("function ~w/~w is unused", [F,A]); -format_error({redefine_bif,{F,A}}) -> - io_lib:format("defining BIF ~w/~w", [F,A]); format_error({call_to_redefined_bif,{F,A}}) -> - io_lib:format("call to ~w/~w will call erlang:~w/~w; " - "not ~w/~w in this module \n" - " (add an explicit module name to the call to avoid this error)", - [F,A,F,A,F,A]); + io_lib:format("ambiguous call of redefined auto-imported BIF ~w/~w~n" + " - use erlang:~w/~w or \"-compile({no_auto_import,[~w/~w]}).\" " + "to resolve name clash", [F,A,F,A,F,A]); +format_error({call_to_redefined_old_bif,{F,A}}) -> + io_lib:format("ambiguous call of redefined pre R14 auto-imported BIF ~w/~w~n" + " - use erlang:~w/~w or \"-compile({no_auto_import,[~w/~w]}).\" " + "to resolve name clash", [F,A,F,A,F,A]); +format_error({redefine_old_bif_import,{F,A}}) -> + io_lib:format("import directive redefines pre R14 auto-imported BIF ~w/~w~n" + " - use \"-compile({no_auto_import,[~w/~w]}).\" " + "to resolve name clash", [F,A,F,A]); +format_error({redefine_bif_import,{F,A}}) -> + io_lib:format("import directive redefines auto-imported BIF ~w/~w~n" + " - use \"-compile({no_auto_import,[~w/~w]}).\" to resolve name clash", [F,A,F,A]); format_error({deprecated, MFA, ReplacementMFA, Rel}) -> io_lib:format("~s is deprecated and will be removed in ~s; use ~s", @@ -242,10 +260,10 @@ format_error({untyped_record,T}) -> format_error({unbound_var,V}) -> io_lib:format("variable ~w is unbound", [V]); format_error({unsafe_var,V,{What,Where}}) -> - io_lib:format("variable ~w unsafe in ~w ~s", + io_lib:format("variable ~w unsafe in ~w ~s", [V,What,format_where(Where)]); format_error({exported_var,V,{What,Where}}) -> - io_lib:format("variable ~w exported from ~w ~s", + io_lib:format("variable ~w exported from ~w ~s", [V,What,format_where(Where)]); format_error({shadowed_var,V,In}) -> io_lib:format("variable ~w shadowed in ~w", [V,In]); @@ -290,22 +308,24 @@ format_error({ill_defined_behaviour_callbacks,Behaviour}) -> %% --- types and specs --- format_error({singleton_typevar, Name}) -> io_lib:format("type variable ~w is only used once (is unbound)", [Name]); +format_error({duplicated_export_type, {T, A}}) -> + io_lib:format("type ~w/~w already exported", [T, A]); format_error({undefined_type, {TypeName, Arity}}) -> io_lib:format("type ~w~s undefined", [TypeName, gen_type_paren(Arity)]); format_error({unused_type, {TypeName, Arity}}) -> io_lib:format("type ~w~s is unused", [TypeName, gen_type_paren(Arity)]); format_error({new_builtin_type, {TypeName, Arity}}) -> io_lib:format("type ~w~s is a new builtin type; " - "its (re)definition is allowed only until the next release", + "its (re)definition is allowed only until the next release", [TypeName, gen_type_paren(Arity)]); format_error({builtin_type, {TypeName, Arity}}) -> - io_lib:format("type ~w~s is a builtin type; it cannot be redefined", + io_lib:format("type ~w~s is a builtin type; it cannot be redefined", [TypeName, gen_type_paren(Arity)]); format_error({renamed_type, OldName, NewName}) -> io_lib:format("type ~w() is now called ~w(); " "please use the new name instead", [OldName, NewName]); format_error({redefine_type, {TypeName, Arity}}) -> - io_lib:format("type ~w~s already defined", + io_lib:format("type ~w~s already defined", [TypeName, gen_type_paren(Arity)]); format_error({type_syntax, Constr}) -> io_lib:format("bad ~w type", [Constr]); @@ -354,7 +374,7 @@ pseudolocals() -> %% %% Used by erl_eval.erl to check commands. -%% +%% exprs(Exprs, BindingsList) -> exprs_opt(Exprs, BindingsList, []). @@ -362,7 +382,7 @@ exprs_opt(Exprs, BindingsList, Opts) -> {St0,Vs} = foldl(fun({{record,_SequenceNumber,_Name},Attr0}, {St1,Vs1}) -> Attr = zip_file_and_line(Attr0, "none"), {attribute_state(Attr, St1),Vs1}; - ({V,_}, {St1,Vs1}) -> + ({V,_}, {St1,Vs1}) -> {St1,[{V,{bound,unused,[]}} | Vs1]} end, {start("nofile",Opts),[]}, BindingsList), Vt = orddict:from_list(Vs), @@ -391,7 +411,7 @@ module(Forms) -> Opts = compiler_options(Forms), St = forms(Forms, start("nofile", Opts)), return_status(St). - + module(Forms, FileName) -> Opts = compiler_options(Forms), St = forms(Forms, start(FileName, Opts)), @@ -506,7 +526,7 @@ pack_errors(Es) -> %% Sort on line number. pack_warnings(Ws) -> - [{File,lists:sort([W || {F,W} <- Ws, F =:= File])} || + [{File,lists:sort([W || {F,W} <- Ws, F =:= File])} || File <- lists:usort([F || {F,_} <- Ws])]. %% add_error(ErrorDescriptor, State) -> State' @@ -516,13 +536,13 @@ pack_warnings(Ws) -> add_error(E, St) -> St#lint{errors=[{St#lint.file,E}|St#lint.errors]}. -add_error(FileLine, E, St) -> +add_error(FileLine, E, St) -> {File,Location} = loc(FileLine), add_error({Location,erl_lint,E}, St#lint{file = File}). add_warning(W, St) -> St#lint{warnings=[{St#lint.file,W}|St#lint.warnings]}. -add_warning(FileLine, W, St) -> +add_warning(FileLine, W, St) -> {File,Location} = loc(FileLine), add_warning({Location,erl_lint,W}, St#lint{file = File}). @@ -538,8 +558,12 @@ loc(L) -> forms(Forms0, St0) -> Forms = eval_file_attribute(Forms0, St0), + Locals = local_functions(Forms), + AutoImportSuppressed = auto_import_suppressed(St0#lint.compile), + StDeprecated = disallowed_compile_flags(Forms,St0), %% Line numbers are from now on pairs {File,Line}. - St1 = includes_qlc_hrl(Forms, St0), + St1 = includes_qlc_hrl(Forms, StDeprecated#lint{locals = Locals, + no_auto = AutoImportSuppressed}), St2 = bif_clashes(Forms, St1), St3 = not_deprecated(Forms, St2), St4 = foldl(fun form/2, pre_scan(Forms, St3), Forms), @@ -561,7 +585,7 @@ pre_scan([_ | Fs], St) -> pre_scan(Fs, St); pre_scan([], St) -> St. - + includes_qlc_hrl(Forms, St) -> %% QLC calls erl_lint several times, sometimes with the compile %% attribute removed. The file attribute, however, is left as is. @@ -667,6 +691,8 @@ attribute_state({attribute,L,extends,_M}, St) -> add_error(L, invalid_extends, St); attribute_state({attribute,L,export,Es}, St) -> export(L, Es, St); +attribute_state({attribute,L,export_type,Es}, St) -> + export_type(L, Es, St); attribute_state({attribute,L,import,Is}, St) -> import(L, Is, St); attribute_state({attribute,L,record,{Name,Fields}}, St) -> @@ -724,27 +750,38 @@ bif_clashes(Forms, St) -> Clashes = ordsets:subtract(ordsets:from_list(Clashes0), Nowarn), St#lint{clashes=Clashes}. --spec is_bif_clash(atom(), byte(), lint_state()) -> boolean(). - -is_bif_clash(_Name, _Arity, #lint{clashes=[]}) -> - false; -is_bif_clash(Name, Arity, #lint{clashes=Clashes}) -> - ordsets:is_element({Name,Arity}, Clashes). - %% not_deprecated(Forms, State0) -> State not_deprecated(Forms, St0) -> %% There are no line numbers in St0#lint.compile. - MFAsL = [{MFA,L} || + MFAsL = [{MFA,L} || {attribute, L, compile, Args} <- Forms, {nowarn_deprecated_function, MFAs0} <- lists:flatten([Args]), MFA <- lists:flatten([MFAs0])], Nowarn = [MFA || {MFA,_L} <- MFAsL], - Bad = [MFAL || {{M,F,A},_L}=MFAL <- MFAsL, + Bad = [MFAL || {{M,F,A},_L}=MFAL <- MFAsL, otp_internal:obsolete(M, F, A) =:= no], St1 = func_line_warning(bad_nowarn_deprecated_function, Bad, St0), St1#lint{not_deprecated = ordsets:from_list(Nowarn)}. +%% The nowarn_bif_clash directive is not only deprecated, it's actually an error from R14A +disallowed_compile_flags(Forms, St0) -> + %% There are (still) no line numbers in St0#lint.compile. + Errors0 = [ {St0#lint.file,{L,erl_lint,disallowed_nowarn_bif_clash}} || + {attribute,[{line,{_,L}}],compile,nowarn_bif_clash} <- Forms ], + Errors1 = [ {St0#lint.file,{L,erl_lint,disallowed_nowarn_bif_clash}} || + {attribute,[{line,{_,L}}],compile,{nowarn_bif_clash, {_,_}}} <- Forms ], + Disabled = (not is_warn_enabled(bif_clash, St0)), + Errors = if + Disabled andalso Errors0 =:= [] -> + [{St0#lint.file,{erl_lint,disallowed_nowarn_bif_clash}} | St0#lint.errors]; + Disabled -> + Errors0 ++ Errors1 ++ St0#lint.errors; + true -> + Errors1 ++ St0#lint.errors + end, + St0#lint{errors=Errors}. + %% post_traversal_check(Forms, State0) -> State. %% Do some further checking after the forms have been traversed and %% data about calls etc. have been collected. @@ -862,7 +899,7 @@ check_deprecated(Forms, St0) -> Bad = [{E,L} || {attribute, L, deprecated, Depr} <- Forms, D <- lists:flatten([Depr]), E <- depr_cat(D, X, Mod)], - foldl(fun ({E,L}, St1) -> + foldl(fun ({E,L}, St1) -> add_error(L, E, St1) end, St0, Bad). @@ -912,7 +949,7 @@ check_imports(Forms, St0) -> true -> Usage = St0#lint.usage, Unused = ordsets:subtract(St0#lint.imports, Usage#usage.imported), - Imports = [{{FA,list_to_atom(package_to_string(Mod))},L} + Imports = [{{FA,list_to_atom(package_to_string(Mod))},L} || {attribute,L,import,{Mod,Fs}} <- Forms, FA <- lists:usort(Fs)], Bad = [{FM,L} || FM <- Unused, {FM2,L} <- Imports, FM =:= FM2], @@ -932,7 +969,7 @@ check_unused_functions(Forms, St0) -> Opts = St1#lint.compile, case member(export_all, Opts) orelse not is_warn_enabled(unused_function, St1) of - true -> + true -> St1; false -> Nowarn = nowarn_function(nowarn_unused_function, Opts), @@ -1003,12 +1040,13 @@ check_option_functions(Forms, Tag0, Type, St0) -> {Tag, FAs0} <- lists:flatten([Args]), Tag0 =:= Tag, FA <- lists:flatten([FAs0])], - DefFunctions = gb_sets:to_list(St0#lint.defined) -- pseudolocals(), + DefFunctions = (gb_sets:to_list(St0#lint.defined) -- pseudolocals()) ++ + [{F,A} || {{F,A},_} <- orddict:to_list(St0#lint.imports)], Bad = [{FA,L} || {FA,L} <- FAsL, not member(FA, DefFunctions)], func_line_error(Type, Bad, St0). nowarn_function(Tag, Opts) -> - ordsets:from_list([FA || {Tag1,FAs} <- Opts, + ordsets:from_list([FA || {Tag1,FAs} <- Opts, Tag1 =:= Tag, FA <- lists:flatten([FAs])]). @@ -1048,10 +1086,10 @@ check_unused_records(Forms, St0) -> %% functions count. Usage = St0#lint.usage, UsedRecords = sets:to_list(Usage#usage.used_records), - URecs = foldl(fun (Used, Recs) -> - dict:erase(Used, Recs) + URecs = foldl(fun (Used, Recs) -> + dict:erase(Used, Recs) end, St0#lint.records, UsedRecords), - Unused = [{Name,FileLine} || + Unused = [{Name,FileLine} || {Name,{FileLine,_Fields}} <- dict:to_list(URecs), element(1, loc(FileLine)) =:= FirstFile], foldl(fun ({N,L}, St) -> @@ -1061,18 +1099,19 @@ check_unused_records(Forms, St0) -> St0 end. -%% For storing the import list we use the orddict module. +%% For storing the import list we use the orddict module. %% We know an empty set is []. -%% export(Line, Exports, State) -> State. +-spec export(line(), [fa()], lint_state()) -> lint_state(). %% Mark functions as exported, also as called from the export line. export(Line, Es, #lint{exports = Es0, called = Called} = St0) -> - {Es1,C1,St1} = + {Es1,C1,St1} = foldl(fun (NA, {E,C,St2}) -> St = case gb_sets:is_element(NA, E) of true -> - add_warning(Line, {duplicated_export, NA}, St2); + Warn = {duplicated_export,NA}, + add_warning(Line, Warn, St2); false -> St2 end, @@ -1081,8 +1120,27 @@ export(Line, Es, #lint{exports = Es0, called = Called} = St0) -> {Es0,Called,St0}, Es), St1#lint{exports = Es1, called = C1}. -%% import(Line, Imports, State) -> State. -%% imported(Name, Arity, State) -> {yes,Module} | no. +-spec export_type(line(), [ta()], lint_state()) -> lint_state(). +%% Mark types as exported; also mark them as used from the export line. + +export_type(Line, ETs, #lint{usage = Usage, exp_types = ETs0} = St0) -> + UTs0 = Usage#usage.used_types, + {ETs1,UTs1,St1} = + foldl(fun (TA, {E,U,St2}) -> + St = case gb_sets:is_element(TA, E) of + true -> + Warn = {duplicated_export_type,TA}, + add_warning(Line, Warn, St2); + false -> + St2 + end, + {gb_sets:add_element(TA, E), dict:store(TA, Line, U), St} + end, + {ETs0,UTs0,St0}, ETs), + St1#lint{usage = Usage#usage{used_types = UTs1}, exp_types = ETs1}. + +-type import() :: {module(), [fa()]} | module(). +-spec import(line(), import(), lint_state()) -> lint_state(). import(Line, {Mod,Fs}, St) -> Mod1 = package_to_string(Mod), @@ -1094,11 +1152,41 @@ import(Line, {Mod,Fs}, St) -> St#lint{imports=add_imports(list_to_atom(Mod1), Mfs, St#lint.imports)}; Efs -> - foldl(fun (Ef, St0) -> - add_error(Line, {redefine_import,Ef}, - St0) + {Err, St1} = + foldl(fun ({bif,{F,A},_}, {Err,St0}) -> + %% BifClash - import directive + Warn = is_warn_enabled(bif_clash, St0) + and (not bif_clash_specifically_disabled(St0,{F,A})), + AutoImpSup = is_autoimport_suppressed(St0#lint.no_auto,{F,A}), + OldBif = erl_internal:old_bif(F,A), + {Err,if + Warn and (not AutoImpSup) and OldBif -> + add_error + (Line, + {redefine_old_bif_import, {F,A}}, + St0); + Warn and (not AutoImpSup) -> + add_warning + (Line, + {redefine_bif_import, {F,A}}, + St0); + true -> + St0 + end}; + (Ef, {_Err,St0}) -> + {true,add_error(Line, + {redefine_import,Ef}, + St0)} end, - St, Efs) + {false,St}, Efs), + if + not Err -> + St1#lint{imports= + add_imports(list_to_atom(Mod1), Mfs, + St#lint.imports)}; + true -> + St1 + end end; false -> add_error(Line, {bad_module_name, Mod1}, St) @@ -1141,13 +1229,15 @@ check_imports(_Line, Fs, Is) -> add_imports(Mod, Fs, Is) -> foldl(fun (F, Is0) -> orddict:store(F, Mod, Is0) end, Is, Fs). +-spec imported(atom(), arity(), lint_state()) -> {'yes',module()} | 'no'. + imported(F, A, St) -> case orddict:find({F,A}, St#lint.imports) of {ok,Mod} -> {yes,Mod}; error -> no end. -%% on_load(Line, Val, State) -> State. +-spec on_load(line(), fa(), lint_state()) -> lint_state(). %% Check an on_load directive and remember it. on_load(Line, {Name,Arity}=Fa, #lint{on_load=OnLoad0}=St0) @@ -1179,7 +1269,7 @@ check_on_load(#lint{defined=Defined,on_load=[{_,0}=Fa], end; check_on_load(St) -> St. -%% call_function(Line, Name, Arity, State) -> State. +-spec call_function(line(), atom(), arity(), lint_state()) -> lint_state(). %% Add to both called and calls. call_function(Line, F, A, #lint{usage=Usage0,called=Cd,func=Func}=St) -> @@ -1191,12 +1281,6 @@ call_function(Line, F, A, #lint{usage=Usage0,called=Cd,func=Func}=St) -> end, St#lint{called=[{NA,Line}|Cd], usage=Usage}. -%% is_function_exported(Name, Arity, State) -> false|true. - -is_function_exported(Name, Arity, #lint{exports=Exports,compile=Compile}) -> - gb_sets:is_element({Name,Arity}, Exports) orelse - member(export_all, Compile). - %% function(Line, Name, Arity, Clauses, State) -> State. function(Line, instance, _Arity, _Cs, St) when St#lint.global_vt =/= [] -> @@ -1205,7 +1289,7 @@ function(Line, Name, Arity, Cs, St0) -> St1 = define_function(Line, Name, Arity, St0#lint{func={Name,Arity}}), clauses(Cs, St1#lint.global_vt, St1). -%% define_function(Line, Name, Arity, State) -> State. +-spec define_function(line(), atom(), arity(), lint_state()) -> lint_state(). define_function(Line, Name, Arity, St0) -> St1 = keyword_warning(Line, Name, St0), @@ -1215,14 +1299,9 @@ define_function(Line, Name, Arity, St0) -> add_error(Line, {redefine_function,NA}, St1); false -> St2 = St1#lint{defined=gb_sets:add_element(NA, St1#lint.defined)}, - St = case erl_internal:bif(Name, Arity) andalso - not is_function_exported(Name, Arity, St2) of - true -> add_warning(Line, {redefine_bif,NA}, St2); - false -> St2 - end, - case imported(Name, Arity, St) of - {yes,_M} -> add_error(Line, {define_import,NA}, St); - no -> St + case imported(Name, Arity, St2) of + {yes,_M} -> add_error(Line, {define_import,NA}, St2); + no -> St2 end end. @@ -1258,7 +1337,7 @@ head([P|Ps], Vt, Old, St0) -> {vtmerge_pat(Pvt, Psvt),vtmerge_pat(Bvt1,Bvt2),St2}; head([], _Vt, _Env, St) -> {[],[],St}. -%% pattern(Pattern, VarTable, Old, BinVarTable, State) -> +%% pattern(Pattern, VarTable, Old, BinVarTable, State) -> %% {UpdVarTable,BinVarTable,State}. %% Check pattern return variables. Old is the set of variables used for %% deciding whether an occurrence is a binding occurrence or a use, and @@ -1276,7 +1355,7 @@ pattern(P, Vt, St) -> pattern({var,_Line,'_'}, _Vt, _Old, _Bvt, St) -> {[],[],St}; %Ignore anonymous variable -pattern({var,Line,V}, _Vt, Old, Bvt, St) -> +pattern({var,Line,V}, _Vt, Old, Bvt, St) -> pat_var(V, Line, Old, Bvt, St); pattern({char,_Line,_C}, _Vt, _Old, _Bvt, St) -> {[],[],St}; pattern({integer,_Line,_I}, _Vt, _Old, _Bvt, St) -> {[],[],St}; @@ -1294,7 +1373,7 @@ pattern({tuple,_Line,Ps}, Vt, Old, Bvt, St) -> %%pattern({struct,_Line,_Tag,Ps}, Vt, Old, Bvt, St) -> %% pattern_list(Ps, Vt, Old, Bvt, St); pattern({record_index,Line,Name,Field}, _Vt, _Old, _Bvt, St) -> - {Vt1,St1} = + {Vt1,St1} = check_record(Line, Name, St, fun (Dfs, St1) -> pattern_field(Field, Name, Dfs, St1) @@ -1309,7 +1388,7 @@ pattern({record_field,Line,_,_}=M, _Vt, _Old, _Bvt, St0) -> end; pattern({record,Line,Name,Pfs}, Vt, Old, Bvt, St) -> case dict:find(Name, St#lint.records) of - {ok,{_Line,Fields}} -> + {ok,{_Line,Fields}} -> St1 = used_record(Name, St), pattern_fields(Pfs, Name, Fields, Vt, Old, Bvt, St1); error -> {[],[],add_error(Line, {undefined_record,Name}, St)} @@ -1369,7 +1448,7 @@ reject_bin_alias({cons,_,H1,T1}, {cons,_,H2,T2}, St0) -> reject_bin_alias(T1, T2, St); reject_bin_alias({tuple,_,Es1}, {tuple,_,Es2}, St) -> reject_bin_alias_list(Es1, Es2, St); -reject_bin_alias({record,_,Name1,Pfs1}, {record,_,Name2,Pfs2}, +reject_bin_alias({record,_,Name1,Pfs1}, {record,_,Name2,Pfs2}, #lint{records=Recs}=St) -> case {dict:find(Name1, Recs),dict:find(Name2, Recs)} of {{ok,{_Line1,Fields1}},{ok,{_Line2,Fields2}}} -> @@ -1451,7 +1530,7 @@ is_pattern_expr_1({op,_Line,Op,A1,A2}) -> erl_internal:arith_op(Op, 2) andalso all(fun is_pattern_expr/1, [A1,A2]); is_pattern_expr_1(_Other) -> false. -%% pattern_bin([Element], VarTable, Old, BinVarTable, State) -> +%% pattern_bin([Element], VarTable, Old, BinVarTable, State) -> %% {UpdVarTable,UpdBinVarTable,State}. %% Check a pattern group. BinVarTable are used binsize variables. @@ -1498,7 +1577,7 @@ good_string_size_type(default, Ts) -> end, Ts); good_string_size_type(_, _) -> false. -%% pat_bit_expr(Pattern, OldVarTable, BinVarTable,State) -> +%% pat_bit_expr(Pattern, OldVarTable, BinVarTable,State) -> %% {UpdVarTable,UpdBinVarTable,State}. %% Check pattern bit expression, only allow really valid patterns! @@ -1513,7 +1592,7 @@ pat_bit_expr(P, _Old, _Bvt, St) -> false -> {[],[],add_error(element(2, P), illegal_pattern, St)} end. -%% pat_bit_size(Size, VarTable, BinVarTable, State) -> +%% pat_bit_size(Size, VarTable, BinVarTable, State) -> %% {Value,UpdVarTable,UpdBinVarTable,State}. %% Check pattern size expression, only allow really valid sizes! @@ -1596,7 +1675,7 @@ bit_size_check(Line, Size, #bittype{type=Type,unit=Unit}, St) -> Sz = Unit * Size, %Total number of bits! St2 = elemtype_check(Line, Type, Sz, St), {Sz,St2}. - + elemtype_check(_Line, float, 32, St) -> St; elemtype_check(_Line, float, 64, St) -> St; elemtype_check(Line, float, _Size, St) -> @@ -1678,8 +1757,6 @@ gexpr({cons,_Line,H,T}, Vt, St) -> gexpr_list([H,T], Vt, St); gexpr({tuple,_Line,Es}, Vt, St) -> gexpr_list(Es, Vt, St); -%%gexpr({struct,_Line,_Tag,Es}, Vt, St) -> -%% gexpr_list(Es, Vt, St); gexpr({record_index,Line,Name,Field}, _Vt, St) -> check_record(Line, Name, St, fun (Dfs, St1) -> record_field(Field, Name, Dfs, St1) end ); @@ -1710,7 +1787,7 @@ gexpr({call,_Line,{atom,_Lr,is_record},[E,{atom,Ln,Name}]}, Vt, St0) -> gexpr({call,Line,{atom,_Lr,is_record},[E,R]}, Vt, St0) -> {Asvt,St1} = gexpr_list([E,R], Vt, St0), {Asvt,add_error(Line, illegal_guard_expr, St1)}; -gexpr({call,Line,{remote,_Lr,{atom,_Lm,erlang},{atom,Lf,is_record}},[E,A]}, +gexpr({call,Line,{remote,_Lr,{atom,_Lm,erlang},{atom,Lf,is_record}},[E,A]}, Vt, St0) -> gexpr({call,Line,{atom,Lf,is_record},[E,A]}, Vt, St0); gexpr({call,_Line,{atom,_Lr,is_record},[E,{atom,_,_Name},{integer,_,_}]}, @@ -1725,14 +1802,16 @@ gexpr({call,Line,{remote,_,{atom,_,erlang},{atom,_,is_record}=Isr},[_,_,_]=Args} gexpr({call,Line,{atom,_La,F},As}, Vt, St0) -> {Asvt,St1} = gexpr_list(As, Vt, St0), A = length(As), - case erl_internal:guard_bif(F, A) of + %% BifClash - Function called in guard + case erl_internal:guard_bif(F, A) andalso no_guard_bif_clash(St1,{F,A}) of true -> %% Also check that it is auto-imported. case erl_internal:bif(F, A) of true -> {Asvt,St1}; false -> {Asvt,add_error(Line, {explicit_export,F,A}, St1)} end; - false -> {Asvt,add_error(Line, illegal_guard_expr, St1)} + false -> + {Asvt,add_error(Line, illegal_guard_expr, St1)} end; gexpr({call,Line,{remote,_Lr,{atom,_Lm,erlang},{atom,_Lf,F}},As}, Vt, St0) -> {Asvt,St1} = gexpr_list(As, Vt, St0), @@ -1777,7 +1856,7 @@ is_guard_test(E) -> %% is_guard_test(Expression, Forms) -> boolean(). is_guard_test(Expression, Forms) -> RecordAttributes = [A || A = {attribute, _, record, _D} <- Forms], - St0 = foldl(fun(Attr0, St1) -> + St0 = foldl(fun(Attr0, St1) -> Attr = zip_file_and_line(Attr0, "none"), attribute_state(Attr, St1) end, start(), RecordAttributes), @@ -1798,7 +1877,7 @@ is_guard_test2(G, RDs) -> %% is_guard_expr(Expression) -> boolean(). %% Test if an expression is a guard expression. -is_guard_expr(E) -> is_gexpr(E, []). +is_guard_expr(E) -> is_gexpr(E, []). is_gexpr({var,_L,_V}, _RDs) -> true; is_gexpr({char,_L,_C}, _RDs) -> true; @@ -1820,7 +1899,7 @@ is_gexpr({record_field,_L,Rec,_Name,Field}, RDs) -> is_gexpr({record,L,Name,Inits}, RDs) -> is_gexpr_fields(Inits, L, Name, RDs); is_gexpr({bin,_L,Fs}, RDs) -> - all(fun ({bin_element,_Line,E,Sz,_Ts}) -> + all(fun ({bin_element,_Line,E,Sz,_Ts}) -> is_gexpr(E, RDs) and (Sz =:= default orelse is_gexpr(Sz, RDs)) end, Fs); is_gexpr({call,_L,{atom,_Lf,F},As}, RDs) -> @@ -1895,15 +1974,13 @@ expr({bc,_Line,E,Qs}, Vt0, St0) -> {vtold(Vt,Vt0),St}; %Don't export local variables expr({tuple,_Line,Es}, Vt, St) -> expr_list(Es, Vt, St); -%%expr({struct,Line,Tag,Es}, Vt, St) -> -%% expr_list(Es, Vt, St); expr({record_index,Line,Name,Field}, _Vt, St) -> check_record(Line, Name, St, fun (Dfs, St1) -> record_field(Field, Name, Dfs, St1) end); expr({record,Line,Name,Inits}, Vt, St) -> check_record(Line, Name, St, - fun (Dfs, St1) -> - init_fields(Inits, Line, Name, Dfs, Vt, St1) + fun (Dfs, St1) -> + init_fields(Inits, Line, Name, Dfs, Vt, St1) end); expr({record_field,Line,_,_}=M, _Vt, St0) -> case expand_package(M, St0) of @@ -1958,8 +2035,11 @@ expr({'fun',Line,Body}, Vt, St) -> {Bvt, St1} = fun_clauses(Cs, Vt, St), {vtupdate(Bvt, Vt), St1}; {function,F,A} -> + %% BifClash - Fun expression %% N.B. Only allows BIFs here as well, NO IMPORTS!! - case erl_internal:bif(F, A) of + case ((not is_local_function(St#lint.locals,{F,A})) andalso + (erl_internal:bif(F, A) andalso + (not is_autoimport_suppressed(St#lint.no_auto,{F,A})))) of true -> {[],St}; false -> {[],call_function(Line, F, A, St)} end; @@ -1969,7 +2049,7 @@ expr({'fun',Line,Body}, Vt, St) -> expr({call,_Line,{atom,_Lr,is_record},[E,{atom,Ln,Name}]}, Vt, St0) -> {Rvt,St1} = expr(E, Vt, St0), {Rvt,exist_record(Ln, Name, St1)}; -expr({call,Line,{remote,_Lr,{atom,_Lm,erlang},{atom,Lf,is_record}},[E,A]}, +expr({call,Line,{remote,_Lr,{atom,_Lm,erlang},{atom,Lf,is_record}},[E,A]}, Vt, St0) -> expr({call,Line,{atom,Lf,is_record},[E,A]}, Vt, St0); expr({call,L,{tuple,Lt,[{atom,Lm,erlang},{atom,Lf,is_record}]},As}, Vt, St) -> @@ -1992,16 +2072,14 @@ expr({call,Line,{atom,La,F},As}, Vt, St0) -> St1 = keyword_warning(La, F, St0), {Asvt,St2} = expr_list(As, Vt, St1), A = length(As), - case erl_internal:bif(F, A) of + IsLocal = is_local_function(St2#lint.locals,{F,A}), + IsAutoBif = erl_internal:bif(F, A), + AutoSuppressed = is_autoimport_suppressed(St2#lint.no_auto,{F,A}), + Warn = is_warn_enabled(bif_clash, St2) and (not bif_clash_specifically_disabled(St2,{F,A})), + case ((not IsLocal) andalso IsAutoBif andalso (not AutoSuppressed)) of true -> St3 = deprecated_function(Line, erlang, F, As, St2), - {Asvt,case is_warn_enabled(bif_clash, St3) andalso - is_bif_clash(F, A, St3) of - false -> - St3; - true -> - add_error(Line, {call_to_redefined_bif,{F,A}}, St3) - end}; + {Asvt,St3}; false -> {Asvt,case imported(F, A, St2) of {yes,M} -> @@ -2010,11 +2088,36 @@ expr({call,Line,{atom,La,F},As}, Vt, St0) -> Imp = ordsets:add_element({{F,A},M},U0#usage.imported), St3#lint{usage=U0#usage{imported = Imp}}; no -> - case {F,A} of - {record_info,2} -> + case {F,A} of + {record_info,2} -> check_record_info_call(Line,La,As,St2); - N when N =:= St2#lint.func -> St2; - _ -> call_function(Line, F, A, St2) + N -> + %% BifClash - function call + %% Issue these warnings/errors even if it's a recursive call + St3 = if + (not AutoSuppressed) andalso IsAutoBif andalso Warn -> + case erl_internal:old_bif(F,A) of + true -> + add_error + (Line, + {call_to_redefined_old_bif, {F,A}}, + St2); + false -> + add_warning + (Line, + {call_to_redefined_bif, {F,A}}, + St2) + end; + true -> + St2 + end, + %% ...but don't lint recursive calls + if + N =:= St3#lint.func -> + St3; + true -> + call_function(Line, F, A, St3) + end end end} end; @@ -2155,7 +2258,7 @@ def_fields(Fs0, Name, St0) -> foldl(fun ({record_field,Lf,{atom,La,F},V}, {Fs,St}) -> case exist_field(F, Fs) of true -> {Fs,add_error(Lf, {redefine_field,Name,F}, St)}; - false -> + false -> St1 = St#lint{recdef_top = true}, {_,St2} = expr(V, [], St1), %% Warnings and errors found are kept, but @@ -2306,7 +2409,7 @@ init_fields(Ifs, Line, Name, Dfs, Vt0, St0) -> Defs = init_fields(Ifs, Line, Dfs), {_,St2} = check_fields(Defs, Name, Dfs, Vt1, St1, fun expr/3), {Vt1,St1#lint{usage = St2#lint.usage}}. - + ginit_fields(Ifs, Line, Name, Dfs, Vt0, St0) -> {Vt1,St1} = check_fields(Ifs, Name, Dfs, Vt0, St0, fun gexpr/3), Defs = init_fields(Ifs, Line, Dfs), @@ -2316,7 +2419,7 @@ ginit_fields(Ifs, Line, Name, Dfs, Vt0, St0) -> IllErrs = [E || {_File,{_Line,erl_lint,illegal_guard_expr}}=E <- Errors], St4 = St1#lint{usage = Usage, errors = IllErrs ++ St1#lint.errors}, {Vt1,St4}. - + %% Default initializations to be carried out init_fields(Ifs, Line, Dfs) -> [ {record_field,Lf,{atom,La,F},copy_expr(Di, Line)} || @@ -2394,7 +2497,7 @@ check_type({ann_type, _L, [_Var, Type]}, SeenVars, St) -> check_type(Type, SeenVars, St); check_type({paren_type, _L, [Type]}, SeenVars, St) -> check_type(Type, SeenVars, St); -check_type({remote_type, L, [{atom, _, Mod}, {atom, _, Name}, Args]}, +check_type({remote_type, L, [{atom, _, Mod}, {atom, _, Name}, Args]}, SeenVars, #lint{module=CurrentMod} = St) -> St1 = case (dict:is_key({Name, length(Args)}, default_types()) @@ -2432,7 +2535,7 @@ check_type({type, L, 'fun', [Dom, Range]}, SeenVars, St) -> check_type({type, -1, product, [Dom, Range]}, SeenVars, St1); check_type({type, L, range, [From, To]}, SeenVars, St) -> St1 = - case {From, To} of + case {erl_eval:partial_eval(From), erl_eval:partial_eval(To)} of {{integer, _, X}, {integer, _, Y}} when X < Y -> St; _ -> add_error(L, {type_syntax, range}, St) end, @@ -2441,8 +2544,8 @@ check_type({type, _L, tuple, any}, SeenVars, St) -> {SeenVars, St}; check_type({type, _L, any}, SeenVars, St) -> {SeenVars, St}; check_type({type, L, binary, [Base, Unit]}, SeenVars, St) -> St1 = - case {Base, Unit} of - {{integer, _, BaseVal}, + case {erl_eval:partial_eval(Base), erl_eval:partial_eval(Unit)} of + {{integer, _, BaseVal}, {integer, _, UnitVal}} when BaseVal >= 0, UnitVal >= 0 -> St; _ -> add_error(L, {type_syntax, binary}, St) end, @@ -2467,7 +2570,13 @@ check_type({type, La, TypeName, Args}, SeenVars, #lint{usage=Usage} = St) -> UsedTypes = dict:store({TypeName, Arity}, La, OldUsed), St#lint{usage=Usage#usage{used_types=UsedTypes}} end, - check_type({type, -1, product, Args}, SeenVars, St1). + check_type({type, -1, product, Args}, SeenVars, St1); +check_type(I, SeenVars, St) -> + case erl_eval:partial_eval(I) of + {integer,_ILn,_Integer} -> {SeenVars, St}; + _Other -> + {SeenVars, add_error(element(2, I), {type_syntax, integer}, St)} + end. check_record_types(Line, Name, Fields, SeenVars, St) -> case dict:find(Name, St#lint.records) of @@ -2475,12 +2584,12 @@ check_record_types(Line, Name, Fields, SeenVars, St) -> case lists:all(fun({type, _, field_type, _}) -> true; (_) -> false end, Fields) of - true -> + true -> check_record_types(Fields, Name, DefFields, SeenVars, St, []); false -> {SeenVars, add_error(Line, {type_syntax, record}, St)} end; - error -> + error -> {SeenVars, add_error(Line, {undefined_record, Name}, St)} end. @@ -2563,7 +2672,6 @@ default_types() -> {set, 0}, {string, 0}, {term, 0}, - {tid, 0}, {timeout, 0}, {var, 1}], dict:from_list([{T, -1} || T <- DefTypes]). @@ -2585,7 +2693,6 @@ is_newly_introduced_builtin_type({gb_tree, 0}) -> true; % opaque is_newly_introduced_builtin_type({iodata, 0}) -> true; is_newly_introduced_builtin_type({queue, 0}) -> true; % opaque is_newly_introduced_builtin_type({set, 0}) -> true; % opaque -is_newly_introduced_builtin_type({tid, 0}) -> true; % opaque %% R13B01 is_newly_introduced_builtin_type({boolean, 0}) -> true; is_newly_introduced_builtin_type({Name, _}) when is_atom(Name) -> false. @@ -2606,7 +2713,7 @@ spec_decl(Line, MFA0, TypeSpecs, St0 = #lint{specs = Specs, module = Mod}) -> check_specs([FunType|Left], Arity, St0) -> {FunType1, CTypes} = case FunType of - {type, _, bounded_fun, [FT = {type, _, 'fun', _}, Cs]} -> + {type, _, bounded_fun, [FT = {type, _, 'fun', _}, Cs]} -> Types0 = [T || {type, _, constraint, [_, T]} <- Cs], {FT, lists:append(Types0)}; {type, _, 'fun', _} = FT -> {FT, []} @@ -2666,10 +2773,12 @@ add_missing_spec_warnings(Forms, St0, Type) -> add_warning(L, {missing_spec,FA}, St) end, St0, Warns). -check_unused_types(Forms, St = #lint{usage=Usage, types=Types}) -> +check_unused_types(Forms, #lint{usage=Usage, types=Ts, exp_types=ExpTs}=St) -> case [File || {attribute,_L,file,{File,_Line}} <- Forms] of [FirstFile|_] -> - UsedTypes = Usage#usage.used_types, + D = Usage#usage.used_types, + L = gb_sets:to_list(ExpTs) ++ dict:fetch_keys(D), + UsedTypes = gb_sets:from_list(L), FoldFun = fun(_Type, -1, AccSt) -> %% Default type @@ -2677,19 +2786,18 @@ check_unused_types(Forms, St = #lint{usage=Usage, types=Types}) -> (Type, FileLine, AccSt) -> case loc(FileLine) of {FirstFile, _} -> - case dict:is_key(Type, UsedTypes) of + case gb_sets:is_member(Type, UsedTypes) of true -> AccSt; - false -> - add_warning(FileLine, - {unused_type, Type}, - AccSt) + false -> + Warn = {unused_type,Type}, + add_warning(FileLine, Warn, AccSt) end; _ -> - %% Don't warn about unused types in include file + %% No warns about unused types in include files AccSt end end, - dict:fold(FoldFun, St, Types); + dict:fold(FoldFun, St, Ts); [] -> St end. @@ -2834,7 +2942,7 @@ fun_clause({clause,_Line,H,G,B}, Vt0, St0) -> %% %% used variable has been used %% unused variable has been bound but not used -%% +%% %% Lines is a list of line numbers where the variable was bound. %% %% Report variable errors/warnings as soon as possible and then change @@ -2864,9 +2972,9 @@ pat_var(V, Line, Vt, Bvt, St) -> case orddict:find(V, Bvt) of {ok, {bound,_Usage,Ls}} -> {[],[{V,{bound,used,Ls}}],St}; - error -> + error -> case orddict:find(V, Vt) of - {ok,{bound,_Usage,Ls}} -> + {ok,{bound,_Usage,Ls}} -> {[{V,{bound,used,Ls}}],[],St}; {ok,{{unsafe,In},_Usage,Ls}} -> {[{V,{bound,used,Ls}}],[], @@ -2919,7 +3027,7 @@ pat_binsize_var(V, Line, Vt, Bvt, St) -> expr_var(V, Line, Vt, St0) -> case orddict:find(V, Vt) of - {ok,{bound,_Usage,Ls}} -> + {ok,{bound,_Usage,Ls}} -> {[{V,{bound,used,Ls}}],St0}; {ok,{{unsafe,In},_Usage,Ls}} -> {[{V,{bound,used,Ls}}], @@ -2957,7 +3065,7 @@ check_old_unused_vars(Vt, Vt0, St0) -> warn_unused_vars(U, Vt, St0). unused_vars(Vt, Vt0, _St0) -> - U0 = orddict:filter(fun (V, {_State,unused,_Ls}) -> + U0 = orddict:filter(fun (V, {_State,unused,_Ls}) -> case atom_to_list(V) of "_"++_ -> false; _ -> true @@ -2973,7 +3081,7 @@ warn_unused_vars(U, Vt, St0) -> false -> St0; true -> foldl(fun ({V,{_,unused,Ls}}, St) -> - foldl(fun (L, St2) -> + foldl(fun (L, St2) -> add_warning(L, {unused_var,V}, St2) end, St, Ls) @@ -3073,7 +3181,7 @@ vt_no_unsafe(Vt) -> [V || {_,{S,_U,_L}}=V <- Vt, -ifdef(NOTUSED). vunion(Vs1, Vs2) -> ordsets:union(vtnames(Vs1), vtnames(Vs2)). -vunion(Vss) -> foldl(fun (Vs, Uvs) -> +vunion(Vss) -> foldl(fun (Vs, Uvs) -> ordsets:union(vtnames(Vs), Uvs) end, [], Vss). @@ -3103,7 +3211,7 @@ modify_line(T, F0) -> %% Forms. modify_line1({function,F,A}, _Mf) -> {function,F,A}; modify_line1({function,M,F,A}, _Mf) -> {function,M,F,A}; -modify_line1({attribute,L,record,{Name,Fields}}, Mf) -> +modify_line1({attribute,L,record,{Name,Fields}}, Mf) -> {attribute,Mf(L),record,{Name,modify_line1(Fields, Mf)}}; modify_line1({attribute,L,spec,{Fun,Types}}, Mf) -> {attribute,Mf(L),spec,{Fun,modify_line1(Types, Mf)}}; @@ -3118,7 +3226,7 @@ modify_line1({warning,W}, _Mf) -> {warning,W}; modify_line1({error,W}, _Mf) -> {error,W}; %% Expressions. modify_line1({clauses,Cs}, Mf) -> {clauses,modify_line1(Cs, Mf)}; -modify_line1({typed_record_field,Field,Type}, Mf) -> +modify_line1({typed_record_field,Field,Type}, Mf) -> {typed_record_field,modify_line1(Field, Mf),modify_line1(Type, Mf)}; modify_line1({Tag,L}, Mf) -> {Tag,Mf(L)}; modify_line1({Tag,L,E1}, Mf) -> @@ -3154,7 +3262,7 @@ check_record_info_call(Line,_La,_As,St) -> has_wildcard_field([{record_field,_Lf,{var,_La,'_'},_Val}|_Fs]) -> true; has_wildcard_field([_|Fs]) -> has_wildcard_field(Fs); has_wildcard_field([]) -> false. - + %% check_remote_function(Line, ModuleName, FuncName, [Arg], State) -> State. %% Perform checks on known remote calls. @@ -3170,7 +3278,7 @@ check_remote_function(Line, M, F, As, St0) -> check_qlc_hrl(Line, M, F, As, St) -> Arity = length(As), case As of - [{lc,_L,_E,_Qs}|_] when M =:= qlc, F =:= q, + [{lc,_L,_E,_Qs}|_] when M =:= qlc, F =:= q, Arity < 3, not St#lint.xqlc -> add_warning(Line, {missing_qlc_hrl, Arity}, St); _ -> @@ -3355,11 +3463,11 @@ extract_sequence(3, [$.,_|Fmt], Need) -> extract_sequence(4, Fmt, Need); extract_sequence(3, Fmt, Need) -> extract_sequence(4, Fmt, Need); -extract_sequence(4, [$t, $c | Fmt], Need) -> - extract_sequence(5, [$c|Fmt], Need); -extract_sequence(4, [$t, $s | Fmt], Need) -> - extract_sequence(5, [$s|Fmt], Need); -extract_sequence(4, [$t, C | _Fmt], _Need) -> +extract_sequence(4, [$t, $c | Fmt], Need) -> + extract_sequence(5, [$c|Fmt], Need); +extract_sequence(4, [$t, $s | Fmt], Need) -> + extract_sequence(5, [$s|Fmt], Need); +extract_sequence(4, [$t, C | _Fmt], _Need) -> {error,"invalid control ~t" ++ [C]}; extract_sequence(4, Fmt, Need) -> extract_sequence(5, Fmt, Need); @@ -3437,3 +3545,56 @@ expand_package(M, St0) -> {error, St1} end end. + + +%% Prebuild set of local functions (to override auto-import) +local_functions(Forms) -> + gb_sets:from_list([ {Func,Arity} || {function,_,Func,Arity,_} <- Forms ]). +%% Predicate to find out if the function is locally defined +is_local_function(LocalSet,{Func,Arity}) -> + gb_sets:is_element({Func,Arity},LocalSet). +%% Predicate to see if a function is explicitly imported +is_imported_function(ImportSet,{Func,Arity}) -> + case orddict:find({Func,Arity}, ImportSet) of + {ok,_Mod} -> true; + error -> false + end. +%% Predicate to see if a function is explicitly imported from the erlang module +is_imported_from_erlang(ImportSet,{Func,Arity}) -> + case orddict:find({Func,Arity}, ImportSet) of + {ok,erlang} -> true; + _ -> false + end. +%% Build set of functions where auto-import is explicitly supressed +auto_import_suppressed(CompileFlags) -> + L0 = [ X || {no_auto_import,X} <- CompileFlags ], + L1 = [ {Y,Z} || {Y,Z} <- lists:flatten(L0), is_atom(Y), is_integer(Z) ], + gb_sets:from_list(L1). +%% Predicate to find out if autoimport is explicitly supressed for a function +is_autoimport_suppressed(NoAutoSet,{Func,Arity}) -> + gb_sets:is_element({Func,Arity},NoAutoSet). +%% Predicate to find out if a function specific bif-clash supression (old deprecated) is present +bif_clash_specifically_disabled(St,{F,A}) -> + Nowarn = nowarn_function(nowarn_bif_clash, St#lint.compile), + lists:member({F,A},Nowarn). + +%% Predicate to find out if an autoimported guard_bif is not overriden in some way +%% Guard Bif without module name is disallowed if +%% * It is overridden by local function +%% * It is overridden by -import and that import is not of itself (i.e. from module erlang) +%% * The autoimport is suppressed or it's not reimported by -import directive +%% Otherwise it's OK (given that it's actually a guard bif and actually is autoimported) +no_guard_bif_clash(St,{F,A}) -> + ( + (not is_local_function(St#lint.locals,{F,A})) + andalso + ( + (not is_imported_function(St#lint.imports,{F,A})) orelse + is_imported_from_erlang(St#lint.imports,{F,A}) + ) + andalso + ( + (not is_autoimport_suppressed(St#lint.no_auto, {F,A})) orelse + is_imported_from_erlang(St#lint.imports,{F,A}) + ) + ). diff --git a/lib/stdlib/src/erl_parse.yrl b/lib/stdlib/src/erl_parse.yrl index 5287f55e59..bb4b18cf9b 100644 --- a/lib/stdlib/src/erl_parse.yrl +++ b/lib/stdlib/src/erl_parse.yrl @@ -47,7 +47,7 @@ opt_bit_size_expr bit_size_expr opt_bit_type_list bit_type_list bit_type top_type top_type_100 top_types type typed_expr typed_attr_val type_sig type_sigs type_guard type_guards fun_type fun_type_100 binary_type type_spec spec_fun typed_exprs typed_record_fields field_types field_type -bin_base_type bin_unit_type int_type. +bin_base_type bin_unit_type type_200 type_300 type_400 type_500. Terminals char integer float atom string var @@ -61,7 +61,7 @@ char integer float atom string var '++' '--' '==' '/=' '=<' '<' '>=' '>' '=:=' '=/=' '<=' '<<' '>>' -'!' '=' '::' +'!' '=' '::' '..' '...' 'spec' % helper dot. @@ -120,8 +120,24 @@ top_types -> top_type ',' top_types : ['$1'|'$3']. top_type -> var '::' top_type_100 : {ann_type, ?line('$1'), ['$1','$3']}. top_type -> top_type_100 : '$1'. -top_type_100 -> type : '$1'. -top_type_100 -> type '|' top_type_100 : lift_unions('$1','$3'). +top_type_100 -> type_200 : '$1'. +top_type_100 -> type_200 '|' top_type_100 : lift_unions('$1','$3'). + +type_200 -> type_300 '..' type_300 : {type, ?line('$1'), range, + [skip_paren('$1'), + skip_paren('$3')]}. +type_200 -> type_300 : '$1'. + +type_300 -> type_300 add_op type_400 : ?mkop2(skip_paren('$1'), + '$2', skip_paren('$3')). +type_300 -> type_400 : '$1'. + +type_400 -> type_400 mult_op type_500 : ?mkop2(skip_paren('$1'), + '$2', skip_paren('$3')). +type_400 -> type_500 : '$1'. + +type_500 -> prefix_op type : ?mkop1('$1', skip_paren('$2')). +type_500 -> type : '$1'. type -> '(' top_type ')' : {paren_type, ?line('$2'), ['$2']}. type -> var : '$1'. @@ -135,7 +151,7 @@ type -> atom ':' atom '(' top_types ')' : {remote_type, ?line('$1'), ['$1', '$3', '$5']}. type -> '[' ']' : {type, ?line('$1'), nil, []}. type -> '[' top_type ']' : {type, ?line('$1'), list, ['$2']}. -type -> '[' top_type ',' '.' '.' '.' ']' : {type, ?line('$1'), +type -> '[' top_type ',' '...' ']' : {type, ?line('$1'), nonempty_list, ['$2']}. type -> '{' '}' : {type, ?line('$1'), tuple, []}. type -> '{' top_types '}' : {type, ?line('$1'), tuple, '$2'}. @@ -143,19 +159,13 @@ type -> '#' atom '{' '}' : {type, ?line('$1'), record, ['$2']}. type -> '#' atom '{' field_types '}' : {type, ?line('$1'), record, ['$2'|'$4']}. type -> binary_type : '$1'. -type -> int_type : '$1'. -type -> int_type '.' '.' int_type : {type, ?line('$1'), range, - ['$1', '$4']}. +type -> integer : '$1'. type -> 'fun' '(' ')' : {type, ?line('$1'), 'fun', []}. type -> 'fun' '(' fun_type_100 ')' : '$3'. -int_type -> integer : '$1'. -int_type -> '-' integer : abstract(-normalise('$2'), - ?line('$2')). - -fun_type_100 -> '(' '.' '.' '.' ')' '->' top_type +fun_type_100 -> '(' '...' ')' '->' top_type : {type, ?line('$1'), 'fun', - [{type, ?line('$1'), any}, '$7']}. + [{type, ?line('$1'), any}, '$5']}. fun_type_100 -> fun_type : '$1'. fun_type -> '(' ')' '->' top_type : {type, ?line('$1'), 'fun', @@ -180,9 +190,9 @@ binary_type -> '<<' bin_unit_type '>>' : {type, ?line('$1'),binary, binary_type -> '<<' bin_base_type ',' bin_unit_type '>>' : {type, ?line('$1'), binary, ['$2', '$4']}. -bin_base_type -> var ':' integer : build_bin_type(['$1'], '$3'). +bin_base_type -> var ':' type : build_bin_type(['$1'], '$3'). -bin_unit_type -> var ':' var '*' integer : build_bin_type(['$1', '$3'], '$5'). +bin_unit_type -> var ':' var '*' type : build_bin_type(['$1', '$3'], '$5'). attr_val -> expr : ['$1']. attr_val -> expr ',' exprs : ['$1' | '$3']. @@ -607,6 +617,11 @@ lift_unions(T1, {type, _La, union, List}) -> lift_unions(T1, T2) -> {type, ?line(T1), union, [T1, T2]}. +skip_paren({paren_type,_L,[Type]}) -> + skip_paren(Type); +skip_paren(Type) -> + Type. + build_gen_type({atom, La, tuple}) -> {type, La, tuple, any}; build_gen_type({atom, La, Name}) -> @@ -615,7 +630,7 @@ build_gen_type({atom, La, Name}) -> build_bin_type([{var, _, '_'}|Left], Int) -> build_bin_type(Left, Int); build_bin_type([], Int) -> - Int; + skip_paren(Int); build_bin_type([{var, La, _}|_], _) -> ret_err(La, "Bad binary type"). diff --git a/lib/stdlib/src/erl_pp.erl b/lib/stdlib/src/erl_pp.erl index 0859bf0466..df4a20b833 100644 --- a/lib/stdlib/src/erl_pp.erl +++ b/lib/stdlib/src/erl_pp.erl @@ -115,7 +115,7 @@ lattribute({attribute,_Line,Name,Arg}, Hook) -> lattribute(module, {M,Vs}, _Hook) -> attr("module",[{var,0,pname(M)}, - foldr(fun(V, C) -> {cons,0,{var,0,V},C} + foldr(fun(V, C) -> {cons,0,{var,0,V},C} end, {nil,0}, Vs)]); lattribute(module, M, _Hook) -> attr("module", [{var,0,pname(M)}]); @@ -140,7 +140,7 @@ typeattr(Tag, {TypeName,Type,Args}, _Hook) -> ltype({ann_type,_Line,[V,T]}) -> typed(lexpr(V, none), T); ltype({paren_type,_Line,[T]}) -> - [$(,ltype(T),$)]; + [$(,ltype(T),$)]; ltype({type,_Line,union,Ts}) -> {seq,[],[],[' |'],ltypes(Ts)}; ltype({type,_Line,list,[T]}) -> @@ -153,7 +153,7 @@ ltype({type,Line,tuple,any}) -> simple_type({atom,Line,tuple}, []); ltype({type,_Line,tuple,Ts}) -> tuple_type(Ts, fun ltype/1); -ltype({type,_Line,record,[N|Fs]}) -> +ltype({type,_Line,record,[{atom,_,N}|Fs]}) -> record_type(N, Fs); ltype({type,_Line,range,[_I1,_I2]=Es}) -> expr_list(Es, '..', fun lexpr/2, none); @@ -174,12 +174,15 @@ ltype({atom,_,T}) -> ltype(E) -> lexpr(E, 0, none). -binary_type({integer,_,Int1}=I1, {integer,_,Int2}=I2) -> - E1 = [[leaf("_:"),lexpr(I1, 0, none)] || Int1 =/= 0], - E2 = [[leaf("_:_*"),lexpr(I2, 0, none)] || Int2 =/= 0], +binary_type(I1, I2) -> + B = [[] || {integer,_,0} <- [I1]] =:= [], + U = [[] || {integer,_,0} <- [I2]] =:= [], + P = max_prec(), + E1 = [[leaf("_:"),lexpr(I1, P, none)] || B], + E2 = [[leaf("_:_*"),lexpr(I2, P, none)] || U], {seq,'<<','>>',[$,],E1++E2}. -record_type({atom,_,Name}, Fields) -> +record_type(Name, Fields) -> {first,[record_name(Name)],field_types(Fields)}. field_types(Fs) -> @@ -443,7 +446,7 @@ lexpr({op,_,Op,Arg}, Prec, Hook) -> Ol = leaf(format("~s ", [Op])), El = [Ol,lexpr(Arg, R, Hook)], maybe_paren(P, Prec, El); -lexpr({op,_,Op,Larg,Rarg}, Prec, Hook) when Op =:= 'orelse'; +lexpr({op,_,Op,Larg,Rarg}, Prec, Hook) when Op =:= 'orelse'; Op =:= 'andalso' -> %% Breaks lines since R12B. {L,P,R} = inop_prec(Op), @@ -727,15 +730,15 @@ frmt(Item, I) -> %%% and indentation are inserted between IPs. %%% - {first,I,IP2}: IP2 follows after I, and is output with an indentation %%% updated with the width of I. -%%% - {seq,Before,After,Separator,IPs}: a sequence of Is separated by -%%% Separator. Before is output before IPs, and the indentation of IPs +%%% - {seq,Before,After,Separator,IPs}: a sequence of Is separated by +%%% Separator. Before is output before IPs, and the indentation of IPs %%% is updated with the width of Before. After follows after IPs. %%% - {force_nl,ExtraInfo,I}: fun-info (a comment) forces linebreak before I. %%% - {prefer_nl,Sep,IPs}: forces linebreak between Is unlesss negative %%% indentation. %%% - {string,S}: a string. %%% - {hook,...}, {ehook,...}: hook expressions. -%%% +%%% %%% list, first, seq, force_nl, and prefer_nl all accept IPs, where each %%% element is either an item or a tuple {step|cstep,I1,I2}. step means %%% that I2 is output after linebreak and an incremented indentation. @@ -761,7 +764,7 @@ f({seq,Before,After,Sep,LItems}, I0, ST, WT) -> {CharsL,SizeL} = unz(CharsSizeL), {BCharsL,BSizeL} = unz1([BCharsSize]), Sizes = BSizeL ++ SizeL, - NSepChars = if + NSepChars = if is_list(Sep), Sep =/= [] -> erlang:max(0, length(CharsL)-1); true -> @@ -876,7 +879,7 @@ nl_indent(I, T) when I > 0 -> [$\n|spaces(I, T)]. same_line(I0, SizeL, NSepChars) -> - try + try Size = lists:sum(SizeL) + NSepChars, true = incr(I0, Size) =< ?MAXLINE, {yes,Size} @@ -956,9 +959,9 @@ write_a_string(S, N, Len) -> -define(N_SPACES, 30). spacetab() -> - {[_|L],_} = mapfoldl(fun(_, A) -> {A,[$\s|A]} + {[_|L],_} = mapfoldl(fun(_, A) -> {A,[$\s|A]} end, [], lists:seq(0, ?N_SPACES)), - list_to_tuple(L). + list_to_tuple(L). spaces(N, T) when N =< ?N_SPACES -> element(N, T); @@ -966,7 +969,7 @@ spaces(N, T) -> [element(?N_SPACES, T)|spaces(N-?N_SPACES, T)]. wordtable() -> - L = [begin {leaf,Sz,S} = leaf(W), {S,Sz} end || + L = [begin {leaf,Sz,S} = leaf(W), {S,Sz} end || W <- [" ->"," =","<<",">>","[]","after","begin","case","catch", "end","fun","if","of","receive","try","when"," ::","..", " |"]], diff --git a/lib/stdlib/src/erl_scan.erl b/lib/stdlib/src/erl_scan.erl index 1013d54bdc..18f64c46d0 100644 --- a/lib/stdlib/src/erl_scan.erl +++ b/lib/stdlib/src/erl_scan.erl @@ -55,18 +55,13 @@ token_info/1,token_info/2, attributes_info/1,attributes_info/2,set_attribute/3]). -%%% Local record. --record(erl_scan, - {resword_fun=fun reserved_word/1, - ws=false, - comment=false, - text=false}). +-export_type([error_info/0, line/0, tokens_result/0]). %%% -%%% Exported functions +%%% Defines and type definitions %%% --define(COLUMN(C), is_integer(C), C >= 1). +-define(COLUMN(C), (is_integer(C) andalso C >= 1)). %% Line numbers less than zero have always been allowed: -define(ALINE(L), is_integer(L)). -define(STRING(S), is_list(S)). @@ -95,6 +90,15 @@ -type error_description() :: term(). -type error_info() :: {location(), module(), error_description()}. +%%% Local record. +-record(erl_scan, + {resword_fun = fun reserved_word/1 :: resword_fun(), + ws = false :: boolean(), + comment = false :: boolean(), + text = false :: boolean()}). + +%%---------------------------------------------------------------------------- + -spec format_error(Error :: term()) -> string(). format_error({string,Quote,Head}) -> lists:flatten(["unterminated " ++ string_thing(Quote) ++ @@ -307,10 +311,10 @@ options(Opt) -> options([Opt]). opts(Options, [Key|Keys], L) -> - V = case lists:keysearch(Key, 1, Options) of - {value,{reserved_word_fun,F}} when ?RESWORDFUN(F) -> + V = case lists:keyfind(Key, 1, Options) of + {reserved_word_fun,F} when ?RESWORDFUN(F) -> {ok,F}; - {value,{Key,_}} -> + {Key,_} -> badarg; false -> {ok,default_option(Key)} @@ -333,12 +337,13 @@ expand_opt(O, Os) -> [O|Os]. attr_info(Attrs, Item) -> - case catch lists:keysearch(Item, 1, Attrs) of - {value,{Item,Value}} -> - {Item,Value}; + try lists:keyfind(Item, 1, Attrs) of + {_Item, _Value} = T -> + T; false -> - undefined; - _ -> + undefined + catch + _:_ -> erlang:error(badarg, [Attrs, Item]) end. @@ -442,6 +447,14 @@ scan1([$\%=C|Cs], St, Line, Col, Toks) -> scan_comment(Cs, St, Line, Col, Toks, [C]); scan1([C|Cs], St, Line, Col, Toks) when ?DIGIT(C) -> scan_number(Cs, St, Line, Col, Toks, [C]); +scan1("..."++Cs, St, Line, Col, Toks) -> + tok2(Cs, St, Line, Col, Toks, "...", '...', 3); +scan1(".."=Cs, _St, Line, Col, Toks) -> + {more,{Cs,Col,Toks,Line,[],fun scan/6}}; +scan1(".."++Cs, St, Line, Col, Toks) -> + tok2(Cs, St, Line, Col, Toks, "..", '..', 2); +scan1("."=Cs, _St, Line, Col, Toks) -> + {more,{Cs,Col,Toks,Line,[],fun scan/6}}; scan1([$.=C|Cs], St, Line, Col, Toks) -> scan_dot(Cs, St, Line, Col, Toks, [C]); scan1([$"|Cs], St, Line, Col, Toks) -> %" Emacs @@ -644,8 +657,6 @@ scan_dot([$\n=C|Cs], St, Line, Col, Toks, Ncs) -> scan_dot([C|Cs], St, Line, Col, Toks, Ncs) when ?WHITE_SPACE(C) -> Attrs = attributes(Line, Col, St, Ncs++[C]), {ok,[{dot,Attrs}|Toks],Cs,Line,incr_column(Col, 2)}; -scan_dot([]=Cs, _St, Line, Col, Toks, Ncs) -> - {more,{Cs,Col,Toks,Line,Ncs,fun scan_dot/6}}; scan_dot(eof=Cs, St, Line, Col, Toks, Ncs) -> Attrs = attributes(Line, Col, St, Ncs), {ok,[{dot,Attrs}|Toks],Cs,Line,incr_column(Col, 1)}; diff --git a/lib/stdlib/src/ets.erl b/lib/stdlib/src/ets.erl index d7b5dbc636..1d033f6f7b 100644 --- a/lib/stdlib/src/ets.erl +++ b/lib/stdlib/src/ets.erl @@ -42,10 +42,15 @@ -export([i/0, i/1, i/2, i/3]). -%%------------------------------------------------------------------------------ +-export_type([tab/0, tid/0]). + +%%----------------------------------------------------------------------------- -type tab() :: atom() | tid(). +%% a similar definition is also in erl_types +-opaque tid() :: integer(). + -type ext_info() :: 'md5sum' | 'object_count'. -type protection() :: 'private' | 'protected' | 'public'. -type type() :: 'bag' | 'duplicate_bag' | 'ordered_set' | 'set'. @@ -63,7 +68,7 @@ -type match_pattern() :: atom() | tuple(). -type match_specs() :: [{match_pattern(), [_], [_]}]. -%%------------------------------------------------------------------------------ +%%----------------------------------------------------------------------------- %% The following functions used to be found in this module, but %% are now BIFs (i.e. implemented in C). diff --git a/lib/stdlib/src/file_sorter.erl b/lib/stdlib/src/file_sorter.erl index e21a0c88f3..3875eca39d 100644 --- a/lib/stdlib/src/file_sorter.erl +++ b/lib/stdlib/src/file_sorter.erl @@ -191,7 +191,7 @@ options([{format, Format} | L], Opts) when Format =:= binary; options([{format, binary_term} | L], Opts) -> options(L, Opts#opts{format = binary_term_fun()}); options([{size, Size} | L], Opts) when is_integer(Size), Size >= 0 -> - options(L, Opts#opts{size = max(Size, 1)}); + options(L, Opts#opts{size = erlang:max(Size, 1)}); options([{no_files, NoFiles} | L], Opts) when is_integer(NoFiles), NoFiles > 1 -> options(L, Opts#opts{no_files = NoFiles}); @@ -997,10 +997,10 @@ close_read_fun(Fd, FileName, fsort) -> file:delete(FileName). read_objs(Fd, FileName, I, L, Bin0, Size0, LSz, W) -> - Max = max(Size0, ?CHUNKSIZE), + Max = erlang:max(Size0, ?CHUNKSIZE), BSz0 = byte_size(Bin0), Min = Size0 - BSz0 + W#w.hdlen, % Min > 0 - NoBytes = max(Min, Max), + NoBytes = erlang:max(Min, Max), case read(Fd, FileName, NoBytes, W) of {ok, Bin} -> BSz = byte_size(Bin), @@ -1180,9 +1180,6 @@ make_key2([Kp], T) -> make_key2([Kp | Kps], T) -> [element(Kp, T) | make_key2(Kps, T)]. -max(A, B) when A < B -> B; -max(A, _) -> A. - infun(W) -> W1 = W#w{in = undefined}, try (W#w.in)(read) of diff --git a/lib/stdlib/src/io.erl b/lib/stdlib/src/io.erl index 1f8076e864..1d0f9374bc 100644 --- a/lib/stdlib/src/io.erl +++ b/lib/stdlib/src/io.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1996-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% -module(io). @@ -32,6 +32,7 @@ parse_erl_form/1,parse_erl_form/2,parse_erl_form/3]). -export([request/1,request/2,requests/1,requests/2]). +-export_type([device/0, format/0]). %%------------------------------------------------------------------------- diff --git a/lib/stdlib/src/io_lib.erl b/lib/stdlib/src/io_lib.erl index 26f6ec8931..4ca9d079b7 100644 --- a/lib/stdlib/src/io_lib.erl +++ b/lib/stdlib/src/io_lib.erl @@ -75,6 +75,8 @@ collect_line/2, collect_line/3, collect_line/4, get_until/3, get_until/4]). +-export_type([chars/0]). + %%---------------------------------------------------------------------- %% XXX: overapproximates a deep list of (unicode) characters diff --git a/lib/stdlib/src/io_lib_fread.erl b/lib/stdlib/src/io_lib_fread.erl index 74316dc730..33553692bc 100644 --- a/lib/stdlib/src/io_lib_fread.erl +++ b/lib/stdlib/src/io_lib_fread.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1996-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% -module(io_lib_fread). @@ -22,6 +22,8 @@ -export([fread/2,fread/3]). +-export_type([continuation/0, fread_2_ret/0, fread_3_ret/0]). + -import(lists, [reverse/1,reverse/2]). %%----------------------------------------------------------------------- diff --git a/lib/stdlib/src/lists.erl b/lib/stdlib/src/lists.erl index 857eda8161..08ee595f4d 100644 --- a/lib/stdlib/src/lists.erl +++ b/lib/stdlib/src/lists.erl @@ -18,6 +18,9 @@ %% -module(lists). +-compile({no_auto_import,[max/2]}). +-compile({no_auto_import,[min/2]}). + -export([append/2, append/1, subtract/2, reverse/1, nth/2, nthtail/2, prefix/2, suffix/2, last/1, seq/2, seq/3, sum/1, duplicate/2, min/1, max/1, sublist/2, sublist/3, diff --git a/lib/stdlib/src/proc_lib.erl b/lib/stdlib/src/proc_lib.erl index 9aa5e0a71e..4fb64a3353 100644 --- a/lib/stdlib/src/proc_lib.erl +++ b/lib/stdlib/src/proc_lib.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1996-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% -module(proc_lib). @@ -34,6 +34,8 @@ %% Internal exports. -export([wake_up/3]). +-export_type([spawn_option/0]). + %%----------------------------------------------------------------------------- -type priority_level() :: 'high' | 'low' | 'max' | 'normal'. diff --git a/lib/stdlib/src/proplists.erl b/lib/stdlib/src/proplists.erl index 35d14891f1..6a45e0f868 100644 --- a/lib/stdlib/src/proplists.erl +++ b/lib/stdlib/src/proplists.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2001-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2001-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %% ===================================================================== @@ -49,6 +49,8 @@ %% --------------------------------------------------------------------- +-export_type([property/0]). + -type property() :: atom() | tuple(). -type aliases() :: [{any(), any()}]. diff --git a/lib/stdlib/src/supervisor.erl b/lib/stdlib/src/supervisor.erl index 22269a8d1b..f5d5441184 100644 --- a/lib/stdlib/src/supervisor.erl +++ b/lib/stdlib/src/supervisor.erl @@ -21,7 +21,7 @@ -behaviour(gen_server). %% External exports --export([start_link/2,start_link/3, +-export([start_link/2, start_link/3, start_child/2, restart_child/2, delete_child/2, terminate_child/2, which_children/1, count_children/1, @@ -33,25 +33,47 @@ -export([init/1, handle_call/3, handle_info/2, terminate/2, code_change/3]). -export([handle_cast/2]). +-export_type([child_spec/0, strategy/0]). + +%%-------------------------------------------------------------------------- + +-type child_id() :: pid() | 'undefined'. +-type mfargs() :: {module(), atom(), [term()]}. +-type modules() :: [module()] | 'dynamic'. +-type restart() :: 'permanent' | 'transient' | 'temporary'. +-type shutdown() :: 'brutal_kill' | timeout(). +-type worker() :: 'worker' | 'supervisor'. +-type sup_name() :: {'local', atom()} | {'global', atom()}. +-type sup_ref() :: atom() | {atom(), atom()} | {'global', atom()} | pid(). +-type child_spec() :: {term(),mfargs(),restart(),shutdown(),worker(),modules()}. + +-type strategy() :: 'one_for_all' | 'one_for_one' + | 'rest_for_one' | 'simple_one_for_one'. + +%%-------------------------------------------------------------------------- + +-record(child, {% pid is undefined when child is not running + pid = undefined :: child_id(), + name, + mfargs :: mfargs(), + restart_type :: restart(), + shutdown :: shutdown(), + child_type :: worker(), + modules = [] :: modules()}). +-type child() :: #child{}. + -define(DICT, dict). -record(state, {name, - strategy, - children = [], - dynamics = ?DICT:new(), - intensity, - period, + strategy :: strategy(), + children = [] :: [child()], + dynamics = ?DICT:new() :: ?DICT(), + intensity :: non_neg_integer(), + period :: pos_integer(), restarts = [], module, args}). - --record(child, {pid = undefined, % pid is undefined when child is not running - name, - mfa, - restart_type, - shutdown, - child_type, - modules = []}). +-type state() :: #state{}. -define(is_simple(State), State#state.strategy =:= simple_one_for_one). @@ -65,21 +87,40 @@ behaviour_info(_Other) -> %%% Servers/processes should/could also be built using gen_server.erl. %%% SupName = {local, atom()} | {global, atom()}. %%% --------------------------------------------------- + +-type startlink_err() :: {'already_started', pid()} | 'shutdown' | term(). +-type startlink_ret() :: {'ok', pid()} | 'ignore' | {'error', startlink_err()}. + +-spec start_link(module(), term()) -> startlink_ret(). start_link(Mod, Args) -> gen_server:start_link(supervisor, {self, Mod, Args}, []). +-spec start_link(sup_name(), module(), term()) -> startlink_ret(). start_link(SupName, Mod, Args) -> gen_server:start_link(SupName, supervisor, {SupName, Mod, Args}, []). %%% --------------------------------------------------- %%% Interface functions. %%% --------------------------------------------------- + +-type info() :: term(). +-type startchild_err() :: 'already_present' + | {'already_started', child_id()} | term(). +-type startchild_ret() :: {'ok', child_id()} | {'ok', child_id(), info()} + | {'error', startchild_err()}. + +-spec start_child(sup_ref(), child_spec() | [term()]) -> startchild_ret(). start_child(Supervisor, ChildSpec) -> call(Supervisor, {start_child, ChildSpec}). +-type restart_err() :: 'running' | 'not_found' | 'simple_one_for_one' | term(). +-spec restart_child(sup_ref(), term()) -> + {'ok', child_id()} | {'ok', child_id(), info()} | {'error', restart_err()}. restart_child(Supervisor, Name) -> call(Supervisor, {restart_child, Name}). +-type del_err() :: 'running' | 'not_found' | 'simple_one_for_one'. +-spec delete_child(sup_ref(), term()) -> 'ok' | {'error', del_err()}. delete_child(Supervisor, Name) -> call(Supervisor, {delete_child, Name}). @@ -89,9 +130,13 @@ delete_child(Supervisor, Name) -> %% Note that the child is *always* terminated in some %% way (maybe killed). %%----------------------------------------------------------------- + +-type term_err() :: 'not_found' | 'simple_one_for_one'. +-spec terminate_child(sup_ref(), term()) -> 'ok' | {'error', term_err()}. terminate_child(Supervisor, Name) -> call(Supervisor, {terminate_child, Name}). +-spec which_children(sup_ref()) -> [{term(), child_id(), worker(), modules()}]. which_children(Supervisor) -> call(Supervisor, which_children). @@ -101,6 +146,7 @@ count_children(Supervisor) -> call(Supervisor, Req) -> gen_server:call(Supervisor, Req, infinity). +-spec check_childspecs([child_spec()]) -> 'ok' | {'error', term()}. check_childspecs(ChildSpecs) when is_list(ChildSpecs) -> case check_startspec(ChildSpecs) of {ok, _} -> ok; @@ -113,6 +159,14 @@ check_childspecs(X) -> {error, {badarg, X}}. %%% Initialize the supervisor. %%% %%% --------------------------------------------------- + +-type stop_rsn() :: 'shutdown' | {'bad_return', {module(),'init', term()}} + | {'bad_start_spec', term()} | {'start_spec', term()} + | {'supervisor_data', term()}. + +-spec init({sup_name(), module(), [term()]}) -> + {'ok', state()} | 'ignore' | {'stop', stop_rsn()}. + init({SupName, Mod, Args}) -> process_flag(trap_exit, true), case Mod:init(Args) of @@ -158,12 +212,12 @@ init_dynamic(_State, StartSpec) -> %%----------------------------------------------------------------- %% Func: start_children/2 -%% Args: Children = [#child] in start order -%% SupName = {local, atom()} | {global, atom()} | {pid(),Mod} +%% Args: Children = [child()] in start order +%% SupName = {local, atom()} | {global, atom()} | {pid(), Mod} %% Purpose: Start all children. The new list contains #child's %% with pids. %% Returns: {ok, NChildren} | {error, NChildren} -%% NChildren = [#child] in termination order (reversed +%% NChildren = [child()] in termination order (reversed %% start order) %%----------------------------------------------------------------- start_children(Children, SupName) -> start_children(Children, [], SupName). @@ -182,8 +236,8 @@ start_children([], NChildren, _SupName) -> {ok, NChildren}. do_start_child(SupName, Child) -> - #child{mfa = {M, F, A}} = Child, - case catch apply(M, F, A) of + #child{mfargs = {M, F, Args}} = Child, + case catch apply(M, F, Args) of {ok, Pid} when is_pid(Pid) -> NChild = Child#child{pid = Pid}, report_progress(NChild, SupName), @@ -192,7 +246,7 @@ do_start_child(SupName, Child) -> NChild = Child#child{pid = Pid}, report_progress(NChild, SupName), {ok, Pid, Extra}; - ignore -> + ignore -> {ok, undefined}; {error, What} -> {error, What}; What -> {error, What} @@ -211,15 +265,17 @@ do_start_child_i(M, F, A) -> What -> {error, What} end. - %%% --------------------------------------------------- %%% %%% Callback functions. %%% %%% --------------------------------------------------- +-type call() :: 'which_children' | 'count_children' | {_, _}. % XXX: refine +-spec handle_call(call(), term(), state()) -> {'reply', term(), state()}. + handle_call({start_child, EArgs}, _From, State) when ?is_simple(State) -> - #child{mfa = {M, F, A}} = hd(State#state.children), + #child{mfargs = {M, F, A}} = hd(State#state.children), Args = A ++ EArgs, case do_start_child_i(M, F, Args) of {ok, Pid} -> @@ -235,7 +291,7 @@ handle_call({start_child, EArgs}, _From, State) when ?is_simple(State) -> end; %%% The requests terminate_child, delete_child and restart_child are -%%% invalid for simple_one_for_one supervisors. +%%% invalid for simple_one_for_one supervisors. handle_call({_Req, _Data}, _From, State) when ?is_simple(State) -> {reply, {error, simple_one_for_one}, State}; @@ -297,7 +353,7 @@ handle_call(which_children, _From, State) -> Resp = lists:map(fun(#child{pid = Pid, name = Name, child_type = ChildType, modules = Mods}) -> - {Name, Pid, ChildType, Mods} + {Name, Pid, ChildType, Mods} end, State#state.children), {reply, Resp, State}; @@ -318,7 +374,6 @@ handle_call(count_children, _From, State) when ?is_simple(State) -> {reply, Reply, State}; handle_call(count_children, _From, State) -> - %% Specs and children are together on the children list... {Specs, Active, Supers, Workers} = lists:foldl(fun(Child, Counts) -> @@ -347,15 +402,19 @@ count_child(#child{pid = Pid, child_type = supervisor}, %%% Hopefully cause a function-clause as there is no API function %%% that utilizes cast. +-spec handle_cast('null', state()) -> {'noreply', state()}. + handle_cast(null, State) -> error_logger:error_msg("ERROR: Supervisor received cast-message 'null'~n", []), - {noreply, State}. %% %% Take care of terminated children. %% +-spec handle_info(term(), state()) -> + {'noreply', state()} | {'stop', 'shutdown', state()}. + handle_info({'EXIT', Pid, Reason}, State) -> case restart_child(Pid, Reason, State) of {ok, State1} -> @@ -368,9 +427,12 @@ handle_info(Msg, State) -> error_logger:error_msg("Supervisor received unexpected message: ~p~n", [Msg]), {noreply, State}. + %% %% Terminate this server. %% +-spec terminate(term(), state()) -> 'ok'. + terminate(_Reason, State) -> terminate_children(State#state.children, State#state.name), ok. @@ -384,6 +446,9 @@ terminate(_Reason, State) -> %% NOTE: This requires that the init function of the call-back module %% does not have any side effects. %% +-spec code_change(term(), state(), term()) -> + {'ok', state()} | {'error', term()}. + code_change(_, State, _) -> case (State#state.module):init(State#state.args) of {ok, {SupFlags, StartSpec}} -> @@ -411,7 +476,7 @@ check_flags({Strategy, MaxIntensity, Period}) -> check_flags(What) -> {bad_flags, What}. -update_childspec(State, StartSpec) when ?is_simple(State) -> +update_childspec(State, StartSpec) when ?is_simple(State) -> case check_startspec(StartSpec) of {ok, [Child]} -> {ok, State#state{children = [Child]}}; @@ -437,7 +502,7 @@ update_childspec1([Child|OldC], Children, KeepOld) -> update_childspec1(OldC, Children, [Child|KeepOld]) end; update_childspec1([], Children, KeepOld) -> - % Return them in (keeped) reverse start order. + %% Return them in (kept) reverse start order. lists:reverse(Children ++ KeepOld). update_chsp(OldCh, Children) -> @@ -482,7 +547,7 @@ handle_start_child(Child, State) -> %%% --------------------------------------------------- %%% Restart. A process has terminated. -%%% Returns: {ok, #state} | {shutdown, #state} +%%% Returns: {ok, state()} | {shutdown, state()} %%% --------------------------------------------------- restart_child(Pid, Reason, State) when ?is_simple(State) -> @@ -490,19 +555,19 @@ restart_child(Pid, Reason, State) when ?is_simple(State) -> {ok, Args} -> [Child] = State#state.children, RestartType = Child#child.restart_type, - {M, F, _} = Child#child.mfa, - NChild = Child#child{pid = Pid, mfa = {M, F, Args}}, + {M, F, _} = Child#child.mfargs, + NChild = Child#child{pid = Pid, mfargs = {M, F, Args}}, do_restart(RestartType, Reason, NChild, State); error -> {ok, State} end; restart_child(Pid, Reason, State) -> Children = State#state.children, - case lists:keysearch(Pid, #child.pid, Children) of - {value, Child} -> + case lists:keyfind(Pid, #child.pid, Children) of + #child{} = Child -> RestartType = Child#child.restart_type, do_restart(RestartType, Reason, Child, State); - _ -> + false -> {ok, State} end. @@ -534,7 +599,7 @@ restart(Child, State) -> end. restart(simple_one_for_one, Child, State) -> - #child{mfa = {M, F, A}} = Child, + #child{mfargs = {M, F, A}} = Child, Dynamics = ?DICT:erase(Child#child.pid, State#state.dynamics), case do_start_child_i(M, F, A) of {ok, Pid} -> @@ -580,9 +645,9 @@ restart(one_for_all, Child, State) -> %%----------------------------------------------------------------- %% Func: terminate_children/2 -%% Args: Children = [#child] in termination order +%% Args: Children = [child()] in termination order %% SupName = {local, atom()} | {global, atom()} | {pid(),Mod} -%% Returns: NChildren = [#child] in +%% Returns: NChildren = [child()] in %% startup order (reversed termination order) %%----------------------------------------------------------------- terminate_children(Children, SupName) -> @@ -617,7 +682,6 @@ do_terminate(Child, _SupName) -> %% Returns: ok | {error, OtherReason} (this should be reported) %%----------------------------------------------------------------- shutdown(Pid, brutal_kill) -> - case monitor_child(Pid) of ok -> exit(Pid, kill), @@ -630,9 +694,7 @@ shutdown(Pid, brutal_kill) -> {error, Reason} -> {error, Reason} end; - shutdown(Pid, Time) -> - case monitor_child(Pid) of ok -> exit(Pid, shutdown), %% Try to shutdown gracefully @@ -738,9 +800,9 @@ remove_child(Child, State) -> %% MaxIntensity = integer() %% Period = integer() %% Mod :== atom() -%% Arsg :== term() +%% Args :== term() %% Purpose: Check that Type is of correct type (!) -%% Returns: {ok, #state} | Error +%% Returns: {ok, state()} | Error %%----------------------------------------------------------------- init_state(SupName, Type, Mod, Args) -> case catch init_state1(SupName, Type, Mod, Args) of @@ -755,11 +817,11 @@ init_state1(SupName, {Strategy, MaxIntensity, Period}, Mod, Args) -> validIntensity(MaxIntensity), validPeriod(Period), {ok, #state{name = supname(SupName,Mod), - strategy = Strategy, - intensity = MaxIntensity, - period = Period, - module = Mod, - args = Args}}; + strategy = Strategy, + intensity = MaxIntensity, + period = Period, + module = Mod, + args = Args}}; init_state1(_SupName, Type, _, _) -> {invalid_type, Type}. @@ -771,26 +833,26 @@ validStrategy(What) -> throw({invalid_strategy, What}). validIntensity(Max) when is_integer(Max), Max >= 0 -> true; -validIntensity(What) -> throw({invalid_intensity, What}). +validIntensity(What) -> throw({invalid_intensity, What}). validPeriod(Period) when is_integer(Period), Period > 0 -> true; validPeriod(What) -> throw({invalid_period, What}). -supname(self,Mod) -> {self(),Mod}; -supname(N,_) -> N. +supname(self, Mod) -> {self(), Mod}; +supname(N, _) -> N. %%% ------------------------------------------------------ %%% Check that the children start specification is valid. %%% Shall be a six (6) tuple %%% {Name, Func, RestartType, Shutdown, ChildType, Modules} %%% where Name is an atom -%%% Func is {Mod, Fun, Args} == {atom, atom, list} +%%% Func is {Mod, Fun, Args} == {atom(), atom(), list()} %%% RestartType is permanent | temporary | transient %%% Shutdown = integer() | infinity | brutal_kill %%% ChildType = supervisor | worker %%% Modules = [atom()] | dynamic -%%% Returns: {ok, [#child]} | Error +%%% Returns: {ok, [child()]} | Error %%% ------------------------------------------------------ check_startspec(Children) -> check_startspec(Children, []). @@ -818,14 +880,14 @@ check_childspec(Name, Func, RestartType, Shutdown, ChildType, Mods) -> validChildType(ChildType), validShutdown(Shutdown, ChildType), validMods(Mods), - {ok, #child{name = Name, mfa = Func, restart_type = RestartType, + {ok, #child{name = Name, mfargs = Func, restart_type = RestartType, shutdown = Shutdown, child_type = ChildType, modules = Mods}}. validChildType(supervisor) -> true; validChildType(worker) -> true; validChildType(What) -> throw({invalid_child_type, What}). -validName(_Name) -> true. +validName(_Name) -> true. validFunc({M, F, A}) when is_atom(M), is_atom(F), @@ -923,7 +985,7 @@ report_error(Error, Reason, Child, SupName) -> extract_child(Child) -> [{pid, Child#child.pid}, {name, Child#child.name}, - {mfa, Child#child.mfa}, + {mfargs, Child#child.mfargs}, {restart_type, Child#child.restart_type}, {shutdown, Child#child.shutdown}, {child_type, Child#child.child_type}]. diff --git a/lib/stdlib/test/epp_SUITE.erl b/lib/stdlib/test/epp_SUITE.erl index 4806b5d361..e31dfdd764 100644 --- a/lib/stdlib/test/epp_SUITE.erl +++ b/lib/stdlib/test/epp_SUITE.erl @@ -19,12 +19,12 @@ -module(epp_SUITE). -export([all/1]). --export([rec_1/1, predef_mac/1, +-export([rec_1/1, predef_mac/1, upcase_mac/1, upcase_mac_1/1, upcase_mac_2/1, variable/1, variable_1/1, otp_4870/1, otp_4871/1, otp_5362/1, pmod/1, not_circular/1, skip_header/1, otp_6277/1, otp_7702/1, otp_8130/1, overload_mac/1, otp_8388/1, otp_8470/1, otp_8503/1, - otp_8562/1]). + otp_8562/1, otp_8665/1]). -export([epp_parse_erl_form/2]). @@ -39,7 +39,7 @@ -define(config(A,B),config(A,B)). %% -define(t, test_server). -define(t, io). -config(priv_dir, _) -> +config(priv_dir, _) -> filename:absname("./epp_SUITE_priv"); config(data_dir, _) -> filename:absname("./epp_SUITE_data"). @@ -64,7 +64,7 @@ all(doc) -> all(suite) -> [rec_1, upcase_mac, predef_mac, variable, otp_4870, otp_4871, otp_5362, pmod, not_circular, skip_header, otp_6277, otp_7702, otp_8130, - overload_mac, otp_8388, otp_8470, otp_8503, otp_8562]. + overload_mac, otp_8388, otp_8470, otp_8503, otp_8562, otp_8665]. rec_1(doc) -> ["Recursive macros hang or crash epp (OTP-1398)."]; @@ -192,7 +192,7 @@ variable_1(Config) when is_list(Config) -> %% variable_1.erl includes variable_1_include.hrl and %% variable_1_include_dir.hrl. ?line {ok, List} = epp:parse_file(File, [], []), - ?line {value, {attribute,_,a,{value1,value2}}} = + ?line {value, {attribute,_,a,{value1,value2}}} = lists:keysearch(a,3,List), ok. @@ -219,13 +219,13 @@ otp_4871(Config) when is_list(Config) -> %% Testing crash in erl_scan. Unfortunately there currently is %% no known way to crash erl_scan so it is emulated by killing the %% file io server. This assumes lots of things about how - %% the processes are started and how monitors are set up, + %% the processes are started and how monitors are set up, %% so there are some sanity checks before killing. ?line {ok,Epp} = epp:open(File, []), timer:sleep(1), ?line {current_function,{epp,_,_}} = process_info(Epp, current_function), ?line {monitored_by,[Io]} = process_info(Epp, monitored_by), - ?line {current_function,{file_io_server,_,_}} = + ?line {current_function,{file_io_server,_,_}} = process_info(Io, current_function), ?line exit(Io, emulate_crash), timer:sleep(1), @@ -302,7 +302,7 @@ otp_5362(Config) when is_list(Config) -> Back_hrl = [<<" -file(\"">>,File_Back,<<"\", 2). ">>], - + ?line ok = file:write_file(File_Back, Back), ?line ok = file:write_file(File_Back_hrl, list_to_binary(Back_hrl)), @@ -333,7 +333,7 @@ otp_5362(Config) when is_list(Config) -> ?line ok = file:write_file(File_Change, list_to_binary(Change)), - ?line {ok, change_5362, ChangeWarnings} = + ?line {ok, change_5362, ChangeWarnings} = compile:file(File_Change, Copts), ?line true = message_compare( [{File_Change,[{{1002,21},erl_lint,{unused_var,'B'}}]}, @@ -441,9 +441,9 @@ skip_header(Config) when is_list(Config) -> that should be skipped -module(epp_test_skip_header). -export([main/1]). - + main(_) -> ?MODULE. - + ">>), ?line {ok, Fd} = file:open(File, [read]), ?line io:get_line(Fd, ''), @@ -494,9 +494,9 @@ otp_7702(Config) when is_list(Config) -> t() -> ?RECEIVE(foo, bar).">>, ?line ok = file:write_file(File, Contents), - ?line {ok, file_7702, []} = + ?line {ok, file_7702, []} = compile:file(File, [debug_info,return,{outdir,Dir}]), - + BeamFile = filename:join(Dir, "file_7702.beam"), {ok, AC} = beam_lib:chunks(BeamFile, [abstract_code]), @@ -506,7 +506,7 @@ otp_7702(Config) when is_list(Config) -> L end, Forms2 = [erl_lint:modify_line(Form, Fun) || Form <- Forms], - ?line + ?line [{attribute,1,file,_}, _, _, @@ -637,7 +637,7 @@ otp_8130(Config) when is_list(Config) -> ], ?line [] = run(Config, Ts), - + Cs = [{otp_8130_c1, <<"-define(M1(A), if\n" "A =:= 1 -> B;\n" @@ -681,7 +681,7 @@ otp_8130(Config) when is_list(Config) -> <<"\n-include_lib(\"$apa/foo.hrl\").\n">>, {errors,[{{2,2},epp,{include,lib,"$apa/foo.hrl"}}],[]}}, - + {otp_8130_c9, <<"-define(S, ?S).\n" "t() -> ?S.\n">>, @@ -775,7 +775,7 @@ otp_8130(Config) when is_list(Config) -> ?line Dir = ?config(priv_dir, Config), ?line File = filename:join(Dir, "otp_8130.erl"), - ?line ok = file:write_file(File, + ?line ok = file:write_file(File, "-module(otp_8130).\n" "-define(a, 3.14).\n" "t() -> ?a.\n"), @@ -788,7 +788,7 @@ otp_8130(Config) when is_list(Config) -> ?line {eof,_} = epp:scan_erl_form(Epp), ?line ['BASE_MODULE','BASE_MODULE_STRING','BEAM','FILE','LINE', 'MACHINE','MODULE','MODULE_STRING',a] = macs(Epp), - ?line epp:close(Epp), + ?line epp:close(Epp), %% escript ModuleStr = "any_name", @@ -815,7 +815,7 @@ otp_8130(Config) when is_list(Config) -> PreDefMacros = [{a,1},a], ?line {error,{redefine,a}} = epp:open(File, [], PreDefMacros) end(), - + ?line {error,enoent} = epp:open("no such file", []), ?line {error,enoent} = epp:parse_file("no such file", [], []), @@ -941,7 +941,7 @@ ifdef(Config) -> <<"\n-if.\n" "-endif.\n">>, {errors,[{{2,2},epp,{'NYI','if'}}],[]}}, - + {define_c7, <<"-ifndef(a).\n" "-elif.\n" @@ -1197,6 +1197,18 @@ otp_8562(Config) when is_list(Config) -> ?line [] = compile(Config, Cs), ok. +otp_8665(doc) -> + ["OTP-8665. Bugfix premature end."]; +otp_8665(suite) -> + []; +otp_8665(Config) when is_list(Config) -> + Cs = [{otp_8562, + <<"-define(A, a)\n">>, + {errors,[{{1,54},epp,premature_end}],[]}} + ], + ?line [] = compile(Config, Cs), + ok. + check(Config, Tests) -> eval_tests(Config, fun check_test/2, Tests). @@ -1213,7 +1225,7 @@ eval_tests(Config, Fun, Tests) -> case message_compare(E, Return) of true -> BadL; - false -> + false -> ?t:format("~nTest ~p failed. Expected~n ~p~n" "but got~n ~p~n", [N, E, Return]), fail() @@ -1228,9 +1240,9 @@ check_test(Config, Test) -> ?line File = filename:join(PrivDir, Filename), ?line ok = file:write_file(File, Test), ?line case epp:parse_file(File, [PrivDir], []) of - {ok,Forms} -> + {ok,Forms} -> [E || E={error,_} <- Forms]; - {error,Error} -> + {error,Error} -> Error end. @@ -1245,7 +1257,7 @@ compile_test(Config, Test0) -> {ok, Ws} -> warnings(File, Ws); Else -> Else end. - + warnings(File, Ws) -> case lists:append([W || {F, W} <- Ws, F =:= File]) of [] -> []; @@ -1289,7 +1301,7 @@ message_compare(T, T) -> message_compare(T1, T2) -> ln(T1) =:= T2. -%% Replaces locations like {Line,Column} with Line. +%% Replaces locations like {Line,Column} with Line. ln({warnings,L}) -> {warnings,ln0(L)}; ln({errors,EL,WL}) -> diff --git a/lib/stdlib/test/erl_lint_SUITE.erl b/lib/stdlib/test/erl_lint_SUITE.erl index 8581b496aa..01f494ee38 100644 --- a/lib/stdlib/test/erl_lint_SUITE.erl +++ b/lib/stdlib/test/erl_lint_SUITE.erl @@ -1784,6 +1784,9 @@ otp_5362(Config) when is_list(Config) -> {15,erl_lint,{undefined_field,ok,nix}}, {16,erl_lint,{field_name_is_variable,ok,'Var'}}]}}, + %% Nowarn_bif_clash has changed behaviour as local functions + %% nowdays supersede auto-imported BIFs, why nowarn_bif_clash in itself generates an error + %% (OTP-8579) /PaN {otp_5362_4, <<"-compile(nowarn_deprecated_function). -compile(nowarn_bif_clash). @@ -1795,9 +1798,8 @@ otp_5362(Config) when is_list(Config) -> warn_deprecated_function, warn_bif_clash]}, {error, - [{5,erl_lint,{call_to_redefined_bif,{spawn,1}}}], - [{3,erl_lint,{redefine_bif,{spawn,1}}}, - {4,erl_lint,{deprecated,{erlang,hash,2},{erlang,phash2,2}, + [{5,erl_lint,{call_to_redefined_old_bif,{spawn,1}}}], + [{4,erl_lint,{deprecated,{erlang,hash,2},{erlang,phash2,2}, "in a future release"}}]}}, {otp_5362_5, @@ -1808,8 +1810,8 @@ otp_5362(Config) when is_list(Config) -> spawn(A). ">>, {[nowarn_unused_function]}, - {warnings, - [{3,erl_lint,{redefine_bif,{spawn,1}}}]}}, + {errors, + [{2,erl_lint,disallowed_nowarn_bif_clash}],[]}}, %% The special nowarn_X are not affected by general warn_X. {otp_5362_6, @@ -1822,8 +1824,8 @@ otp_5362(Config) when is_list(Config) -> {[nowarn_unused_function, warn_deprecated_function, warn_bif_clash]}, - {warnings, - [{3,erl_lint,{redefine_bif,{spawn,1}}}]}}, + {errors, + [{2,erl_lint,disallowed_nowarn_bif_clash}],[]}}, {otp_5362_7, <<"-export([spawn/1]). @@ -1838,7 +1840,9 @@ otp_5362(Config) when is_list(Config) -> spawn(A). ">>, {[nowarn_unused_function]}, - {error,[{4,erl_lint,{bad_nowarn_bif_clash,{spawn,2}}}], + {error,[{3,erl_lint,disallowed_nowarn_bif_clash}, + {4,erl_lint,disallowed_nowarn_bif_clash}, + {4,erl_lint,{bad_nowarn_bif_clash,{spawn,2}}}], [{5,erl_lint,{bad_nowarn_deprecated_function,{3,hash,-1}}}, {5,erl_lint,{bad_nowarn_deprecated_function,{erlang,hash,-1}}}, {5,erl_lint,{bad_nowarn_deprecated_function,{{a,b,c},hash,-1}}}]} @@ -1865,7 +1869,21 @@ otp_5362(Config) when is_list(Config) -> t() -> #a{}. ">>, {[]}, - []} + []}, + + {otp_5362_10, + <<"-compile({nowarn_deprecated_function,{erlang,hash,2}}). + -compile({nowarn_bif_clash,{spawn,1}}). + -import(x,[spawn/1]). + spin(A) -> + erlang:hash(A, 3000), + spawn(A). + ">>, + {[nowarn_unused_function, + warn_deprecated_function, + warn_bif_clash]}, + {errors, + [{2,erl_lint,disallowed_nowarn_bif_clash}],[]}} ], @@ -2389,9 +2407,9 @@ bif_clash(Config) when is_list(Config) -> N. ">>, [], - {errors,[{2,erl_lint,{call_to_redefined_bif,{size,1}}}],[]}}, + {errors,[{2,erl_lint,{call_to_redefined_old_bif,{size,1}}}],[]}}, - %% Verify that (some) warnings can be turned off. + %% Verify that warnings can not be turned off in the old way. {clash2, <<"-export([t/1,size/1]). t(X) -> @@ -2400,17 +2418,189 @@ bif_clash(Config) when is_list(Config) -> size({N,_}) -> N. - %% My own abs/1 function works on lists too. - %% Unfortunately, it is not exported, so there will - %% be a warning that can't be turned off. + %% My own abs/1 function works on lists too. From R14 this really works. abs([H|T]) when $a =< H, H =< $z -> [H-($a-$A)|abs(T)]; abs([H|T]) -> [H|abs(T)]; abs([]) -> []; abs(X) -> erlang:abs(X). ">>, - {[nowarn_bif_clash]}, - {warnings,[{11,erl_lint,{redefine_bif,{abs,1}}}, - {11,erl_lint,{unused_function,{abs,1}}}]}}], + {[nowarn_unused_function,nowarn_bif_clash]}, + {errors,[{erl_lint,disallowed_nowarn_bif_clash}],[]}}, + %% As long as noone calls an overridden BIF, it's totally OK + {clash3, + <<"-export([size/1]). + size({N,_}) -> + N; + size(X) -> + erlang:size(X). + ">>, + [], + []}, + %% But this is totally wrong - meaning of the program changed in R14, so this is an error + {clash4, + <<"-export([size/1]). + size({N,_}) -> + N; + size(X) -> + size(X). + ">>, + [], + {errors,[{5,erl_lint,{call_to_redefined_old_bif,{size,1}}}],[]}}, + %% For a post R14 bif, its only a warning + {clash5, + <<"-export([binary_part/2]). + binary_part({B,_},{X,Y}) -> + binary_part(B,{X,Y}); + binary_part(B,{X,Y}) -> + binary:part(B,X,Y). + ">>, + [], + {warnings,[{3,erl_lint,{call_to_redefined_bif,{binary_part,2}}}]}}, + %% If you really mean to call yourself here, you can "unimport" size/1 + {clash6, + <<"-export([size/1]). + -compile({no_auto_import,[size/1]}). + size([]) -> + 0; + size({N,_}) -> + N; + size([_|T]) -> + 1+size(T). + ">>, + [], + []}, + %% Same for the post R14 autoimport warning + {clash7, + <<"-export([binary_part/2]). + -compile({no_auto_import,[binary_part/2]}). + binary_part({B,_},{X,Y}) -> + binary_part(B,{X,Y}); + binary_part(B,{X,Y}) -> + binary:part(B,X,Y). + ">>, + [], + []}, + %% but this doesn't mean the local function is allowed in a guard... + {clash8, + <<"-export([x/1]). + -compile({no_auto_import,[binary_part/2]}). + x(X) when binary_part(X,{1,2}) =:= <<1,2>> -> + hej. + binary_part({B,_},{X,Y}) -> + binary_part(B,{X,Y}); + binary_part(B,{X,Y}) -> + binary:part(B,X,Y). + ">>, + [], + {errors,[{3,erl_lint,illegal_guard_expr}],[]}}, + %% no_auto_import is not like nowarn_bif_clash, it actually removes the autoimport + {clash9, + <<"-export([x/1]). + -compile({no_auto_import,[binary_part/2]}). + x(X) -> + binary_part(X,{1,2}) =:= <<1,2>>. + ">>, + [], + {errors,[{4,erl_lint,{undefined_function,{binary_part,2}}}],[]}}, + %% but we could import it again... + {clash10, + <<"-export([x/1]). + -compile({no_auto_import,[binary_part/2]}). + -import(erlang,[binary_part/2]). + x(X) -> + binary_part(X,{1,2}) =:= <<1,2>>. + ">>, + [], + []}, + %% and actually use it in a guard... + {clash11, + <<"-export([x/1]). + -compile({no_auto_import,[binary_part/2]}). + -import(erlang,[binary_part/2]). + x(X) when binary_part(X,{0,1}) =:= <<0>> -> + binary_part(X,{1,2}) =:= <<1,2>>. + ">>, + [], + []}, + %% but for non-obvious historical reasons, imported functions cannot be used in + %% fun construction without the module name... + {clash12, + <<"-export([x/1]). + -compile({no_auto_import,[binary_part/2]}). + -import(erlang,[binary_part/2]). + x(X) when binary_part(X,{0,1}) =:= <<0>> -> + binary_part(X,{1,2}) =:= fun binary_part/2. + ">>, + [], + {errors,[{5,erl_lint,{undefined_function,{binary_part,2}}}],[]}}, + %% Not from erlang and not from anywhere else + {clash13, + <<"-export([x/1]). + -compile({no_auto_import,[binary_part/2]}). + -import(x,[binary_part/2]). + x(X) -> + binary_part(X,{1,2}) =:= fun binary_part/2. + ">>, + [], + {errors,[{5,erl_lint,{undefined_function,{binary_part,2}}}],[]}}, + %% ...while real auto-import is OK. + {clash14, + <<"-export([x/1]). + x(X) when binary_part(X,{0,1}) =:= <<0>> -> + binary_part(X,{1,2}) =:= fun binary_part/2. + ">>, + [], + []}, + %% Import directive clashing with old bif is an error, regardless of if it's called or not + {clash15, + <<"-export([x/1]). + -import(x,[abs/1]). + x(X) -> + binary_part(X,{1,2}). + ">>, + [], + {errors,[{2,erl_lint,{redefine_old_bif_import,{abs,1}}}],[]}}, + %% For a new BIF, it's only a warning + {clash16, + <<"-export([x/1]). + -import(x,[binary_part/3]). + x(X) -> + abs(X). + ">>, + [], + {warnings,[{2,erl_lint,{redefine_bif_import,{binary_part,3}}}]}}, + %% And, you cannot redefine already imported things that aren't auto-imported + {clash17, + <<"-export([x/1]). + -import(x,[binary_port/3]). + -import(y,[binary_port/3]). + x(X) -> + abs(X). + ">>, + [], + {errors,[{3,erl_lint,{redefine_import,{{binary_port,3},x}}}],[]}}, + %% Not with local functions either + {clash18, + <<"-export([x/1]). + -import(x,[binary_port/3]). + binary_port(A,B,C) -> + binary_part(A,B,C). + x(X) -> + abs(X). + ">>, + [], + {errors,[{3,erl_lint,{define_import,{binary_port,3}}}],[]}}, + %% Like clash8: Dont accept a guard if it's explicitly module-name called either + {clash19, + <<"-export([binary_port/3]). + -compile({no_auto_import,[binary_part/3]}). + -import(x,[binary_part/3]). + binary_port(A,B,C) when x:binary_part(A,B,C) -> + binary_part(A,B,C+1). + ">>, + [], + {errors,[{4,erl_lint,illegal_guard_expr}],[]}} + ], ?line [] = run(Config, Ts), ok. diff --git a/lib/stdlib/test/erl_pp_SUITE.erl b/lib/stdlib/test/erl_pp_SUITE.erl index 66730b7b94..c57541fba9 100644 --- a/lib/stdlib/test/erl_pp_SUITE.erl +++ b/lib/stdlib/test/erl_pp_SUITE.erl @@ -46,7 +46,7 @@ neg_indent/1, tickets/1, otp_6321/1, otp_6911/1, otp_6914/1, otp_8150/1, otp_8238/1, - otp_8473/1, otp_8522/1, otp_8567/1]). + otp_8473/1, otp_8522/1, otp_8567/1, otp_8664/1]). %% Internal export. -export([ehook/6]). @@ -765,7 +765,7 @@ neg_indent(Config) when is_list(Config) -> tickets(suite) -> [otp_6321, otp_6911, otp_6914, otp_8150, otp_8238, otp_8473, otp_8522, - otp_8567]. + otp_8567, otp_8664]. otp_6321(doc) -> "OTP_6321. Bug fix of exprs()."; @@ -995,6 +995,38 @@ otp_8567(Config) when is_list(Config) -> ok. +otp_8664(doc) -> + "OTP_8664. Types with integer expressions."; +otp_8664(suite) -> []; +otp_8664(Config) when is_list(Config) -> + FileName = filename('otp_8664.erl', Config), + C1 = <<"-module(otp_8664).\n" + "-export([t/0]).\n" + "-define(A, -3).\n" + "-define(B, (?A*(-1 band (((2)))))).\n" + "-type t1() :: ?B | ?A.\n" + "-type t2() :: ?B-1 .. -?B.\n" + "-type t3() :: 9 band (8 - 3) | 1+2 | 5 band 3.\n" + "-type b1() :: <<_:_*(3-(-1))>>\n" + " | <<_:(-(?B))>>\n" + " | <<_:4>>.\n" + "-type u() :: 1 .. 2 | 3.. 4 | (8-3) ..6 | 5+0..6.\n" + "-type t() :: t1() | t2() | t3() | b1() | u().\n" + "-spec t() -> t().\n" + "t() -> 3.\n">>, + ?line ok = file:write_file(FileName, C1), + ?line {ok, _, []} = compile:file(FileName, [return]), + + C2 = <<"-module(otp_8664).\n" + "-export([t/0]).\n" + "-spec t() -> 9 and 4.\n" + "t() -> 0.\n">>, + ?line ok = file:write_file(FileName, C2), + ?line {error,[{_,[{3,erl_lint,{type_syntax,integer}}]}],_} = + compile:file(FileName, [return]), + + ok. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% compile(Config, Tests) -> diff --git a/lib/stdlib/test/erl_scan_SUITE.erl b/lib/stdlib/test/erl_scan_SUITE.erl index afeb67eeb1..32eb97bc92 100644 --- a/lib/stdlib/test/erl_scan_SUITE.erl +++ b/lib/stdlib/test/erl_scan_SUITE.erl @@ -185,7 +185,7 @@ reserved_words() -> 'andalso', 'orelse', 'end', 'fun', 'if', 'let', 'of', 'query', 'receive', 'when', 'bnot', 'not', 'div', 'rem', 'band', 'and', 'bor', 'bxor', 'bsl', 'bsr', - 'or', 'xor'] , + 'or', 'xor'], [begin ?line {RW, true} = {RW, erl_scan:reserved_word(RW)}, S = atom_to_list(RW), @@ -244,6 +244,9 @@ punctuations() -> {'\\',1},{'^',1},{'`',1},{'~',1}], ?line test_string("#&*+/:<>?@\\^`~", PTs2), + ?line test_string(".. ", [{'..',1}]), + ?line test("1 .. 2"), + ?line test_string("...", [{'...',1}]), ok. comments() -> diff --git a/lib/stdlib/test/qlc_SUITE.erl b/lib/stdlib/test/qlc_SUITE.erl index aa12ed57da..e21de8770a 100644 --- a/lib/stdlib/test/qlc_SUITE.erl +++ b/lib/stdlib/test/qlc_SUITE.erl @@ -3184,7 +3184,9 @@ lookup2(Config) when is_list(Config) -> [] = qlc:e(Q), false = lookup_keys(Q) end, [{1,b},{2,3}])">>, - {warnings,[{{3,48},qlc,nomatch_filter}]}}, + {warnings,[{2,sys_core_fold,nomatch_guard}, + {3,qlc,nomatch_filter}, + {3,sys_core_fold,{eval_failure,badarg}}]}}, <<"etsc(fun(E) -> Q = qlc:q([X || {X} <- ets:table(E), element(1,{X}) =:= 1]), diff --git a/lib/syntax_tools/src/erl_comment_scan.erl b/lib/syntax_tools/src/erl_comment_scan.erl index e2c6976a2b..108ab3bffd 100644 --- a/lib/syntax_tools/src/erl_comment_scan.erl +++ b/lib/syntax_tools/src/erl_comment_scan.erl @@ -26,6 +26,7 @@ -export([file/1, join_lines/1, scan_lines/1, string/1]). +-export_type([comment/0]). %% ===================================================================== diff --git a/lib/syntax_tools/src/erl_recomment.erl b/lib/syntax_tools/src/erl_recomment.erl index 145bbc6f37..94e760dad7 100644 --- a/lib/syntax_tools/src/erl_recomment.erl +++ b/lib/syntax_tools/src/erl_recomment.erl @@ -486,7 +486,7 @@ build_tree(Node) -> %% Include L, while preserving Min =< Max. tree_node(minpos(L, Min), - max(L, Max), + erlang:max(L, Max), erl_syntax:type(Node), erl_syntax:get_attrs(Node), Subtrees) @@ -507,7 +507,7 @@ build_list(Ts) -> build_list([T | Ts], Min, Max, Ack) -> Node = build_tree(T), Min1 = minpos(node_min(Node), Min), - Max1 = max(node_max(Node), Max), + Max1 = erlang:max(node_max(Node), Max), build_list(Ts, Min1, Max1, [Node | Ack]); build_list([], Min, Max, Ack) -> list_node(Min, Max, lists:reverse(Ack)). @@ -518,7 +518,7 @@ build_list_list(Ls) -> build_list_list([L | Ls], Min, Max, Ack) -> Node = build_list(L), Min1 = minpos(node_min(Node), Min), - Max1 = max(node_max(Node), Max), + Max1 = erlang:max(node_max(Node), Max), build_list_list(Ls, Min1, Max1, [Node | Ack]); build_list_list([], Min, Max, Ack) -> {lists:reverse(Ack), Min, Max}. @@ -723,9 +723,6 @@ tree_node_attrs(#tree{attrs = Attrs}) -> %% Just the generic "maximum" function -max(X, Y) when X > Y -> X; -max(_, Y) -> Y. - %% Return the least positive integer of X and Y, or zero if none of them %% are positive. (This is necessary for computing minimum source line %% numbers, since zero (or negative) numbers may occur, but they diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index 9a2967d550..a40bf83c5a 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -309,6 +309,7 @@ data/1, is_tree/1]). +-export_type([forms/0, syntaxTree/0, syntaxTreeAttributes/0]). %% ===================================================================== %% IMPLEMENTATION NOTES: diff --git a/lib/syntax_tools/src/erl_syntax_lib.erl b/lib/syntax_tools/src/erl_syntax_lib.erl index 5c4e074488..4808971a59 100644 --- a/lib/syntax_tools/src/erl_syntax_lib.erl +++ b/lib/syntax_tools/src/erl_syntax_lib.erl @@ -46,6 +46,8 @@ new_variable_names/2, new_variable_names/3, strip_comments/1, to_comment/1, to_comment/2, to_comment/3, variables/1]). +-export_type([info_pair/0]). + %% ===================================================================== -type ordset(X) :: [X]. % XXX: TAKE ME OUT @@ -400,10 +402,7 @@ new_variable_name(N, R, _T, F, S) -> %% implementation of `sets'. start_range(S) -> - max(sets:size(S) * ?START_RANGE_FACTOR, ?MINIMUM_RANGE). - -max(X, Y) when X > Y -> X; -max(_, Y) -> Y. + erlang:max(sets:size(S) * ?START_RANGE_FACTOR, ?MINIMUM_RANGE). %% The previous number might or might not be used to compute the %% next number to be tried. It is currently not used. diff --git a/lib/syntax_tools/src/prettypr.erl b/lib/syntax_tools/src/prettypr.erl index 1868f63e54..c13fa30998 100644 --- a/lib/syntax_tools/src/prettypr.erl +++ b/lib/syntax_tools/src/prettypr.erl @@ -48,6 +48,8 @@ nest/2, par/1, par/2, sep/1, text/1, null_text/1, text_par/1, text_par/2]). +-export_type([document/0]). + %% --------------------------------------------------------------------- -type deep_string() :: [char() | deep_string()]. diff --git a/lib/tools/emacs/Makefile b/lib/tools/emacs/Makefile index 0028df247c..8533488463 100644 --- a/lib/tools/emacs/Makefile +++ b/lib/tools/emacs/Makefile @@ -42,6 +42,7 @@ EMACS_FILES= \ erlang_appwiz \ erlang-start \ erlang-eunit \ + erlang-flymake \ erlang README_FILES= README diff --git a/lib/tools/emacs/README b/lib/tools/emacs/README index ca068d04c4..cc107dcd41 100644 --- a/lib/tools/emacs/README +++ b/lib/tools/emacs/README @@ -42,7 +42,14 @@ Files\erl-<Ver>: (setq erlang-root-dir "C:/Program Files/erl<Ver>") (setq exec-path (cons "C:/Program Files/erl<Ver>/bin" exec-path)) (require 'erlang-start) - +Miscellaneous addons +-------------------- + +In order to check erlang source code on the fly, add the following +line to your .emacs file (after erlang-start, see above). See +erlang-flymake.el for more information on how to customize the syntax +check. + (require 'erlang-flymake) diff --git a/lib/tools/emacs/erlang-eunit.el b/lib/tools/emacs/erlang-eunit.el index 970afe2e9f..f2c0db67dd 100644 --- a/lib/tools/emacs/erlang-eunit.el +++ b/lib/tools/emacs/erlang-eunit.el @@ -23,8 +23,22 @@ (eval-when-compile (require 'cl)) -(defvar erlang-eunit-separate-src-and-test-directories t - "*Whether or not to keep source and EUnit test files in separate directories") +(defvar erlang-eunit-src-candidate-dirs '("../src" ".") + "*Name of directories which to search for source files matching +an EUnit test file. The first directory in the list will be used, +if there is no match.") + +(defvar erlang-eunit-test-candidate-dirs '("../test" ".") + "*Name of directories which to search for EUnit test files matching +a source file. The first directory in the list will be used, +if there is no match.") + +(defvar erlang-eunit-autosave nil + "*Set to non-nil to automtically save unsaved buffers before running tests. +This is useful, reducing the save-compile-load-test cycle to one keychord.") + +(defvar erlang-eunit-recent-info '((mode . nil) (module . nil) (test . nil) (cover . nil)) + "Info about the most recent running of an EUnit test representation.") ;;; ;;; Switch between src/EUnit test buffers @@ -44,7 +58,6 @@ buffer and vice versa" "Open the EUnit test file which corresponds to a src file" (find-file-other-window (erlang-eunit-test-filename src-file-path))) - ;;; ;;; Open the src file which corresponds to the an EUnit test file ;;; @@ -55,37 +68,55 @@ buffer and vice versa" ;;; Return the name and path of the EUnit test file ;;, (input may be either the source filename itself or the EUnit test filename) (defun erlang-eunit-test-filename (file-path) - (erlang-eunit-rewrite-filename file-path "test" "_tests")) + (if (erlang-eunit-test-file-p file-path) + file-path + (erlang-eunit-rewrite-filename file-path erlang-eunit-test-candidate-dirs))) ;;; Return the name and path of the source file ;;, (input may be either the source filename itself or the EUnit test filename) (defun erlang-eunit-src-filename (file-path) - (erlang-eunit-rewrite-filename file-path "src" "")) + (if (erlang-eunit-src-file-p file-path) + file-path + (erlang-eunit-rewrite-filename file-path erlang-eunit-src-candidate-dirs))) ;;; Rewrite a filename from the src or test filename to the other -(defun erlang-eunit-rewrite-filename (orig-file-path dest-dirname dest-suffix) - (let* ((root-dir-name (erlang-eunit-file-root-dir-name orig-file-path)) - (src-module-name (erlang-eunit-source-module-name orig-file-path)) - (dest-base-name (concat src-module-name dest-suffix ".erl")) - (dest-dir-name-1 (file-name-directory orig-file-path)) - (dest-dir-name-2 (filename-join root-dir-name dest-dirname)) - (dest-file-name-1 (filename-join dest-dir-name-1 dest-base-name)) - (dest-file-name-2 (filename-join dest-dir-name-2 dest-base-name))) - ;; This function tries to be a bit intelligent: - ;; * if there already is a test (or source) file in the same - ;; directory as a source (or test) file, it'll be picked - ;; * if there already is a test (or source) file in a separate - ;; test (or src) directory, it'll be picked - ;; * otherwise it'll resort to whatever alternative (same or - ;; separate directories) that the user has chosen - (cond ((file-readable-p dest-file-name-1) - dest-file-name-1) - ((file-readable-p dest-file-name-2) - dest-file-name-2) - (erlang-eunit-separate-src-and-test-directories - dest-file-name-2) - (t - dest-file-name-1)))) +(defun erlang-eunit-rewrite-filename (orig-file-path candidate-dirs) + (or (erlang-eunit-locate-buddy orig-file-path candidate-dirs) + (erlang-eunit-buddy-file-path orig-file-path (car candidate-dirs)))) + +;;; Search for a file's buddy file (a source file's EUnit test file, +;;; or an EUnit test file's source file) in a list of candidate +;;; directories. +(defun erlang-eunit-locate-buddy (orig-file-path candidate-dirs) + (when candidate-dirs + (let ((buddy-file-path (erlang-eunit-buddy-file-path + orig-file-path + (car candidate-dirs)))) + (if (file-readable-p buddy-file-path) + buddy-file-path + (erlang-eunit-locate-buddy orig-file-path (cdr candidate-dirs)))))) + +(defun erlang-eunit-buddy-file-path (orig-file-path buddy-dir-name) + (let* ((orig-dir-name (file-name-directory orig-file-path)) + (buddy-dir-name (file-truename + (filename-join orig-dir-name buddy-dir-name))) + (buddy-base-name (erlang-eunit-buddy-basename orig-file-path))) + (filename-join buddy-dir-name buddy-base-name))) + +;;; Return the basename of the buddy file: +;;; /tmp/foo/src/x.erl --> x_tests.erl +;;; /tmp/foo/test/x_tests.erl --> x.erl +(defun erlang-eunit-buddy-basename (file-path) + (let ((src-module-name (erlang-eunit-source-module-name file-path))) + (cond + ((erlang-eunit-src-file-p file-path) + (concat src-module-name "_tests.erl")) + ((erlang-eunit-test-file-p file-path) + (concat src-module-name ".erl"))))) + +;;; Checks whether a file is a source file or not +(defun erlang-eunit-src-file-p (file-path) + (not (erlang-eunit-test-file-p file-path))) ;;; Checks whether a file is a EUnit test file or not (defun erlang-eunit-test-file-p (file-path) @@ -96,11 +127,10 @@ buffer and vice versa" ;;; /tmp/foo/test/x_tests.erl --> x (defun erlang-eunit-source-module-name (file-path) (interactive) - (let* ((file-name (file-name-nondirectory file-path)) - (base-name (file-name-sans-extension file-name))) - (if (string-match "^\\(.+\\)_tests$" base-name) - (substring base-name (match-beginning 1) (match-end 1)) - base-name))) + (let ((module-name (erlang-eunit-module-name file-path))) + (if (string-match "^\\(.+\\)_tests$" module-name) + (substring module-name (match-beginning 1) (match-end 1)) + module-name))) ;;; Return the module name of the file ;;; /tmp/foo/src/x.erl --> x @@ -109,18 +139,6 @@ buffer and vice versa" (interactive) (file-name-sans-extension (file-name-nondirectory file-path))) -;;; Return the directory name which is common to both src and test -;;; /tmp/foo/src/x.erl --> /tmp/foo -;;; /tmp/foo/test/x_tests.erl --> /tmp/foo -(defun erlang-eunit-file-root-dir-name (file-path) - (erlang-eunit-dir-parent-dirname (file-name-directory file-path))) - -;;; Return the parent directory name of a directory -;;; /tmp/foo/ --> /tmp -;;; /tmp/foo --> /tmp -(defun erlang-eunit-dir-parent-dirname (dir-name) - (file-name-directory (directory-file-name dir-name))) - ;;; Older emacsen don't have string-match-p. (defun erlang-eunit-string-match-p (regexp string &optional start) (if (fboundp 'string-match-p) ;; appeared in emacs 23 @@ -135,12 +153,28 @@ buffer and vice versa" (concat dir file) (concat dir "/" file))) +;;; Get info about the most recent running of EUnit +(defun erlang-eunit-recent (key) + (cdr (assq key erlang-eunit-recent-info))) + +;;; Record info about the most recent running of EUnit +;;; Known modes are 'module-mode and 'test-mode +(defun erlang-eunit-record-recent (mode module test) + (setcdr (assq 'mode erlang-eunit-recent-info) mode) + (setcdr (assq 'module erlang-eunit-recent-info) module) + (setcdr (assq 'test erlang-eunit-recent-info) test)) + +;;; Record whether the most recent running of EUnit included cover +;;; compilation +(defun erlang-eunit-record-recent-compile (under-cover) + (setcdr (assq 'cover erlang-eunit-recent-info) under-cover)) + ;;; Determine options for EUnit. (defun erlang-eunit-opts () (if current-prefix-arg ", [verbose]" "")) ;;; Determine current test function -(defun erlang-eunit-test-name () +(defun erlang-eunit-current-test () (save-excursion (erlang-end-of-function 1) (erlang-beginning-of-function 1) @@ -152,45 +186,125 @@ buffer and vice versa" (defun erlang-eunit-test-generator-p (test-name) (if (erlang-eunit-string-match-p "^\\(.+\\)_test_$" test-name) t nil)) -;;; Run the current EUnit test -(defun erlang-eunit-run-current-test () - (let* ((module-name (erlang-add-quotes-if-needed - (erlang-eunit-module-name buffer-file-name))) - (test-name (erlang-eunit-test-name)) - (command - (cond ((erlang-eunit-simple-test-p test-name) - (format "eunit:test({%s, %s}%s)." - module-name test-name (erlang-eunit-opts))) - ((erlang-eunit-test-generator-p test-name) - (format "eunit:test({generator, %s, %s}%s)." - module-name test-name (erlang-eunit-opts))) - (t (format "%% WARNING: '%s' is not a test function" test-name))))) +;;; Run one EUnit test +(defun erlang-eunit-run-test (module-name test-name) + (let ((command + (cond ((erlang-eunit-simple-test-p test-name) + (format "eunit:test({%s, %s}%s)." + module-name test-name (erlang-eunit-opts))) + ((erlang-eunit-test-generator-p test-name) + (format "eunit:test({generator, %s, %s}%s)." + module-name test-name (erlang-eunit-opts))) + (t (format "%% WARNING: '%s' is not a test function" test-name))))) + (erlang-eunit-record-recent 'test-mode module-name test-name) (erlang-eunit-inferior-erlang-send-command command))) ;;; Run EUnit tests for the current module -(defun erlang-eunit-run-module-tests () - (let* ((module-name (erlang-add-quotes-if-needed - (erlang-eunit-source-module-name buffer-file-name))) - (command (format "eunit:test(%s%s)." module-name (erlang-eunit-opts)))) +(defun erlang-eunit-run-module-tests (module-name) + (let ((command (format "eunit:test(%s%s)." module-name (erlang-eunit-opts)))) + (erlang-eunit-record-recent 'module-mode module-name nil) (erlang-eunit-inferior-erlang-send-command command))) +(defun erlang-eunit-compile-and-run-recent () + "Compile the source and test files and repeat the most recent EUnit test run. + +With prefix arg, compiles for debug and runs tests with the verbose flag set." + (interactive) + (case (erlang-eunit-recent 'mode) + ('test-mode + (erlang-eunit-compile-and-test + 'erlang-eunit-run-test (list (erlang-eunit-recent 'module) + (erlang-eunit-recent 'test)))) + ('module-mode + (erlang-eunit-compile-and-test + 'erlang-eunit-run-module-tests (list (erlang-eunit-recent 'module)) + (erlang-eunit-recent 'cover))) + (t (error "EUnit has not yet been run. Please run a test first.")))) + +(defun erlang-eunit-cover-compile () + "Cover compile current module." + (interactive) + (let* ((erlang-compile-extra-opts + (append (list 'debug_info) erlang-compile-extra-opts)) + (module-name + (erlang-add-quotes-if-needed + (erlang-eunit-module-name buffer-file-name))) + (compile-command + (format "cover:compile_beam(%s)." module-name))) + (erlang-compile) + (if (erlang-eunit-last-compilation-successful-p) + (erlang-eunit-inferior-erlang-send-command compile-command)))) + +(defun erlang-eunit-analyze-coverage () + "Analyze the data collected by cover tool for the module in the +current buffer. + +Assumes that the module has been cover compiled prior to this +call. This function will do two things: print the number of +covered and uncovered functions in the erlang shell and display a +new buffer called *<module name> coverage* which shows the source +code along with the coverage analysis results." + (interactive) + (let* ((module-name (erlang-add-quotes-if-needed + (erlang-eunit-module-name buffer-file-name))) + (tmp-filename (make-temp-file "cover")) + (analyze-command (format "cover:analyze_to_file(%s, \"%s\"). " + module-name tmp-filename)) + (buf-name (format "*%s coverage*" module-name))) + (erlang-eunit-inferior-erlang-send-command analyze-command) + ;; The purpose of the following snippet is to get the result of the + ;; analysis from a file into a new buffer (or an old, if one with + ;; the specified name already exists). Also we want the erlang-mode + ;; *and* view-mode to be enabled. + (save-excursion + (let ((buf (get-buffer-create (format "*%s coverage*" module-name)))) + (set-buffer buf) + (setq buffer-read-only nil) + (insert-file-contents tmp-filename nil nil nil t) + (if (= (buffer-size) 0) + (kill-buffer buf) + ;; FIXME: this would be a good place to enable (emacs-mode) + ;; to get some nice syntax highlighting in the + ;; coverage report, but it doesn't play well with + ;; flymake. Leave it off for now. + (view-buffer buf)))) + (delete-file tmp-filename))) + (defun erlang-eunit-compile-and-run-current-test () "Compile the source and test files and run the current EUnit test. With prefix arg, compiles for debug and runs tests with the verbose flag set." (interactive) - (erlang-eunit-compile-and-test 'erlang-eunit-run-current-test)) + (let ((module-name (erlang-add-quotes-if-needed + (erlang-eunit-module-name buffer-file-name))) + (test-name (erlang-eunit-current-test))) + (erlang-eunit-compile-and-test + 'erlang-eunit-run-test (list module-name test-name)))) (defun erlang-eunit-compile-and-run-module-tests () "Compile the source and test files and run all EUnit tests in the module. With prefix arg, compiles for debug and runs tests with the verbose flag set." (interactive) - (erlang-eunit-compile-and-test 'erlang-eunit-run-module-tests)) + (let ((module-name (erlang-add-quotes-if-needed + (erlang-eunit-source-module-name buffer-file-name)))) + (erlang-eunit-compile-and-test + 'erlang-eunit-run-module-tests (list module-name)))) ;;; Compile source and EUnit test file and finally run EUnit tests for ;;; the current module -(defun erlang-eunit-compile-and-test (run-tests) +(defun erlang-eunit-compile-and-test (test-fun test-args &optional under-cover) + "Compile the source and test files and run the EUnit test suite. + +If under-cover is set to t, the module under test is compile for +code coverage analysis. If under-cover is left out or not set, +coverage analysis is disabled. The result of the code coverage +is both printed to the erlang shell (the number of covered vs +uncovered functions in a module) and written to a buffer called +*<module> coverage* (which shows the source code for the module +and the number of times each line is covered). +With prefix arg, compiles for debug and runs tests with the verbose flag set." + (erlang-eunit-record-recent-compile under-cover) (let ((src-filename (erlang-eunit-src-filename buffer-file-name)) (test-filename (erlang-eunit-test-filename buffer-file-name))) @@ -198,7 +312,7 @@ With prefix arg, compiles for debug and runs tests with the verbose flag set." ;; below, is to ask the question about saving buffers only once, ;; instead of possibly several: one for each file to compile, ;; for instance for both x.erl and x_tests.erl. - (save-some-buffers) + (save-some-buffers erlang-eunit-autosave) (flet ((save-some-buffers (&optional any) nil)) ;; Compilation of the source file is mandatory (the file must @@ -206,23 +320,56 @@ With prefix arg, compiles for debug and runs tests with the verbose flag set." ;; test file on the other hand, is optional, since eunit tests may ;; be placed in the source file instead. Any compilation error ;; will prevent the subsequent steps to be run (hence the `and') - (and (erlang-eunit-compile-file src-filename) + (and (erlang-eunit-compile-file src-filename under-cover) (if (file-readable-p test-filename) (erlang-eunit-compile-file test-filename) t) - (funcall run-tests))))) + (apply test-fun test-args) + (if under-cover + (save-excursion + (set-buffer (find-file-noselect src-filename)) + (erlang-eunit-analyze-coverage))))))) -(defun erlang-eunit-compile-file (file-path) +(defun erlang-eunit-compile-and-run-module-tests-under-cover () + "Compile the source and test files and run the EUnit test suite and measure +code coverage. + +With prefix arg, compiles for debug and runs tests with the verbose flag set." + (interactive) + (let ((module-name (erlang-add-quotes-if-needed + (erlang-eunit-source-module-name buffer-file-name)))) + (erlang-eunit-compile-and-test + 'erlang-eunit-run-module-tests (list module-name) t))) + +(defun erlang-eunit-compile-file (file-path &optional under-cover) (if (file-readable-p file-path) (save-excursion - (set-buffer (find-file-noselect file-path)) - (erlang-compile) - (erlang-eunit-last-compilation-successful-p)) + (set-buffer (find-file-noselect file-path)) + ;; In order to run a code coverage analysis on a + ;; module, we have two options: + ;; + ;; * either compile the module with cover:compile instead of the + ;; regular compiler + ;; + ;; * or first compile the module with the regular compiler (but + ;; *with* debug_info) and then compile it for coverage + ;; analysis using cover:compile_beam. + ;; + ;; We could accomplish the first by changing the + ;; erlang-compile-erlang-function to cover:compile, but there's + ;; a risk that that's used for other purposes. Therefore, a + ;; safer alternative (although with more steps) is to add + ;; debug_info to the list of compiler options and go for the + ;; second alternative. + (if under-cover + (erlang-eunit-cover-compile) + (erlang-compile)) + (erlang-eunit-last-compilation-successful-p)) (let ((msg (format "Could not read %s" file-path))) - (erlang-eunit-inferior-erlang-send-command + (erlang-eunit-inferior-erlang-send-command (format "%% WARNING: %s" msg)) (error msg)))) - + (defun erlang-eunit-last-compilation-successful-p () (save-excursion (set-buffer inferior-erlang-buffer) @@ -231,7 +378,7 @@ With prefix arg, compiles for debug and runs tests with the verbose flag set." (lambda (re) (let ((continue t) (result t)) (while continue ; ignore warnings, stop at errors - (if (re-search-forward re (point-max) t) + (if (re-search-forward re (point-max) t) (if (erlang-eunit-is-compilation-warning) t (setq result nil) @@ -242,7 +389,7 @@ With prefix arg, compiles for debug and runs tests with the verbose flag set." (mapcar (lambda (e) (car e)) erlang-error-regexp-alist)))) (defun erlang-eunit-is-compilation-warning () - (erlang-eunit-string-match-p + (erlang-eunit-string-match-p "[0-9]+: Warning:" (buffer-substring (line-beginning-position) (line-end-position)))) @@ -271,7 +418,11 @@ With prefix arg, compiles for debug and runs tests with the verbose flag set." (defconst erlang-eunit-key-bindings '(("\C-c\C-et" erlang-eunit-toggle-src-and-test-file-other-window) ("\C-c\C-ek" erlang-eunit-compile-and-run-module-tests) - ("\C-c\C-ej" erlang-eunit-compile-and-run-current-test))) + ("\C-c\C-ej" erlang-eunit-compile-and-run-current-test) + ("\C-c\C-el" erlang-eunit-compile-and-run-recent) + ("\C-c\C-ec" erlang-eunit-compile-and-run-module-tests-under-cover) + ("\C-c\C-ev" erlang-eunit-cover-compile) + ("\C-c\C-ea" erlang-eunit-analyze-coverage))) (defun erlang-eunit-add-key-bindings () (dolist (binding erlang-eunit-key-bindings) diff --git a/lib/tools/emacs/erlang-flymake.el b/lib/tools/emacs/erlang-flymake.el new file mode 100644 index 0000000000..bc368e9454 --- /dev/null +++ b/lib/tools/emacs/erlang-flymake.el @@ -0,0 +1,102 @@ +;; erlang-flymake.el +;; +;; Syntax check erlang source code on the fly (integrates with flymake). +;; +;; Start using flymake with erlang by putting the following somewhere +;; in your .emacs file: +;; +;; (require 'erlang-flymake) +;; +;; Flymake is rather eager and does its syntax checks frequently by +;; default and if you are bothered by this, you might want to put the +;; following in your .emacs as well: +;; +;; (erlang-flymake-only-on-save) +;; +;; There are a couple of variables which control the compilation options: +;; * erlang-flymake-get-code-path-dirs-function +;; * erlang-flymake-get-include-dirs-function +;; * erlang-flymake-extra-opts +;; +;; This code is inspired by http://www.emacswiki.org/emacs/FlymakeErlang. + +(require 'flymake) +(eval-when-compile + (require 'cl)) + +(defvar erlang-flymake-command + "erlc" + "The command that will be used to perform the syntax check") + +(defvar erlang-flymake-get-code-path-dirs-function + 'erlang-flymake-get-code-path-dirs + "Return a list of ebin directories to add to the code path.") + +(defvar erlang-flymake-get-include-dirs-function + 'erlang-flymake-get-include-dirs + "Return a list of include directories to add to the compiler options.") + +(defvar erlang-flymake-extra-opts + (list "+warn_obsolete_guard" + "+warn_unused_import" + "+warn_shadow_vars" + "+warn_export_vars" + "+strong_validation" + "+report") + "A list of options that will be passed to the compiler") + +(defun erlang-flymake-only-on-save () + "Trigger flymake only when the buffer is saved (disables syntax +check on newline and when there are no changes)." + (interactive) + ;; There doesn't seem to be a way of disabling this; set to the + ;; largest int available as a workaround (most-positive-fixnum + ;; equates to 8.5 years on my machine, so it ought to be enough ;-) ) + (setq flymake-no-changes-timeout most-positive-fixnum) + (setq flymake-start-syntax-check-on-newline nil)) + + +(defun erlang-flymake-get-code-path-dirs () + (list (concat (erlang-flymake-get-app-dir) "ebin"))) + +(defun erlang-flymake-get-include-dirs () + (list (concat (erlang-flymake-get-app-dir) "include"))) + +(defun erlang-flymake-get-app-dir () + (let ((src-path (file-name-directory (buffer-file-name)))) + (file-name-directory (directory-file-name src-path)))) + +(defun erlang-flymake-init () + (let* ((temp-file + (flet ((flymake-get-temp-dir () (erlang-flymake-temp-dir))) + (flymake-init-create-temp-buffer-copy + 'flymake-create-temp-with-folder-structure))) + (code-dir-opts + (erlang-flymake-flatten + (mapcar (lambda (dir) (list "-pa" dir)) + (funcall erlang-flymake-get-code-path-dirs-function)))) + (inc-dir-opts + (erlang-flymake-flatten + (mapcar (lambda (dir) (list "-I" dir)) + (funcall erlang-flymake-get-include-dirs-function)))) + (compile-opts + (append inc-dir-opts + code-dir-opts + erlang-flymake-extra-opts))) + (list erlang-flymake-command (append compile-opts (list temp-file))))) + +(defun erlang-flymake-temp-dir () + ;; Squeeze the user's name in there in order to make sure that files + ;; for two users who are working on the same computer (like a linux + ;; box) don't collide + (format "%s/flymake-%s" temporary-file-directory user-login-name)) + +(defun erlang-flymake-flatten (list) + (apply #'append list)) + +(add-to-list 'flymake-allowed-file-name-masks + '("\\.erl\\'" erlang-flymake-init)) +(add-hook 'erlang-mode-hook 'flymake-mode) + +(provide 'erlang-flymake) +;; erlang-flymake ends here diff --git a/lib/tools/emacs/erlang.el b/lib/tools/emacs/erlang.el index c31f76025e..91acfdf2b6 100644 --- a/lib/tools/emacs/erlang.el +++ b/lib/tools/emacs/erlang.el @@ -659,24 +659,30 @@ resulting regexp is surrounded by \\_< and \\_>." (eval-and-compile (defconst erlang-guards-regexp (erlang-regexp-opt erlang-guards 'symbols))) - (eval-and-compile (defvar erlang-predefined-types '("any" "arity" + "boolean" "byte" "char" "cons" "deep_string" + "iolist" "maybe_improper_list" + "module" "mfa" "nil" + "neg_integer" "none" "non_neg_integer" "nonempty_list" "nonempty_improper_list" "nonempty_maybe_improper_list" + "no_return" + "pos_integer" "string" + "term" "timeout") "Erlang type specs types")) @@ -2484,9 +2490,10 @@ Value is list (stack token-start token-type in-what)." ((looking-at "\\(of\\)[^_a-zA-Z0-9]") ;; Must handle separately, try X of -> catch (if (and stack (eq (car (car stack)) 'try)) - (let ((try-column (nth 2 (car stack)))) + (let ((try-column (nth 2 (car stack))) + (try-pos (nth 1 (car stack)))) (erlang-pop stack) - (erlang-push (list 'icr token try-column) stack)))) + (erlang-push (list 'icr try-pos try-column) stack)))) ((looking-at "\\(fun\\)[^_a-zA-Z0-9]") ;; Push a new layer if we are defining a `fun' @@ -2747,7 +2754,7 @@ Return nil if inside string, t if in a comment." ;; ;; `after' should be indented to the same level as the ;; corresponding receive. - (cond ((looking-at "\\(after\\|catch\\|of\\)\\($\\|[^_a-zA-Z0-9]\\)") + (cond ((looking-at "\\(after\\|of\\)\\($\\|[^_a-zA-Z0-9]\\)") (nth 2 stack-top)) ((looking-at "when[^_a-zA-Z0-9]") ;; Handling one when part @@ -2766,7 +2773,7 @@ Return nil if inside string, t if in a comment." ((and (eq (car stack-top) '||) (looking-at "\\(]\\|>>\\)[^_a-zA-Z0-9]")) (nth 2 (car (cdr stack)))) ;; Real indentation, where operators create extra indentation etc. - ((memq (car stack-top) '(-> || begin try)) + ((memq (car stack-top) '(-> || try begin)) (if (looking-at "\\(of\\)[^_a-zA-Z0-9]") (nth 2 stack-top) (goto-char (nth 1 stack-top)) @@ -2795,19 +2802,24 @@ Return nil if inside string, t if in a comment." (erlang-caddr (car stack)) 0)) ((looking-at "catch\\($\\|[^_a-zA-Z0-9]\\)") - (if (or (eq (car stack-top) 'try) - (eq (car (car (cdr stack))) 'icr)) - (progn - (if (eq (car stack-top) '->) - (erlang-pop stack)) - (if stack - (erlang-caddr (car stack)) - 0)) - base)) ;; old catch + ;; Are we in a try + (let ((start (if (eq (car stack-top) '->) + (car (cdr stack)) + stack-top))) + (if (null start) nil + (goto-char (nth 1 start))) + (cond ((looking-at "try\\($\\|[^_a-zA-Z0-9]\\)") + (progn + (if (eq (car stack-top) '->) + (erlang-pop stack)) + (if stack + (erlang-caddr (car stack)) + 0))) + (t (erlang-indent-standard indent-point token base 'nil))))) ;; old catch (t (erlang-indent-standard indent-point token base 'nil) )))) - )) + )) ((eq (car stack-top) 'when) (goto-char (nth 1 stack-top)) (if (looking-at "when\\s *\\($\\|%\\)") @@ -2833,27 +2845,32 @@ Return nil if inside string, t if in a comment." (current-column))) ;; Type and Spec indentation ((eq (car stack-top) '::) - (cond ((null erlang-argument-indent) - ;; indent to next column. - (+ 2 (nth 2 stack-top))) - ((looking-at "::[^_a-zA-Z0-9]") - (nth 2 stack-top)) - (t - (let ((start-alternativ (if (looking-at "|") 2 0))) - (goto-char (nth 1 stack-top)) - (- (cond ((looking-at "::\\s *\\($\\|%\\)") - ;; Line ends with :: - (if (eq (car (car (last stack))) 'spec) + (if (looking-at "}") + ;; Closing record definition with types + ;; pop stack and recurse + (erlang-calculate-stack-indent indent-point + (cons (erlang-pop stack) (cdr state))) + (cond ((null erlang-argument-indent) + ;; indent to next column. + (+ 2 (nth 2 stack-top))) + ((looking-at "::[^_a-zA-Z0-9]") + (nth 2 stack-top)) + (t + (let ((start-alternativ (if (looking-at "|") 2 0))) + (goto-char (nth 1 stack-top)) + (- (cond ((looking-at "::\\s *\\($\\|%\\)") + ;; Line ends with :: + (if (eq (car (car (last stack))) 'spec) (+ (erlang-indent-find-preceding-expr 1) erlang-argument-indent) - (+ (erlang-indent-find-preceding-expr 2) - erlang-argument-indent))) - (t - ;; Indent to the same column as the first - ;; argument. - (goto-char (+ 2 (nth 1 stack-top))) - (skip-chars-forward " \t") - (current-column))) start-alternativ))))) + (+ (erlang-indent-find-preceding-expr 2) + erlang-argument-indent))) + (t + ;; Indent to the same column as the first + ;; argument. + (goto-char (+ 2 (nth 1 stack-top))) + (skip-chars-forward " \t") + (current-column))) start-alternativ)))))) ))) (defun erlang-indent-standard (indent-point token base inside-parenthesis) diff --git a/lib/tools/emacs/test.erl.indented b/lib/tools/emacs/test.erl.indented index 1dc976d8dc..2948ccf1b5 100644 --- a/lib/tools/emacs/test.erl.indented +++ b/lib/tools/emacs/test.erl.indented @@ -93,11 +93,27 @@ -type t13() :: maybe_improper_list(integer(), t11()). -type t14() :: [erl_scan:foo() | %% Should be highlighted - non_neg_integer() | nonempty_list() | + term() | + bool() | + byte() | + char() | + non_neg_integer() | nonempty_list() | + pos_integer() | + neg_integer() | + number() | + list() | nonempty_improper_list() | nonempty_maybe_improper_list() | + maybe_improper_list() | string() | iolist() | byte() | + module() | + mfa() | + node() | + timeout() | + no_return() | %% Should not be highlighted nonempty_() | nonlist() | - erl_scan:bar(34, 92) | t13() | m:f(integer() | <<_:_*16>>)]. + erl_scan:bar(34, 92) | t13() | m:f(integer() | <<_:_*16>>)]. + + -type t15() :: {binary(),<<>>,<<_:34>>,<<_:_*42>>, <<_:3,_:_*14>>,<<>>} | [<<>>|<<_:34>>|<<_:16>>| <<_:3,_:_*1472>>|<<_:19,_:_*14>>| <<_:34>>| @@ -172,6 +188,9 @@ f19 = 3 :: integer()|undefined, f5 = 3 :: undefined|integer()}). +-record(state, { + sequence_number = 1 :: integer() + }). highlighting(X) % Function definitions should be highlighted @@ -493,7 +512,9 @@ indent_try_catch() -> file:close(Xfile) end; indent_try_catch() -> - try foo(bar) of + try + foo(bar) + of X when true andalso kalle -> io:format(stdout, "Parsing file ~s, ", @@ -551,14 +572,57 @@ indent_catch() -> C = catch B + float(43.1), - case catch (X) of + case catch foo(X) of + A -> + B + end, + + case + catch foo(X) + of A -> B end, + + case + foo(X) + of + A -> + catch B, + X + end, + try sune of _ -> foo catch _:_ -> baf - end. + end, + + try + sune + of + _ -> + X = 5, + (catch foo(X)), + X + 10 + catch _:_ -> baf + end, + + try + (catch sune) + of + _ -> + catch foo() %% BUGBUG can't handle catch inside try without parentheses + catch _:_ -> + baf + end, + + try + (catch exit()) + catch + _ -> + catch baf() + end, + ok. indent_binary() -> X = lists:foldr(fun(M) -> diff --git a/lib/tools/emacs/test.erl.orig b/lib/tools/emacs/test.erl.orig index feb9e4e5a1..1221c5655e 100644 --- a/lib/tools/emacs/test.erl.orig +++ b/lib/tools/emacs/test.erl.orig @@ -93,11 +93,27 @@ -type t13() :: maybe_improper_list(integer(), t11()). -type t14() :: [erl_scan:foo() | %% Should be highlighted - non_neg_integer() | nonempty_list() | + term() | + bool() | + byte() | + char() | + non_neg_integer() | nonempty_list() | + pos_integer() | + neg_integer() | + number() | + list() | nonempty_improper_list() | nonempty_maybe_improper_list() | + maybe_improper_list() | string() | iolist() | byte() | + module() | + mfa() | + node() | + timeout() | + no_return() | %% Should not be highlighted nonempty_() | nonlist() | -erl_scan:bar(34, 92) | t13() | m:f(integer() | <<_:_*16>>)]. + erl_scan:bar(34, 92) | t13() | m:f(integer() | <<_:_*16>>)]. + + -type t15() :: {binary(),<<>>,<<_:34>>,<<_:_*42>>, <<_:3,_:_*14>>,<<>>} | [<<>>|<<_:34>>|<<_:16>>| <<_:3,_:_*1472>>|<<_:19,_:_*14>>| <<_:34>>| @@ -172,6 +188,9 @@ f18 :: 1 | 2 | 'undefined', f19 = 3 :: integer()|undefined, f5 = 3 :: undefined|integer()}). +-record(state, { + sequence_number = 1 :: integer() + }). highlighting(X) % Function definitions should be highlighted @@ -493,7 +512,9 @@ indent_try_catch() -> file:close(Xfile) end; indent_try_catch() -> - try foo(bar) of + try + foo(bar) + of X when true andalso kalle -> io:format(stdout, "Parsing file ~s, ", @@ -551,14 +572,57 @@ indent_catch() -> C = catch B + float(43.1), - case catch (X) of + case catch foo(X) of A -> B end, + + case + catch foo(X) + of + A -> + B + end, + + case + foo(X) + of + A -> + catch B, + X + end, + try sune of - _ -> foo - catch _:_ -> baf - end. + _ -> foo + catch _:_ -> baf + end, + + try +sune + of + _ -> + X = 5, + (catch foo(X)), + X + 10 + catch _:_ -> baf + end, + + try + (catch sune) + of + _ -> + catch foo() %% BUGBUG can't handle catch inside try without parentheses + catch _:_ -> + baf + end, + + try +(catch exit()) + catch +_ -> + catch baf() + end, + ok. indent_binary() -> X = lists:foldr(fun(M) -> diff --git a/lib/tv/src/tv_io_lib_format.erl b/lib/tv/src/tv_io_lib_format.erl index 5042fd3f9d..e043d9296e 100644 --- a/lib/tv/src/tv_io_lib_format.erl +++ b/lib/tv/src/tv_io_lib_format.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1998-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1998-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% -module(tv_io_lib_format). @@ -188,7 +188,7 @@ indentation([], I) -> I. term(T, none, _Adj, none, _Pad) -> T; term(T, none, Adj, P, Pad) -> term(T, P, Adj, P, Pad); -term(T, F, Adj, none, Pad) -> term(T, F, Adj, min(flat_length(T), F), Pad); +term(T, F, Adj, none, Pad) -> term(T, F, Adj, erlang:min(flat_length(T), F), Pad); term(T, F, Adj, P, Pad) when F >= P -> adjust_error(T, F, Adj, P, Pad). @@ -316,7 +316,7 @@ fwrite_g(Fl, F, Adj, P, Pad) -> string(S, none, _Adj, none, _Pad) -> S; string(S, F, Adj, none, Pad) -> - string(S, F, Adj, min(flat_length(S), F), Pad); + string(S, F, Adj, erlang:min(flat_length(S), F), Pad); string(S, none, _Adj, P, Pad) -> string:left(flatten(S), P, Pad); string(S, F, Adj, P, Pad) when F >= P -> @@ -362,9 +362,6 @@ reverse([H|T], Stack) -> reverse(T, [H|Stack]); reverse([], Stack) -> Stack. -min(L, R) when L < R -> L; -min(_, R) -> R. - %% flatten(List) %% Flatten a list. diff --git a/lib/tv/src/tv_pb.erl b/lib/tv/src/tv_pb.erl index 34db8d0772..78a27185dc 100644 --- a/lib/tv/src/tv_pb.erl +++ b/lib/tv/src/tv_pb.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1997-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% -module(tv_pb). @@ -522,7 +522,7 @@ handle_col_resizing(RbtnId, RealCol, VirtualCol, Xpos, ProcVars) -> get_xdiff(Id, Btn, LastXdiff, LineId, LineXpos, MinAllowedXdiff) -> receive {gs, Id, motion, {resbtn, _RealCol, _VirtCol, _OldXpos}, [NewXdiff | _T]} -> - UsedXdiff = max(MinAllowedXdiff, NewXdiff), + UsedXdiff = erlang:max(MinAllowedXdiff, NewXdiff), gs:config(LineId, [{x, LineXpos + UsedXdiff}]), get_xdiff(Id, Btn, UsedXdiff, LineId, LineXpos, MinAllowedXdiff); {gs, Id, buttonrelease, _Data, [Btn | _T]} -> @@ -658,28 +658,3 @@ update_vbtns(Msg, ProcVars) -> update_keys(Msg, ProcVars) -> #pb_key_info{list_of_keys = KeyList} = Msg, tv_pb_funcs:update_keys(KeyList, ProcVars). - - - - - - - - -%%====================================================================== -%% Function: -%% -%% Return Value: -%% -%% Description: -%% -%% Parameters: -%%====================================================================== - - -max(A, B) when A >= B -> - A; -max(_, B) -> - B. - - diff --git a/lib/tv/src/tv_pg_gridfcns.erl b/lib/tv/src/tv_pg_gridfcns.erl index 809403fd96..ab88e2864f 100644 --- a/lib/tv/src/tv_pg_gridfcns.erl +++ b/lib/tv/src/tv_pg_gridfcns.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1997-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% -module(tv_pg_gridfcns). @@ -98,7 +98,7 @@ init_grid(GridParentId, GridWidth, nof_rows_shown = NofRowsShown }, - NewNofCols = max(length(ColsShown), NofCols), + NewNofCols = erlang:max(length(ColsShown), NofCols), % The GridColWidths list shall contain the current width of each frame. NewColWidths = update_col_widths(ColsShown, ColWidths, FirstColShown, @@ -270,7 +270,7 @@ resize_grid_column(RealCol, VirtualCol, Xdiff, ProcVars) -> lists_as_strings = ListAsStr} = GridP, % Get new width! - Width = min(MaxColWidth, max((lists:nth(VirtualCol, ColWidths) + Xdiff), + Width = erlang:min(MaxColWidth, erlang:max((lists:nth(VirtualCol, ColWidths) + Xdiff), MinColWidth)), % Resize the column. @@ -1336,7 +1336,7 @@ resize_all_grid_columns(RealCol, [ColWidth | Tail], ColFrameIds, MaxColWidth, Mi resize_one_column(RealCol, Width, ColFrameIds, MaxW, MinW) -> - NewWidthOfCol = min(MaxW, max(Width, MinW)), + NewWidthOfCol = erlang:min(MaxW, erlang:max(Width, MinW)), case length(ColFrameIds) of RealCol -> done; @@ -1894,46 +1894,3 @@ extract_ids_for_one_row(N, [ColIds | Tail]) -> %%%--------------------------------------------------------------------- %%% END of functions used to create the grid. %%%--------------------------------------------------------------------- - - - - - -%%====================================================================== -%% Function: -%% -%% Return Value: -%% -%% Description: -%% -%% Parameters: -%%====================================================================== - - -max(A, B) when A > B -> - A; -max(_, B) -> - B. - - - - - - - -%%====================================================================== -%% Function: -%% -%% Return Value: -%% -%% Description: -%% -%% Parameters: -%%====================================================================== - - -min(A, B) when A < B -> - A; -min(_, B) -> - B. - diff --git a/lib/xmerl/doc/src/notes.xml b/lib/xmerl/doc/src/notes.xml index 207f6fdf16..0403fbca27 100644 --- a/lib/xmerl/doc/src/notes.xml +++ b/lib/xmerl/doc/src/notes.xml @@ -50,6 +50,14 @@ Own Id: OTP-8537 </p> </item> + <item> + <p> + An empty element declared as a simpleContent was not properly validated. + </p> + <p> + Own Id: OTP-8599 + </p> + </item> </list> </section> diff --git a/lib/xmerl/src/xmerl_xsd.erl b/lib/xmerl/src/xmerl_xsd.erl index c7bca86205..1aedc9e270 100644 --- a/lib/xmerl/src/xmerl_xsd.erl +++ b/lib/xmerl/src/xmerl_xsd.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2006-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2006-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -2687,13 +2687,16 @@ check_element_type(XML=[E=#xmlElement{name=Name}|Rest], _ -> {error,{error_path(E,Name),?MODULE,{element_bad_match,E,Any,Env}}} end; -check_element_type([],CM,_Env,_Block,_S,Checked) -> +check_element_type([],CM,_Env,_Block,S,Checked) -> %% #schema_complex_type, any, #schema_group, anyType and lists are %% catched above. case CM of + #schema_simple_type{} -> + {NewVal,S2} = check_type(CM,[],unapplied,S), + {NewVal,[],S2}; {simpleType,_} -> - {error,{error_path(Checked,undefined),?MODULE, - {empty_content_not_allowed,CM}}}; + {NewVal,S2} = check_type(CM,[],unapplied,S), + {NewVal,[],S2}; _ -> {error,{error_path(Checked,undefined),?MODULE, {empty_content_not_allowed,CM}}} diff --git a/system/doc/reference_manual/expressions.xml b/system/doc/reference_manual/expressions.xml index b56b8acbf0..714ecccaf6 100644 --- a/system/doc/reference_manual/expressions.xml +++ b/system/doc/reference_manual/expressions.xml @@ -217,12 +217,16 @@ lists:keysearch(Name, 1, List)</code> <p>In the second form of function calls, <c>ExprF(Expr1,...,ExprN)</c>, <c>ExprF</c> must be an atom or evaluate to a fun.</p> + <p>If <c>ExprF</c> is an atom the function is said to be called by - using the <em>implicitly qualified function name</em>. If - <c>ExprF/N</c> is the name of a function explicitly or - automatically imported from module <c>M</c>, then the call is - short for <c>M:ExprF(Expr1,...,ExprN)</c>. Otherwise, - <c>ExprF/N</c> must be a locally defined function. Examples:</p> + using the <em>implicitly qualified function name</em>. If the + function <c>ExprF</c> is locally defined, it is called. + Alternatively if <c>ExprF</c> is explicitly imported from module + <c>M</c>, <c>M:ExprF(Expr1,...,ExprN)</c> is called. If + <c>ExprF</c> is neither declared locally nor explicitly + imported, <c>ExprF</c> must be the name of an automatically + imported BIF. Examples:</p> + <code type="none"> handle(Msg, State) spawn(m, init, [])</code> @@ -238,16 +242,85 @@ Fun2([1,2], [3,4]) fun lists:append/2([1,2], [3,4]) => [1,2,3,4]</code> - <p>To avoid possible ambiguities, the fully qualified function - name must be used when calling a function with the same name as - a BIF, and the compiler does not allow defining a function with - the same name as an explicitly imported function.</p> + <p>Note that when calling a local function, there is a difference - between using the implicitly or fully qualified function name, as - the latter always refers to the latest version of the module. See - <seealso marker="code_loading">Compilation and Code Loading</seealso>.</p> + between using the implicitly or fully qualified function name, as + the latter always refers to the latest version of the module. See + <seealso marker="code_loading">Compilation and Code Loading</seealso>.</p> + <p>See also the chapter about <seealso marker="functions#eval">Function Evaluation</seealso>.</p> + + <section> + <title>Local Function Names Clashing With Auto-imported BIFs</title> + <p>If a local function has the same name as an auto-imported BIF, + the semantics is that implicitly qualified function calls are + directed to the locally defined function, not to the BIF. To avoid + confusion, there is a compiler directive available, + <c>-compile({no_auto_import,[F/A]})</c>, that makes a BIF not + being auto-imported. In certain situations, such a compile-directive + is mandatory.</p> + + <warning><p>Before OTP R14A (ERTS version 5.8), an implicitly + qualified function call to a function having the same name as an + auto-imported BIF always resulted in the BIF being called. In + newer versions of the compiler the local function is instead + called. The change is there to avoid that future additions to the + set of auto-imported BIFs does not silently change the behavior + of old code.</p> + + <p>However, to avoid that old (pre R14) code changed it's + behavior when compiled with OTP version R14A or later, the + following restriction applies: If you override the name of a BIF + that was auto-imported in OTP versions prior to R14A (ERTS version + 5.8) and have an implicitly qualified call to that function in + your code, you either need to explicitly remove the auto-import + using a compiler directive, or replace the call with a fully + qualified function call, otherwise you will get a compilation + error. See example below:</p> </warning> + + <code type="none"> +-export([length/1,f/1]). + +-compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported + +length([]) -> + 0; +length([H|T]) -> + 1 + length(T). %% Calls the local funtion length/1 + +f(X) when erlang:length(X) > 3 -> %% Calls erlang:length/1, + %% which is allowed in guards + long.</code> + + <p>The same logic applies to explicitly imported functions from + other modules as to locally defined functions. To both import a + function from another module and have the function declared in the + module at the same time is not allowed.</p> + + <code type="none"> +-export([f/1]). + +-compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported + +-import(mod,[length/1]). + +f(X) when erlang:length(X) > 33 -> %% Calls erlang:lenght/1, + %% which is allowed in guards + + erlang:length(X); %% Explicit call to erlang:length in body + +f(X) -> + length(X). %% mod:length/1 is called</code> + + + <p>For auto-imported BIFs added to Erlang in release R14A and thereafter, + overriding the name with a local function or explicit import is always + allowed. However, if the <c>-compile({no_auto_import,[F/A])</c> + directive is not used, the compiler will issue a warning whenever + the function is called in the module using the implicitly qualified + function name.</p> + </section> </section> <section> diff --git a/xcomp/erl-xcomp-mips-linux.conf b/xcomp/erl-xcomp-mips-linux.conf new file mode 100644 index 0000000000..3ac057bb4c --- /dev/null +++ b/xcomp/erl-xcomp-mips-linux.conf @@ -0,0 +1,258 @@ +## -*-shell-script-*- +## +## %CopyrightBegin% +## +## Copyright Ericsson AB 2010. All Rights Reserved. +## +## The contents of this file are subject to the Erlang Public License, +## Version 1.1, (the "License"); you may not use this file except in +## compliance with the License. You should have received a copy of the +## Erlang Public License along with this software. If not, it can be +## retrieved online at http://www.erlang.org/. +## +## Software distributed under the License is distributed on an "AS IS" +## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +## the License for the specific language governing rights and limitations +## under the License. +## +## %CopyrightEnd% +## +## File: erl-xcomp-mips-linux.conf +## Author: Matthias Lang +## +## ----------------------------------------------------------------------------- +## When cross compiling Erlang/OTP using `otp_build', copy this file and set +## the variables needed below. Then pass the path to the copy of this file as +## an argument to `otp_build' in the configure stage: +## `otp_build configure --xcomp-conf=<FILE>' +## ----------------------------------------------------------------------------- + +## Note that you cannot define arbitrary variables in a cross compilation +## configuration file. Only the ones listed below will be guaranteed to be +## visible throughout the whole execution of all `configure' scripts. Other +## variables needs to be defined as arguments to `configure' or exported in +## the environment. + +## -- Variables for `otp_build' Only ------------------------------------------- + +## Variables in this section are only used, when configuring Erlang/OTP for +## cross compilation using `$ERL_TOP/otp_build configure'. + +## *NOTE*! These variables currently have *no* effect if you configure using +## the `configure' script directly. + +# * `erl_xcomp_build' - The build system used. This value will be passed as +# `--build=$erl_xcomp_build' argument to the `configure' script. It does +# not have to be a full `CPU-VENDOR-OS' triplet, but can be. The full +# `CPU-VENDOR-OS' triplet will be created by +# `$ERL_TOP/erts/autoconf/config.sub $erl_xcomp_build'. If set to `guess', +# the build system will be guessed using +# `$ERL_TOP/erts/autoconf/config.guess'. +erl_xcomp_build=guess + +# * `erl_xcomp_host' - Cross host/target system to build for. This value will +# be passed as `--host=$erl_xcomp_host' argument to the `configure' script. +# It does not have to be a full `CPU-VENDOR-OS' triplet, but can be. The +# full `CPU-VENDOR-OS' triplet will be created by +# `$ERL_TOP/erts/autoconf/config.sub $erl_xcomp_host'. +erl_xcomp_host=mips-linux + +# * `erl_xcomp_configure_flags' - Extra configure flags to pass to the +# `configure' script. +#erl_xcomp_configure_flags= + +## -- Cross Compiler and Other Tools ------------------------------------------- + +## If the cross compilation tools are prefixed by `<HOST>-' you probably do +## not need to set these variables (where `<HOST>' is what has been passed as +## `--host=<HOST>' argument to `configure'). + +## All variables in this section can also be used when native compiling. + +# * `CC' - C compiler. +#CC= + +# * `CFLAGS' - C compiler flags. +CFLAGS=-Os + +# * `STATIC_CFLAGS' - Static C compiler flags. +#STATIC_CFLAGS= + +# * `CFLAG_RUNTIME_LIBRARY_PATH' - This flag should set runtime library +# search path for the shared libraries. Note that this actually is a +# linker flag, but it needs to be passed via the compiler. +#CFLAG_RUNTIME_LIBRARY_PATH= + +# * `CPP' - C pre-processor. +#CPP= + +# * `CPPFLAGS' - C pre-processor flags. +#CPPFLAGS= + +# * `CXX' - C++ compiler. +#CXX= + +# * `CXXFLAGS' - C++ compiler flags. +#CXXFLAGS= + +# * `LD' - Linker. +#LD= + +# * `LDFLAGS' - Linker flags. +#LDFLAGS= + +# * `LIBS' - Libraries. +#LIBS= + +## -- *D*ynamic *E*rlang *D*river Linking -- + +## *NOTE*! Either set all or none of the `DED_LD*' variables. + +# * `DED_LD' - Linker for Dynamically loaded Erlang Drivers. +#DED_LD= + +# * `DED_LDFLAGS' - Linker flags to use with `DED_LD'. +#DED_LDFLAGS= + +# * `DED_LD_FLAG_RUNTIME_LIBRARY_PATH' - This flag should set runtime library +# search path for shared libraries when linking with `DED_LD'. +#DED_LD_FLAG_RUNTIME_LIBRARY_PATH= + +## -- Large File Support -- + +## *NOTE*! Either set all or none of the `LFS_*' variables. + +# * `LFS_CFLAGS' - Large file support C compiler flags. +#LFS_CFLAGS= + +# * `LFS_LDFLAGS' - Large file support linker flags. +#LFS_LDFLAGS= + +# * `LFS_LIBS' - Large file support libraries. +#LFS_LIBS= + +## -- Other Tools -- + +# * `RANLIB' - `ranlib' archive index tool. +#RANLIB= + +# * `AR' - `ar' archiving tool. +#AR= + +# * `GETCONF' - `getconf' system configuration inspection tool. `getconf' is +# currently used for finding out large file support flags to use, and +# on Linux systems for finding out if we have an NPTL thread library or +# not. +#GETCONF= + +## -- Cross System Root Locations ---------------------------------------------- + +# * `erl_xcomp_sysroot' - The absolute path to the system root of the cross +# compilation environment. Currently, the `crypto', `odbc', `ssh' and +# `ssl' applications need the system root. These applications will be +# skipped if the system root has not been set. The system root might be +# needed for other things too. If this is the case and the system root +# has not been set, `configure' will fail and request you to set it. +#erl_xcomp_sysroot= + +# * `erl_xcomp_isysroot' - The absolute path to the system root for includes +# of the cross compilation environment. If not set, this value defaults +# to `$erl_xcomp_sysroot', i.e., only set this value if the include system +# root path is not the same as the system root path. +#erl_xcomp_isysroot= + +## -- Optional Feature, and Bug Tests ------------------------------------------ + +## These tests cannot (always) be done automatically when cross compiling. You +## usually do not need to set these variables. Only set these if you really +## know what you are doing. + +## Note that some of these values will override results of tests performed +## by `configure', and some will not be used until `configure' is sure that +## it cannot figure the result out. + +## The `configure' script will issue a warning when a default value is used. +## When a variable has been set, no warning will be issued. + +# * `erl_xcomp_after_morecore_hook' - `yes|no'. Defaults to `no'. If `yes', +# the target system must have a working `__after_morecore_hook' that can be +# used for tracking used `malloc()' implementations core memory usage. +# This is currently only used by unsupported features. +#erl_xcomp_after_morecore_hook= + +# * `erl_xcomp_bigendian' - `yes|no'. No default. If `yes', the target system +# must be big endian. If `no', little endian. This can often be +# automatically detected, but not always. If not automatically detected, +# `configure' will fail unless this variable is set. Since no default +# value is used, `configure' will try to figure this out automatically. +#erl_xcomp_bigendian= + +# * `erl_xcomp_clock_gettime_cpu_time' - `yes|no'. Defaults to `no'. If `yes', +# the target system must have a working `clock_gettime()' implementation +# that can be used for retrieving process CPU time. +#erl_xcomp_clock_gettime_cpu_time= + +# * `erl_xcomp_getaddrinfo' - `yes|no'. Defaults to `no'. If `yes', the target +# system must have a working `getaddrinfo()' implementation that can +# handle both IPv4 and IPv6. +#erl_xcomp_getaddrinfo= + +# * `erl_xcomp_gethrvtime_procfs_ioctl' - `yes|no'. Defaults to `no'. If `yes', +# the target system must have a working `gethrvtime()' implementation and +# is used with procfs `ioctl()'. +#erl_xcomp_gethrvtime_procfs_ioctl= + +# * `erl_xcomp_dlsym_brk_wrappers' - `yes|no'. Defaults to `no'. If `yes', the +# target system must have a working `dlsym(RTLD_NEXT, <S>)' implementation +# that can be used on `brk' and `sbrk' symbols used by the `malloc()' +# implementation in use, and by this track the `malloc()' implementations +# core memory usage. This is currently only used by unsupported features. +#erl_xcomp_dlsym_brk_wrappers= + +# * `erl_xcomp_kqueue' - `yes|no'. Defaults to `no'. If `yes', the target +# system must have a working `kqueue()' implementation that returns a file +# descriptor which can be used by `poll()' and/or `select()'. If `no' and +# the target system has not got `epoll()' or `/dev/poll', the kernel-poll +# feature will be disabled. +#erl_xcomp_kqueue= + +# * `erl_xcomp_linux_clock_gettime_correction' - `yes|no'. Defaults to `yes' on +# Linux; otherwise, `no'. If `yes', `clock_gettime(CLOCK_MONOTONIC, _)' on +# the target system must work. This variable is recommended to be set to +# `no' on Linux systems with kernel versions less than 2.6. +#erl_xcomp_linux_clock_gettime_correction= + +# * `erl_xcomp_linux_nptl' - `yes|no'. Defaults to `yes' on Linux; otherwise, +# `no'. If `yes', the target system must have NPTL (Native POSIX Thread +# Library). Older Linux systems have LinuxThreads instead of NPTL (Linux +# kernel versions typically less than 2.6). +#erl_xcomp_linux_nptl= + +# * `erl_xcomp_linux_usable_sigaltstack' - `yes|no'. Defaults to `yes' on Linux; +# otherwise, `no'. If `yes', `sigaltstack()' must be usable on the target +# system. `sigaltstack()' on Linux kernel versions less than 2.4 are +# broken. +#erl_xcomp_linux_usable_sigaltstack= + +# * `erl_xcomp_linux_usable_sigusrx' - `yes|no'. Defaults to `yes'. If `yes', +# the `SIGUSR1' and `SIGUSR2' signals must be usable by the ERTS. Old +# LinuxThreads thread libraries (Linux kernel versions typically less than +# 2.2) used these signals and made them unusable by the ERTS. +#erl_xcomp_linux_usable_sigusrx= + +# * `erl_xcomp_poll' - `yes|no'. Defaults to `no' on Darwin/MacOSX; otherwise, +# `yes'. If `yes', the target system must have a working `poll()' +# implementation that also can handle devices. If `no', `select()' will be +# used instead of `poll()'. +#erl_xcomp_poll= + +# * `erl_xcomp_putenv_copy' - `yes|no'. Defaults to `no'. If `yes', the target +# system must have a `putenv()' implementation that stores a copy of the +# key/value pair. +#erl_xcomp_putenv_copy= + +# * `erl_xcomp_reliable_fpe' - `yes|no'. Defaults to `no'. If `yes', the target +# system must have reliable floating point exceptions. +#erl_xcomp_reliable_fpe= + +## ----------------------------------------------------------------------------- |