%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1997-2017. 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, call/1, call_return/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, call, call_return, spawn, exit,
link, unlink, getting_linked, getting_unlinked,
register, unregister, in, out, gc_start, gc_end]}].
init_per_suite(Config) ->
erlang:trace_pattern({'_','_','_'}, false, [local]),
erlang:trace_pattern({'_','_','_'}, false, []),
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),
common_init_per_testcase(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,
common_init_per_testcase(Config).
common_init_per_testcase(Config) ->
Killer = erlang:spawn(fun() -> killer_loop([]) end),
register(killer_process, Killer),
Config.
end_per_testcase(TC, _Config) when TC =:= load; TC =:= reload ->
purge(),
exit(whereis(tracer_test_config), kill),
kill_processes();
end_per_testcase(_, _Config) ->
purge(),
kill_processes().
kill_processes() ->
killer_process ! {get_pids,self()},
receive
{pids_to_kill,Pids} -> ok
end,
_ = [begin
case erlang:is_process_alive(P) of
true ->
io:format("Killing ~p\n", [P]);
false ->
ok
end,
erlang:unlink(P),
exit(P, kill)
end || P <- Pids],
ok.
killer_loop(Pids) ->
receive
{add_pid,Pid} ->
killer_loop([Pid|Pids]);
{get_pids,To} ->
To ! {pids_to_kill,Pids}
end.
kill_me(Pid) ->
killer_process ! {add_pid,Pid},
Pid.
%%% Test cases follow.
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 _M -> ok after 0 -> ct:fail(timeout) end,
receive M0 -> ct:fail({unexpected_message0, M0}) after 0 -> ok end,
code:purge(tracer_test),
code:delete(tracer_test),
Tc(1),
receive M1 -> ct:fail({unexpected_message1, M1}) after 0 -> ok end,
code:purge(tracer_test),
Tc(1),
receive M2 -> ct:fail({unexpected_message2, 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),
timer:sleep(10)
end || _ <- lists:seq(1,15)],
ok.
reload_loop() ->
?MODULE:all(),
?MODULE:all(),
?MODULE:all(),
?MODULE:all(),
?MODULE:all(),
timer:sleep(1),
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 ->
{send, State, Pid, ok, Opts} = Msg,
check_opts(EOpts, Opts, Self)
end
end,
test(send, Tc, Expect).
recv(_Config) ->
Tc = fun(Pid) ->
Pid ! ok
end,
Expect = fun(Pid, State, EOpts) ->
receive
Msg ->
{'receive', State, Pid, ok, Opts} = Msg,
check_opts(EOpts, Opts)
end
end,
test('receive', Tc, Expect, false).
call(_Config) ->
Self = self(),
Tc = fun(Pid) ->
Pid ! fun() -> call_test(Self), Self ! ok end,
receive ok -> ok after 100 -> ct:fail(timeout) end
end,
erlang:trace_pattern({?MODULE, call_test, 1}, [], [local]),
Expect = fun(Pid, State, EOpts) ->
receive
Msg ->
{call, State, Pid, {?MODULE, call_test, [Self]}, Opts} = Msg,
check_opts(EOpts, Opts)
end
end,
test(call, Tc, Expect).
call_return(_Config) ->
Self = self(),
Tc = fun(Pid) ->
Pid ! fun() -> call_test(undefined), Self ! ok end,
receive ok -> ok after 100 -> ct:fail(timeout) end
end,
1 = erlang:trace_pattern({?MODULE, call_test, 1}, [{'_',[],[{return_trace}]}], [local]),
Expect = fun(Pid, State, EOpts) ->
receive
CallMsg ->
{call, State, Pid, {?MODULE, call_test, [undefined]}, COpts} = CallMsg,
check_opts(EOpts, COpts)
end,
receive
RetMsg ->
{return_from, State, Pid, {?MODULE, call_test, 1}, ROpts} = RetMsg,
check_opts(EOpts, ROpts, undefined)
end
end,
test(call, Tc, Expect).
call_test(Arg) ->
Arg.
spawn(_Config) ->
Tc = fun(Pid) ->
Pid ! fun() -> kill_me(erlang:spawn(lists,seq,[1,10])), ok end
end,
Expect =
fun(Pid, State, EOpts) ->
receive
Msg ->
{spawn, State, Pid, NewPid, Opts} = Msg,
check_opts(EOpts, Opts, {lists,seq,[1,10]}),
true = is_pid(NewPid) andalso NewPid /= Pid
end
end,
test(spawn, procs, Tc, Expect, false).
exit(_Config) ->
Tc = fun(Pid) ->
Pid ! fun() -> exit end
end,
Expect =
fun(Pid, State, EOpts) ->
receive
Msg ->
{exit, State, Pid, normal, 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 ->
{link, State, Pid, NewPid, Opts} = Msg,
check_opts(EOpts, Opts),
true = is_pid(NewPid) andalso NewPid /= Pid
end
end,
test(link, procs, Tc, Expect, false).
unlink(_Config) ->
Tc = fun(Pid) ->
Pid ! fun() ->
SPid = erlang:spawn(fun() -> receive _ -> ok end end),
erlang:link(SPid),
erlang:unlink(SPid),
kill_me(SPid),
ok
end
end,
Expect =
fun(Pid, State, EOpts) ->
receive
Msg ->
{unlink, State, Pid, NewPid, Opts} = Msg,
check_opts(EOpts, Opts),
true = is_pid(NewPid) andalso NewPid /= Pid
end
end,
test(unlink, procs, Tc, Expect, false).
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 ->
{getting_linked, State, Pid, NewPid, 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 ->
{getting_unlinked, State, Pid, NewPid, 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 ->
{register, State, Pid, ?MODULE, Opts} = Msg,
check_opts(EOpts, Opts)
end
end,
test(register, procs, Tc, Expect, false).
unregister(_Config) ->
Tc = fun(Pid) ->
Pid ! fun() ->
erlang:register(?MODULE, self()),
erlang:unregister(?MODULE),
ok
end
end,
Expect =
fun(Pid, State, EOpts) ->
receive
Msg ->
{unregister, State, Pid, ?MODULE, Opts} = Msg,
check_opts(EOpts, Opts)
end
end,
test(unregister, procs, Tc, Expect, false).
in(_Config) ->
Tc = fun(Pid) ->
Self = self(),
Pid ! fun() -> receive after 10 -> Self ! ok end end,
receive ok -> ok end
end,
Expect =
fun(Pid, State, EOpts) ->
N = (fun F(N) ->
receive
Msg ->
{in, State, Pid, _, Opts} = Msg,
check_opts(EOpts, Opts),
F(N+1)
after 0 -> N
end
end)(0),
true = N > 0
end,
test(in, running, Tc, Expect, false).
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 ->
{out, State, Pid, _, Opts} = Msg,
check_opts(EOpts, Opts),
F(N+1)
after 0 -> N
end
end)(0),
true = N > 0
end,
test(out, running, Tc, Expect, false, true).
gc_start(_Config) ->
Tc = fun(Pid) ->
Pid ! fun() ->
erlang:garbage_collect(),
ok
end
end,
Expect =
fun(Pid, State, EOpts) ->
receive
Msg ->
{gc_major_start, State, Pid, _, Opts} = Msg,
check_opts(EOpts, Opts)
end
end,
test(gc_major_start, garbage_collection, Tc, Expect, false).
gc_end(_Config) ->
Tc = fun(Pid) ->
Pid ! fun() ->
erlang:garbage_collect(),
ok
end
end,
Expect =
fun(Pid, State, EOpts) ->
receive
Msg ->
{gc_major_end, State, Pid, _, Opts} = Msg,
check_opts(EOpts, Opts)
end
end,
test(gc_major_end, garbage_collection, Tc, Expect, false).
test(Event, Tc, Expect) ->
test(Event, Tc, Expect, false).
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 = #{ },
%% 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,
ok.
check_opts(E, O, Extra) ->
check_opts(E#{ extra => Extra }, O).
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.