aboutsummaryrefslogtreecommitdiffstats
path: root/erts/emulator/test
diff options
context:
space:
mode:
Diffstat (limited to 'erts/emulator/test')
-rw-r--r--erts/emulator/test/Makefile3
-rw-r--r--erts/emulator/test/call_trace_SUITE.erl66
-rw-r--r--erts/emulator/test/match_spec_SUITE.erl98
-rw-r--r--erts/emulator/test/port_trace_SUITE.erl600
-rw-r--r--erts/emulator/test/port_trace_SUITE_data/Makefile.src3
-rw-r--r--erts/emulator/test/port_trace_SUITE_data/echo_drv.c237
-rw-r--r--erts/emulator/test/trace_SUITE.erl198
-rw-r--r--erts/emulator/test/trace_bif_SUITE.erl9
-rw-r--r--erts/emulator/test/trace_local_SUITE.erl24
-rw-r--r--erts/emulator/test/trace_port_SUITE.erl167
-rw-r--r--erts/emulator/test/tracer_SUITE.erl627
-rw-r--r--erts/emulator/test/tracer_SUITE_data/Makefile.src8
-rw-r--r--erts/emulator/test/tracer_SUITE_data/tracer_test.c122
-rw-r--r--erts/emulator/test/tracer_test.erl55
14 files changed, 1942 insertions, 275 deletions
diff --git a/erts/emulator/test/Makefile b/erts/emulator/test/Makefile
index 2221b5830c..de395dfb97 100644
--- a/erts/emulator/test/Makefile
+++ b/erts/emulator/test/Makefile
@@ -110,8 +110,11 @@ MODULES= \
trace_meta_SUITE \
trace_call_count_SUITE \
trace_call_time_SUITE \
+ tracer_SUITE \
+ tracer_test \
scheduler_SUITE \
old_scheduler_SUITE \
+ port_trace_SUITE \
unique_SUITE \
z_SUITE \
old_mod \
diff --git a/erts/emulator/test/call_trace_SUITE.erl b/erts/emulator/test/call_trace_SUITE.erl
index 2159546054..6ba6301c7c 100644
--- a/erts/emulator/test/call_trace_SUITE.erl
+++ b/erts/emulator/test/call_trace_SUITE.erl
@@ -84,32 +84,46 @@ process_specs(Config) when is_list(Config) ->
{tracer,Tracer} = trace_info(self(), tracer),
trace_func({?MODULE,worker_foo,1}, []),
- %% Test the 'new' flag.
-
- {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),
- expect({trace,Work1B,call,{?MODULE,worker_foo,[A1B]}}),
- unlink(Work1B),
- Mref = erlang:monitor(process, Work1B),
- exit(Work1B, kill),
- receive
- {'DOWN',Mref,_,_,_} -> ok
- end,
- undefined = trace_info(Work1B, flags),
- {flags,[]} = trace_info(new, flags),
- {tracer,[]} = trace_info(new, tracer),
-
- %% Test the 'existing' flag.
- {Work2A,_Work2B} = start_and_trace(existing, A2A=[5,6,7], [7,6,5]),
- expect({trace,Work2A,call,{?MODULE,worker_foo,[A2A]}}),
-
- %% Test the 'all' flag.
- {Work3A,Work3B} = start_and_trace(all, A3A=[12,13], A3B=[13,12]),
- expect({trace,Work3A,call,{?MODULE,worker_foo,[A3A]}}),
- expect({trace,Work3B,call,{?MODULE,worker_foo,[A3B]}}),
+ %% Test the 'new' and 'new_processes' flags.
+
+ New = fun(Flag) ->
+ {Work1A,Work1B} = start_and_trace(Flag, [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),
+ expect({trace,Work1B,call,{?MODULE,worker_foo,[A1B]}}),
+ unlink(Work1B),
+ Mref = erlang:monitor(process, Work1B),
+ exit(Work1B, kill),
+ receive
+ {'DOWN',Mref,_,_,_} -> ok
+ end,
+ undefined = trace_info(Work1B, flags),
+ {flags,[]} = trace_info(Flag, flags),
+ {tracer,[]} = trace_info(Flag, tracer)
+ end,
+ New(new),
+ New(new_processes),
+
+ %% Test the 'existing' and 'existing_processes' flags.
+ Existing =
+ fun(Flag) ->
+ {Work2A,_Work2B} = start_and_trace(Flag, A2A=[5,6,7], [7,6,5]),
+ expect({trace,Work2A,call,{?MODULE,worker_foo,[A2A]}})
+ end,
+ Existing(existing),
+ Existing(existing_processes),
+
+ %% Test the 'all' and 'processes' flags.
+ All =
+ fun(Flag) ->
+ {Work3A,Work3B} = start_and_trace(Flag, A3A=[12,13], A3B=[13,12]),
+ expect({trace,Work3A,call,{?MODULE,worker_foo,[A3A]}}),
+ expect({trace,Work3B,call,{?MODULE,worker_foo,[A3B]}})
+ end,
+ All(all),
+ All(processes),
ok.
diff --git a/erts/emulator/test/match_spec_SUITE.erl b/erts/emulator/test/match_spec_SUITE.erl
index 6b8d14b487..ea973276db 100644
--- a/erts/emulator/test/match_spec_SUITE.erl
+++ b/erts/emulator/test/match_spec_SUITE.erl
@@ -23,7 +23,7 @@
-export([all/0, suite/0, not_run/1]).
-export([test_1/1, test_2/1, test_3/1, bad_match_spec_bin/1,
trace_control_word/1, silent/1, silent_no_ms/1, silent_test/1,
- ms_trace2/1, ms_trace3/1, boxed_and_small/1,
+ ms_trace2/1, ms_trace3/1, ms_trace_dead/1, boxed_and_small/1,
destructive_in_test_bif/1, guard_exceptions/1,
empty_list/1,
unary_plus/1, unary_minus/1, moving_labels/1]).
@@ -49,7 +49,7 @@ all() ->
false ->
[test_1, test_2, test_3, bad_match_spec_bin,
trace_control_word, silent, silent_no_ms, silent_test, ms_trace2,
- ms_trace3, boxed_and_small, destructive_in_test_bif,
+ ms_trace3, ms_trace_dead, boxed_and_small, destructive_in_test_bif,
guard_exceptions, unary_plus, unary_minus, fpe,
moving_labels,
faulty_seq_trace,
@@ -500,6 +500,8 @@ ms_trace2(Config) when is_list(Config) ->
[[call,return_to],[]]},
ms_trace2}]
end),
+ %% Silence valgrind
+ erlang:trace_pattern({?MODULE,fn,'_'},[],[]),
ok.
@@ -595,7 +597,35 @@ ms_trace3(Config) when is_list(Config) ->
end),
ok.
-
+ms_trace_dead(doc) ->
+ ["Test that a dead tracer is removed using ms"];
+ms_trace_dead(suite) -> [];
+ms_trace_dead(Config) when is_list(Config) ->
+ Self = self(),
+ TFun = fun F() -> receive M -> Self ! M, F() end end,
+ {Tracer, MRef} = spawn_monitor(TFun),
+ MetaTracer = spawn_link(TFun),
+ erlang:trace_pattern({?MODULE, f1, '_'},
+ [{'_',[],[{trace,[],
+ [call,{const,{tracer,Tracer}}]}]}],
+ [{meta, MetaTracer}]),
+ erlang:trace_pattern({?MODULE, f2, '_'}, []),
+ ?MODULE:f2(1,2),
+ ?MODULE:f1(1),
+ {tracer,Tracer} = erlang:trace_info(self(), tracer),
+ {flags,[call]} = erlang:trace_info(self(), flags),
+ ?MODULE:f2(2,3),
+ receive {trace, Self, call, {?MODULE, f2, _}} -> ok end,
+ exit(Tracer, stop),
+ receive {'DOWN',MRef,_,_,_} -> ok end,
+ ?MODULE:f1(2),
+ {tracer,[]} = erlang:trace_info(self(), tracer),
+ ?MODULE:f2(3,4),
+ TRef = erlang:trace_delivered(all),
+ receive {trace_delivered, _, TRef} -> ok end,
+ receive {trace_ts, Self, call, {?MODULE, f1, _}, _} -> ok end,
+ receive {trace_ts, Self, call, {?MODULE, f1, _}, _} -> ok end,
+ receive M -> ct:fail({unexpected, M}) after 10 -> ok end.
%% Test that destructive operations in test bif does not really happen
destructive_in_test_bif(Config) when is_list(Config) ->
@@ -905,34 +935,40 @@ collect([]) ->
collect([TM | TMs]) ->
io:format( "Expecting: ~p~n", [TM]),
receive
- M0 ->
- M = case element(1, M0) of
- trace_ts ->
- list_to_tuple(lists:reverse(
- tl(lists:reverse(tuple_to_list(M0)))));
- _ -> M0
- end,
- case is_function(TM,1) of
- true ->
- case (catch TM(M)) of
- true ->
- io:format("Got: ~p~n", [M]),
- collect(TMs);
- _ ->
- io:format("Got unexpected: ~p~n", [M]),
- flush({got_unexpected,M})
- end;
-
- false ->
- case M of
- TM ->
- io:format("Got: ~p~n", [M]),
- collect(TMs);
- _ ->
- io:format("Got unexpected: ~p~n", [M]),
- flush({got_unexpected,M})
- end
- end
+ %% We only look at trace messages with the same tracee
+ %% as the message we are looking for. This because
+ %% the order of trace messages is only guaranteed from
+ %% within a single process.
+ M0 when element(2, M0) =:= element(2, TM); is_function(TM, 1) ->
+ M = case element(1, M0) of
+ trace_ts ->
+ list_to_tuple(lists:reverse(
+ tl(lists:reverse(tuple_to_list(M0)))));
+ _ -> M0
+ end,
+ case is_function(TM,1) of
+ true ->
+ case (catch TM(M)) of
+ true ->
+ io:format("Got: ~p~n", [M]),
+ collect(TMs);
+ _ ->
+ io:format("Got unexpected: ~p~n", [M]),
+ flush({got_unexpected,M})
+ end;
+
+ false ->
+ case M of
+ TM ->
+ io:format("Got: ~p~n", [M]),
+ collect(TMs);
+ _ ->
+ io:format("Got unexpected: ~p~n", [M]),
+ flush({got_unexpected,M})
+ end
+ end
+ after 15000 ->
+ flush(timeout)
end.
flush(Reason) ->
diff --git a/erts/emulator/test/port_trace_SUITE.erl b/erts/emulator/test/port_trace_SUITE.erl
new file mode 100644
index 0000000000..41e8a316c4
--- /dev/null
+++ b/erts/emulator/test/port_trace_SUITE.erl
@@ -0,0 +1,600 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1999-2012. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+
+-module(port_trace_SUITE).
+
+-export([all/0, suite/0,init_per_suite/1, end_per_suite/1,
+ init_per_group/2,end_per_group/2,
+ init_per_testcase/2,end_per_testcase/2]).
+-export([port_specs/1, ports/1, open_close/1,
+ command/1, control/1, connect/1, call/1,
+ output/1, output2/1, output_binary/1,
+ outputv/1, set_timer/1, failure_eof/1,
+ failure_atom/1, failure_posix/1,
+ failure/1, output_term/1,
+ driver_output_term/1,
+ send_term/1, driver_send_term/1]).
+
+-define(ECHO_DRV_NOOP, 0).
+-define(ECHO_DRV_OUTPUT, 1).
+-define(ECHO_DRV_OUTPUT2, 2).
+-define(ECHO_DRV_OUTPUT_BINARY, 3).
+-define(ECHO_DRV_OUTPUTV, 4).
+-define(ECHO_DRV_SET_TIMER, 5).
+-define(ECHO_DRV_FAILURE_EOF, 6).
+-define(ECHO_DRV_FAILURE_ATOM, 7).
+-define(ECHO_DRV_FAILURE_POSIX, 8).
+-define(ECHO_DRV_FAILURE, 9).
+-define(ECHO_DRV_OUTPUT_TERM, 10).
+-define(ECHO_DRV_DRIVER_OUTPUT_TERM, 11).
+-define(ECHO_DRV_SEND_TERM, 12).
+-define(ECHO_DRV_DRIVER_SEND_TERM, 13).
+-define(ECHO_DRV_SAVE_CALLER, 14).
+
+suite() -> [{ct_hooks,[ts_install_cth]},
+ {timetrap, {seconds, 30}}].
+
+all() ->
+ [port_specs, ports, open_close,
+ command, control, connect, call,
+ output, output2, output_binary,
+ outputv, set_timer, failure_eof,
+ failure_atom, failure_posix,
+ failure, output_term,
+ driver_output_term,
+ send_term, driver_send_term].
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+
+init_per_testcase(Func, Config) when is_atom(Func), is_list(Config) ->
+ erlang:trace(all, false, [all]),
+ os:unsetenv("OUTPUTV"),
+ reload_drv(Config),
+ Config.
+
+end_per_testcase(_Func, _Config) ->
+ erlang:trace(all, false, [all]),
+ ok.
+
+%% Test the first argument of trace/3
+port_specs(_Config) ->
+
+ S = self(),
+
+ Tracer = fun F() ->
+ receive
+ stop ->
+ ok;
+ M ->
+ S ! M,
+ F()
+ end
+ end,
+
+ Test = fun(TraceSpec, Info1, Info2) ->
+ {TracerPid,Ref} = spawn_monitor(Tracer),
+ Prt1 = erlang:open_port({spawn, echo_drv}, [binary]),
+ erlang:trace(TraceSpec, true, ['receive', {tracer, TracerPid}]),
+ %% We disable trace messages from the testcase process
+ erlang:trace(self(), false, ['receive']),
+ Prt2 = erlang:open_port({spawn, echo_drv}, [binary]),
+
+ InfoCheck =
+ fun(Info, Prt) ->
+ if
+ Info ->
+ {tracer, TracerPid} = erlang:trace_info(Prt, tracer),
+ {flags,['receive']} = erlang:trace_info(Prt, flags);
+ not Info ->
+ {tracer,[]} = erlang:trace_info(Prt, tracer),
+ {flags,[]} = erlang:trace_info(Prt, flags)
+ end
+ end,
+ InfoCheck(Info1, Prt1),
+ InfoCheck(Info2, Prt2),
+
+ %% These may create trace messages
+ erlang:port_command(Prt1, <<?ECHO_DRV_NOOP>>),
+ erlang:port_command(Prt2, <<?ECHO_DRV_NOOP>>),
+
+ %% Test what happens when the tracer dies
+ trace_delivered(),
+ TracerPid ! stop,
+ receive {'DOWN', Ref, process, TracerPid, normal} -> ok end,
+
+ %% These should not generate any trace messages
+ erlang:port_command(Prt1, <<?ECHO_DRV_NOOP>>),
+ erlang:port_command(Prt2, <<?ECHO_DRV_NOOP>>),
+
+ InfoCheck(false, Prt1),
+ InfoCheck(false, Prt2),
+
+ erlang:port_close(Prt1),
+ erlang:port_close(Prt2),
+ erlang:trace(all, false, [all]),
+ {Prt1, Prt2}
+ end,
+
+ {_Prt11, Prt12} = Test(new, false, true),
+ [{trace, Prt12, 'receive', {S, {command,<<?ECHO_DRV_NOOP>>}}}]
+ = flush(Prt12),
+
+ {_Prt21, Prt22} = Test(new_ports, false, true),
+ [{trace, Prt22, 'receive', {S, {command,<<?ECHO_DRV_NOOP>>}}}]
+ = flush(Prt22),
+
+ {Prt31, _Prt32} = Test(existing, true, false),
+ [{trace, Prt31, 'receive', {S, {command,<<?ECHO_DRV_NOOP>>}}}]
+ = flush(Prt31),
+
+ {Prt41, _Prt42} = Test(existing_ports, true, false),
+ [{trace, Prt41, 'receive', {S, {command,<<?ECHO_DRV_NOOP>>}}}]
+ = flush(Prt41),
+
+ {Prt51, Prt52} = Test(all, true, true),
+ [{trace, Prt51, 'receive', {S, {command,<<?ECHO_DRV_NOOP>>}}}]
+ = flush(Prt51),
+ [{trace, Prt52, 'receive', {S, {command,<<?ECHO_DRV_NOOP>>}}}]
+ = flush(Prt52),
+
+ {Prt61, Prt62} = Test(ports, true, true),
+ [{trace, Prt61, 'receive', {S, {command,<<?ECHO_DRV_NOOP>>}}}]
+ = flush(Prt61),
+ [{trace, Prt62, 'receive', {S, {command,<<?ECHO_DRV_NOOP>>}}}]
+ = flush(Prt62),
+
+ ok.
+
+%% Test that the 'ports' trace flag works
+ports(_Config) ->
+
+ {Prt, S} = trace_and_open([ports],[binary]),
+
+ [{trace, Prt, open, S, echo_drv},
+ {trace, Prt, getting_linked, S}] = flush(),
+
+ register(?MODULE, Prt),
+ unregister(?MODULE),
+ register(?MODULE, Prt),
+
+ [{trace,Prt,register,port_trace_SUITE},
+ {trace,Prt,unregister,port_trace_SUITE},
+ {trace,Prt,register,port_trace_SUITE}] = flush(),
+
+ unlink(Prt),
+ link(Prt),
+
+ [{trace,Prt,getting_unlinked,S},
+ {trace,Prt,getting_linked,S}] = flush(),
+
+ erlang:port_close(Prt),
+
+ [{trace,Prt,closed,normal},
+ {trace,Prt,unregister,port_trace_SUITE},
+ {trace,Prt,unlink,S}] = flush(),
+
+ ok.
+
+%% Test that port_close and ! close generate correct trace messages
+open_close(_Config) ->
+
+ S = trace_ports([send,'receive']),
+
+ Prt = erlang:open_port({spawn, echo_drv}, [binary]),
+ erlang:port_close(Prt),
+ [{trace, Prt, 'receive', {S, close}}] = flush(),
+
+ Prt2 = erlang:open_port({spawn, echo_drv}, [binary]),
+ Prt2 ! {S, close},
+ recv({Prt2, closed}),
+ [{trace, Prt2, 'receive', {S, close}},
+ {trace, Prt2, send, closed, S}] = flush(),
+
+ catch erlang:port_close(Prt2),
+ [] = flush(),
+
+ ok.
+
+%% Test that port_command and ! command generate correct trace messages
+command(Config) ->
+
+ Flags = [send,'receive'],
+ S = trace_ports(Flags),
+ Prt = erlang:open_port({spawn, echo_drv}, [binary]),
+
+ erlang:port_command(Prt, <<?ECHO_DRV_NOOP:8>>),
+ [{trace, Prt, 'receive', {S, {command, <<?ECHO_DRV_NOOP:8>>}}}] = flush(),
+
+ erlang:port_command(Prt, [?ECHO_DRV_NOOP, <<0:8>>]),
+ [{trace, Prt, 'receive', {S, {command, <<?ECHO_DRV_NOOP:8,0:8>>}}}] = flush(),
+
+ Prt ! {S, {command, <<?ECHO_DRV_NOOP:8>>}},
+ [{trace, Prt, 'receive', {S, {command, <<?ECHO_DRV_NOOP:8>>}}}] = flush(),
+
+ close(Prt, Flags),
+
+ os:putenv("OUTPUTV","true"),
+ reload_drv(Config),
+
+ Prt2 = erlang:open_port({spawn, echo_drv}, [binary]),
+ Msg = [<<0:8>>,<<0:(8*512)>>,<<0:(8*256)>>,<<0:8>>],
+
+ erlang:port_command(Prt2, Msg),
+ [{trace, Prt2, 'receive', {S, {command, Msg}}}] = flush(),
+
+ Prt2 ! {S, {command, Msg}},
+ [{trace, Prt2, 'receive', {S, {command, Msg}}}] = flush(),
+
+ close(Prt2, Flags),
+
+ os:unsetenv("OUTPUTV"),
+
+ ok.
+
+%% Test that port_control generate correct trace messages
+control(_Config) ->
+
+ Flags = [send,'receive'],
+ {Prt, S} = trace_and_open(Flags,[binary]),
+
+ [0] = erlang:port_control(Prt, 1, <<?ECHO_DRV_NOOP:8, 0:8>>),
+ [{trace, Prt, 'receive', {S, {control, {1, <<?ECHO_DRV_NOOP:8, 0:8>>}}}},
+ {trace, Prt, send, {Prt, {control, <<0:8>>}}, S}] = flush(),
+
+ [0] = erlang:port_control(Prt, (1 bsl 32) - 1, <<?ECHO_DRV_NOOP:8, 0:8>>),
+ [{trace, Prt, 'receive', {S, {control, {(1 bsl 32) - 1, <<?ECHO_DRV_NOOP:8, 0:8>>}}}},
+ {trace, Prt, send, {Prt, {control, <<0:8>>}}, S}] = flush(),
+
+ close(Prt, Flags),
+
+ ok.
+
+%% Test that port_connect and ! connect generate correct trace messages
+%% This includes that the proper getting_linked messages are sent
+connect(_Config) ->
+
+
+ {Prt, S} = trace_and_open([send, 'receive', ports],[binary]),
+
+ flush(),
+
+ {Pid,Ref} = spawn_monitor(
+ fun() ->
+ receive
+ go ->
+ Prt ! {self(), {connect, S}},
+ receive {Prt, connected} -> unlink(Prt) end
+ end
+ end),
+ erlang:trace(Pid, true, [send, 'receive', procs]),
+
+ erlang:port_connect(Prt, Pid),
+ unlink(Prt),
+
+ [{trace,Prt,getting_linked,Pid},
+ {trace,Prt,'receive',{S,{connect,Pid}}},
+ {trace,Prt,send,{Prt,connected},S},
+ {trace,Prt,getting_unlinked, S}] = flush(Prt),
+
+ [{trace,Pid,getting_linked,Prt}] = flush(),
+
+ Pid ! go,
+ recv({'DOWN',Ref,process,Pid,normal}),
+
+ [{trace,Prt,'receive',{Pid,{connect,S}}},
+ {trace,Prt,send,{Prt,connected},Pid},
+ {trace,Prt,getting_unlinked,Pid}] = flush(Prt),
+
+ [{trace,Pid,'receive',go},
+ {trace,Pid,send,{Pid,{connect,S}}, Prt},
+ {trace,Pid,'receive',{Prt,connected}},
+ {trace,Pid,unlink,Prt},
+ {trace,Pid,exit,normal}] = flush(),
+
+ erlang:port_close(Prt),
+ [{trace, Prt, 'receive', {S, close}},
+ {trace, Prt, closed, normal}] = flush(),
+ ok.
+
+%% Test that port_call generate correct trace messages
+call(_Config) ->
+
+ Flags = [send,'receive'],
+ {Prt, S} = trace_and_open(Flags,[binary]),
+
+ Msg = {hello, world, make_ref()},
+ BinMsg = term_to_binary(Msg),
+
+ Msg = erlang:port_call(Prt, 0, Msg),
+ [{trace, Prt, 'receive', {S, {call, {0, BinMsg}}}},
+ {trace, Prt, send, {Prt, {call, BinMsg}}, S}] = flush(),
+
+ close(Prt, Flags),
+
+ ok.
+
+%% Test that driver_output generate correct trace messages
+output(_Config) ->
+
+ Flags = [send],
+ {Prt, S} = trace_and_open(Flags,[binary]),
+
+ erlang:port_command(Prt, <<?ECHO_DRV_OUTPUT, 123456:32>>),
+ recv({Prt,{data,<<123456:32>>}}),
+
+ [{trace, Prt, send, {Prt, {data, <<123456:32>>}}, S}] = flush(),
+
+ close(Prt, Flags),
+
+ ok.
+
+%% Test that driver_output2 generate correct trace messages
+output2(_Config) ->
+
+ Flags = [send],
+ {Prt, S} = trace_and_open(Flags,[binary]),
+
+ erlang:port_command(Prt, <<?ECHO_DRV_OUTPUT2, 123456:32>>),
+ recv({Prt,{data,[$a|<<123456:32>>]}}),
+ [{trace, Prt, send, {Prt, {data, [$a|<<123456:32>>]}}, S}] = flush(),
+
+ close(Prt, Flags),
+
+ ok.
+
+%% Test that driver_output_binary generate correct trace messages
+output_binary(_Config) ->
+
+ Flags = [send],
+ {Prt, S} = trace_and_open(Flags,[binary]),
+
+ erlang:port_command(Prt, <<?ECHO_DRV_OUTPUT_BINARY, 0, 123456:32>>),
+ recv({Prt,{data,[$a|<<123456:32>>]}}),
+ [{trace, Prt, send, {Prt, {data, [$a|<<123456:32>>]}}, S}] = flush(),
+
+ close(Prt, Flags),
+
+ ok.
+
+%% Test that driver_outputv generate correct trace messages
+outputv(_Config) ->
+
+ Flags = [send],
+ {Prt, S} = trace_and_open(Flags,[binary]),
+
+ erlang:port_command(Prt, <<?ECHO_DRV_OUTPUTV, 123456:32>>),
+ recv({Prt,{data,[$a|<<123456:32>>]}}),
+
+ [{trace, Prt, send, {Prt, {data, [$a|<<123456:32>>]}}, S}] = flush(),
+
+ erlang:port_close(Prt),
+ [] = flush(),
+
+ ok.
+
+%% Test that driver_set_timer generate correct trace messages
+set_timer(_Config) ->
+
+ Flags = [send,'receive'],
+ {Prt, S} = trace_and_open(Flags,[binary]),
+
+ erlang:port_command(Prt, <<?ECHO_DRV_SET_TIMER>>),
+ timer:sleep(100),
+ [{trace, Prt, 'receive', {S, {command, <<?ECHO_DRV_SET_TIMER>>}}},
+ {trace, Prt, 'receive', timeout}] = flush(),
+
+ close(Prt, Flags),
+
+ ok.
+
+%% Test that driver_failure* generate correct trace messages
+failure_eof(_Config) ->
+
+ Flags = [send,'receive', ports],
+ S = trace_ports(Flags),
+
+ Prt = erlang:open_port({spawn, echo_drv}, [eof, binary]),
+ [{trace, Prt, open, S, echo_drv},
+ {trace, Prt, getting_linked, S}] = flush(),
+
+ erlang:port_command(Prt, <<?ECHO_DRV_FAILURE_EOF>>),
+ recv({Prt,eof}),
+ [{trace, Prt, 'receive', {S, {command, <<?ECHO_DRV_FAILURE_EOF>>}}},
+ {trace, Prt, send, {Prt, eof}, S}] = flush(),
+
+ close(Prt, Flags),
+
+ %% Run same test without eof option
+ failure_test(<<?ECHO_DRV_FAILURE_EOF>>, normal).
+
+failure_atom(_Config) ->
+ failure_test(<<?ECHO_DRV_FAILURE_ATOM, "failure\0">>, failure).
+failure_posix(_Config) ->
+ failure_test(<<?ECHO_DRV_FAILURE_POSIX>>, eagain).
+failure(_Config) ->
+ failure_test(<<?ECHO_DRV_FAILURE, 1>>, 1).
+
+failure_test(Failure, Reason) ->
+
+ {Prt, S} = trace_and_open([send, 'receive', ports],[binary]),
+
+ [{trace, Prt, open, S, echo_drv},
+ {trace, Prt, getting_linked, S}] = flush(),
+
+ process_flag(trap_exit, true),
+ erlang:port_command(Prt, Failure),
+ try
+ recv({'EXIT',Prt,Reason})
+ after
+ process_flag(trap_exit, false)
+ end,
+ [{trace, Prt, 'receive', {S, {command, Failure}}},
+ {trace, Prt, closed, Reason},
+ {trace, Prt, unlink, S}] = flush(),
+
+ ok.
+
+%% Test that erl_drv_output_term generate correct trace messages
+output_term(_Config) ->
+
+ Flags = [send],
+ {Prt, S} = trace_and_open(Flags,[binary]),
+
+ erlang:port_command(Prt, <<?ECHO_DRV_OUTPUT_TERM, 123456:32>>),
+ recv({echo, Prt, <<123456:32>>}),
+ [{trace, Prt, send, {echo, Prt, <<123456:32>>}, S}] = flush(),
+
+ close(Prt, Flags),
+
+ ok.
+
+%% Test that driver_output_term generate correct trace messages
+driver_output_term(_Config) ->
+
+ Flags = [send],
+ {Prt, S} = trace_and_open(Flags,[binary]),
+
+ erlang:port_command(Prt, <<?ECHO_DRV_DRIVER_OUTPUT_TERM, 123456:32>>),
+ recv({echo, Prt, <<123456:32>>}),
+ [{trace, Prt, send, {echo, Prt, <<123456:32>>}, S}] = flush(),
+
+ close(Prt, Flags),
+
+ ok.
+
+%% Test that erl_drv_send_term generate correct trace messages
+send_term(_Config) ->
+
+ Flags = [send],
+ {Prt, S} = trace_and_open(Flags,[binary]),
+
+ erlang:port_command(Prt, <<?ECHO_DRV_SEND_TERM, 123456:32>>),
+ recv({echo, Prt, <<123456:32>>}),
+ [{trace, Prt, send, {echo, Prt, <<123456:32>>}, S}] = flush(),
+
+ {Pid, Ref} = spawn_monitor(fun() -> erlang:port_command(Prt, <<?ECHO_DRV_SAVE_CALLER>>) end),
+ recv({'DOWN',Ref,process,Pid,normal}),
+ erlang:port_command(Prt, <<?ECHO_DRV_SEND_TERM, 123456:32>>),
+ [{trace, Prt, send_to_non_existing_process, {echo, Prt, <<123456:32>>}, Pid}] = flush(),
+
+ close(Prt, Flags),
+
+ ok.
+
+%% Test that driver_send_term generate correct trace messages
+driver_send_term(_Config) ->
+
+ Flags = [send],
+ {Prt, S} = trace_and_open(Flags,[binary]),
+
+ erlang:port_command(Prt, <<?ECHO_DRV_DRIVER_SEND_TERM, 123456:32>>),
+ recv({echo, Prt, <<123456:32>>}),
+ [{trace, Prt, send, {echo, Prt, <<123456:32>>}, S}] = flush(),
+
+ {Pid, Ref} = spawn_monitor(fun() -> erlang:port_command(Prt, <<?ECHO_DRV_SAVE_CALLER>>) end),
+ recv({'DOWN',Ref,process,Pid,normal}),
+ erlang:port_command(Prt, <<?ECHO_DRV_SEND_TERM, 123456:32>>),
+ [{trace, Prt, send_to_non_existing_process, {echo, Prt, <<123456:32>>}, Pid}] = flush(),
+
+ close(Prt, Flags),
+
+ ok.
+
+%%%%%%%%%%%%%%%%%%%
+%% Helper functions
+%%%%%%%%%%%%%%%%%%%
+
+trace_ports(TraceFlags) ->
+ erlang:trace(new_ports, true, TraceFlags),
+ self().
+
+trace_and_open(TraceFlags, OpenFlags) ->
+ S = self(),
+ Ports = proplists:get_value(ports, TraceFlags),
+ [trace_ports(TraceFlags) || Ports],
+ Prt = erlang:open_port({spawn, echo_drv}, OpenFlags),
+ [erlang:trace(Prt, true, TraceFlags) || Ports == undefined],
+ {Prt, S}.
+
+close(Prt, Flags) ->
+ Recv = proplists:get_value('receive', Flags),
+ Ports = proplists:get_value(ports, Flags),
+ S = self(),
+
+ erlang:port_close(Prt),
+
+ if Recv, Ports ->
+ [{trace, Prt, 'receive', {S, close}},
+ {trace, Prt, closed, normal},
+ {trace, Prt, unlink, S}] = flush();
+ Recv ->
+ [{trace, Prt, 'receive', {S, close}}] = flush();
+ Ports ->
+ [{trace, Prt, closed, normal},
+ {trace, Prt, unlink, S}] = flush();
+ true ->
+ [] = flush()
+ end.
+
+trace_delivered() ->
+ Ref = erlang:trace_delivered(all),
+ receive {trace_delivered, all, Ref} -> ok end.
+
+flush() ->
+ flush(all).
+flush(From) ->
+ trace_delivered(),
+ f(From).
+
+f(From) ->
+ receive
+ M when From =:= all; element(2, M) == From ->
+ [M | f(From)]
+ after 0 ->
+ []
+ end.
+
+recv(Msg) ->
+ receive Msg -> ok after 100 -> ct:fail({did_not_get_data,Msg,flush()}) end.
+
+load_drv(Config) ->
+ Path = proplists:get_value(data_dir, Config),
+ case erl_ddll:load_driver(Path, echo_drv) of
+ ok -> ok;
+ {error, Error} = Res ->
+ io:format("~s\n", [erl_ddll:format_error(Error)]),
+ ct:fail(Res)
+ end.
+
+reload_drv(Config) ->
+ erl_ddll:unload_driver(echo_drv),
+ load_drv(Config).
diff --git a/erts/emulator/test/port_trace_SUITE_data/Makefile.src b/erts/emulator/test/port_trace_SUITE_data/Makefile.src
new file mode 100644
index 0000000000..c1bf142ccf
--- /dev/null
+++ b/erts/emulator/test/port_trace_SUITE_data/Makefile.src
@@ -0,0 +1,3 @@
+all: echo_drv@dll@
+
+@SHLIB_RULES@
diff --git a/erts/emulator/test/port_trace_SUITE_data/echo_drv.c b/erts/emulator/test/port_trace_SUITE_data/echo_drv.c
new file mode 100644
index 0000000000..b5728bc170
--- /dev/null
+++ b/erts/emulator/test/port_trace_SUITE_data/echo_drv.c
@@ -0,0 +1,237 @@
+#include <stdio.h>
+#include "erl_driver.h"
+#include <errno.h>
+#include <string.h>
+
+
+/* -------------------------------------------------------------------------
+** Data types
+**/
+
+
+typedef struct _erl_drv_data {
+ ErlDrvPort erlang_port;
+ ErlDrvTermData caller;
+} EchoDrvData;
+
+#define ECHO_DRV_NOOP 0
+#define ECHO_DRV_OUTPUT 1
+#define ECHO_DRV_OUTPUT2 2
+#define ECHO_DRV_OUTPUT_BINARY 3
+#define ECHO_DRV_OUTPUTV 4
+#define ECHO_DRV_SET_TIMER 5
+#define ECHO_DRV_FAILURE_EOF 6
+#define ECHO_DRV_FAILURE_ATOM 7
+#define ECHO_DRV_FAILURE_POSIX 8
+#define ECHO_DRV_FAILURE 9
+#define ECHO_DRV_OUTPUT_TERM 10
+#define ECHO_DRV_DRIVER_OUTPUT_TERM 11
+#define ECHO_DRV_SEND_TERM 12
+#define ECHO_DRV_DRIVER_SEND_TERM 13
+#define ECHO_DRV_SAVE_CALLER 14
+
+
+/* -------------------------------------------------------------------------
+** Entry struct
+**/
+
+static EchoDrvData *echo_drv_start(ErlDrvPort port, char *command);
+static void echo_drv_stop(ErlDrvData drv_data);
+static void echo_drv_output(ErlDrvData drv_data, char *buf,
+ ErlDrvSizeT len);
+static void echo_drv_outputv(ErlDrvData drv_data, ErlIOVec *iov);
+static void echo_drv_finish(void);
+static ErlDrvSSizeT echo_drv_control(ErlDrvData drv_data,
+ unsigned int command,
+ char *buf, ErlDrvSizeT len,
+ char **rbuf, ErlDrvSizeT rlen);
+static void echo_drv_timeout(ErlDrvData drv_data);
+static ErlDrvSSizeT echo_drv_call(ErlDrvData drv_data,
+ unsigned int command,
+ char *buf, ErlDrvSizeT len,
+ char **rbuf, ErlDrvSizeT rlen,
+ unsigned int *flags);
+
+static ErlDrvEntry echo_drv_entry = {
+ NULL, /* init */
+ echo_drv_start,
+ echo_drv_stop,
+ echo_drv_output,
+ NULL, /* ready_input */
+ NULL, /* ready_output */
+ "echo_drv",
+ echo_drv_finish,
+ NULL, /* handle */
+ echo_drv_control,
+ echo_drv_timeout, /* timeout */
+ NULL, /* outputv */
+ NULL, /* ready_async */
+ NULL, /* flush */
+ echo_drv_call, /* call */
+ NULL, /* event */
+ ERL_DRV_EXTENDED_MARKER,
+ ERL_DRV_EXTENDED_MAJOR_VERSION,
+ ERL_DRV_EXTENDED_MINOR_VERSION,
+ 0,
+ NULL,
+ NULL,
+ NULL
+};
+
+/* -------------------------------------------------------------------------
+** Entry functions
+**/
+
+DRIVER_INIT(echo_drv)
+{
+ char buff[5];
+ size_t size = sizeof(buff);
+
+ if (erl_drv_getenv("OUTPUTV", buff, &size) == -1) {
+ echo_drv_entry.outputv = NULL;
+ } else {
+ echo_drv_entry.outputv = echo_drv_outputv;
+ }
+
+ return &echo_drv_entry;
+}
+
+static EchoDrvData *echo_drv_start(ErlDrvPort port, char *command)
+{
+ EchoDrvData *echo_drv_data_p = driver_alloc(sizeof(EchoDrvData));
+ echo_drv_data_p->erlang_port = port;
+ echo_drv_data_p->caller = driver_caller(port);
+ return echo_drv_data_p;
+}
+
+static void echo_drv_stop(EchoDrvData *data_p) {
+ driver_free(data_p);
+}
+
+static void echo_drv_outputv(ErlDrvData drv_data, ErlIOVec *iov)
+{
+ return;
+}
+
+static void echo_drv_output(ErlDrvData drv_data, char *buf, ErlDrvSizeT len) {
+ EchoDrvData* data_p = (EchoDrvData *) drv_data;
+ ErlDrvPort port = data_p->erlang_port;
+
+ switch (buf[0]) {
+ case ECHO_DRV_OUTPUT:
+ {
+ driver_output(port, buf+1, len-1);
+ break;
+ }
+ case ECHO_DRV_OUTPUT2:
+ {
+ driver_output2(port, "a", 1, buf+1, len-1);
+ break;
+ }
+ case ECHO_DRV_OUTPUT_BINARY:
+ {
+ ErlDrvBinary *bin = driver_alloc_binary(len-1);
+ memcpy(&bin->orig_bytes, buf+1, len-1);
+ driver_output_binary(port, "a", 1, bin, 1, len - 2);
+ driver_free_binary(bin);
+ break;
+ }
+ case ECHO_DRV_OUTPUTV:
+ {
+ ErlIOVec iov;
+ ErlDrvSizeT sz;
+ driver_enq(port, buf + 1, len - 1);
+ sz = driver_peekqv(port, &iov);
+ driver_outputv(port, "a", 1, &iov, 0);
+ driver_deq(port, sz);
+ break;
+ }
+ case ECHO_DRV_SET_TIMER:
+ {
+ driver_set_timer(port, 10);
+ break;
+ }
+ case ECHO_DRV_FAILURE_EOF:
+ {
+ driver_failure_eof(port);
+ break;
+ }
+ case ECHO_DRV_FAILURE_ATOM:
+ {
+ driver_failure_atom(port, buf+1);
+ break;
+ }
+ case ECHO_DRV_FAILURE_POSIX:
+ {
+ driver_failure_posix(port, EAGAIN);
+ break;
+ }
+ case ECHO_DRV_FAILURE:
+ {
+ driver_failure(port, buf[1]);
+ break;
+ }
+ case ECHO_DRV_OUTPUT_TERM:
+ case ECHO_DRV_DRIVER_OUTPUT_TERM:
+ case ECHO_DRV_SEND_TERM:
+ case ECHO_DRV_DRIVER_SEND_TERM:
+ {
+ ErlDrvTermData term[] = {
+ ERL_DRV_ATOM, driver_mk_atom("echo"),
+ ERL_DRV_PORT, driver_mk_port(port),
+ ERL_DRV_BUF2BINARY, (ErlDrvTermData)(buf+1),
+ (ErlDrvTermData)(len - 1),
+ ERL_DRV_TUPLE, 3};
+ switch (buf[0]) {
+ case ECHO_DRV_OUTPUT_TERM:
+ erl_drv_output_term(driver_mk_port(port), term, sizeof(term) / sizeof(ErlDrvTermData));
+ break;
+ case ECHO_DRV_DRIVER_OUTPUT_TERM:
+ driver_output_term(port, term, sizeof(term) / sizeof(ErlDrvTermData));
+ break;
+ case ECHO_DRV_SEND_TERM:
+ driver_send_term(port, data_p->caller,
+ term, sizeof(term) / sizeof(ErlDrvTermData));
+ break;
+ case ECHO_DRV_DRIVER_SEND_TERM:
+ erl_drv_send_term(driver_mk_port(port), data_p->caller,
+ term, sizeof(term) / sizeof(ErlDrvTermData));
+ break;
+ }
+ break;
+ }
+ case ECHO_DRV_SAVE_CALLER:
+ data_p->caller = driver_caller(port);
+ break;
+ default:
+ break;
+ }
+}
+
+static void echo_drv_finish() {
+
+}
+
+static ErlDrvSSizeT echo_drv_control(ErlDrvData drv_data,
+ unsigned int command,
+ char *buf, ErlDrvSizeT len,
+ char **rbuf, ErlDrvSizeT rlen)
+{
+ memcpy(*rbuf, buf+1, len-1);
+ return len-1;
+}
+
+static void echo_drv_timeout(ErlDrvData drv_data)
+{
+
+}
+
+static ErlDrvSSizeT echo_drv_call(ErlDrvData drv_data,
+ unsigned int command,
+ char *buf, ErlDrvSizeT len,
+ char **rbuf, ErlDrvSizeT rlen,
+ unsigned int *flags)
+{
+ memcpy(*rbuf, buf+command, len-command);
+ return len-command;
+}
diff --git a/erts/emulator/test/trace_SUITE.erl b/erts/emulator/test/trace_SUITE.erl
index 7aece926e3..b7f312635e 100644
--- a/erts/emulator/test/trace_SUITE.erl
+++ b/erts/emulator/test/trace_SUITE.erl
@@ -24,8 +24,8 @@
%%% Tests the trace BIF.
%%%
--export([all/0, suite/0,
- receive_trace/1, self_send/1,
+-export([all/0, suite/0, link_receive_call_correlation/0,
+ receive_trace/1, link_receive_call_correlation/1, self_send/1,
timeout_trace/1, send_trace/1,
procs_trace/1, dist_procs_trace/1,
suspend/1, mutual_suspend/1, suspend_exit/1, suspender_exit/1,
@@ -48,7 +48,8 @@ suite() ->
{timetrap, {seconds, 5}}].
all() ->
- [cpu_timestamp, receive_trace, self_send, timeout_trace,
+ [cpu_timestamp, receive_trace, link_receive_call_correlation,
+ self_send, timeout_trace,
send_trace, procs_trace, dist_procs_trace, suspend,
mutual_suspend, suspend_exit, suspender_exit,
suspend_system_limit, suspend_opts, suspend_waiting,
@@ -87,10 +88,10 @@ receive_trace(Config) when is_list(Config) ->
1 = erlang:trace(Receiver, true, ['receive']),
Hello = {hello, world},
Receiver ! Hello,
- {trace, Receiver, 'receive', Hello} = receive_first(),
+ {trace, Receiver, 'receive', Hello} = receive_first_trace(),
Hello2 = {hello, again, world},
Receiver ! Hello2,
- {trace, Receiver, 'receive', Hello2} = receive_first(),
+ {trace, Receiver, 'receive', Hello2} = receive_first_trace(),
receive_nothing(),
%% Another process should not be able to trace Receiver.
@@ -104,6 +105,112 @@ receive_trace(Config) when is_list(Config) ->
receive_nothing(),
ok.
+%% Tests that receive of a message always happens before a call with
+%% that message and that links/unlinks are ordered together with the
+%% 'receive'.
+link_receive_call_correlation() ->
+ [{timetrap, {minutes, 5}}].
+link_receive_call_correlation(Config) when is_list(Config) ->
+ Receiver = fun_spawn(fun F() ->
+ receive
+ stop -> ok;
+ M -> receive_msg(M), F()
+ end
+ end),
+ process_flag(trap_exit, true),
+
+ %% Trace the process; make sure that we receive the trace messages.
+ 1 = erlang:trace(Receiver, true, ['receive', procs, call, timestamp, scheduler_id]),
+ 1 = erlang:trace_pattern({?MODULE, receive_msg, '_'}, [], [local]),
+
+ Num = 100000,
+
+ (fun F(0) -> [];
+ F(N) ->
+ if N rem 2 == 0 ->
+ link(Receiver);
+ true ->
+ unlink(Receiver)
+ end,
+ [Receiver ! N | F(N-1)]
+ end)(Num),
+
+ Receiver ! stop,
+ MonRef = erlang:monitor(process, Receiver),
+ receive {'DOWN', MonRef, _, _, _} -> ok end,
+ Ref = erlang:trace_delivered(Receiver),
+ receive {trace_delivered, _, Ref} -> ok end,
+
+ Msgs = (fun F() -> receive M -> [M | F()] after 1 -> [] end end)(),
+
+ case check_consistent(Receiver, Num, Num, Num, Msgs) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ ct:log("~p", [Msgs]),
+ ct:fail({error, Reason})
+ end.
+
+-define(schedid, , _).
+
+check_consistent(_Pid, Recv, Call, _LU, [Msg | _]) when Recv > Call ->
+ {error, Msg};
+check_consistent(Pid, Recv, Call, LU, [Msg | Msgs]) ->
+
+ case Msg of
+ {trace, Pid, 'receive', Recv ?schedid} ->
+ check_consistent(Pid,Recv - 1, Call, LU, Msgs);
+ {trace_ts, Pid, 'receive', Recv ?schedid, _} ->
+ check_consistent(Pid,Recv - 1, Call, LU, Msgs);
+
+ {trace, Pid, call, {?MODULE, receive_msg, [Call]} ?schedid} ->
+ check_consistent(Pid,Recv, Call - 1, LU, Msgs);
+ {trace_ts, Pid, call, {?MODULE, receive_msg, [Call]} ?schedid, _} ->
+ check_consistent(Pid,Recv, Call - 1, LU, Msgs);
+
+ %% We check that for each receive we have gotten a
+ %% getting_linked or getting_unlinked message. Also
+ %% if we receive a getting_linked, then the next
+ %% message we expect to receive is an even number
+ %% and odd number for getting_unlinked.
+ {trace, Pid, getting_linked, _Self ?schedid}
+ when Recv rem 2 == 0, Recv == LU ->
+ check_consistent(Pid, Recv, Call, LU - 1, Msgs);
+ {trace_ts, Pid, getting_linked, _Self ?schedid, _}
+ when Recv rem 2 == 0, Recv == LU ->
+ check_consistent(Pid, Recv, Call, LU - 1, Msgs);
+
+ {trace, Pid, getting_unlinked, _Self ?schedid}
+ when Recv rem 2 == 1, Recv == LU ->
+ check_consistent(Pid, Recv, Call, LU - 1, Msgs);
+ {trace_ts, Pid, getting_unlinked, _Self ?schedid, _}
+ when Recv rem 2 == 1, Recv == LU ->
+ check_consistent(Pid, Recv, Call, LU - 1, Msgs);
+
+ {trace,Pid,'receive',Ignore ?schedid}
+ when Ignore == stop; Ignore == timeout ->
+ check_consistent(Pid, Recv, Call, LU, Msgs);
+ {trace_ts,Pid,'receive',Ignore ?schedid,_}
+ when Ignore == stop; Ignore == timeout ->
+ check_consistent(Pid, Recv, Call, LU, Msgs);
+
+ {trace, Pid, exit, normal ?schedid} ->
+ check_consistent(Pid, Recv, Call, LU, Msgs);
+ {trace_ts, Pid, exit, normal ?schedid, _} ->
+ check_consistent(Pid, Recv, Call, LU, Msgs);
+ {'EXIT', Pid, normal} ->
+ check_consistent(Pid, Recv, Call, LU, Msgs);
+ Msg ->
+ {error, Msg}
+ end;
+check_consistent(_, 0, 0, 0, []) ->
+ ok;
+check_consistent(_, Recv, Call, LU, []) ->
+ {error,{Recv, Call, LU}}.
+
+receive_msg(M) ->
+ M.
+
%% Test that traces are generated for messages sent
%% and received to/from self().
self_send(Config) when is_list(Config) ->
@@ -134,8 +241,8 @@ timeout_trace(Config) when is_list(Config) ->
Process = fun_spawn(fun process/0),
1 = erlang:trace(Process, true, ['receive']),
Process ! timeout_please,
- {trace, Process, 'receive', timeout_please} = receive_first(),
- {trace, Process, 'receive', timeout} = receive_first(),
+ {trace, Process, 'receive', timeout_please} = receive_first_trace(),
+ {trace, Process, 'receive', timeout} = receive_first_trace(),
receive_nothing(),
ok.
@@ -149,13 +256,13 @@ send_trace(Config) when is_list(Config) ->
%% Check that a message sent to another process is traced.
1 = erlang:trace(Sender, true, [send]),
Sender ! {send_please, Receiver, to_receiver},
- {trace, Sender, send, to_receiver, Receiver} = receive_first(),
+ {trace, Sender, send, to_receiver, Receiver} = receive_first_trace(),
receive_nothing(),
%% Check that a message sent to another registered process is traced.
register(?MODULE,Receiver),
Sender ! {send_please, ?MODULE, to_receiver},
- {trace, Sender, send, to_receiver, ?MODULE} = receive_first(),
+ {trace, Sender, send, to_receiver, ?MODULE} = receive_first_trace(),
receive_nothing(),
unregister(?MODULE),
@@ -163,14 +270,14 @@ send_trace(Config) when is_list(Config) ->
Sender ! {send_please, self(), to_myself},
receive to_myself -> ok end,
Self = self(),
- {trace, Sender, send, to_myself, Self} = receive_first(),
+ {trace, Sender, send, to_myself, Self} = receive_first_trace(),
receive_nothing(),
%% Check that a message sent to dead process is traced.
{Pid,Ref} = spawn_monitor(fun() -> ok end),
receive {'DOWN',Ref,_,_,_} -> ok end,
Sender ! {send_please, Pid, to_dead},
- {trace, Sender, send_to_non_existing_process, to_dead, Pid} = receive_first(),
+ {trace, Sender, send_to_non_existing_process, to_dead, Pid} = receive_first_trace(),
receive_nothing(),
%% Check that a message sent to unknown registrated process is traced.
@@ -178,7 +285,7 @@ send_trace(Config) when is_list(Config) ->
1 = erlang:trace(BadargSender, true, [send]),
unlink(BadargSender),
BadargSender ! {send_please, not_registered, to_unknown},
- {trace, BadargSender, send, to_unknown, not_registered} = receive_first(),
+ {trace, BadargSender, send, to_unknown, not_registered} = receive_first_trace(),
receive_nothing(),
%% Another process should not be able to trace Sender.
@@ -205,15 +312,17 @@ procs_trace(Config) when is_list(Config) ->
Proc2 = spawn(?MODULE, process, [Self]),
io:format("Proc2 = ~p ~n", [Proc2]),
%%
- 1 = erlang:trace(Proc1, true, [procs]),
+ 1 = erlang:trace(Proc1, true, [procs, set_on_first_spawn]),
MFA = {?MODULE, process, [Self]},
%%
%% spawn, link
Proc1 ! {spawn_link_please, Self, MFA},
Proc3 = receive {spawned, Proc1, P3} -> P3 end,
- {trace, Proc1, spawn, Proc3, MFA} = receive_first(),
+ receive {trace, Proc3, spawned, Proc1, MFA} -> ok end,
+ receive {trace, Proc3, getting_linked, Proc1} -> ok end,
+ {trace, Proc1, spawn, Proc3, MFA} = receive_first_trace(),
io:format("Proc3 = ~p ~n", [Proc3]),
- {trace, Proc1, link, Proc3} = receive_first(),
+ {trace, Proc1, link, Proc3} = receive_first_trace(),
receive_nothing(),
%%
%% getting_unlinked by exit()
@@ -221,60 +330,61 @@ procs_trace(Config) when is_list(Config) ->
Reason3 = make_ref(),
Proc1 ! {send_please, Proc3, {exit_please, Reason3}},
receive {Proc1, {'EXIT', Proc3, Reason3}} -> ok end,
- {trace, Proc1, getting_unlinked, Proc3} = receive_first(),
+ receive {trace, Proc3, exit, Reason3} -> ok end,
+ {trace, Proc1, getting_unlinked, Proc3} = receive_first_trace(),
Proc1 ! {trap_exit_please, false},
receive_nothing(),
%%
%% link
Proc1 ! {link_please, Proc2},
- {trace, Proc1, link, Proc2} = receive_first(),
+ {trace, Proc1, link, Proc2} = receive_first_trace(),
receive_nothing(),
%%
%% unlink
Proc1 ! {unlink_please, Proc2},
- {trace, Proc1, unlink, Proc2} = receive_first(),
+ {trace, Proc1, unlink, Proc2} = receive_first_trace(),
receive_nothing(),
%%
%% getting_linked
Proc2 ! {link_please, Proc1},
- {trace, Proc1, getting_linked, Proc2} = receive_first(),
+ {trace, Proc1, getting_linked, Proc2} = receive_first_trace(),
receive_nothing(),
%%
%% getting_unlinked
Proc2 ! {unlink_please, Proc1},
- {trace, Proc1, getting_unlinked, Proc2} = receive_first(),
+ {trace, Proc1, getting_unlinked, Proc2} = receive_first_trace(),
receive_nothing(),
%%
%% register
true = register(Name, Proc1),
- {trace, Proc1, register, Name} = receive_first(),
+ {trace, Proc1, register, Name} = receive_first_trace(),
receive_nothing(),
%%
%% unregister
true = unregister(Name),
- {trace, Proc1, unregister, Name} = receive_first(),
+ {trace, Proc1, unregister, Name} = receive_first_trace(),
receive_nothing(),
%%
%% exit (with registered name, due to link)
Reason4 = make_ref(),
Proc1 ! {spawn_link_please, Self, MFA},
Proc4 = receive {spawned, Proc1, P4} -> P4 end,
- {trace, Proc1, spawn, Proc4, MFA} = receive_first(),
+ {trace, Proc1, spawn, Proc4, MFA} = receive_first_trace(),
io:format("Proc4 = ~p ~n", [Proc4]),
- {trace, Proc1, link, Proc4} = receive_first(),
+ {trace, Proc1, link, Proc4} = receive_first_trace(),
Proc1 ! {register_please, Name, Proc1},
- {trace, Proc1, register, Name} = receive_first(),
+ {trace, Proc1, register, Name} = receive_first_trace(),
Proc4 ! {exit_please, Reason4},
receive {'EXIT', Proc1, Reason4} -> ok end,
- {trace, Proc1, exit, Reason4} = receive_first(),
- {trace, Proc1, unregister, Name} = receive_first(),
+ {trace, Proc1, exit, Reason4} = receive_first_trace(),
+ {trace, Proc1, unregister, Name} = receive_first_trace(),
receive_nothing(),
%%
%% exit (not linked to tracing process)
1 = erlang:trace(Proc2, true, [procs]),
Reason2 = make_ref(),
Proc2 ! {exit_please, Reason2},
- {trace, Proc2, exit, Reason2} = receive_first(),
+ {trace, Proc2, exit, Reason2} = receive_first_trace(),
receive_nothing(),
ok.
@@ -299,45 +409,45 @@ dist_procs_trace(Config) when is_list(Config) ->
Proc1 ! {trap_exit_please, true},
Proc3 = receive {spawned, Proc1, P3} -> P3 end,
io:format("Proc3 = ~p ~n", [Proc3]),
- {trace, Proc1, getting_linked, Proc3} = receive_first(),
+ {trace, Proc1, getting_linked, Proc3} = receive_first_trace(),
Reason3 = make_ref(),
Proc1 ! {send_please, Proc3, {exit_please, Reason3}},
receive {Proc1, {'EXIT', Proc3, Reason3}} -> ok end,
- {trace, Proc1, getting_unlinked, Proc3} = receive_first(),
+ {trace, Proc1, getting_unlinked, Proc3} = receive_first_trace(),
Proc1 ! {trap_exit_please, false},
receive_nothing(),
%%
%% link
Proc1 ! {link_please, Proc2},
- {trace, Proc1, link, Proc2} = receive_first(),
+ {trace, Proc1, link, Proc2} = receive_first_trace(),
receive_nothing(),
%%
%% unlink
Proc1 ! {unlink_please, Proc2},
- {trace, Proc1, unlink, Proc2} = receive_first(),
+ {trace, Proc1, unlink, Proc2} = receive_first_trace(),
receive_nothing(),
%%
%% getting_linked
Proc2 ! {link_please, Proc1},
- {trace, Proc1, getting_linked, Proc2} = receive_first(),
+ {trace, Proc1, getting_linked, Proc2} = receive_first_trace(),
receive_nothing(),
%%
%% getting_unlinked
Proc2 ! {unlink_please, Proc1},
- {trace, Proc1, getting_unlinked, Proc2} = receive_first(),
+ {trace, Proc1, getting_unlinked, Proc2} = receive_first_trace(),
receive_nothing(),
%%
%% exit (with registered name, due to link)
Name = list_to_atom(OtherName),
Reason2 = make_ref(),
Proc1 ! {link_please, Proc2},
- {trace, Proc1, link, Proc2} = receive_first(),
+ {trace, Proc1, link, Proc2} = receive_first_trace(),
Proc1 ! {register_please, Name, Proc1},
- {trace, Proc1, register, Name} = receive_first(),
+ {trace, Proc1, register, Name} = receive_first_trace(),
Proc2 ! {exit_please, Reason2},
receive {'EXIT', Proc1, Reason2} -> ok end,
- {trace, Proc1, exit, Reason2} = receive_first(),
- {trace, Proc1, unregister, Name} = receive_first(),
+ {trace, Proc1, exit, Reason2} = receive_first_trace(),
+ {trace, Proc1, unregister, Name} = receive_first_trace(),
receive_nothing(),
%%
%% Done.
@@ -1247,7 +1357,8 @@ existing_clear(Config) when is_list(Config) ->
[N, M]),
{flags, []} = erlang:trace_info(Self, flags),
{tracer, []} = erlang:trace_info(Self, tracer),
- M = N + 1, % Since trace could not be enabled on the tracer.
+ M = N, % Used to be N + 1, but from 19.0 the tracer is also traced
+
ok.
%% Test that an invalid flag cause badarg
@@ -1317,6 +1428,13 @@ receive_first() ->
Any -> Any
end.
+%% Waits for and returns the first message in the message queue.
+
+receive_first_trace() ->
+ receive
+ Any when element(1,Any) =:= trace; element(1,Any) =:= trace_ts -> Any
+ end.
+
%% Ensures that there is no message in the message queue.
receive_nothing() ->
@@ -1421,7 +1539,7 @@ fun_spawn(Fun, Args) ->
start_node(Name) ->
Pa = filename:dirname(code:which(?MODULE)),
Cookie = atom_to_list(erlang:get_cookie()),
- test_server:start_node(Name, slave,
+ test_server:start_node(Name, slave,
[{args, "-setcookie " ++ Cookie ++" -pa " ++ Pa}]).
stop_node(Node) ->
diff --git a/erts/emulator/test/trace_bif_SUITE.erl b/erts/emulator/test/trace_bif_SUITE.erl
index a2edf0662d..8c3ffccc45 100644
--- a/erts/emulator/test/trace_bif_SUITE.erl
+++ b/erts/emulator/test/trace_bif_SUITE.erl
@@ -247,7 +247,8 @@ receive_trace_msg(Mess) ->
receive_trace_msg_ts({trace_ts, Pid, call, {erlang,F,A}}, PrevTs, TsType) ->
receive
- {trace_ts, Pid, call, {erlang, F, A}, Ts} ->
+ {trace_ts, Pid, call, {erlang, F, A}, Ts} = M ->
+ io:format("~p (PrevTs: ~p)~n",[M, PrevTs]),
check_ts(TsType, PrevTs, Ts),
Ts;
Other ->
@@ -260,7 +261,8 @@ receive_trace_msg_ts({trace_ts, Pid, call, {erlang,F,A}}, PrevTs, TsType) ->
receive_trace_msg_ts_return_from({trace_ts, Pid, return_from, {erlang,F,A}}, PrevTs, TsType) ->
receive
- {trace_ts, Pid, return_from, {erlang, F, A}, _Value, Ts} ->
+ {trace_ts, Pid, return_from, {erlang, F, A}, _Value, Ts} = M ->
+ io:format("~p (PrevTs: ~p)~n",[M, PrevTs]),
check_ts(TsType, PrevTs, Ts),
Ts;
Other ->
@@ -272,7 +274,8 @@ receive_trace_msg_ts_return_from({trace_ts, Pid, return_from, {erlang,F,A}}, Pre
receive_trace_msg_ts_return_to({trace_ts, Pid, return_to, {M,F,A}}, PrevTs, TsType) ->
receive
- {trace_ts, Pid, return_to, {M, F, A}, Ts} ->
+ {trace_ts, Pid, return_to, {M, F, A}, Ts} = Msg ->
+ io:format("~p (PrevTs: ~p)~n",[Msg, PrevTs]),
check_ts(TsType, PrevTs, Ts),
Ts;
Other ->
diff --git a/erts/emulator/test/trace_local_SUITE.erl b/erts/emulator/test/trace_local_SUITE.erl
index 8901beebca..74c05f24e0 100644
--- a/erts/emulator/test/trace_local_SUITE.erl
+++ b/erts/emulator/test/trace_local_SUITE.erl
@@ -1224,18 +1224,22 @@ setup(ProcFlags) ->
shutdown() ->
trace_off(),
- {Pid,Mref} = get(slave),
- try erlang:is_process_alive(Pid) of
- true ->
- Pid ! die,
- receive
- {'DOWN',Mref,process,Pid,Reason} ->
- Reason
+ case get(slave) of
+ {Pid,Mref} ->
+ try erlang:is_process_alive(Pid) of
+ true ->
+ Pid ! die,
+ receive
+ {'DOWN',Mref,process,Pid,Reason} ->
+ Reason
+ end;
+ _ ->
+ not_alive
+ catch _:_ ->
+ undefined
end;
_ ->
- not_alive
- catch _:_ ->
- undefined
+ undefined
end.
trace_off() ->
diff --git a/erts/emulator/test/trace_port_SUITE.erl b/erts/emulator/test/trace_port_SUITE.erl
index 98dc96bfa7..1068c1d22d 100644
--- a/erts/emulator/test/trace_port_SUITE.erl
+++ b/erts/emulator/test/trace_port_SUITE.erl
@@ -28,10 +28,6 @@
receive_trace/1,
process_events/1,
schedule/1,
- fake_schedule/1,
- fake_schedule_after_register/1,
- fake_schedule_after_getting_linked/1,
- fake_schedule_after_getting_unlinked/1,
gc/1,
default_tracer/1,
tracer_port_crash/1]).
@@ -44,10 +40,7 @@ suite() ->
all() ->
[call_trace, return_trace, send, receive_trace,
- process_events, schedule, fake_schedule,
- fake_schedule_after_register,
- fake_schedule_after_getting_linked,
- fake_schedule_after_getting_unlinked, gc,
+ process_events, schedule, gc,
default_tracer, tracer_port_crash].
%% Test sending call trace messages to a port.
@@ -234,162 +227,6 @@ schedule(Config) when is_list(Config) ->
ok.
-run_fake_sched_test(Fun, Config) when is_function(Fun), is_list(Config) ->
- case catch erlang:system_info(smp_support) of
- true ->
- {skipped,
- "No need for faked schedule out/in trace messages "
- "when smp support is enabled"};
- _ ->
- Fun(Config)
- end.
-
-%% Tests time compensating fake out/in scheduling.
-fake_schedule(Config) when is_list(Config) ->
- run_fake_sched_test(fun fake_schedule_test/1, Config).
-
-fake_schedule_test(Config) when is_list(Config) ->
- Tracer = start_tracer(Config),
- Port = get(tracer_port),
- General = fun_spawn(fun general/0),
- %%
- trac(General, true, [send, running]),
- %%
- %% Test that fake out/in scheduling is not generated unless
- %% both 'running' and 'timestamp' is active.
- [] = erlang:port_control(Port, $h, []),
- General ! nop,
- expect({trace, General, in, {?MODULE, general, 0}}),
- expect({trace, General, out, {?MODULE, general, 0}}),
- expect(),
- %%
- trac(General, false, [running]),
- trac(General, true, [timestamp]),
- %%
- Ref1 = make_ref(),
- Msg1 = {Port, {data, term_to_binary(Ref1)}},
- [] = erlang:port_control(Port, $h, []),
- General ! {send, Tracer, Msg1},
- expect({trace_ts, General, send, Msg1, Tracer, ts}),
- expect(Ref1),
- expect(),
- %%
- trac(General, true, [running]),
- %%
- %% Test that fake out/in scheduling can be generated by the driver
- Ref2 = make_ref(),
- Msg2 = {Port, {data, term_to_binary(Ref2)}},
- [] = erlang:port_control(Port, $h, []),
- General ! {send, Tracer, Msg2},
- {_,_,_,_,Ts} =
- expect({trace_ts, General, in, {?MODULE, general, 0}, ts}),
- expect({trace_ts, General, out, 0, Ts}),
- expect({trace_ts, General, in, 0, ts}),
- expect({trace_ts, General, send, Msg2, Tracer, ts}),
- expect(Ref2),
- expect({trace_ts, General, out, {?MODULE, general, 0}, ts}),
- expect(),
- %%
- %% Test that fake out/in scheduling is not generated after an
- %% 'out' scheduling event
- Ref3 = make_ref(),
- Msg3 = {Port, {data, term_to_binary(Ref3)}},
- General ! {apply, {erlang, port_control, [Port, $h, []]}},
- expect({trace_ts, General, in, {?MODULE, general, 0}, ts}),
- expect({trace_ts, General, out, {?MODULE, general, 0}, ts}),
- General ! {send, Tracer, Msg3},
- expect({trace_ts, General, in, {?MODULE, general, 0}, ts}),
- expect({trace_ts, General, send, Msg3, Tracer, ts}),
- expect(Ref3),
- expect({trace_ts, General, out, {?MODULE, general, 0}, ts}),
- expect(),
- %%
- ok.
-
-%% Tests fake out/in scheduling contents.
-fake_schedule_after_register(Config) when is_list(Config) ->
- run_fake_sched_test(fun fake_schedule_after_register_test/1, Config).
-
-fake_schedule_after_register_test(Config) when is_list(Config) ->
- start_tracer(Config),
- Port = get(tracer_port),
- G1 = fun_spawn(fun general/0),
- G2 = fun_spawn(fun general/0),
- %%
- trac(G1, true, [running, timestamp, procs]),
- trac(G2, true, [running, timestamp]),
- %%
- %% Test fake out/in scheduling after certain messages
- erlang:yield(),
- G2 ! {apply, {erlang, port_control, [Port, $h, []]}},
- G2 ! {apply, {erlang, register, [fake_schedule_after_register, G1]}},
- expect({trace_ts, G2, in, {?MODULE, general, 0}, ts}),
- {_,_,_,_,Ts} =
- expect({trace_ts, G1, register, fake_schedule_after_register, ts}),
- expect({trace_ts, G2, out, 0, Ts}),
- expect({trace_ts, G2, in, 0, ts}),
- expect({trace_ts, G2, out, {?MODULE, general, 0}, ts}),
- expect(),
- %%
- ok.
-
-%% Tests fake out/in scheduling contents.
-fake_schedule_after_getting_linked(Config) when is_list(Config) ->
- run_fake_sched_test(fun fake_schedule_after_getting_linked_test/1,
- Config).
-
-fake_schedule_after_getting_linked_test(Config) when is_list(Config) ->
- start_tracer(Config),
- Port = get(tracer_port),
- G1 = fun_spawn(fun general/0),
- G2 = fun_spawn(fun general/0),
- %%
- trac(G1, true, [running, timestamp, procs]),
- trac(G2, true, [running, timestamp]),
- %%
- %% Test fake out/in scheduling after certain messages
- erlang:yield(),
- G2 ! {apply, {erlang, port_control, [Port, $h, []]}},
- G2 ! {apply, {erlang, link, [G1]}},
- expect({trace_ts, G2, in, {?MODULE, general, 0}, ts}),
- {_,_,_,_,Ts} =
- expect({trace_ts, G1, getting_linked, G2, ts}),
- expect({trace_ts, G2, out, 0, Ts}),
- expect({trace_ts, G2, in, 0, ts}),
- expect({trace_ts, G2, out, {?MODULE, general, 0}, ts}),
- expect(),
- %%
- ok.
-
-%% Tests fake out/in scheduling contents.
-fake_schedule_after_getting_unlinked(Config) when is_list(Config) ->
- run_fake_sched_test(fun fake_schedule_after_getting_unlinked_test/1,
- Config).
-
-fake_schedule_after_getting_unlinked_test(Config) when is_list(Config) ->
- start_tracer(Config),
- Port = get(tracer_port),
- G1 = fun_spawn(fun general/0),
- G2 = fun_spawn(fun general/0),
- %%
- trac(G1, true, [running, procs]),
- trac(G2, true, [running, timestamp]),
- %%
- %% Test fake out/in scheduling after certain messages
- erlang:yield(),
- G2 ! {apply, {erlang, link, [G1]}},
- G2 ! {apply, {erlang, port_control, [Port, $h, []]}},
- G2 ! {apply, {erlang, unlink, [G1]}},
- expect({trace_ts, G2, in, {?MODULE, general, 0}, ts}),
- expect({trace, G1, getting_linked, G2}),
- expect({trace, G1, getting_unlinked, G2}),
- expect({trace_ts, G2, out, 0, ts}),
- expect({trace_ts, G2, in, 0, ts}),
- expect({trace_ts, G2, out, {?MODULE, general, 0}, ts}),
- expect(),
- %%
- ok.
-
%% Test sending garbage collection events to a port.
gc(Config) when is_list(Config) ->
start_tracer(Config),
@@ -440,7 +277,7 @@ default_tracer(Config) when is_list(Config) ->
{tracer, []} = erlang:trace_info(G1, tracer),
G1 ! {apply,{erlang,exit,[normal]}},
io:format("~p = ~p.~n", [M, N]),
- M = N,
+ M = N - 1, % G1 has been started, but Tracer and Port have died
ok.
tracer_port_crash(Config) when is_list(Config) ->
diff --git a/erts/emulator/test/tracer_SUITE.erl b/erts/emulator/test/tracer_SUITE.erl
new file mode 100644
index 0000000000..812e834562
--- /dev/null
+++ b/erts/emulator/test/tracer_SUITE.erl
@@ -0,0 +1,627 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2013. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(tracer_SUITE).
+
+%%%
+%%% Tests the tracer module interface
+%%%
+
+-export([all/0, suite/0,groups/0, init_per_suite/1, end_per_suite/1,
+ init_per_group/2,end_per_group/2, init_per_testcase/2,
+ end_per_testcase/2]).
+-export([load/1, unload/1, reload/1, invalid_tracers/1]).
+-export([send/1, recv/1, spawn/1, exit/1, link/1, unlink/1,
+ getting_linked/1, getting_unlinked/1, register/1, unregister/1,
+ in/1, out/1, gc_start/1, gc_end/1]).
+
+suite() -> [{ct_hooks,[ts_install_cth]},
+ {timetrap, {minutes, 1}}].
+
+all() ->
+ [load, unload, reload, invalid_tracers, {group, basic}].
+
+groups() ->
+ [{ basic, [], [send, recv, spawn, exit, link, unlink, getting_linked,
+ getting_unlinked, register, unregister, in, out,
+ gc_start, gc_end]}].
+
+init_per_suite(Config) ->
+ purge(),
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+init_per_testcase(TC, Config) when TC =:= load; TC =:= reload ->
+
+ DataDir = proplists:get_value(data_dir, Config),
+
+ Pid = erlang:spawn(fun F() ->
+ receive
+ {get, Pid} ->
+ Pid ! DataDir,
+ F()
+ end
+ end),
+ register(tracer_test_config, Pid),
+ Config;
+init_per_testcase(_, Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+ case catch tracer_test:enabled(trace_status, self(), self()) of
+ discard ->
+ ok;
+ _ ->
+ tracer_test:load(DataDir)
+ end,
+ Config.
+
+end_per_testcase(TC, _Config) when TC =:= load; TC =:= reload ->
+ purge(),
+ exit(whereis(tracer_test_config), kill),
+ ok;
+end_per_testcase(_, _Config) ->
+ purge(),
+ ok.
+
+load(_Config) ->
+ purge(),
+ 1 = erlang:trace(self(), true, [{tracer, tracer_test, []}, call]),
+ purge(),
+ 1 = erlang:trace_pattern({?MODULE, all, 0}, [],
+ [{meta, tracer_test, []}]),
+ ok.
+
+unload(_Config) ->
+
+ ServerFun = fun F(0, undefined) ->
+ receive
+ {N, Pid} -> F(N, Pid)
+ end;
+ F(0, Pid) ->
+ Pid ! done,
+ F(0, undefined);
+ F(N, Pid) ->
+ ?MODULE:all(),
+ F(N-1, Pid)
+ end,
+
+ Pid = erlang:spawn_link(fun() -> ServerFun(0, undefined) end),
+
+
+ Tc = fun(N) ->
+ Pid ! {N, self()},
+ receive done -> ok after 1000 -> ct:fail(timeout) end,
+ trace_delivered(Pid)
+ end,
+
+ 1 = erlang:trace(Pid, true, [{tracer, tracer_test,
+ {#{ call => trace}, self(), []}},
+ call]),
+ 1 = erlang:trace_pattern({?MODULE, all, 0}, [], []),
+
+ Tc(1),
+ receive _ -> ok after 0 -> ct:fail(timeout) end,
+
+ code:purge(tracer_test),
+ code:delete(tracer_test),
+
+ Tc(1),
+ receive M1 -> ct:fail({unexpected_message, M1}) after 0 -> ok end,
+
+ code:purge(tracer_test),
+
+ Tc(1),
+ receive M2 -> ct:fail({unexpected_message, M2}) after 0 -> ok end,
+
+ ok.
+
+%% This testcase is here to make sure there are not
+%% segfaults when reloading the current nifs.
+reload(_Config) ->
+
+ Tracer = spawn_opt(fun F() -> receive _M -> F() end end,
+ [{message_queue_data, off_heap}]),
+ erlang:link(Tracer),
+ Tracee = spawn_link(fun reload_loop/0),
+
+ [begin
+ Ref = make_ref(),
+ State = {#{ call => trace }, Tracer, [Ref]},
+ erlang:trace(Tracee, true, [{tracer, tracer_test,State}, call]),
+ erlang:trace_pattern({?MODULE, all, 0}, []),
+
+ false = code:purge(tracer_test),
+ {module, _} = code:load_file(tracer_test),
+
+ %% There is a race involved in between when the internal nif cache
+ %% is purged and when the reload_loop needs the tracer module
+ %% so the tracer may be removed or still there.
+ case erlang:trace_info(Tracee, tracer) of
+ {tracer, []} -> ok;
+ {tracer, {tracer_test, State}} -> ok
+ end,
+
+ false = code:purge(tracer_test),
+ true = code:delete(tracer_test),
+ false = code:purge(tracer_test)
+ end || _ <- lists:seq(1,15)],
+
+ ok.
+
+reload_loop() ->
+ ?MODULE:all(),
+ reload_loop().
+
+invalid_tracers(_Config) ->
+ FailTrace = fun(A) ->
+ try erlang:trace(self(), true, A) of
+ _ -> ct:fail(A)
+ catch _:_ -> ok end
+ end,
+
+ FailTrace([{tracer, foobar}, call]),
+ FailTrace([{tracer, foobar, []}, call]),
+ FailTrace([{tracer, make_ref(), []}, call]),
+ FailTrace([{tracer, lists, []}, call]),
+
+ FailTP = fun(MS,FL) ->
+ try erlang:trace_pattern({?MODULE,all,0}, MS, FL) of
+ _ -> ct:fail({MS, FL})
+ catch _:_ -> ok end
+ end,
+
+ FailTP([],[{meta, foobar}]),
+ FailTP([],[{meta, foobar, []}]),
+ FailTP([],[{meta, make_ref(), []}]),
+ FailTP([],[{meta, lists, []}]),
+
+ ok.
+
+
+
+send(_Config) ->
+
+ Self = self(),
+ Tc = fun(Pid) ->
+ Pid ! fun() -> Self ! ok end,
+ receive ok -> ok after 100 -> ct:fail(timeout) end
+ end,
+
+ Expect = fun(Pid, State, EOpts) ->
+ receive
+ Msg ->
+ {Pid, send, State, Pid, ok, Self, Opts} = Msg,
+ check_opts(EOpts, Opts)
+ end
+ end,
+ test(send, Tc, Expect).
+
+
+recv(_Config) ->
+
+ Tc = fun(Pid) ->
+ Pid ! ok
+ end,
+
+ Expect = fun(Pid, State, EOpts) ->
+ receive
+ Msg ->
+ {undefined, 'receive', State, Pid, ok, undefined, Opts} = Msg,
+ check_opts(EOpts, Opts)
+ end
+ end,
+
+ test('receive', Tc, Expect, false).
+
+spawn(_Config) ->
+
+ Tc = fun(Pid) ->
+ Pid ! fun() -> erlang:spawn(lists,seq,[1,10]), ok end
+ end,
+
+ Expect =
+ fun(Pid, State, EOpts) ->
+ receive
+ Msg ->
+ {Pid, spawn, State, Pid, NewPid,
+ {lists,seq,[1,10]}, Opts} = Msg,
+ check_opts(EOpts, Opts),
+ true = is_pid(NewPid) andalso NewPid /= Pid
+ end
+ end,
+
+ test(spawn, procs, Tc, Expect, true).
+
+exit(_Config) ->
+ Tc = fun(Pid) ->
+ Pid ! fun() -> exit end
+ end,
+
+ Expect =
+ fun(Pid, State, EOpts) ->
+ receive
+ Msg ->
+ {Pid, exit, State, Pid, normal, undefined, Opts} = Msg,
+ check_opts(EOpts, Opts)
+ end
+ end,
+
+ test(exit, procs, Tc, Expect, true, true).
+
+link(_Config) ->
+
+ Tc = fun(Pid) ->
+ Pid ! fun() ->
+ SPid = erlang:spawn(fun() -> receive _ -> ok end end),
+ erlang:link(SPid),
+ ok
+ end
+ end,
+
+ Expect =
+ fun(Pid, State, EOpts) ->
+ receive
+ Msg ->
+ {Pid, link, State, Pid, NewPid, undefined, Opts} = Msg,
+ check_opts(EOpts, Opts),
+ true = is_pid(NewPid) andalso NewPid /= Pid
+ end
+ end,
+
+ test(link, procs, Tc, Expect, true).
+
+unlink(_Config) ->
+
+ Tc = fun(Pid) ->
+ Pid ! fun() ->
+ SPid = erlang:spawn(fun() -> receive _ -> ok end end),
+ erlang:link(SPid),
+ erlang:unlink(SPid),
+ ok
+ end
+ end,
+
+ Expect =
+ fun(Pid, State, EOpts) ->
+ receive
+ Msg ->
+ {Pid, unlink, State, Pid, NewPid, undefined, Opts} = Msg,
+ check_opts(EOpts, Opts),
+ true = is_pid(NewPid) andalso NewPid /= Pid
+ end
+ end,
+
+ test(unlink, procs, Tc, Expect, true).
+
+getting_linked(_Config) ->
+
+ Tc = fun(Pid) ->
+ Pid ! fun() ->
+ Self = self(),
+ erlang:spawn(fun() -> erlang:link(Self) end),
+ ok
+ end
+ end,
+
+ Expect =
+ fun(Pid, State, EOpts) ->
+ receive
+ Msg ->
+ {NewPid, getting_linked, State, Pid, NewPid, undefined, Opts} = Msg,
+ check_opts(EOpts, Opts),
+ true = is_pid(NewPid) andalso NewPid /= Pid
+ end
+ end,
+
+ test(getting_linked, procs, Tc, Expect, false).
+
+getting_unlinked(_Config) ->
+ Tc = fun(Pid) ->
+ Pid ! fun() ->
+ Self = self(),
+ erlang:spawn(fun() ->
+ erlang:link(Self),
+ erlang:unlink(Self)
+ end),
+ ok
+ end
+ end,
+
+ Expect =
+ fun(Pid, State, EOpts) ->
+ receive
+ Msg ->
+ {NewPid, getting_unlinked, State, Pid, NewPid, undefined, Opts} = Msg,
+ check_opts(EOpts, Opts),
+ true = is_pid(NewPid) andalso NewPid /= Pid
+ end
+ end,
+
+ test(getting_unlinked, procs, Tc, Expect, false).
+
+register(_Config) ->
+
+ Tc = fun(Pid) ->
+ Pid ! fun() ->
+ erlang:register(?MODULE, self()),
+ erlang:unregister(?MODULE),
+ ok
+ end
+ end,
+
+ Expect =
+ fun(Pid, State, EOpts) ->
+ receive
+ Msg ->
+ {Pid, register, State, Pid, ?MODULE, undefined, Opts} = Msg,
+ check_opts(EOpts, Opts)
+ end
+ end,
+
+ test(register, procs, Tc, Expect, true).
+
+unregister(_Config) ->
+
+ Tc = fun(Pid) ->
+ Pid ! fun() ->
+ erlang:register(?MODULE, self()),
+ erlang:unregister(?MODULE),
+ ok
+ end
+ end,
+
+ Expect =
+ fun(Pid, State, EOpts) ->
+ receive
+ Msg ->
+ {Pid, unregister, State, Pid, ?MODULE, undefined, Opts} = Msg,
+ check_opts(EOpts, Opts)
+ end
+ end,
+
+ test(unregister, procs, Tc, Expect, true).
+
+in(_Config) ->
+
+ Tc = fun(Pid) ->
+ Self = self(),
+ Pid ! fun() -> receive after 1 -> Self ! ok end end,
+ receive ok -> ok end
+ end,
+
+ Expect =
+ fun(Pid, State, EOpts) ->
+ N = (fun F(N) ->
+ receive
+ Msg ->
+ {Pid, in, State, Pid, _,
+ undefined, Opts} = Msg,
+ check_opts(EOpts, Opts),
+ F(N+1)
+ after 0 -> N
+ end
+ end)(0),
+ true = N > 0
+ end,
+
+ test(in, running, Tc, Expect, true).
+
+out(_Config) ->
+ Tc = fun(Pid) ->
+ Pid ! fun() -> receive after 10 -> exit end end,
+ Ref = erlang:monitor(process, Pid),
+ receive {'DOWN', Ref, _, _, _} -> ok end
+ end,
+
+ Expect =
+ fun(Pid, State, EOpts) ->
+ %% We cannot predict how many out schedules there will be
+ N = (fun F(N) ->
+ receive
+ Msg ->
+ {Pid, out, State, Pid, _,
+ undefined, Opts} = Msg,
+ check_opts(EOpts, Opts),
+ F(N+1)
+ after 0 -> N
+ end
+ end)(0),
+ true = N > 0
+ end,
+
+ test(out, running, Tc, Expect, true, true).
+
+gc_start(_Config) ->
+
+ Tc = fun(Pid) ->
+ Pid ! fun() ->
+ erlang:garbage_collect(),
+ ok
+ end
+ end,
+
+ Expect =
+ fun(Pid, State, EOpts) ->
+ receive
+ Msg ->
+ {Pid, gc_start, State, Pid, _, undefined, Opts} = Msg,
+ check_opts(EOpts, Opts)
+ end
+ end,
+
+ test(gc_start, garbage_collection, Tc, Expect, true).
+
+gc_end(_Config) ->
+
+ Tc = fun(Pid) ->
+ Pid ! fun() ->
+ erlang:garbage_collect(),
+ ok
+ end
+ end,
+
+ Expect =
+ fun(Pid, State, EOpts) ->
+ receive
+ Msg ->
+ {Pid, gc_end, State, Pid, _, undefined, Opts} = Msg,
+ check_opts(EOpts, Opts)
+ end
+ end,
+
+ test(gc_end, garbage_collection, Tc, Expect, true).
+
+test(Event, Tc, Expect) ->
+ test(Event, Tc, Expect, true).
+test(Event, Tc, Expect, Removes) ->
+ test(Event, Event, Tc, Expect, Removes).
+test(Event, TraceFlag, Tc, Expect, Removes) ->
+ test(Event, TraceFlag, Tc, Expect, Removes, false).
+test(Event, TraceFlag, Tc, Expect, Removes, Dies) ->
+
+ ComplexState = {fun() -> ok end, <<0:(128*8)>>},
+ Opts = #{ timestamp => undefined,
+ scheduler_id => undefined,
+ match_spec_result => true },
+
+ %% Test that trace works
+ State1 = {#{ Event => trace }, self(), ComplexState},
+ Pid1 = start_tracee(),
+ 1 = erlang:trace(Pid1, true, [TraceFlag, {tracer, tracer_test, State1}]),
+ Tc(Pid1),
+ ok = trace_delivered(Pid1),
+
+ Expect(Pid1, State1, Opts),
+ receive M11 -> ct:fail({unexpected, M11}) after 0 -> ok end,
+ if not Dies ->
+ {flags, [TraceFlag]} = erlang:trace_info(Pid1, flags),
+ {tracer, {tracer_test, State1}} = erlang:trace_info(Pid1, tracer),
+ erlang:trace(Pid1, false, [TraceFlag]);
+ true -> ok
+ end,
+
+ %% Test that trace works with scheduler id and timestamp
+ Pid1T = start_tracee(),
+ 1 = erlang:trace(Pid1T, true, [TraceFlag, {tracer, tracer_test, State1},
+ timestamp, scheduler_id]),
+ Tc(Pid1T),
+ ok = trace_delivered(Pid1T),
+
+ Expect(Pid1T, State1, Opts#{ scheduler_id := number,
+ timestamp := timestamp}),
+ receive M11T -> ct:fail({unexpected, M11T}) after 0 -> ok end,
+ if not Dies ->
+ {flags, [scheduler_id, TraceFlag, timestamp]}
+ = erlang:trace_info(Pid1T, flags),
+ {tracer, {tracer_test, State1}} = erlang:trace_info(Pid1T, tracer),
+ erlang:trace(Pid1T, false, [TraceFlag]);
+ true -> ok
+ end,
+
+ %% Test that discard works
+ Pid2 = start_tracee(),
+ State2 = {#{ Event => discard }, self(), ComplexState},
+ 1 = erlang:trace(Pid2, true, [TraceFlag, {tracer, tracer_test, State2}]),
+ Tc(Pid2),
+ ok = trace_delivered(Pid2),
+ receive M2 -> ct:fail({unexpected, M2}) after 0 -> ok end,
+ if not Dies ->
+ {flags, [TraceFlag]} = erlang:trace_info(Pid2, flags),
+ {tracer, {tracer_test, State2}} = erlang:trace_info(Pid2, tracer),
+ erlang:trace(Pid2, false, [TraceFlag]);
+ true ->
+ ok
+ end,
+
+ %% Test that remove works
+ Pid3 = start_tracee(),
+ State3 = {#{ Event => remove }, self(), ComplexState},
+ 1 = erlang:trace(Pid3, true, [TraceFlag, {tracer, tracer_test, State3}]),
+ Tc(Pid3),
+ ok = trace_delivered(Pid3),
+ receive M3 -> ct:fail({unexpected, M3}) after 0 -> ok end,
+ if not Dies ->
+ if Removes ->
+ {flags, []} = erlang:trace_info(Pid3, flags),
+ {tracer, []} = erlang:trace_info(Pid3, tracer);
+ true ->
+ {flags, [TraceFlag]} = erlang:trace_info(Pid3, flags),
+ {tracer, {tracer_test, State3}} = erlang:trace_info(Pid3, tracer)
+ end,
+ erlang:trace(Pid3, false, [TraceFlag]);
+ true ->
+ ok
+ end,
+ ok.
+
+check_opts(#{ scheduler_id := number } = E, #{ scheduler_id := N } = O)
+ when is_integer(N) ->
+ E1 = maps:remove(scheduler_id, E),
+ O1 = maps:remove(scheduler_id, O),
+ if E1 == O1 -> ok;
+ true -> ct:fail({invalid_opts, E, O})
+ end;
+check_opts(Opts, Opts) ->
+ ok;
+check_opts(E,O) ->
+ ct:fail({invalid_opts, E, O}).
+
+start_tracee() ->
+ spawn_link(
+ fun F() ->
+ receive
+ Action when is_function(Action) ->
+ case Action() of
+ ok ->
+ F();
+ Err ->
+ Err
+ end;
+ _ ->
+ F()
+ end
+ end).
+
+trace_delivered(Pid) ->
+ Ref = erlang:trace_delivered(Pid),
+ receive
+ {trace_delivered, Pid, Ref} ->
+ ok
+ after 1000 ->
+ timeout
+ end.
+
+purge() ->
+ %% Make sure module is not loaded
+ case erlang:module_loaded(tracer_test) of
+ true ->
+ code:purge(tracer_test),
+ true = code:delete(tracer_test),
+ code:purge(tracer_test);
+ _ ->
+ ok
+ end.
diff --git a/erts/emulator/test/tracer_SUITE_data/Makefile.src b/erts/emulator/test/tracer_SUITE_data/Makefile.src
new file mode 100644
index 0000000000..154bd70ccc
--- /dev/null
+++ b/erts/emulator/test/tracer_SUITE_data/Makefile.src
@@ -0,0 +1,8 @@
+
+NIF_LIBS = tracer_test@dll@
+
+all: $(NIF_LIBS)
+
+@SHLIB_RULES@
+
+$(NIF_LIBS): tracer_test.c
diff --git a/erts/emulator/test/tracer_SUITE_data/tracer_test.c b/erts/emulator/test/tracer_SUITE_data/tracer_test.c
new file mode 100644
index 0000000000..8b4be1345d
--- /dev/null
+++ b/erts/emulator/test/tracer_SUITE_data/tracer_test.c
@@ -0,0 +1,122 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2009-2014. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+#include "erl_nif.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <limits.h>
+
+/* NIF interface declarations */
+static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info);
+static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info);
+static void unload(ErlNifEnv* env, void* priv_data);
+
+/* The NIFs: */
+static ERL_NIF_TERM enabled(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM trace(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+static ErlNifFunc nif_funcs[] = {
+ {"enabled", 3, enabled},
+ {"trace", 6, trace}
+};
+
+ERL_NIF_INIT(tracer_test, nif_funcs, load, NULL, upgrade, unload)
+
+static ERL_NIF_TERM atom_discard;
+static ERL_NIF_TERM atom_ok;
+
+#define ASSERT(expr) assert(expr)
+
+static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
+{
+
+ atom_discard = enif_make_atom(env, "discard");
+ atom_ok = enif_make_atom(env, "ok");
+
+ *priv_data = NULL;
+
+ return 0;
+}
+
+static void unload(ErlNifEnv* env, void* priv_data)
+{
+
+}
+
+static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data,
+ ERL_NIF_TERM load_info)
+{
+ if (*old_priv_data != NULL) {
+ return -1; /* Don't know how to do that */
+ }
+ if (*priv_data != NULL) {
+ return -1; /* Don't know how to do that */
+ }
+ if (load(env, priv_data, load_info)) {
+ return -1;
+ }
+ return 0;
+}
+
+static ERL_NIF_TERM enabled(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ int state_arity;
+ const ERL_NIF_TERM *state_tuple;
+ ERL_NIF_TERM value;
+ ASSERT(argc == 3);
+
+ if (!enif_get_tuple(env, argv[1], &state_arity, &state_tuple))
+ return atom_discard;
+
+ if (enif_get_map_value(env, state_tuple[0], argv[0], &value)) {
+ return value;
+ } else {
+ return atom_discard;
+ }
+}
+
+static ERL_NIF_TERM trace(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ int state_arity;
+ ErlNifPid self, to;
+ ERL_NIF_TERM *tuple, msg;
+ const ERL_NIF_TERM *state_tuple;
+ ASSERT(argc == 6);
+
+ enif_get_tuple(env, argv[1], &state_arity, &state_tuple);
+
+ tuple = enif_alloc(sizeof(ERL_NIF_TERM)*(argc+1));
+ memcpy(tuple+1,argv,sizeof(ERL_NIF_TERM)*argc);
+
+ if (enif_self(env, &self)) {
+ tuple[0] = enif_make_pid(env, &self);
+ } else {
+ tuple[0] = enif_make_atom(env, "undefined");
+ }
+
+ msg = enif_make_tuple_from_array(env, tuple, argc + 1);
+ enif_get_local_pid(env, state_tuple[1], &to);
+ enif_send(env, &to, NULL, msg);
+ enif_free(tuple);
+
+ return atom_ok;
+}
diff --git a/erts/emulator/test/tracer_test.erl b/erts/emulator/test/tracer_test.erl
new file mode 100644
index 0000000000..d4778f4531
--- /dev/null
+++ b/erts/emulator/test/tracer_test.erl
@@ -0,0 +1,55 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2013. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(tracer_test).
+
+%%%
+%%% Test tracer
+%%%
+
+-export([enabled/3, trace/6]).
+-export([load/1, load/2]).
+-on_load(load/0).
+
+enabled(_, _, _) ->
+ erlang:nif_error(nif_not_loaded).
+
+trace(_, _, _, _, _, _) ->
+ erlang:nif_error(nif_not_loaded).
+
+load() ->
+ case whereis(tracer_test_config) of
+ undefined ->
+ ok;
+ Pid ->
+ Pid ! {get, self()},
+ receive
+ {Conf, Postfix} ->
+ load(Conf, Postfix);
+ Conf ->
+ load(Conf)
+ end
+ end.
+
+load(DataDir) ->
+ load(DataDir, "").
+load(DataDir, Postfix) ->
+ SoFile = atom_to_list(?MODULE) ++ Postfix,
+ erlang:load_nif(filename:join(DataDir, SoFile) , 0).