aboutsummaryrefslogtreecommitdiffstats
path: root/erts/emulator/test/call_trace_SUITE.erl
diff options
context:
space:
mode:
Diffstat (limited to 'erts/emulator/test/call_trace_SUITE.erl')
-rw-r--r--erts/emulator/test/call_trace_SUITE.erl1240
1 files changed, 1240 insertions, 0 deletions
diff --git a/erts/emulator/test/call_trace_SUITE.erl b/erts/emulator/test/call_trace_SUITE.erl
new file mode 100644
index 0000000000..e0528955b0
--- /dev/null
+++ b/erts/emulator/test/call_trace_SUITE.erl
@@ -0,0 +1,1240 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1999-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%
+
+%%% Purpose : Tests the new call_trace BIF.
+
+-module(call_trace_SUITE).
+
+-export([all/1,init_per_testcase/2,fin_per_testcase/2,
+ hipe/1,process_specs/1,basic/1,flags/1,errors/1,pam/1,change_pam/1,
+ return_trace/1,exception_trace/1,on_load/1,deep_exception/1,
+ exception_nocatch/1,bit_syntax/1]).
+
+%% Helper functions.
+
+-export([bar/0,foo/0,foo/1,foo/2,expect/1,worker_foo/1,pam_foo/2,nasty/0,
+ id/1,deep/3,deep_1/3,deep_2/2,deep_3/2,deep_4/1,deep_5/1,
+ bs_sum_a/2,bs_sum_b/2]).
+
+%% Debug
+-export([abbr/1,abbr/2]).
+
+
+-include("test_server.hrl").
+
+-define(P, 20).
+
+all(suite) ->
+ Common = [errors,on_load],
+ NotHipe = [process_specs,basic,flags,pam,change_pam,return_trace,
+ exception_trace,deep_exception,exception_nocatch,bit_syntax],
+ Hipe = [hipe],
+ case test_server:is_native(?MODULE) of
+ true -> Hipe ++ Common;
+ false -> NotHipe ++ Common
+ end.
+
+init_per_testcase(Func, Config) when is_atom(Func), is_list(Config) ->
+ Dog = ?t:timetrap(?t:seconds(30)),
+ [{watchdog, Dog}|Config].
+
+fin_per_testcase(_Func, Config) ->
+ Dog = ?config(watchdog, Config),
+ ?t:timetrap_cancel(Dog).
+
+hipe(Config) when is_list(Config) ->
+ ?line 0 = erlang:trace_pattern({?MODULE,worker_foo,1}, true),
+ ?line 0 = erlang:trace_pattern({?MODULE,worker_foo,1}, true, [local]),
+ ?line AllFuncs = erlang:trace_pattern({'_','_','_'}, true),
+
+ %% Make sure that a traced, exported function can still be found.
+ ?line true = erlang:function_exported(error_handler, undefined_function, 3),
+ ?line AllFuncs = erlang:trace_pattern({'_','_','_'}, false),
+ ok.
+
+process_specs(doc) ->
+ "Tests 'all', 'new', and 'existing' for specifying processes.";
+process_specs(suite) -> [];
+process_specs(Config) when is_list(Config) ->
+ ?line Tracer = start_tracer(),
+ ?line {flags,[call]} = trace_info(self(), flags),
+ ?line {tracer,Tracer} = trace_info(self(), tracer),
+ ?line trace_func({?MODULE,worker_foo,1}, []),
+
+ %% Test the 'new' flag.
+
+ ?line {Work1A,Work1B} = start_and_trace(new, [1,2,3], A1B={3,2,1}),
+ {flags,[]} = trace_info(Work1A, flags),
+ {tracer,[]} = trace_info(Work1A, tracer),
+ {tracer,Tracer} = trace_info(Work1B, tracer),
+ {flags,[call]} = trace_info(Work1B, flags),
+ ?line expect({trace,Work1B,call,{?MODULE,worker_foo,[A1B]}}),
+ ?line unlink(Work1B),
+ ?line Mref = erlang:monitor(process, Work1B),
+ ?line exit(Work1B, kill),
+ receive
+ {'DOWN',Mref,_,_,_} -> ok
+ end,
+ ?line undefined = trace_info(Work1B, flags),
+ ?line {flags,[]} = trace_info(new, flags),
+ ?line {tracer,[]} = trace_info(new, tracer),
+
+ %% Test the 'existing' flag.
+ ?line {Work2A,_Work2B} = start_and_trace(existing, A2A=[5,6,7], [7,6,5]),
+ ?line expect({trace,Work2A,call,{?MODULE,worker_foo,[A2A]}}),
+
+ %% Test the 'all' flag.
+ ?line {Work3A,Work3B} = start_and_trace(all, A3A=[12,13], A3B=[13,12]),
+ ?line expect({trace,Work3A,call,{?MODULE,worker_foo,[A3A]}}),
+ ?line expect({trace,Work3B,call,{?MODULE,worker_foo,[A3B]}}),
+
+ ok.
+
+start_and_trace(Flag, A1, A2) ->
+ W1 = start_worker(),
+ trace_pid(Flag, true, [call]),
+ W2 = start_worker(),
+ call_worker(W1, A1),
+ call_worker(W2, A2),
+ case Flag of
+ new ->
+ {flags,[call]} = trace_info(new, flags),
+ {tracer,_} = trace_info(new, tracer);
+ _Other ->
+ ok
+ end,
+ trace_pid(Flag, false, [call]),
+ {W1,W2}.
+
+start_worker() ->
+ ?line spawn(fun worker_loop/0).
+
+call_worker(Pid, Arg) ->
+ Pid ! {self(),{call,Arg}},
+ receive
+ {result,Res} -> Res
+ after 5000 ->
+ ?line ?t:fail(no_answer_from_worker)
+ end.
+
+worker_loop() ->
+ receive
+ {From,{call,Arg}} ->
+ From ! {result,?MODULE:worker_foo(Arg)},
+ worker_loop();
+ Other ->
+ exit({unexpected_message,Other})
+ end.
+
+worker_foo(_Arg) ->
+ ok.
+
+basic(doc) ->
+ "Basic test of the call tracing (we trace one process).";
+basic(suite) -> [];
+basic(Config) when is_list(Config) ->
+ ?line start_tracer(),
+ ?line trace_info(self(), flags),
+ ?line trace_info(self(), tracer),
+ ?line 0 = trace_func({?MODULE,no_such_function,0}, []),
+ ?line {traced,undefined} =
+ trace_info({?MODULE,no_such_function,0}, traced),
+ ?line {match_spec, undefined} =
+ trace_info({?MODULE,no_such_function,0}, match_spec),
+
+ %% Trace some functions...
+
+ ?line trace_func({lists,'_','_'}, []),
+ ?line 3 = trace_func({?MODULE,foo,'_'}, true),
+ ?line 1 = trace_func({?MODULE,bar,0}, true),
+ ?line {traced,global} = trace_info({?MODULE,bar,0}, traced),
+ ?line 1 = trace_func({erlang,list_to_integer,1}, true),
+ ?line {traced,global} = trace_info({erlang,list_to_integer,1}, traced),
+
+ %% ... and call them...
+
+ ?line AList = [x,y,z],
+ ?line true = lists:member(y, AList),
+ ?line foo0 = ?MODULE:foo(),
+ ?line 4 = ?MODULE:foo(3),
+ ?line 11 = ?MODULE:foo(7, 4),
+ ?line ok = ?MODULE:bar(),
+ ?line 42 = list_to_integer(non_literal("42")),
+
+ %% ... make sure the we got trace messages (but not for ?MODULE:expect/1).
+
+ ?line Self = self(),
+ ?line ?MODULE:expect({trace,Self,call,{lists,member,[y,AList]}}),
+ ?line ?MODULE:expect({trace,Self,call,{?MODULE,foo,[]}}),
+ ?line ?MODULE:expect({trace,Self,call,{?MODULE,foo,[3]}}),
+ ?line ?MODULE:expect({trace,Self,call,{?MODULE,foo,[7,4]}}),
+ ?line ?MODULE:expect({trace,Self,call,{?MODULE,bar,[]}}),
+ ?line ?MODULE:expect({trace,Self,call,{erlang,list_to_integer,["42"]}}),
+
+ %% Turn off trace for this module and call functions...
+
+ ?line trace_func({?MODULE,'_','_'}, false),
+ ?line {traced,false} = trace_info({?MODULE,bar,0}, traced),
+ ?line foo0 = ?MODULE:foo(),
+ ?line 4 = ?MODULE:foo(3),
+ ?line 11 = ?MODULE:foo(7, 4),
+ ?line ok = ?MODULE:bar(),
+ ?line [1,2,3,4,5,6,7,8,9,10] = lists:seq(1, 10),
+ ?line 777 = list_to_integer(non_literal("777")),
+
+ %% ... turn on all trace messages...
+
+ ?line trace_func({'_','_','_'}, false),
+ ?line [b,a] = lists:reverse([a,b]),
+
+ %% Read out the remaing trace messages.
+
+ ?line ?MODULE:expect({trace,Self,call,{lists,seq,[1,10]}}),
+ ?line ?MODULE:expect({trace,Self,call,{erlang,list_to_integer,["777"]}}),
+ receive
+ Any ->
+ ?line ?t:fail({unexpected_message,Any})
+ after 1 ->
+ ok
+ end,
+
+ %% Turn on and then off tracing on all external functions.
+ %% This might cause the emulator to crasch later if it doesn't
+ %% restore all export entries properly.
+
+ ?line AllFuncs = trace_func({'_','_','_'}, true),
+ io:format("AllFuncs = ~p", [AllFuncs]),
+ %% Make sure that a traced, exported function can still be found.
+ ?line true = erlang:function_exported(error_handler, undefined_function, 3),
+ ?line AllFuncs = trace_func({'_','_','_'}, false),
+ ?line erlang:trace_delivered(all),
+ receive
+ {trace_delivered,_,_} -> ok
+ end,
+ c:flush(), % Print the traces messages.
+ c:flush(), % Print the traces messages.
+
+ ?line {traced,false} = trace_info({erlang,list_to_integer,1}, traced),
+
+ ok.
+
+non_literal(X) -> X.
+
+bar() ->
+ ok.
+
+foo() -> foo0.
+foo(X) -> X+1.
+foo(X, Y) -> X+Y.
+
+flags(doc) -> "Test flags (arity, timestamp) for call_trace/3. "
+ "Also, test the '{tracer,Pid}' option.";
+flags(Config) when is_list(Config) ->
+ ?line Tracer = start_tracer_loop(),
+ ?line trace_pid(self(), true, [call,{tracer,Tracer}]),
+
+ %% Trace some functions...
+
+ ?line trace_func({filename,'_','_'}, true),
+
+ %% ... and call them...
+
+ ?line Self = self(),
+ ?line filename:absname("nisse"),
+ ?line ?MODULE:expect({trace,Self,call,{filename,absname,["nisse"]}}),
+ ?line trace_pid(Self, true, [call,arity]),
+ ?line filename:absname("kalle"),
+ ?line filename:absname("kalle", "/root"),
+ ?line ?MODULE:expect({trace,Self,call,{filename,absname,1}}),
+ ?line ?MODULE:expect({trace,Self,call,{filename,absname,2}}),
+ ?line trace_info(Self, flags),
+
+ %% Timestamp + arity.
+
+ flag_test(fun() ->
+ ?line trace_pid(Self, true, [timestamp]),
+ ?line "dum" = filename:basename("/abcd/dum"),
+ ?line Ts = expect({trace_ts,Self,call,{filename,basename,1},ts}),
+ ?line trace_info(Self, flags),
+ Ts
+ end),
+
+ %% Timestamp.
+
+ ?line AnArg = "/abcd/hejsan",
+ flag_test(fun() ->
+ ?line trace_pid(Self, false, [arity]),
+ ?line "hejsan" = filename:basename(AnArg),
+ ?line Ts = expect({trace_ts,Self,call,
+ {filename,basename,[AnArg]},ts}),
+ ?line trace_info(Self, flags),
+ Ts
+ end),
+
+ %% All flags turned off.
+
+ ?line trace_pid(Self, false, [timestamp]),
+ ?line AnotherArg = filename:join(AnArg, "hoppsan"),
+ ?line "hoppsan" = filename:basename(AnotherArg),
+ ?line expect({trace,Self,call,{filename,join,[AnArg,"hoppsan"]}}),
+ ?line expect({trace,Self,call,{filename,basename,[AnotherArg]}}),
+ ?line trace_info(Self, flags),
+
+ ok.
+
+flag_test(Test) ->
+ Now = now(),
+ Ts = Test(),
+ case timer:now_diff(Ts, Now) of
+ Time when Time < 5*1000000 ->
+ %% Reasonable short time.
+ ok;
+ _Diff ->
+ %% Too large difference.
+ io:format("Now = ~p\n", [Now]),
+ io:format("Ts = ~p\n", [Ts]),
+ ?line ?t:fail()
+ end,
+ flag_test_cpu_timestamp(Test).
+
+flag_test_cpu_timestamp(Test) ->
+ try erlang:trace(all, true, [cpu_timestamp]) of
+ _ ->
+ io:format("CPU timestamps"),
+ Ts = Test(),
+ erlang:trace(all, false, [cpu_timestamp]),
+ Origin = {0,0,0},
+ Hour = 3600*1000000,
+ case timer:now_diff(Ts, Origin) of
+ Diff when Diff < 4*Hour ->
+ %% In the worst case, CPU timestamps count from when this
+ %% Erlang emulator was started. The above test is a conservative
+ %% test that all CPU timestamps should pass.
+ ok;
+ _Time ->
+ io:format("Strange CPU timestamp: ~p", [Ts]),
+ ?line ?t:fail()
+ end,
+ io:format("Turned off CPU timestamps")
+ catch
+ error:badarg -> ok
+ end.
+
+errors(doc) -> "Test bad arguments for trace/3 and trace_pattern/3.";
+errors(suite) -> [];
+errors(Config) when is_list(Config) ->
+ ?line expect_badarg_pid(aaa, true, []),
+ ?line expect_badarg_pid({pid,dum}, false, []),
+ ?line expect_badarg_func({'_','_',1}, []),
+ ?line expect_badarg_func({'_',gosh,1}, []),
+ ?line expect_badarg_func({xxx,'_',2}, []),
+ ?line expect_badarg_func({xxx,yyy,b}, glurp),
+ ok.
+
+expect_badarg_pid(What, How, Flags) ->
+ case catch erlang:trace(What, How, Flags) of
+ {'EXIT',{badarg,Where}} ->
+ io:format("trace(~p, ~p, ~p) ->\n {'EXIT',{badarg,~p}}",
+ [What,How,Flags,Where]),
+ ok;
+ Other ->
+ io:format("trace(~p, ~p, ~p) -> ~p",
+ [What,How,Flags,Other]),
+ ?t:fail({unexpected,Other})
+ end.
+
+expect_badarg_func(MFA, Pattern) ->
+ case catch erlang:trace_pattern(MFA, Pattern) of
+ {'EXIT',{badarg,Where}} ->
+ io:format("trace_pattern(~p, ~p) ->\n {'EXIT',{badarg,~p}}",
+ [MFA,Pattern,Where]),
+ ok;
+ Other ->
+ io:format("trace_pattern(~p, ~p) -> ~p",
+ [MFA, Pattern, Other]),
+ ?t:fail({unexpected,Other})
+ end.
+
+pam(doc) -> "Basic test of PAM.";
+pam(suite) -> [];
+pam(Config) when is_list(Config) ->
+ ?line start_tracer(),
+ ?line Self = self(),
+
+ %% Build the match program.
+ ?line Prog1 = {[{a,tuple},'$1'],[],[]},
+ ?line Prog2 = {[{a,bigger,tuple},'$1'],[],[{message,'$1'}]},
+ ?line MatchProg = [Prog1,Prog2],
+ ?line pam_trace(MatchProg),
+
+ %% Do some calls.
+ ?line ?MODULE:pam_foo(not_a_tuple, [a,b]),
+ ?line ?MODULE:pam_foo({a,tuple}, [a,list]),
+ ?line ?MODULE:pam_foo([this,one,will,'not',match], dummy_arg),
+ ?line LongList = lists:seq(1,10),
+ ?line ?MODULE:pam_foo({a,bigger,tuple}, LongList),
+
+ %% Check that we get the correct trace messages.
+ ?line expect({trace,Self,call,{?MODULE,pam_foo,[{a,tuple},[a,list]]}}),
+ ?line expect({trace,Self,call,
+ {?MODULE,pam_foo,[{a,bigger,tuple},LongList]},
+ LongList}),
+
+ ?line trace_func({?MODULE,pam_foo,'_'}, false),
+ ok.
+
+pam_trace(Prog) ->
+ 1 = trace_func({?MODULE,pam_foo,'_'}, Prog),
+ {match_spec,Prog} = trace_info({?MODULE,pam_foo,2}, match_spec),
+ ok.
+
+pam_foo(A, B) ->
+ {ok,A,B}.
+
+
+change_pam(doc) -> "Test changing PAM programs for a function.";
+change_pam(suite) -> [];
+change_pam(Config) when is_list(Config) ->
+ ?line start_tracer(),
+ ?line Self = self(),
+
+ %% Install the first match program.
+ %% Test using timestamp at the same time.
+
+ ?line trace_pid(Self, true, [call,arity,timestamp]),
+ ?line Prog1 = [{['$1','$2'],[],[{message,'$1'}]}],
+ ?line change_pam_trace(Prog1),
+ ?line [x,y] = lists:append(id([x]), id([y])),
+ ?line {heap_size,_} = erlang:process_info(Self, heap_size),
+ ?line expect({trace_ts,Self,call,{lists,append,2},[x],ts}),
+ ?line expect({trace_ts,Self,call,{erlang,process_info,2},Self,ts}),
+
+ %% Install a new PAM program.
+
+ ?line Prog2 = [{['$1','$2'],[],[{message,'$2'}]}],
+ ?line change_pam_trace(Prog2),
+ ?line [xx,yy] = lists:append(id([xx]), id([yy])),
+ ?line {current_function,_} = erlang:process_info(Self, current_function),
+ ?line expect({trace_ts,Self,call,{lists,append,2},[yy],ts}),
+ ?line expect({trace_ts,Self,call,{erlang,process_info,2},current_function,ts}),
+
+ ?line 1 = trace_func({lists,append,2}, false),
+ ?line 1 = trace_func({erlang,process_info,2}, false),
+ ?line {match_spec,false} = trace_info({lists,append,2}, match_spec),
+ ?line {match_spec,false} = trace_info({erlang,process_info,2}, match_spec),
+
+ ok.
+
+change_pam_trace(Prog) ->
+ 1 = trace_func({lists,append,2}, Prog),
+ 1 = trace_func({erlang,process_info,2}, Prog),
+ {match_spec,Prog} = trace_info({lists,append,2}, match_spec),
+ {match_spec,Prog} = trace_info({erlang,process_info,2}, match_spec),
+ ok.
+
+return_trace(doc) -> "Test the new return trace.";
+return_trace(suite) -> [];
+return_trace(Config) when is_list(Config) ->
+ return_trace().
+
+return_trace() ->
+ X = {save,me},
+ ?line start_tracer(),
+ ?line Self = self(),
+
+ %% Test call and return trace and timestamp.
+
+ ?line trace_pid(Self, true, [call,timestamp]),
+ Stupid = {pointless,tuple},
+ ?line Prog1 = [{['$1','$2'],[],[{return_trace},{message,{Stupid}}]}],
+ ?line 1 = trace_func({lists,append,2}, Prog1),
+ ?line 1 = trace_func({erlang,process_info,2}, Prog1),
+ ?line {match_spec,Prog1} = trace_info({lists,append,2}, match_spec),
+ ?line {match_spec,Prog1} = trace_info({erlang,process_info,2}, match_spec),
+
+ ?line [x,y] = lists:append(id([x]), id([y])),
+ Current = {current_function,{?MODULE,return_trace,0}},
+ ?line Current = erlang:process_info(Self, current_function),
+ ?line expect({trace_ts,Self,call,{lists,append,[[x],[y]]},Stupid,ts}),
+ ?line expect({trace_ts,Self,return_from,{lists,append,2},[x,y],ts}),
+ ?line expect({trace_ts,Self,call,{erlang,process_info,[Self,current_function]},
+ Stupid,ts}),
+ ?line expect({trace_ts,Self,return_from,{erlang,process_info,2},Current,ts}),
+
+ %% Try catch/exit.
+
+ ?line 1 = trace_func({?MODULE,nasty,0}, [{[],[],[{return_trace},{message,false}]}]),
+ ?line {'EXIT',good_bye} = (catch ?MODULE:nasty()),
+ ?line 1 = trace_func({?MODULE,nasty,0}, false),
+
+ %% Turn off trace.
+
+ ?line 1 = trace_func({lists,append,2}, false),
+ ?line 1 = trace_func({erlang,process_info,2}, false),
+ ?line {match_spec,false} = trace_info({lists,append,2}, match_spec),
+ ?line {match_spec,false} = trace_info({erlang,process_info,2}, match_spec),
+
+ %% No timestamp, no trace message for call.
+
+ ?line trace_pid(Self, false, [timestamp]),
+ ?line Prog2 = [{['$1','$2'],[],[{return_trace},{message,false}]},
+ {['$1'],[],[{return_trace},{message,false}]}],
+ ?line 1 = trace_func({lists,seq,2}, Prog2),
+ ?line 1 = trace_func({erlang,atom_to_list,1}, Prog2),
+ ?line {match_spec,Prog2} = trace_info({lists,seq,2}, match_spec),
+ ?line {match_spec,Prog2} = trace_info({erlang,atom_to_list,1}, match_spec),
+
+ ?line lists:seq(2, 7),
+ ?line atom_to_list(non_literal(nisse)),
+ ?line expect({trace,Self,return_from,{lists,seq,2},[2,3,4,5,6,7]}),
+ ?line expect({trace,Self,return_from,{erlang,atom_to_list,1},"nisse"}),
+
+ %% Turn off trace.
+
+ ?line 1 = trace_func({lists,seq,2}, false),
+ ?line 1 = trace_func({erlang,atom_to_list,1}, false),
+ ?line {match_spec,false} = trace_info({lists,seq,2}, match_spec),
+ ?line {match_spec,false} = trace_info({erlang,atom_to_list,1}, match_spec),
+
+ ?line {save,me} = X,
+
+ ok.
+
+nasty() ->
+ exit(good_bye).
+
+exception_trace(doc) -> "Test the new exception trace.";
+exception_trace(suite) -> [];
+exception_trace(Config) when is_list(Config) ->
+ exception_trace().
+
+exception_trace() ->
+ X = {save,me},
+ ?line start_tracer(),
+ ?line Self = self(),
+
+ %% Test call and return trace and timestamp.
+
+ ?line trace_pid(Self, true, [call,timestamp]),
+ Stupid = {pointless,tuple},
+ ?line Prog1 = [{['$1','$2'],[],[{exception_trace},{message,{Stupid}}]}],
+ ?line 1 = trace_func({lists,append,2}, Prog1),
+ ?line 1 = trace_func({erlang,process_info,2}, Prog1),
+ ?line {match_spec,Prog1} = trace_info({lists,append,2}, match_spec),
+ ?line {match_spec,Prog1} =
+ trace_info({erlang,process_info,2}, match_spec),
+
+ ?line [x,y] = lists:append(id([x]), id([y])),
+ Current = {current_function,{?MODULE,exception_trace,0}},
+ ?line Current = erlang:process_info(Self, current_function),
+ ?line expect({trace_ts,Self,call,{lists,append,[[x],[y]]},Stupid,ts}),
+ ?line expect({trace_ts,Self,return_from,{lists,append,2},[x,y],ts}),
+ ?line expect({trace_ts,Self,call,{erlang,process_info,
+ [Self,current_function]},
+ Stupid,ts}),
+ ?line expect({trace_ts,Self,return_from,
+ {erlang,process_info,2},Current,ts}),
+
+ %% Try catch/exit.
+
+ ?line 1 = trace_func({?MODULE,nasty,0},
+ [{[],[],[{exception_trace},{message,false}]}]),
+ ?line {'EXIT',good_bye} = (catch ?MODULE:nasty()),
+ ?line expect({trace_ts,Self,exception_from,
+ {?MODULE,nasty,0},{exit,good_bye},ts}),
+ ?line 1 = trace_func({?MODULE,nasty,0}, false),
+
+ %% Turn off trace.
+
+ ?line 1 = trace_func({lists,append,2}, false),
+ ?line 1 = trace_func({erlang,process_info,2}, false),
+ ?line {match_spec,false} = trace_info({lists,append,2}, match_spec),
+ ?line {match_spec,false} =
+ trace_info({erlang,process_info,2}, match_spec),
+
+ %% No timestamp, no trace message for call.
+
+ ?line trace_pid(Self, false, [timestamp]),
+ ?line Prog2 = [{['$1','$2'],[],[{exception_trace},{message,false}]},
+ {['$1'],[],[{exception_trace},{message,false}]}],
+ ?line 1 = trace_func({lists,seq,2}, Prog2),
+ ?line 1 = trace_func({erlang,atom_to_list,1}, Prog2),
+ ?line {match_spec,Prog2} = trace_info({lists,seq,2}, match_spec),
+ ?line {match_spec,Prog2} =
+ trace_info({erlang,atom_to_list,1}, match_spec),
+
+ ?line lists:seq(2, 7),
+ ?line atom_to_list(non_literal(nisse)),
+ ?line expect({trace,Self,return_from,{lists,seq,2},[2,3,4,5,6,7]}),
+ ?line expect({trace,Self,return_from,{erlang,atom_to_list,1},"nisse"}),
+
+ %% Turn off trace.
+
+ ?line 1 = trace_func({lists,seq,2}, false),
+ ?line 1 = trace_func({erlang,atom_to_list,1}, false),
+ ?line {match_spec,false} = trace_info({lists,seq,2}, match_spec),
+ ?line {match_spec,false} =
+ trace_info({erlang,atom_to_list,1}, match_spec),
+
+ ?line expect(),
+ ?line {save,me} = X,
+ ok.
+
+on_load(doc) -> "Test the on_load argument for trace_pattern/3.";
+on_load(suite) -> [];
+on_load(Config) when is_list(Config) ->
+ ?line 0 = erlang:trace_pattern(on_load, []),
+ ?line {traced,global} = erlang:trace_info(on_load, traced),
+ ?line {match_spec,[]} = erlang:trace_info(on_load, match_spec),
+
+ ?line 0 = erlang:trace_pattern(on_load, true, [local]),
+ ?line {traced,local} = erlang:trace_info(on_load, traced),
+ ?line {match_spec,[]} = erlang:trace_info(on_load, match_spec),
+
+ ?line 0 = erlang:trace_pattern(on_load, false, [local]),
+ ?line {traced,false} = erlang:trace_info(on_load, traced),
+ ?line {match_spec,false} = erlang:trace_info(on_load, match_spec),
+
+ ?line Pam1 = [{[],[],[{message,false}]}],
+ ?line 0 = erlang:trace_pattern(on_load, Pam1),
+ ?line {traced,global} = erlang:trace_info(on_load, traced),
+ ?line {match_spec,Pam1} = erlang:trace_info(on_load, match_spec),
+
+ ?line 0 = erlang:trace_pattern(on_load, true, [local]),
+ ?line 0 = erlang:trace_pattern(on_load, false, [local]),
+
+ ok.
+
+
+
+deep_exception(doc) -> "Test the new exception trace.";
+deep_exception(suite) -> [];
+deep_exception(Config) when is_list(Config) ->
+ deep_exception().
+
+deep_exception() ->
+ ?line start_tracer(),
+ ?line Self = self(),
+ ?line N = 200000,
+ ?line LongImproperList = seq(1, N-1, N),
+
+ Prog = [{'_',[],[{exception_trace}]}],
+%% ?line 1 = trace_pid(Self, true, [call]),
+ ?line 1 = trace_func({?MODULE,deep,'_'}, Prog),
+ ?line 1 = trace_func({?MODULE,deep_1,'_'}, Prog),
+ ?line 1 = trace_func({?MODULE,deep_2,'_'}, Prog),
+ ?line 1 = trace_func({?MODULE,deep_3,'_'}, Prog),
+ ?line 1 = trace_func({?MODULE,deep_4,'_'}, Prog),
+ ?line 1 = trace_func({?MODULE,deep_5,'_'}, Prog),
+ ?line 1 = trace_func({?MODULE,id,'_'}, Prog),
+ ?line 1 = trace_func({erlang,'++','_'}, Prog),
+ ?line 1 = trace_func({erlang,exit,1}, Prog),
+ ?line 1 = trace_func({erlang,throw,1}, Prog),
+ ?line 2 = trace_func({erlang,error,'_'}, Prog),
+ ?line 1 = trace_func({lists,reverse,2}, Prog),
+
+ ?line deep_exception(?LINE, exit, [paprika], 1,
+ [{trace,Self,call,{erlang,exit,[paprika]}},
+ {trace,Self,exception_from,{erlang,exit,1},
+ {exit,paprika}}],
+ exception_from, {exit,paprika}),
+ ?line deep_exception(?LINE, throw, [3.14], 2,
+ [{trace,Self,call,{erlang,throw,[3.14]}},
+ {trace,Self,exception_from,{erlang,throw,1},
+ {throw,3.14}}],
+ exception_from, {throw,3.14}),
+ ?line deep_exception(?LINE, error, [{paprika}], 3,
+ [{trace,Self,call,{erlang,error,[{paprika}]}},
+ {trace,Self,exception_from,{erlang,error,1},
+ {error,{paprika}}}],
+ exception_from, {error,{paprika}}),
+ ?line deep_exception(?LINE, error, ["{paprika}",[]], 3,
+ [{trace,Self,call,{erlang,error,["{paprika}",[]]}},
+ {trace,Self,exception_from,{erlang,error,2},
+ {error,"{paprika}"}}],
+ exception_from, {error,"{paprika}"}),
+ ?line deep_exception(?LINE, id, [broccoli], 4, [],
+ return_from, broccoli),
+ ?line deep_exception(
+ ?LINE, append, [1,2], 5,
+ [{trace,Self,call,{erlang,'++',[1,2]}},
+ {trace,Self,exception_from,{erlang,'++',2},{error,badarg}}],
+ exception_from, {error,badarg}),
+ ?line deep_exception(?LINE, '=', [1,2], 6, [],
+ exception_from, {error,{badmatch,2}}),
+ %%
+ ?line io:format("== Subtest: ~w", [?LINE]),
+ ?line try lists:reverse(LongImproperList, []) of
+ R1 -> test_server:fail({returned,abbr(R1)})
+ catch error:badarg -> ok
+ end,
+ ?line expect(fun ({trace,S,call,{lists,reverse,[L1,L2]}})
+ when is_list(L1), is_list(L2), S == Self ->
+ next;
+ ({trace,S,exception_from,
+ {lists,reverse,2},{error,badarg}})
+ when S == Self ->
+ expected;
+ ('_') ->
+ {trace,Self,exception_from,
+ {lists,reverse,2},{error,badarg}};
+ (_) ->
+ {unexpected,
+ {trace,Self,exception_from,
+ {lists,reverse,2},{error,badarg}}}
+ end),
+ ?line deep_exception(?LINE, deep_5, [1,2], 7,
+ [{trace,Self,call,{erlang,error,[undef]}},
+ {trace,Self,exception_from,{erlang,error,1},
+ {error,undef}}],
+ exception_from, {error,undef}),
+ ?line deep_exception(?LINE, deep_5, [undef], 8,
+ [{trace,Self,call,{?MODULE,deep_5,[undef]}},
+ {trace,Self,exception_from,{?MODULE,deep_5,1},
+ {error,function_clause}}],
+ exception_from, {error,function_clause}),
+
+ %% Apply
+ %%
+ ?line deep_exception(?LINE, apply, [erlang,error,[[mo|rot]]], 1,
+ [{trace,Self,call,{erlang,error,[[mo|rot]]}},
+ {trace,Self,exception_from,{erlang,error,1},
+ {error,[mo|rot]}}],
+ exception_from, {error,[mo|rot]}),
+ ?line deep_exception(?LINE, apply, [erlang,error,[[mo|"rot"],[]]], 1,
+ [{trace,Self,call,{erlang,error,[[mo|"rot"],[]]}},
+ {trace,Self,exception_from,{erlang,error,2},
+ {error,[mo|"rot"]}}],
+ exception_from, {error,[mo|"rot"]}),
+ ?line Morot = make_ref(),
+ ?line deep_exception(?LINE, apply, [erlang,throw,[Morot]], 3,
+ [{trace,Self,call,{erlang,throw,[Morot]}},
+ {trace,Self,exception_from,{erlang,throw,1},
+ {throw,Morot}}],
+ exception_from, {throw,Morot}),
+ ?line deep_exception(?LINE, apply, [erlang,exit,[["morot"|Morot]]], 2,
+ [{trace,Self,call,{erlang,exit,[["morot"|Morot]]}},
+ {trace,Self,exception_from,{erlang,exit,1},
+ {exit,["morot"|Morot]}}],
+ exception_from, {exit,["morot"|Morot]}),
+ ?line deep_exception(
+ ?LINE, apply, [?MODULE,id,[spenat]], 4,
+ [{trace,Self,call,{?MODULE,id,[spenat]}},
+ {trace,Self,return_from,{?MODULE,id,1},spenat}],
+ return_from, spenat),
+ ?line deep_exception(
+ ?LINE, apply, [erlang,'++',[1,2]], 5,
+ [{trace,Self,call,{erlang,'++',[1,2]}},
+ {trace,Self,exception_from,{erlang,'++',2},{error,badarg}}],
+ exception_from, {error,badarg}),
+ ?line io:format("== Subtest: ~w", [?LINE]),
+ ?line try apply(lists, reverse, [LongImproperList, []]) of
+ R2 -> test_server:fail({returned,abbr(R2)})
+ catch error:badarg -> ok
+ end,
+ ?line expect(fun ({trace,S,call,{lists,reverse,[L1,L2]}})
+ when is_list(L1), is_list(L2), S == Self ->
+ next;
+ ({trace,S,exception_from,
+ {lists,reverse,2},{error,badarg}})
+ when S == Self ->
+ expected;
+ ('_') ->
+ {trace,Self,exception_from,
+ {lists,reverse,2},{error,badarg}};
+ (_) ->
+ {unexpected,
+ {trace,Self,exception_from,
+ {lists,reverse,2},{error,badarg}}}
+ end),
+ ?line deep_exception(?LINE, apply, [?MODULE,deep_5,[1,2]], 7,
+ [{trace,Self,call,{erlang,error,[undef]}},
+ {trace,Self,exception_from,{erlang,error,1},
+ {error,undef}}],
+ exception_from, {error,undef}),
+ ?line deep_exception(?LINE, apply, [?MODULE,deep_5,[undef]], 8,
+ [{trace,Self,call,{?MODULE,deep_5,[undef]}},
+ {trace,Self,exception_from,{?MODULE,deep_5,1},
+ {error,function_clause}}],
+ exception_from, {error,function_clause}),
+ %% Apply of fun
+ %%
+ ?line deep_exception(?LINE, apply,
+ [fun () ->
+ erlang:error([{"palsternacka",3.14},17])
+ end, []], 1,
+ [{trace,Self,call,
+ {erlang,error,[[{"palsternacka",3.14},17]]}},
+ {trace,Self,exception_from,{erlang,error,1},
+ {error,[{"palsternacka",3.14},17]}}],
+ exception_from, {error,[{"palsternacka",3.14},17]}),
+ ?line deep_exception(?LINE, apply,
+ [fun () ->
+ erlang:error(["palsternacka",17], [])
+ end, []], 1,
+ [{trace,Self,call,
+ {erlang,error,[["palsternacka",17],[]]}},
+ {trace,Self,exception_from,{erlang,error,2},
+ {error,["palsternacka",17]}}],
+ exception_from, {error,["palsternacka",17]}),
+ ?line deep_exception(?LINE, apply,
+ [fun () -> erlang:throw(Self) end, []], 2,
+ [{trace,Self,call,{erlang,throw,[Self]}},
+ {trace,Self,exception_from,{erlang,throw,1},
+ {throw,Self}}],
+ exception_from, {throw,Self}),
+ ?line deep_exception(?LINE, apply,
+ [fun () ->
+ erlang:exit({1,2,3,4,[5,palsternacka]})
+ end, []], 3,
+ [{trace,Self,call,
+ {erlang,exit,[{1,2,3,4,[5,palsternacka]}]}},
+ {trace,Self,exception_from,{erlang,exit,1},
+ {exit,{1,2,3,4,[5,palsternacka]}}}],
+ exception_from, {exit,{1,2,3,4,[5,palsternacka]}}),
+ ?line deep_exception(?LINE, apply,
+ [fun () -> ?MODULE:id(bladsallad) end, []], 4,
+ [{trace,Self,call,{?MODULE,id,[bladsallad]}},
+ {trace,Self,return_from,{?MODULE,id,1},bladsallad}],
+ return_from, bladsallad),
+ ?line deep_exception(?LINE, apply,
+ [fun (A, B) -> A ++ B end, [1,2]], 5,
+ [{trace,Self,call,{erlang,'++',[1,2]}},
+ {trace,Self,exception_from,
+ {erlang,'++',2},{error,badarg}}],
+ exception_from, {error,badarg}),
+ ?line deep_exception(?LINE, apply, [fun (A, B) -> A = B end, [1,2]], 6,
+ [],
+ exception_from, {error,{badmatch,2}}),
+ ?line io:format("== Subtest: ~w", [?LINE]),
+ ?line try apply(fun() -> lists:reverse(LongImproperList, []) end, []) of
+ R3 -> test_server:fail({returned,abbr(R3)})
+ catch error:badarg -> ok
+ end,
+ ?line expect(fun ({trace,S,call,{lists,reverse,[L1,L2]}})
+ when is_list(L1), is_list(L2), S == Self ->
+ next;
+ ({trace,S,exception_from,
+ {lists,reverse,2},{error,badarg}})
+ when S == Self ->
+ expected;
+ ('_') ->
+ {trace,Self,exception_from,
+ {lists,reverse,2},{error,badarg}};
+ (_) ->
+ {unexpected,
+ {trace,Self,exception_from,
+ {lists,reverse,2},{error,badarg}}}
+ end),
+ ?line deep_exception(?LINE, apply,
+ [fun () -> ?MODULE:deep_5(1,2) end, []], 7,
+ [{trace,Self,call,{erlang,error,[undef]}},
+ {trace,Self,exception_from,{erlang,error,1},
+ {error,undef}}],
+ exception_from, {error,undef}),
+ ?line deep_exception(?LINE, apply,
+ [fun () -> ?MODULE:deep_5(undef) end, []], 8,
+ [{trace,Self,call,{?MODULE,deep_5,[undef]}},
+ {trace,Self,exception_from,{?MODULE,deep_5,1},
+ {error,function_clause}}],
+ exception_from, {error,function_clause}),
+
+ ?line trace_func({?MODULE,'_','_'}, false),
+ ?line trace_func({erlang,'_','_'}, false),
+ ?line trace_func({lists,'_','_'}, false),
+ ?line expect(),
+ ?line ok.
+
+
+deep_exception(Line, B, Q, N, Extra, Tag, R) ->
+ ?line Self = self(),
+ ?line io:format("== Subtest: ~w", [Line]),
+ ?line Result = ?MODULE:deep(N, B, Q),
+ ?line Result = deep_expect(Self, B, Q, N, Extra, Tag, R).
+
+deep_expect(Self, B, Q, N, Extra, Tag, R) ->
+ ?line expect({trace,Self,call,{?MODULE,deep,[N,B,Q]}}),
+ ?line Result = deep_expect_N(Self, B, Q, N, Extra, Tag, R),
+ ?line expect({trace,Self,return_from,{?MODULE,deep,3},Result}),
+ ?line Result.
+
+deep_expect_N(Self, B, Q, N, Extra, Tag, R) ->
+ deep_expect_N(Self, B, Q, N, Extra, Tag, R, N).
+
+deep_expect_N(Self, B, Q, N, Extra, Tag, R, J) when J > 0 ->
+ ?line expect({trace,Self,call,{?MODULE,deep_1,[J,B,Q]}}),
+ ?line deep_expect_N(Self, B, Q, N, Extra, Tag, R, J-1);
+deep_expect_N(Self, B, Q, N, Extra, Tag, R, 0) ->
+ ?line expect({trace,Self,call,{?MODULE,deep_2,[B,Q]}}),
+ ?line expect({trace,Self,call,{?MODULE,deep_3,[B,Q]}}),
+ ?line expect({trace,Self,return_from,{?MODULE,deep_3,2},{B,Q}}),
+ ?line expect({trace,Self,call,{?MODULE,deep_4,[{B,Q}]}}),
+ ?line expect({trace,Self,call,{?MODULE,id,[{B,Q}]}}),
+ ?line expect({trace,Self,return_from,{?MODULE,id,1},{B,Q}}),
+ ?line deep_expect_Extra(Self, N, Extra, Tag, R),
+ ?line expect({trace,Self,Tag,{?MODULE,deep_4,1},R}),
+ ?line expect({trace,Self,Tag,{?MODULE,deep_2,2},R}),
+ ?line deep_expect_N(Self, N, Tag, R).
+
+deep_expect_Extra(Self, N, [E|Es], Tag, R) ->
+ ?line expect(E),
+ ?line deep_expect_Extra(Self, N, Es, Tag, R);
+deep_expect_Extra(_Self, _N, [], _Tag, _R) ->
+ ?line ok.
+
+deep_expect_N(Self, N, Tag, R) when N > 0 ->
+ ?line expect({trace,Self,Tag,{?MODULE,deep_1,3},R}),
+ ?line deep_expect_N(Self, N-1, Tag, R);
+deep_expect_N(_Self, 0, return_from, R) ->
+ ?line {value,R};
+deep_expect_N(_Self, 0, exception_from, R) ->
+ ?line R.
+
+
+
+exception_nocatch(doc) -> "Test the new exception trace.";
+exception_nocatch(suite) -> [];
+exception_nocatch(Config) when is_list(Config) ->
+ exception_nocatch().
+
+exception_nocatch() ->
+ Prog = [{'_',[],[{exception_trace}]}],
+ ?line 1 = erlang:trace_pattern({?MODULE,deep_1,'_'}, Prog),
+ ?line 1 = erlang:trace_pattern({?MODULE,deep_2,'_'}, Prog),
+ ?line 1 = erlang:trace_pattern({?MODULE,deep_3,'_'}, Prog),
+ ?line 1 = erlang:trace_pattern({?MODULE,deep_4,'_'}, Prog),
+ ?line 1 = erlang:trace_pattern({?MODULE,deep_5,'_'}, Prog),
+ ?line 1 = erlang:trace_pattern({?MODULE,id,'_'}, Prog),
+ ?line 1 = erlang:trace_pattern({erlang,exit,1}, Prog),
+ ?line 1 = erlang:trace_pattern({erlang,throw,1}, Prog),
+ ?line 2 = erlang:trace_pattern({erlang,error,'_'}, Prog),
+ ?line Q1 = {make_ref(),Prog},
+ ?line T1 =
+ exception_nocatch(?LINE, exit, [Q1], 3,
+ [{trace,t1,call,{erlang,exit,[Q1]}},
+ {trace,t1,exception_from,{erlang,exit,1},
+ {exit,Q1}}],
+ exception_from, {exit,Q1}),
+ ?line expect({trace,T1,exit,Q1}),
+ ?line Q2 = {cake,14.125},
+ ?line T2 =
+ exception_nocatch(?LINE, throw, [Q2], 2,
+ [{trace,t2,call,{erlang,throw,[Q2]}},
+ {trace,t2,exception_from,{erlang,throw,1},
+ {error,{nocatch,Q2}}}],
+ exception_from, {error,{nocatch,Q2}}),
+ ?line expect({trace,T2,exit,{{nocatch,Q2},[{erlang,throw,[Q2]},
+ {?MODULE,deep_4,1}]}}),
+ ?line Q3 = {dump,[dump,{dump}]},
+ ?line T3 =
+ exception_nocatch(?LINE, error, [Q3], 4,
+ [{trace,t3,call,{erlang,error,[Q3]}},
+ {trace,t3,exception_from,{erlang,error,1},
+ {error,Q3}}],
+ exception_from, {error,Q3}),
+ ?line expect({trace,T3,exit,{Q3,[{erlang,error,[Q3]},
+ {?MODULE,deep_4,1}]}}),
+ ?line T4 =
+ exception_nocatch(?LINE, '=', [17,4711], 5, [],
+ exception_from, {error,{badmatch,4711}}),
+ ?line expect({trace,T4,exit,{{badmatch,4711},[{?MODULE,deep_4,1}]}}),
+ %%
+ ?line erlang:trace_pattern({?MODULE,'_','_'}, false),
+ ?line erlang:trace_pattern({erlang,'_','_'}, false),
+ ?line expect(),
+ ?line ok.
+
+exception_nocatch(Line, B, Q, N, Extra, Tag, R) ->
+ ?line io:format("== Subtest: ~w", [Line]),
+ ?line Go = make_ref(),
+ ?line Tracee =
+ spawn(fun () ->
+ receive
+ Go ->
+ deep_1(N, B, Q)
+ end
+ end),
+ ?line 1 = erlang:trace(Tracee, true, [call,return_to,procs]),
+ ?line Tracee ! Go,
+ ?line deep_expect_N(Tracee, B, Q, N-1,
+ [setelement(2, T, Tracee) || T <- Extra], Tag, R),
+ ?line Tracee.
+
+%% Make sure that code that uses the optimized bit syntax matching
+%% can be traced without crashing the emulator. (Actually, it seems
+%% that we can't trigger the bug using external call trace, but we
+%% will keep the test case anyway.)
+
+bit_syntax(Config) when is_list(Config) ->
+ ?line start_tracer(),
+ ?line 1 = trace_func({?MODULE,bs_sum_a,'_'}, []),
+ ?line 1 = trace_func({?MODULE,bs_sum_b,'_'}, []),
+
+ ?line 6 = call_bs_sum_a(<<1,2,3>>),
+ ?line 10 = call_bs_sum_b(<<1,2,3,4>>),
+
+ ?line trace_func({?MODULE,'_','_'}, false),
+ ?line erlang:trace_delivered(all),
+ receive
+ {trace_delivered,_,_} -> ok
+ end,
+
+ Self = self(),
+ ?line expect({trace,Self,call,{?MODULE,bs_sum_a,[<<2,3>>,1]}}),
+ ?line expect({trace,Self,call,{?MODULE,bs_sum_b,[1,<<2,3,4>>]}}),
+
+ ok.
+
+call_bs_sum_a(<<H,T/binary>>) ->
+ ?MODULE:bs_sum_a(T, H).
+
+call_bs_sum_b(<<H,T/binary>>) ->
+ ?MODULE:bs_sum_b(H, T).
+
+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.
+
+
+
+
+%%% Help functions.
+
+expect() ->
+ case flush() of
+ [] -> ok;
+ Msgs ->
+ test_server:fail({unexpected,abbr(Msgs)})
+ end.
+
+expect({trace_ts,Pid,Type,MFA,Term,ts}=Message) ->
+ receive
+ M ->
+ case M of
+ {trace_ts,Pid,Type,MFA,Term,Ts}=MessageTs ->
+ ok = io:format("Expected and got ~p", [abbr(MessageTs)]),
+ Ts;
+ _ ->
+ io:format("Expected ~p; got ~p", [abbr(Message),abbr(M)]),
+ test_server:fail({unexpected,abbr([M|flush()])})
+ end
+ after 5000 ->
+ io:format("Expected ~p; got nothing", [abbr(Message)]),
+ test_server:fail(no_trace_message)
+ end;
+expect({trace_ts,Pid,Type,MFA,ts}=Message) ->
+ receive
+ M ->
+ case M of
+ {trace_ts,Pid,Type,MFA,Ts} ->
+ ok = io:format("Expected and got ~p", [abbr(M)]),
+ Ts;
+ _ ->
+ io:format("Expected ~p; got ~p", [abbr(Message),abbr(M)]),
+ test_server:fail({unexpected,abbr([M|flush()])})
+ end
+ after 5000 ->
+ io:format("Expected ~p; got nothing", [abbr(Message)]),
+ test_server:fail(no_trace_message)
+ end;
+expect(Validator) when is_function(Validator) ->
+ receive
+ M ->
+ case Validator(M) of
+ expected ->
+ ok = io:format("Expected and got ~p", [abbr(M)]);
+ next ->
+ ok = io:format("Expected and got ~p", [abbr(M)]),
+ expect(Validator);
+ {unexpected,Message} ->
+ io:format("Expected ~p; got ~p", [abbr(Message),abbr(M)]),
+ test_server:fail({unexpected,abbr([M|flush()])})
+ end
+ after 5000 ->
+ io:format("Expected ~p; got nothing", [abbr(Validator('_'))]),
+ test_server:fail(no_trace_message)
+ end;
+expect(Message) ->
+ receive
+ M ->
+ case M of
+ Message ->
+ ok = io:format("Expected and got ~p", [abbr(Message)]);
+ Other ->
+ io:format("Expected ~p; got ~p",
+ [abbr(Message),abbr(Other)]),
+ test_server:fail({unexpected,abbr([Other|flush()])})
+ end
+ after 5000 ->
+ io:format("Expected ~p; got nothing", [abbr(Message)]),
+ test_server:fail(no_trace_message)
+ end.
+
+trace_info(What, Key) ->
+ get(tracer) ! {apply,self(),{erlang,trace_info,[What,Key]}},
+ Res = receive
+ {apply_result,Result} -> Result
+ end,
+ ok = io:format("erlang:trace_info(~p, ~p) -> ~p",
+ [What,Key,Res]),
+ Res.
+
+trace_func(MFA, MatchSpec) ->
+ get(tracer) ! {apply,self(),{erlang,trace_pattern,[MFA, MatchSpec]}},
+ Res = receive
+ {apply_result,Result} -> Result
+ end,
+ ok = io:format("trace_pattern(~p, ~p) -> ~p", [MFA,MatchSpec,Res]),
+ Res.
+
+trace_pid(Pid, On, Flags) ->
+ get(tracer) ! {apply,self(),{erlang,trace,[Pid,On,Flags]}},
+ Res = receive
+ {apply_result,Result} -> Result
+ end,
+ ok = io:format("trace(~p, ~p, ~p) -> ~p", [Pid,On,Flags,Res]),
+ Res.
+
+start_tracer() ->
+ Self = self(),
+ put(tracer, spawn(fun() -> tracer(Self) end)),
+ get(tracer).
+
+start_tracer_loop() ->
+ Self = self(),
+ put(tracer, spawn(fun() -> tracer_loop(Self) end)),
+ get(tracer).
+
+tracer(RelayTo) ->
+ erlang:trace(RelayTo, true, [call]),
+ tracer_loop(RelayTo).
+
+tracer_loop(RelayTo) ->
+ receive
+ {apply,From,{M,F,A}} ->
+ From ! {apply_result,apply(M, F, A)},
+ tracer_loop(RelayTo);
+ Msg ->
+ RelayTo ! Msg,
+ tracer_loop(RelayTo)
+ end.
+
+id(I) -> I.
+
+deep(N, Class, Reason) ->
+ try ?MODULE:deep_1(N, Class, Reason) of
+ Value -> {value,Value}
+ catch C:R -> {C,R}
+ end.
+
+deep_1(1, Class, Reason) ->
+ ?MODULE:deep_2(Class, Reason);
+deep_1(N, Class, Reason) when is_integer(N), N > 1 ->
+ ?MODULE:deep_1(N-1, Class, Reason).
+
+deep_2(Class, Reason) ->
+ ?MODULE:deep_4(?MODULE:deep_3(Class, Reason)).
+
+deep_3(Class, Reason) ->
+ {Class,Reason}.
+
+deep_4(CR) ->
+ case ?MODULE:id(CR) of
+ {exit,[Reason]} ->
+ erlang:exit(Reason);
+ {throw,[Reason]} ->
+ erlang:throw(Reason);
+ {error,[Reason,Arglist]} ->
+ erlang:error(Reason, Arglist);
+ {error,[Reason]} ->
+ erlang:error(Reason);
+ {id,[Reason]} ->
+ Reason;
+ {reverse,[A,B]} ->
+ lists:reverse(A, B);
+ {append,[A,B]} ->
+ A ++ B;
+ {apply,[Fun,Args]} ->
+ erlang:apply(Fun, Args);
+ {apply,[M,F,Args]} ->
+ erlang:apply(M, F, Args);
+ {deep_5,[A,B]} ->
+ ?MODULE:deep_5(A, B);
+ {deep_5,[A]} ->
+ ?MODULE:deep_5(A);
+ {'=',[A,B]} ->
+ A = B
+ end.
+
+deep_5(A) when is_integer(A) ->
+ A.
+
+flush() ->
+ receive X ->
+ [X|flush()]
+ after 1000 ->
+ []
+ end.
+
+%% Abbreviate large complex terms
+abbr(Term) ->
+ abbr(Term, 20).
+%%
+abbr(Tuple, N) when is_tuple(Tuple) ->
+ abbr_tuple(Tuple, 1, N, []);
+abbr(List, N) when is_list(List) ->
+ abbr_list(List, N, []);
+abbr(Term, _) -> Term.
+%%
+abbr_tuple(_, _, 0, R) ->
+ list_to_tuple(reverse(R, ['...']));
+abbr_tuple(Tuple, J, N, R) when J =< size(Tuple) ->
+ M = N-1,
+ abbr_tuple(Tuple, J+1, M, [abbr(element(J, Tuple), M)|R]);
+abbr_tuple(_, _, _, R) ->
+ list_to_tuple(reverse(R)).
+%%
+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).
+
+%% Lean and mean list functions
+
+%% Do not build garbage
+seq(M, N, R) when M =< N ->
+ seq(M, N-1, [N|R]);
+seq(_, _, R) -> R.
+
+%% lists:reverse can not be called since it is traced
+reverse(L) ->
+ reverse(L, []).
+%%
+reverse([], R) -> R;
+reverse([H|T], R) ->
+ reverse(T, [H|R]).