%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2000-2011. 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(trace_local_SUITE). -compile({nowarn_deprecated_function, {erlang,hash,2}}). -export([basic_test/0, bit_syntax_test/0, return_test/0, on_and_off_test/0, stack_grow_test/0, info_test/0, delete_test/1, exception_test/1, not_run/1]). -export([exported/1, exported_wrap/1, loop/4, apply_slave_async/5, match/2, clause/2, id/1, undef/1, lists_reverse/2]). %% %% Define to run outside of test server %% %% (rotten feature) %% %%-define(STANDALONE,1). %% %% Define for debug output %% %%-define(debug,1). -ifdef(STANDALONE). -define(config(A,B),config(A,B)). -export([config/2]). -define(DEFAULT_RECEIVE_TIMEOUT, 1000). -else. -include_lib("test_server/include/test_server.hrl"). -define(DEFAULT_RECEIVE_TIMEOUT, infinity). -endif. -ifdef(debug). -ifdef(STANDALONE). -define(line, erlang:display({?MODULE,?LINE}), ). -endif. -define(dbgformat(A,B),io:format(A,B)). -else. -ifdef(STANDALONE). -define(line, noop, ). -endif. -define(dbgformat(A,B),noop). -endif. -ifdef(STANDALONE). config(priv_dir,_) -> ".". -else. %%% When run in test server %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, basic/1, bit_syntax/1, return/1, on_and_off/1, stack_grow/1,info/1, delete/1, exception/1, exception_apply/1, exception_function/1, exception_apply_function/1, exception_nocatch/1, exception_nocatch_apply/1, exception_nocatch_function/1, exception_nocatch_apply_function/1, exception_meta/1, exception_meta_apply/1, exception_meta_function/1, exception_meta_apply_function/1, exception_meta_nocatch/1, exception_meta_nocatch_apply/1, exception_meta_nocatch_function/1, exception_meta_nocatch_apply_function/1, init_per_testcase/2, end_per_testcase/2]). init_per_testcase(_Case, Config) -> ?line Dog=test_server:timetrap(test_server:minutes(2)), [{watchdog, Dog}|Config]. end_per_testcase(_Case, Config) -> shutdown(), Dog=?config(watchdog, Config), test_server:timetrap_cancel(Dog), ok. suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> case test_server:is_native(trace_local_SUITE) of true -> [not_run]; false -> [basic, bit_syntax, return, on_and_off, stack_grow, info, delete, exception, exception_apply, exception_function, exception_apply_function, exception_nocatch, exception_nocatch_apply, exception_nocatch_function, exception_nocatch_apply_function, exception_meta, exception_meta_apply, exception_meta_function, exception_meta_apply_function, exception_meta_nocatch, exception_meta_nocatch_apply, exception_meta_nocatch_function, exception_meta_nocatch_apply_function] end. groups() -> []. init_per_suite(Config) -> Config. end_per_suite(_Config) -> ok. init_per_group(_GroupName, Config) -> Config. end_per_group(_GroupName, Config) -> Config. not_run(Config) when is_list(Config) -> {skipped,"Native code"}. basic(doc) -> ["Tests basic local call-trace"]; basic(Config) when is_list(Config) -> basic_test(). bit_syntax(doc) -> "OTP-7399: Make sure that code that uses the optimized bit syntax matching " "can be traced without crashing the emulator."; bit_syntax(Config) when is_list(Config) -> bit_syntax_test(). return(doc) -> ["Tests the different types of return trace"]; return(Config) when is_list(Config) -> return_test(). on_and_off(doc) -> ["Tests turning trace parameters on and off, " "both for trace and trace_pattern"]; on_and_off(Config) when is_list(Config) -> on_and_off_test(). stack_grow(doc) -> ["Tests the stack growth during return traces"]; stack_grow(Config) when is_list(Config) -> stack_grow_test(). info(doc) -> ["Tests the trace_info BIF"]; info(Config) when is_list(Config) -> info_test(). delete(doc) -> ["Tests putting trace on deleted modules"]; delete(Config) when is_list(Config) -> delete_test(Config). exception(doc) -> ["Tests exception_trace"]; exception(Config) when is_list(Config) -> exception_test([]). exception_apply(doc) -> ["Tests exception_trace"]; exception_apply(Config) when is_list(Config) -> exception_test([apply]). exception_function(doc) -> ["Tests exception_trace"]; exception_function(Config) when is_list(Config) -> exception_test([function]). exception_apply_function(doc) -> ["Tests exception_trace"]; exception_apply_function(Config) when is_list(Config) -> exception_test([apply,function]). exception_nocatch(doc) -> ["Tests exception_trace"]; exception_nocatch(Config) when is_list(Config) -> exception_test([nocatch]). exception_nocatch_apply(doc) -> ["Tests exception_trace"]; exception_nocatch_apply(Config) when is_list(Config) -> exception_test([nocatch,apply]). exception_nocatch_function(doc) -> ["Tests exception_trace"]; exception_nocatch_function(Config) when is_list(Config) -> exception_test([nocatch,function]). exception_nocatch_apply_function(doc) -> ["Tests exception_trace"]; exception_nocatch_apply_function(Config) when is_list(Config) -> exception_test([nocatch,apply,function]). exception_meta(doc) -> ["Tests meta exception_trace"]; exception_meta(Config) when is_list(Config) -> exception_test([meta]). exception_meta_apply(doc) -> ["Tests meta exception_trace"]; exception_meta_apply(Config) when is_list(Config) -> exception_test([meta,apply]). exception_meta_function(doc) -> ["Tests meta exception_trace"]; exception_meta_function(Config) when is_list(Config) -> exception_test([meta,function]). exception_meta_apply_function(doc) -> ["Tests meta exception_trace"]; exception_meta_apply_function(Config) when is_list(Config) -> exception_test([meta,apply,function]). exception_meta_nocatch(doc) -> ["Tests meta exception_trace"]; exception_meta_nocatch(Config) when is_list(Config) -> exception_test([meta,nocatch]). exception_meta_nocatch_apply(doc) -> ["Tests meta exception_trace"]; exception_meta_nocatch_apply(Config) when is_list(Config) -> exception_test([meta,nocatch,apply]). exception_meta_nocatch_function(doc) -> ["Tests meta exception_trace"]; exception_meta_nocatch_function(Config) when is_list(Config) -> exception_test([meta,nocatch,function]). exception_meta_nocatch_apply_function(doc) -> ["Tests meta exception_trace"]; exception_meta_nocatch_apply_function(Config) when is_list(Config) -> exception_test([meta,nocatch,apply,function]). -endif. %%% Message patterns and expect functions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -define(pCT(P,M,F,A), {trace, P,call,{M,F,A}}). -define(pCTT(P,M,F,A), {trace_ts,P,call,{M,F,A},{_,_,_}}). -define(pRF(P,M,F,A,V), {trace, P,return_from,{M,F,A},V}). -define(pRFT(P,M,F,A,V),{trace_ts,P,return_from,{M,F,A},V,{_,_,_}}). -define(pEF(P,M,F,A,V), {trace, P,exception_from,{M,F,A},V}). -define(pEFT(P,M,F,A,V),{trace_ts,P,exception_from,{M,F,A},V,{_,_,_}}). -define(pRT(P,M,F,A), {trace, P,return_to,{M,F,A}}). -define(pRTT(P,M,F,A), {trace_ts,P,return_to,{M,F,A},{_,_,_}}). -define(CT(M,F,A), ?pCT(_,M,F,A) = receive_next()). -define(CTT(M,F,A), ?pCTT(_,M,F,A) = receive_next()). -define(RF(M,F,A,V), ?pRF(_,M,F,A,V) = receive_next()). -define(RFT(M,F,A,V), ?pRFT(_,M,F,A,V) = receive_next()). -define(EF(M,F,A,V), ?pEF(_,M,F,A,V) = receive_next()). -define(EFT(M,F,A,V), ?pEFT(_,M,F,A,V) = receive_next()). -define(RT(M,F,A), ?pRT(_,M,F,A) = receive_next()). -define(RTT(M,F,A), ?pRTT(_,M,F,A) = receive_next()). -define(NM, receive_no_next(100)). expect() -> {Pid,_} = get(slave), expect_receive(Pid). expect(Msg) -> {Pid,_} = get(slave), expect_pid(Pid, Msg). expect_pid(_Pid, []) -> ok; expect_pid(Pid, [Line|T]) when is_integer(Line) -> put(test_server_loc, {?MODULE,Line}), expect_pid(Pid, T); expect_pid(Pid, [true|[_|_]=T]) -> expect_pid(Pid, T); expect_pid(Pid, [false|[_|T]]) -> expect_pid(Pid, T); expect_pid(Pid, [H|T]) -> expect_pid(Pid, H), expect_pid(Pid, T); expect_pid(Pid, Msg) when is_tuple(Msg) -> same(Msg, expect_receive(Pid)); expect_pid(Pid, Fun) when is_function(Fun, 1) -> case Fun(expect_receive(Pid)) of next -> expect_pid(Pid, Fun); done -> ok; Other -> expect_pid(Pid, Other) end. expect_receive(Pid) when is_pid(Pid) -> receive Msg when is_tuple(Msg), element(1, Msg) == trace, element(2, Msg) =/= Pid; %% is_tuple(Msg), element(1, Msg) == trace_ts, element(2, Msg) =/= Pid -> expect_receive(Pid); Msg -> expect_msg(Pid, Msg) after 100 -> {nm} end. expect_msg(P, ?pCT(P,M,F,Args)) -> {ct,{M,F},Args}; expect_msg(P, ?pCTT(P,M,F,Args)) -> {ctt,{M,F},Args}; expect_msg(P, ?pRF(P,M,F,Arity,V)) -> {rf,{M,F,Arity},V}; expect_msg(P, ?pRFT(P,M,F,Arity,V)) -> {rft,{M,F,Arity},V}; expect_msg(P, ?pEF(P,M,F,Arity,V)) -> {ef,{M,F,Arity},V}; expect_msg(P, ?pEFT(P,M,F,Arity,V)) -> {eft,{M,F,Arity},V}; expect_msg(P, ?pRT(P,M,F,Arity)) -> {rt,{M,F,Arity}}; expect_msg(P, ?pRTT(P,M,F,Arity)) -> {rtt,{M,F,Arity}}; expect_msg(P, Msg) when is_tuple(Msg) -> case tuple_to_list(Msg) of [trace,P|T] -> list_to_tuple([trace|T]); [trace_ts,P|[_|_]=T] -> list_to_tuple([trace_ts|reverse(tl(reverse(T)))]); _ -> Msg end. same(A, B) -> case [A|B] of [X|X] -> ok end. %%% tests %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% basic_test() -> ?line setup([call]), ?line erlang:trace_pattern({?MODULE,'_','_'},[],[local]), ?line erlang:trace_pattern({?MODULE,slave,'_'},false,[local]), ?line [1,1,1,1] = apply_slave(?MODULE,exported_wrap,[1]), ?line ?CT(?MODULE,exported_wrap,[1]), ?line ?CT(?MODULE,exported,[1]), ?line ?CT(?MODULE,local,[1]), ?line ?CT(?MODULE,local2,[1]), ?line ?CT(?MODULE,local_tail,[1]), ?line erlang:trace_pattern({?MODULE,'_','_'},[],[]), ?line erlang:trace_pattern({?MODULE,slave,'_'},false,[local]), ?line [1,1,1,1] = apply_slave(?MODULE,exported_wrap,[1]), ?line ?CT(?MODULE,exported_wrap,[1]), ?line [1,1,1,1] = lambda_slave(fun() -> exported_wrap(1) end), ?line ?NM, ?line erlang:trace_pattern({?MODULE,'_','_'},[],[local]), ?line erlang:trace_pattern({?MODULE,slave,'_'},false,[local]), ?line [1,1,1,1] = lambda_slave(fun() -> exported_wrap(1) end), ?line ?CT(?MODULE,_,_), %% The fun ?line ?CT(?MODULE,exported_wrap,[1]), ?line ?CT(?MODULE,exported,[1]), ?line ?CT(?MODULE,local,[1]), ?line ?CT(?MODULE,local2,[1]), ?line ?CT(?MODULE,local_tail,[1]), ?line erlang:trace_pattern({?MODULE,'_','_'},false,[local]), ?line shutdown(), ?line ?NM, ok. %% OTP-7399. bit_syntax_test() -> ?line setup([call]), ?line erlang:trace_pattern({?MODULE,'_','_'},[],[local]), ?line erlang:trace_pattern({?MODULE,slave,'_'},false,[local]), ?line lambda_slave(fun() -> 6 = bs_sum_a(<<1,2,3>>, 0), 10 = bs_sum_b(0, <<1,2,3,4>>), 26 = bs_sum_c(<<3:4,5:4,7:4,11:4>>, 0) end), ?line ?CT(?MODULE,_,[]), %Ignore call to the fun. ?line ?CT(?MODULE,bs_sum_a,[<<1,2,3>>,0]), ?line ?CT(?MODULE,bs_sum_a,[<<2,3>>,1]), ?line ?CT(?MODULE,bs_sum_a,[<<3>>,3]), ?line ?CT(?MODULE,bs_sum_a,[<<>>,6]), ?line ?CT(?MODULE,bs_sum_b,[0,<<1,2,3,4>>]), ?line ?CT(?MODULE,bs_sum_b,[1,<<2,3,4>>]), ?line ?CT(?MODULE,bs_sum_b,[3,<<3,4>>]), ?line ?CT(?MODULE,bs_sum_b,[6,<<4>>]), ?line ?CT(?MODULE,bs_sum_b,[10,<<>>]), ?line ?CT(?MODULE,bs_sum_c,[<<3:4,5:4,7:4,11:4>>, 0]), ?line ?CT(?MODULE,bs_sum_c,[<<5:4,7:4,11:4>>, 3]), ?line ?CT(?MODULE,bs_sum_c,[<<7:4,11:4>>, 8]), ?line ?CT(?MODULE,bs_sum_c,[<<11:4>>, 15]), ?line ?CT(?MODULE,bs_sum_c,[<<>>, 26]), ?line erlang:trace_pattern({?MODULE,'_','_'},false,[local]), ?line shutdown(), ?line ?NM, ok. bs_sum_a(<<H,T/binary>>, Acc) -> bs_sum_a(T, H+Acc); bs_sum_a(<<>>, Acc) -> Acc. bs_sum_b(Acc, <<H,T/binary>>) -> bs_sum_b(H+Acc, T); bs_sum_b(Acc, <<>>) -> Acc. bs_sum_c(<<H:4,T/bits>>, Acc) -> bs_sum_c(T, H+Acc); bs_sum_c(<<>>, Acc) -> Acc. return_test() -> ?line setup([call]), ?line erlang:trace_pattern({?MODULE,'_','_'},[{'_',[],[{return_trace}]}], [local]), ?line erlang:trace_pattern({erlang,hash,'_'},[{'_',[],[{return_trace}]}], [local]), ?line erlang:trace_pattern({?MODULE,slave,'_'},false,[local]), ?line [1,1,1,1] = apply_slave(?MODULE,exported_wrap,[1]), ?line ?CT(?MODULE,exported_wrap,[1]), ?line ?CT(?MODULE,exported,[1]), ?line ?CT(?MODULE,local,[1]), ?line ?CT(?MODULE,local2,[1]), ?line ?CT(?MODULE,local_tail,[1]), ?line ?CT(erlang,hash,[1,1]), ?line ?RF(erlang,hash,2,1), ?line ?RF(?MODULE,local_tail,1,[1,1]), ?line ?RF(?MODULE,local2,1,[1,1]), ?line ?RF(?MODULE,local,1,[1,1,1]), ?line ?RF(?MODULE,exported,1,[1,1,1,1]), ?line ?RF(?MODULE,exported_wrap,1,[1,1,1,1]), ?line shutdown(), ?line setup([call,return_to]), ?line erlang:trace_pattern({?MODULE,'_','_'},[], [local]), ?line erlang:trace_pattern({erlang,hash,'_'},[], [local]), ?line erlang:trace_pattern({?MODULE,slave,'_'},false,[local]), ?line [1,1,1,1] = apply_slave(?MODULE,exported_wrap,[1]), ?line ?CT(?MODULE,exported_wrap,[1]), ?line ?CT(?MODULE,exported,[1]), ?line ?CT(?MODULE,local,[1]), ?line ?CT(?MODULE,local2,[1]), ?line ?CT(?MODULE,local_tail,[1]), ?line ?CT(erlang,hash,[1,1]), ?line ?RT(?MODULE,local_tail,1), ?line ?RT(?MODULE,local,1), ?line ?RT(?MODULE,exported,1), ?line ?RT(?MODULE,slave,2), ?line shutdown(), ?line setup([call,return_to]), ?line erlang:trace_pattern({?MODULE,'_','_'},[{'_',[],[{return_trace}]}], [local]), ?line erlang:trace_pattern({erlang,hash,'_'},[{'_',[],[{return_trace}]}], [local]), ?line erlang:trace_pattern({?MODULE,slave,'_'},false,[local]), ?line [1,1,1,1] = apply_slave(?MODULE,exported_wrap,[1]), ?line ?CT(?MODULE,exported_wrap,[1]), ?line ?CT(?MODULE,exported,[1]), ?line ?CT(?MODULE,local,[1]), ?line ?CT(?MODULE,local2,[1]), ?line ?CT(?MODULE,local_tail,[1]), ?line ?CT(erlang,hash,[1,1]), ?line ?RF(erlang,hash,2,1), ?line ?RT(?MODULE,local_tail,1), ?line ?RF(?MODULE,local_tail,1,[1,1]), ?line ?RF(?MODULE,local2,1,[1,1]), ?line ?RT(?MODULE,local,1), ?line ?RF(?MODULE,local,1,[1,1,1]), ?line ?RT(?MODULE,exported,1), ?line ?RF(?MODULE,exported,1,[1,1,1,1]), ?line ?RF(?MODULE,exported_wrap,1,[1,1,1,1]), ?line ?RT(?MODULE,slave,2), ?line shutdown(), ?line ?NM, ok. on_and_off_test() -> ?line Pid = setup([call]), ?line 1 = erlang:trace_pattern({?MODULE,local_tail,1},[],[local]), ?line erlang:trace_pattern({?MODULE,slave,'_'},false,[local]), ?line LocalTail = fun() -> local_tail(1) end, ?line [1,1] = lambda_slave(LocalTail), ?line ?CT(?MODULE,local_tail,[1]), ?line erlang:trace(Pid,true,[return_to]), ?line [1,1] = lambda_slave(LocalTail), ?line ?CT(?MODULE,local_tail,[1]), ?line ?RT(?MODULE,_,_), ?line 0 = erlang:trace_pattern({?MODULE,local_tail,1},[],[global]), ?line [1,1] = lambda_slave(LocalTail), ?line ?NM, ?line 1 = erlang:trace_pattern({?MODULE,exported_wrap,1},[],[global]), ?line [1,1,1,1] = apply_slave(?MODULE,exported_wrap,[1]), ?line ?CT(?MODULE,exported_wrap,[1]), ?line 1 = erlang:trace_pattern({?MODULE,exported_wrap,1},[],[local]), ?line [1,1,1,1] = apply_slave(?MODULE,exported_wrap,[1]), ?line ?CT(?MODULE,exported_wrap,[1]), ?line ?RT(?MODULE,slave,2), ?line 1 = erlang:trace_pattern({erlang,hash,2},[],[local]), ?line [1,1,1,1] = apply_slave(?MODULE,exported_wrap,[1]), ?line ?CT(?MODULE,exported_wrap,[1]), ?line ?CT(erlang,hash,[1,1]), ?line ?RT(?MODULE,local_tail,1), ?line ?RT(?MODULE,slave,2), ?line erlang:trace(Pid,true,[timestamp]), ?line [1,1,1,1] = apply_slave(?MODULE,exported_wrap,[1]), ?line ?CTT(?MODULE,exported_wrap,[1]), ?line ?CTT(erlang,hash,[1,1]), ?line ?RTT(?MODULE,local_tail,1), ?line ?RTT(?MODULE,slave,2), ?line erlang:trace(Pid,false,[return_to,timestamp]), ?line [1,1,1,1] = apply_slave(?MODULE,exported_wrap,[1]), ?line ?CT(?MODULE,exported_wrap,[1]), ?line ?CT(erlang,hash,[1,1]), ?line erlang:trace(Pid,true,[return_to]), ?line 1 = erlang:trace_pattern({erlang,hash,2},[],[]), ?line [1,1,1,1] = apply_slave(?MODULE,exported_wrap,[1]), ?line ?CT(?MODULE,exported_wrap,[1]), ?line ?CT(erlang,hash,[1,1]), ?line ?RT(?MODULE,slave,2), ?line 1 = erlang:trace_pattern({?MODULE,exported_wrap,1},[],[]), ?line [1,1,1,1] = apply_slave(?MODULE,exported_wrap,[1]), ?line ?CT(?MODULE,exported_wrap,[1]), ?line ?CT(erlang,hash,[1,1]), ?line shutdown(), ?line erlang:trace_pattern({'_','_','_'},false,[local]), ?line N = erlang:trace_pattern({erlang,'_','_'},true,[local]), ?line case erlang:trace_pattern({erlang,'_','_'},false,[local]) of N -> ok; Else -> exit({number_mismatch, {expected, N}, {got, Else}}) end, ?line case erlang:trace_pattern({erlang,'_','_'},false,[local]) of N -> ok; Else2 -> exit({number_mismatch, {expected, N}, {got, Else2}}) end, ?line M = erlang:trace_pattern({erlang,'_','_'},true,[]), ?line case erlang:trace_pattern({erlang,'_','_'},false,[]) of M -> ok; Else3 -> exit({number_mismatch, {expected, N}, {got, Else3}}) end, ?line case erlang:trace_pattern({erlang,'_','_'},false,[]) of M -> ok; Else4 -> exit({number_mismatch, {expected, N}, {got, Else4}}) end, ?line ?NM, ok. stack_grow_test() -> ?line setup([call,return_to]), ?line 1 = erlang:trace_pattern({?MODULE,loop,4}, [{'_',[],[{return_trace}]}],[local]), ?line erlang:trace_pattern({?MODULE,slave,'_'},false,[local]), ?line Num = 1 bsl 15, ?line Fun = fun(_F,0) -> ok; (F,N) -> receive _A -> receive _B -> receive _C -> F(F,N-1) end end end end, ?line apply_slave_async(?MODULE,loop,[{hej,hopp},[a,b,c],4.5,Num]), ?line Fun(Fun,Num + 1), ?line ?NM, ok. info_test() -> ?line Flags1 = lists:sort([call,return_to]), ?line Pid = setup(Flags1), ?line Prog = [{['$1'],[{is_integer,'$1'}],[{message, false}]}, {'_',[],[]}], ?line erlang:trace_pattern({?MODULE,exported_wrap,1},Prog,[local]), ?line erlang:trace_pattern({?MODULE,slave,'_'},false,[local]), ?line Self = self(), ?line {flags,L} = erlang:trace_info(Pid,flags), ?line case lists:sort(L) of Flags1 -> ok; Wrong1 -> exit({bad_result, {erlang,trace_info,[Pid,flags]}, {expected, Flags1}, {got, Wrong1}}) end, ?line {tracer,Tracer} = erlang:trace_info(Pid,tracer), ?line case Tracer of Self -> ok; Wrong2 -> exit({bad_result, {erlang,trace_info,[Pid,tracer]}, {expected, Self}, {got, Wrong2}}) end, ?line {traced,local} = erlang:trace_info({?MODULE,exported_wrap,1},traced), ?line {match_spec, MS} = erlang:trace_info({?MODULE,exported_wrap,1},match_spec), ?line case MS of Prog -> ok; Wrong3 -> exit({bad_result, {erlang,trace_info, [{?MODULE,exported_wrap,1}, match_spec]}, {expected, Prog}, {got, Wrong3}}) end, ?line erlang:garbage_collect(self()), ?line receive after 1 -> ok end, ?line io:format("~p~n",[MS]), ?line {match_spec,MS2} = erlang:trace_info({?MODULE,exported_wrap,1},match_spec), ?line io:format("~p~n",[MS2]), ?line erlang:trace_pattern({?MODULE,exported_wrap,1},[],[]), ?line {traced,global} = erlang:trace_info({?MODULE,exported_wrap,1},traced), ?line {match_spec,[]} = erlang:trace_info({?MODULE,exported_wrap,1},match_spec), ?line {traced,undefined} = erlang:trace_info({?MODULE,exported_wrap,2},traced), ?line {match_spec,undefined} = erlang:trace_info({?MODULE,exported_wrap,2},match_spec), ?line {traced,false} = erlang:trace_info({?MODULE,exported,1},traced), ?line {match_spec,false} = erlang:trace_info({?MODULE,exported,1},match_spec), ?line shutdown(), ok. delete_test(Config) -> ?line Priv = ?config(priv_dir, Config), ?line Data = ?config(data_dir, Config), ?line File = filename:join(Data, "trace_local_dummy"), ?line {ok,trace_local_dummy} = c:c(File, [{outdir,Priv}]), ?line code:purge(trace_local_dummy), ?line code:delete(trace_local_dummy), ?line 0 = erlang:trace_pattern({trace_local_dummy,'_','_'},true,[local]), ?line ?NM, ok. %%% exception_test %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% exception_test(Opts) -> ?line {ProcFlags,PatFlags} = case proplists:get_bool(meta, Opts) of true -> {[timestamp],[meta]}; false -> {[call,return_to,timestamp],[local]} end, ?line case proplists:get_bool(nocatch, Opts) of false -> ?line Exceptions = exceptions(), ?line exception_test_setup(ProcFlags, PatFlags), ?line lists:foreach( fun ({Func,Args}) -> ?line exception_test(Opts, Func, Args) end, Exceptions), ?line shutdown(); true -> ?line Exceptions = exceptions(), ?line lists:foreach( fun ({Func,Args}) -> ?line exception_test_setup( [procs|ProcFlags], PatFlags), ?line exception_test(Opts, Func, Args), ?line shutdown() end, Exceptions) end, ?line ok. exceptions() -> ?line Ref = make_ref(), ?line N = case os:type() of vxworks -> ?line 2000; % Limited memory on themachines, not actually % VxWorks' fault /PaN _ -> ?line 200000 end, ?line LiL = seq(1, N-1, N), % Long Improper List ?line LL = seq(1, N, []), % Long List [{{erlang,exit}, [done]}, {{erlang,error}, [1.0]}, {{erlang,error}, [Ref,[]]}, {{erlang,throw}, [4711]}, {{erlang,'++'}, [[17],seventeen]}, {{erlang,'++'}, [Ref,[125.125]]}, {{?MODULE,match}, [ref,Ref]}, {{?MODULE,match}, [Ref,Ref]}, {{?MODULE,clause}, [ref,Ref]}, {{?MODULE,clause}, [Ref,Ref]}, {{?MODULE,id}, [4711.0]}, {{?MODULE,undef}, [[Ref|Ref]]}, {{?MODULE,lists_reverse}, [LiL,[]]}, {{?MODULE,lists_reverse}, [LL,[]]}]. exception_test_setup(ProcFlags, PatFlags) -> ?line Pid = setup(ProcFlags), ?line io:format("=== exception_test_setup(~p, ~p): ~p~n", [ProcFlags,PatFlags,Pid]), ?line Mprog = [{'_',[],[{exception_trace}]}], ?line erlang:trace_pattern({?MODULE,'_','_'}, Mprog, PatFlags), ?line erlang:trace_pattern({?MODULE,slave,'_'},false,PatFlags), ?line [1,1,1,1,1] = [erlang:trace_pattern({erlang,F,A}, Mprog, PatFlags) || {F,A} <- [{exit,1},{error,1},{error,2},{throw,1},{'++',2}]], ?line 1 = erlang:trace_pattern({lists,reverse,2}, Mprog, PatFlags), ?line ok. -record(exc_opts, {nocatch=false, meta=false}). exception_test(Opts, Func0, Args0) -> ?line io:format("=== exception_test(~p, ~p, ~p)~n", [Opts,Func0,abbr(Args0)]), ?line Apply = proplists:get_bool(apply, Opts), ?line Function = proplists:get_bool(function, Opts), ?line Nocatch = proplists:get_bool(nocatch, Opts), ?line Meta = proplists:get_bool(meta, Opts), ?line ExcOpts = #exc_opts{nocatch=Nocatch,meta=Meta}, %% Func0 and Args0 are for the innermost call, now we will %% wrap them in wrappers... ?line {Func1,Args1} = case Function of true -> {fun exc/2,[Func0,Args0]}; false -> {Func0,Args0} end, ?line {Func,Args} = case Apply of true -> {{erlang,apply},[Func1,Args1]}; false -> {Func1,Args1} end, ?line R1 = exc_slave(ExcOpts, Func, Args), ?line Stack2 = [{?MODULE,exc_top,3,[]},{?MODULE,slave,2,[]}], ?line Stack3 = [{?MODULE,exc,2,[]}|Stack2], ?line Rs = case x_exc_top(ExcOpts, Func, Args) of % Emulation {crash,{Reason,Stack}}=R when is_list(Stack) -> [R, {crash,{Reason,Stack++Stack2}}, {crash,{Reason,Stack++Stack3}}]; R -> [R] end, ?line exception_validate(R1, Rs), ?line case R1 of {crash,Crash} -> ?line expect({trace_ts,exit,Crash}); _ when not Meta -> ?line expect({rtt,{?MODULE,slave,2}}); _ -> ok end, ?line expect({nm}). exception_validate(R0, Rs0) -> R = clean_location(R0), Rs = [clean_location(E) || E <- Rs0], exception_validate_1(R, Rs). exception_validate_1(R1, [R2|Rs]) -> case [R1|R2] of [R|R] -> ok; [{crash,{badarg,[{lists,reverse,[L1a,L1b],_}|T]}}| {crash,{badarg,[{lists,reverse,[L2a,L2b],_}|T]}}] -> same({crash,{badarg,[{lists,reverse, [lists:reverse(L1b, L1a),[]],[]}|T]}}, {crash,{badarg,[{lists,reverse, [lists:reverse(L2b, L2a),[]],[]}|T]}}); _ when is_list(Rs), Rs =/= [] -> exception_validate(R1, Rs) end. clean_location({crash,{Reason,Stk0}}) -> Stk = [{M,F,A,[]} || {M,F,A,_} <- Stk0], {crash,{Reason,Stk}}; clean_location(Term) -> Term. %%% Tracee target functions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% loop(D1,D2,D3,0) -> io:format("~p~n",[[D1,D2,D3]]), 0; loop(D1,D2,D3,N) -> max(N,loop(D1,D2,D3,N-1)). exported_wrap(Val) -> exported(Val). exported(Val) -> [Val | local(Val)]. %% Non tail recursive local call local(Val) -> [Val | local2(Val)]. %% Non tail recursive local call local2(Val) -> local_tail(Val). %% Tail recursive call local_tail(Val) -> [Val , erlang:hash(1,1)]. %%% exc_slave/3 tracee target functions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% exc_top(ExcOpts, Func, Args) -> case ExcOpts#exc_opts.nocatch of false -> try exc_jump(Func, Args) of Value -> {value,Value} catch Class:Reason -> {Class,Reason} end; true -> {value,exc_jump(Func, Args)} end. %% x_* functions emulate the non-x_* ones. %% x_* functions below x_exc_top %% return {value,Value} or {Class,Reason}. %% The only possible place for exception %% is below exc/2. x_exc_top(ExcOpts, Func, Args) -> ?line Rtt = not ExcOpts#exc_opts.meta, ?line expect({ctt,{?MODULE,exc_top},[ExcOpts,Func,Args]}), ?line case x_exc_jump(ExcOpts, Func, Args) of Result when not ExcOpts#exc_opts.nocatch -> ?line expect([Rtt,{rtt,{?MODULE,exc_top,3}}, ?LINE,{rft,{?MODULE,exc_top,3},Result}]), ?line Result; {value,_}=Result -> ?line expect([Rtt,{rtt,{?MODULE,exc_top,3}}, ?LINE,{rft,{?MODULE,exc_top,3},Result}]), ?line Result; {exit,Reason}=CR -> ?line expect({eft,{?MODULE,exc_top,3},CR}), ?line {crash,Reason}; {error,Reason}=CR -> ?line expect({eft,{?MODULE,exc_top,3},CR}), ?line {crash,{Reason,x_exc_stacktrace()}}; CR -> ?line expect({eft,{?MODULE,exc_top,3},CR}), ?line {crash,CR} end. exc_jump(Func, Args) -> exc(Func, Args, jump). x_exc_jump(ExcOpts, Func, Args) -> ?line expect({ctt,{?MODULE,exc_jump},[Func,Args]}), ?line case x_exc(ExcOpts, Func, Args, jump) of {value,Value}=Result -> ?line expect({rft,{?MODULE,exc_jump,2},Value}), ?line Result; CR -> ?line expect({eft,{?MODULE,exc_jump,2},CR}), ?line CR end. exc(Func, Args, jump) -> exc(Func, Args, do); exc(Func, Args, do) -> exc(Func, Args). x_exc(ExcOpts, Func, Args, jump) -> ?line expect({ctt,{?MODULE,exc},[Func,Args,jump]}), ?line case x_exc(ExcOpts, Func, Args, do) of {value,Value}=Result -> ?line expect({rft,{?MODULE,exc,3},Value}), ?line Result; CR -> ?line expect({eft,{?MODULE,exc,3},CR}), ?line CR end; x_exc(ExcOpts, Func, Args, do) -> ?line expect({ctt,{?MODULE,exc},[Func,Args,do]}), ?line case x_exc(ExcOpts, Func, Args) of {value,Value}=Result -> ?line expect({rft,{?MODULE,exc,3},Value}), ?line Result; CR -> ?line expect({eft,{?MODULE,exc,3},CR}), ?line CR end. exc({erlang,apply}, [{M,F},A]) -> erlang:apply(M, F, id(A)); exc({erlang,apply}, [F,A]) -> erlang:apply(F, id(A)); exc({erlang,error}, [E]) -> erlang:error(id(E)); exc({erlang,error}, [E,S]) -> erlang:error(E, id(S)); exc({erlang,exit}, [E]) -> erlang:exit(id(E)); exc({erlang,throw}, [E]) -> erlang:throw(id(E)); exc({erlang,'++'}, [A,B]) -> erlang:'++'(A, id(B)); exc({?MODULE,match}, [A,B]) -> match(A, id(B)); exc({?MODULE,clause}, [A,B]) -> clause(A, id(B)); exc({?MODULE,id}, [E]) -> id(id(E)); exc({?MODULE,undef}, [E]) -> undef(id(E)); exc({?MODULE,lists_reverse}, [A,B]) -> lists_reverse(A, id(B)); exc(Func, [A,B]) when is_function(Func, 2) -> Func(A, id(B)). x_exc(ExcOpts, {erlang,apply}=Func0, [{_,_}=Func,Args]=Args0) -> ?line expect({ctt,{?MODULE,exc},[Func0,Args0]}), ?line x_exc_body(ExcOpts, Func, Args, true); x_exc(ExcOpts, {erlang,apply}=Func0, [Func,Args]=Args0) when is_function(Func, 2)-> ?line expect({ctt,{?MODULE,exc},[Func0,Args0]}), ?line x_exc_func(ExcOpts, Func, Args, Args); x_exc(ExcOpts, {_,_}=Func, Args) -> ?line expect({ctt,{?MODULE,exc},[Func,Args]}), ?line x_exc_body(ExcOpts, Func, Args, false); x_exc(ExcOpts, Func0, [_,Args]=Args0) when is_function(Func0, 2) -> ?line expect({ctt,{?MODULE,exc},[Func0,Args0]}), ?line x_exc_func(ExcOpts, Func0, Args0, Args). x_exc_func(ExcOpts, Func, [Func1,Args1]=Args, Id) -> %% Assumes the called fun =:= fun exc/2, %% will utterly fail otherwise. ?line Rtt = not ExcOpts#exc_opts.meta, ?line {module,M} = erlang:fun_info(Func, module), ?line {name,F} = erlang:fun_info(Func, name), ?line expect([{ctt,{?MODULE,id},[Id]}, ?LINE,{rft,{?MODULE,id,1},Id}, ?LINE,Rtt,{rtt,{?MODULE,exc,2}}, ?LINE,{ctt,{M,F},Args}]), ?line case x_exc(ExcOpts, Func1, Args1) of {value,Value}=Result -> ?line expect([{rft,{M,F,2},Value}, ?LINE,{rft,{?MODULE,exc,2},Value}]), ?line Result; CR -> ?line expect([{eft,{M,F,2},CR}, ?LINE,{eft,{?MODULE,exc,2},CR}]), ?line CR end. x_exc_body(ExcOpts, {M,F}=Func, Args, Apply) -> ?line Nocatch = ExcOpts#exc_opts.nocatch, ?line Rtt = not ExcOpts#exc_opts.meta, ?line Id = case Apply of true -> Args; false -> lists:last(Args) end, ?line expect([{ctt,{?MODULE,id},[Id]}, ?LINE,{rft,{?MODULE,id,1},Id}, ?LINE,Rtt,{rtt,{?MODULE,exc,2}}, ?LINE,{ctt,{M,F},Args}]), ?line Arity = length(Args), ?line try exc(Func, Args) of Value -> ?line x_exc_value(Rtt, M, F, Args, Arity, Value), ?line case expect() of {rtt,{M,F,Arity}} when Rtt, Apply -> %% We may get the above when %% applying a BIF. ?line expect({rft,{?MODULE,exc,2},Value}); {rtt,{?MODULE,exc,2}} when Rtt, not Apply -> %% We may get the above when %% calling a BIF. ?line expect({rft,{?MODULE,exc,2},Value}); {rft,{?MODULE,exc,2},Value} -> ?line ok end, ?line {value,Value} catch Thrown when Nocatch -> ?line CR = {error,{nocatch,Thrown}}, ?line x_exc_exception(Rtt, M, F, Args, Arity, CR), ?line expect({eft,{?MODULE,exc,2},CR}), ?line CR; Class:Reason -> ?line CR = {Class,Reason}, ?line x_exc_exception(Rtt, M, F, Args, Arity, CR), ?line expect({eft,{?MODULE,exc,2},CR}), ?line CR end. x_exc_value(Rtt, ?MODULE, lists_reverse, [La,Lb], 2, R) -> ?line L = lists:reverse(Lb, La), ?line expect([fun ({ctt,{lists,reverse},[L1,L2]}) -> ?line same(L, lists:reverse(L2, L1)), ?line next; (Msg) -> ?line same({rft,{lists,reverse,2},R}, Msg), ?line same(R, lists:reverse(L, [])), ?line done end, ?LINE,Rtt,{rtt,{?MODULE,lists_reverse,2}}, ?LINE,{rft,{?MODULE,lists_reverse,2},R}]); x_exc_value(_Rtt, M, F, _, Arity, Value) -> ?line expect({rft,{M,F,Arity},Value}). x_exc_exception(_Rtt, ?MODULE, lists_reverse, [La,Lb], 2, CR) -> ?line L = lists:reverse(Lb, La), ?line expect([fun ({ctt,{lists,reverse},[L1,L2]}) -> ?line same(L, lists:reverse(L2, L1)), ?line next; (Msg) -> ?line same({eft,{lists,reverse,2},CR}, Msg), ?line done end, ?LINE,{eft,{?MODULE,lists_reverse,2},CR}]); x_exc_exception(Rtt, ?MODULE, undef, [_], 1, {Class,Reason}=CR) -> ?line expect([{ctt,{erlang,Class},[Reason]}, ?LINE,{eft,{erlang,Class,1},CR}, ?LINE,Rtt,{rtt,{error_handler,crash,1}}, ?LINE,{eft,{?MODULE,undef,1},CR}]); x_exc_exception(_Rtt, M, F, _, Arity, CR) -> ?line expect({eft,{M,F,Arity},CR}). x_exc_stacktrace() -> x_exc_stacktrace(erlang:get_stacktrace()). %% Truncate stacktrace to below exc/2 x_exc_stacktrace([{?MODULE,x_exc,4,_}|_]) -> []; x_exc_stacktrace([{?MODULE,x_exc_func,4,_}|_]) -> []; x_exc_stacktrace([{?MODULE,x_exc_body,4,_}|_]) -> []; x_exc_stacktrace([{?MODULE,exc,2,_}|_]) -> []; x_exc_stacktrace([H|T]) -> [H|x_exc_stacktrace(T)]. match(A, B) -> A = B. clause(A, A) -> A. id(Id) -> Id. undef(X) -> ?MODULE:undef(X, X). % undef lists_reverse(A, B) -> lists:reverse(A, B). %%% Tracee (slave) handling %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% slave(Dest, Sync) -> Dest ! Sync, receive {From,Tag,{apply,M,F,A}} when is_pid(From) -> ?line ?dbgformat("Apply: ~p:~p/~p (~p)~n",[M,F,length(A),A]), ?line Res = apply(M,F,A), ?line ?dbgformat("done Apply: ~p:~p/~p (~p)~n",[M,F,length(A),A]), From ! {Tag,Res}, slave(From, Tag); {From,Tag,{lambda,Fun}} when is_pid(From) -> Res = Fun(), From ! {Tag,Res}, slave(From, Tag); {From,Tag,{exc_top,Catch,Func,Args}} when is_pid(From) -> ?line ?dbgformat("Exc: ~p ~p~p ~n",[Catch,Func,Args]), ?line Res = exc_top(Catch, Func, Args), ?line ?dbgformat("done Exc: ~p ~p~p ~n",[Catch,Func,Args]), From ! {Tag,Res}, slave(From,Tag); die -> exit(normal) end. setup(ProcFlags) -> trace_off(), flush(100), Self = self(), Sync = make_ref(), Pid = spawn(fun () -> slave(Self, Sync) end), Mref = erlang:monitor(process, Pid), receive Sync -> put(slave, {Pid,Mref}), case ProcFlags of [] -> ok; _ -> erlang:trace(Pid, true, ProcFlags) end, Pid end. shutdown() -> trace_off(), {Pid,Mref} = get(slave), try erlang:is_process_alive(Pid) of true -> Pid ! die, receive {'DOWN',Mref,process,Pid,Reason} -> Reason end; _ -> not_alive catch _:_ -> undefined end. trace_off() -> erlang:trace_pattern({'_','_','_'},false,[]), erlang:trace_pattern({'_','_','_'},false,[local]), erlang:trace_pattern({'_','_','_'},false,[meta]), erlang:trace(all, false, [all]). apply_slave_async(M,F,A) -> {Pid,Mref} = get(slave), spawn(?MODULE,apply_slave_async,[M,F,A,Pid,Mref]), Pid. apply_slave_async(M,F,A,Pid,Mref) -> Tag = make_ref(), Pid ! {self(),Tag,{apply,M,F,A}}, result(Tag, Mref). apply_slave(M,F,A) -> request({apply,M,F,A}). lambda_slave(Fun) -> request({lambda,Fun}). exc_slave(Opts, Func, Args) -> try request({exc_top,Opts,Func,Args}) catch Reason -> {crash,Reason} end. request(Request) -> Tag = make_ref(), {Pid,Mref} = get(slave), Pid ! {self(),Tag,Request}, result(Tag, Mref). result(Tag, Mref) -> receive {Tag,Result} -> receive Tag -> Result end; {'DOWN',Mref,process,_Pid,Reason} -> throw(Reason) end. %%% Some receive helpers %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% receive_next() -> receive_next(?DEFAULT_RECEIVE_TIMEOUT). receive_next(TO) -> receive M -> M after TO -> ?t:fail(timeout) end. receive_no_next(TO) -> receive M -> ?t:fail({unexpected_message,[M|flush(TO)]}) after TO -> ok end. flush(T) -> receive M -> [M|flush(T)] after T -> [] end. %%% Helpers %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% %% Do not build garbage %% seq(M, N, R) when M =< N -> seq(M, N-1, [N|R]); seq(_, _, R) -> R. %% Do not call traced lists:reverse reverse(L) -> reverse(L, []). %% reverse([], R) -> R; reverse([H|T], R) -> reverse(T, [H|R]). %% Abbreviate large complex terms to avoid croaking printout %% abbr(Term) -> abbr(Term, 20). %% abbr(Tuple, N) when is_tuple(Tuple) -> list_to_tuple(abbr_tuple(Tuple, N, 1)); abbr(List, N) when is_list(List) -> abbr_list(List, N, []); abbr(Term, _) -> Term. %% abbr_tuple(Tuple, N, J) when J =< size(Tuple) -> if J > N; N =< 0 -> ['...']; true -> [abbr(element(J, Tuple), N-1)|abbr_tuple(Tuple, J+1, N)] end; abbr_tuple(_, _, _) -> []. %% abbr_list(_, 0, R) -> case io_lib:printable_list(R) of true -> reverse(R, "..."); false -> reverse(R, '...') end; abbr_list([H|T], N, R) -> M = N-1, abbr_list(T, M, [abbr(H, M)|R]); abbr_list(T, _, R) -> reverse(R, T).