aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/runtime_tools/test/Makefile65
-rw-r--r--lib/runtime_tools/test/dbg_SUITE.erl901
-rw-r--r--lib/runtime_tools/test/dbg_SUITE_data/dbg_test.erl87
-rw-r--r--lib/runtime_tools/test/dbg_SUITE_data/exref_td.erl50
-rw-r--r--lib/runtime_tools/test/erts_alloc_config_SUITE.erl206
-rw-r--r--lib/runtime_tools/test/inviso_SUITE.erl2840
-rw-r--r--lib/runtime_tools/test/inviso_testmodule1_foo.erl9
-rw-r--r--lib/runtime_tools/test/runtime_tools.cover1
-rw-r--r--lib/runtime_tools/test/runtime_tools.spec1
-rw-r--r--lib/runtime_tools/test/runtime_tools_SUITE.erl50
10 files changed, 4210 insertions, 0 deletions
diff --git a/lib/runtime_tools/test/Makefile b/lib/runtime_tools/test/Makefile
new file mode 100644
index 0000000000..873d395277
--- /dev/null
+++ b/lib/runtime_tools/test/Makefile
@@ -0,0 +1,65 @@
+#
+include $(ERL_TOP)/make/target.mk
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+MODULES = \
+ runtime_tools_SUITE \
+ inviso_testmodule1_foo \
+ inviso_SUITE \
+ dbg_SUITE \
+ erts_alloc_config_SUITE
+
+ERL_FILES= $(MODULES:%=%.erl)
+
+TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
+INSTALL_PROGS= $(TARGET_FILES)
+
+EMAKEFILE=Emakefile
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+RELSYSDIR = $(RELEASE_PATH)/runtime_tools_test
+
+# ----------------------------------------------------
+# FLAGS
+# ----------------------------------------------------
+
+ERL_MAKE_FLAGS +=
+ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/test_server/include
+
+EBIN = .
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+
+make_emakefile:
+ $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES)\
+ > $(EMAKEFILE)
+
+tests debug opt: make_emakefile
+ erl $(ERL_MAKE_FLAGS) -make
+
+clean:
+ rm -f $(EMAKEFILE)
+ rm -f $(TARGET_FILES)
+ rm -f core
+
+docs:
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_spec: opt
+
+release_tests_spec: make_emakefile
+ $(INSTALL_DIR) $(RELSYSDIR)
+ $(INSTALL_DATA) runtime_tools.spec $(ERL_FILES) $(RELSYSDIR)
+ $(INSTALL_DATA) $(EMAKEFILE) runtime_tools.cover $(RELSYSDIR)
+ chmod -f -R u+w $(RELSYSDIR)
+ @tar cf - *_SUITE_data | (cd $(RELSYSDIR); tar xf -)
+
+release_docs_spec:
diff --git a/lib/runtime_tools/test/dbg_SUITE.erl b/lib/runtime_tools/test/dbg_SUITE.erl
new file mode 100644
index 0000000000..ff96af5e86
--- /dev/null
+++ b/lib/runtime_tools/test/dbg_SUITE.erl
@@ -0,0 +1,901 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dbg_SUITE).
+
+%% Test functions
+-export([all/1, big/1, tiny/1, simple/1, message/1, distributed/1,
+ ip_port/1, file_port/1, file_port2/1, file_port_schedfix/1,
+ ip_port_busy/1, wrap_port/1, wrap_port_time/1,
+ with_seq_trace/1, dead_suspend/1, local_trace/1,
+ saved_patterns/1]).
+-export([init_per_testcase/2, fin_per_testcase/2]).
+-export([tracee1/1, tracee2/1]).
+-export([dummy/0, exported/1]).
+
+-include("test_server.hrl").
+-define(default_timeout, ?t:minutes(1)).
+
+init_per_testcase(_Case, Config) ->
+ ?line Dog=test_server:timetrap(?default_timeout),
+ [{watchdog, Dog}|Config].
+fin_per_testcase(_Case, Config) ->
+ Dog=?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+all(suite) -> [big, tiny, simple, message, distributed,
+ ip_port, file_port, file_port2, file_port_schedfix,
+ ip_port_busy, wrap_port, wrap_port_time,
+ with_seq_trace, dead_suspend, local_trace, saved_patterns].
+
+big(suite) -> [];
+big(doc) -> ["Rudimentary interface test"];
+big(Config) when is_list(Config) ->
+ ?line {ok,OldCurDir} = file:get_cwd(),
+ Datadir=?config(data_dir, Config),
+ Privdir=?config(priv_dir, Config),
+ ?line ok=file:set_cwd(Privdir),
+ try
+ %% make sure dbg is stopped (and returns correctly)
+ ?line ok = dbg:stop(),
+
+ %% compile test module and make sure it is loaded.
+ ?line {ok,Mod} = compile:file(Datadir++"/dbg_test",[trace]),
+ ?line code:purge(dbg_test),
+ ?line {module, Mod}=code:load_file(dbg_test),
+
+ %% run/debug a named test function.
+ ?line Pid = spawn_link(dbg_test, loop, [Config]),
+ ?line true = register(dbg_test_loop, Pid),
+ ?line {ok,_} = dbg:tracer(),
+ ?line {ok,[{matched, _node, 1}]} = dbg:p(dbg_test_loop, [m,p,c]),
+ ?line ok = dbg:c(dbg_test, test, [Config]),
+ ?line ok = dbg:i(),
+ ?line dbg_test_loop ! {dbg_test, stop},
+ unregister(dbg_test_loop),
+ ?line ok = dbg:stop(),
+
+ %% run/debug a Pid.
+ ?line Pid2=spawn_link(dbg_test,loop,[Config]),
+ ?line {ok,_} = dbg:tracer(),
+ ?line {ok,[{matched, _node, 1}]} = dbg:p(Pid2,[s,r,p]),
+ ?line ok = dbg:c(dbg_test, test, [Config]),
+ ?line ok = dbg:i(),
+ ?line Pid2 ! {dbg_test, stop},
+
+ ?line ok=file:set_cwd(OldCurDir)
+ after
+ ?line dbg:stop()
+ end,
+ ok.
+
+
+tiny(suite) -> [];
+tiny(doc) -> ["Rudimentary interface test"];
+tiny(Config) when is_list(Config) ->
+ ?line {ok,OldCurDir} = file:get_cwd(),
+ Datadir=?config(data_dir, Config),
+ Privdir=?config(priv_dir, Config),
+ ?line ok=file:set_cwd(Privdir),
+ try
+ %% compile test module and make sure it is loaded.
+ ?line {ok, Mod} = compile:file(Datadir++"/dbg_test",[trace]),
+ ?line code:purge(dbg_test),
+ ?line {module, Mod}=code:load_file(dbg_test),
+
+ ?line Pid=spawn_link(dbg_test,loop,[Config]),
+ if
+ is_pid(Pid) ->
+ ?line dbg:tracer(),
+ ?line {ok,[{matched, _node, 1}]} = dbg:p(Pid,[s,r,m,p,c]),
+ ?line ok = dbg:c(dbg_test,test,[Config]),
+ ?line ok = dbg:i(),
+ ?line Pid ! {dbg_test, stop};
+ true ->
+ ?line ok=file:set_cwd(OldCurDir),
+ ?t:fail("Could not spawn external test process.~n"),
+ failure
+ end
+ after
+ ?line ok = dbg:stop(),
+ ?line ok = file:set_cwd(OldCurDir)
+ end,
+ ok.
+
+simple(suite) ->
+ [];
+simple(doc) ->
+ ["Simple interface test with own handler"];
+simple(Config) when is_list(Config) ->
+ try
+ ?line start(),
+ ?line dbg:p(self(),call),
+ ?line dbg:tp(dbg,ltp,[]),
+ ?line dbg:ltp(),
+ ?line stop(),
+ ?line S = self(),
+ ?line [{trace,S,call,{dbg,ltp,[]}}] = flush()
+ after
+ ?line dbg:stop()
+ end,
+ ok.
+
+message(suite) ->
+ [];
+message(doc) ->
+ ["Simple interface test with pam code that appends a message"];
+message(Config) when is_list(Config) ->
+ ?line {ok, _} = start(),
+ try
+ ?line {ok, [{matched, _node, 1}]} = dbg:p(self(),call),
+ ?line {ok, X} = dbg:tp(dbg,ltp,[{'_',[],[{message, {self}}]}]),
+ ?line {value, {saved, Saved}} = lists:keysearch(saved, 1, X),
+ ?line {ok, Y} = dbg:tp(dbg,ln,Saved),
+ ?line {value, {saved, Saved}} = lists:keysearch(saved, 1, Y),
+ ?line ok = dbg:ltp(),
+ ?line ok = dbg:ln()
+ after
+ ?line stop()
+ end,
+ ?line S = self(),
+ ?line [{trace,S,call,{dbg,ltp,[]},S},
+ {trace,S,call,{dbg,ln,[]},S}] = flush(),
+ ok.
+
+distributed(suite) ->
+ [];
+distributed(doc) ->
+ ["Simple test of distributed tracing"];
+distributed(Config) when is_list(Config) ->
+ ?line {ok, _} = start(),
+ ?line Node = start_slave(),
+ try
+ ?line RexPid = rpc:call(Node, erlang, whereis, [rex]),
+ ?line RexPidList = pid_to_list(RexPid),
+ ?line {ok, Node} = dbg:n(Node),
+ ?line {ok, X} = dbg:p(all,call),
+ ?line {value, {matched, Node, _}} = lists:keysearch(Node, 2, X),
+ ?line {ok, Y} = dbg:p(RexPidList, s),
+ ?line {value, {matched, Node, 1}} = lists:keysearch(Node, 2, Y),
+ ?line {ok, Z} = dbg:tp(dbg,ltp,[]),
+ ?line {value, {matched, Node, 1}} = lists:keysearch(Node, 2, Z),
+ ?line dbg:cn(Node),
+ ?line dbg:tp(dbg,ln,[]),
+ ?line ok = rpc:call(Node, dbg, ltp, []),
+ ?line ok = rpc:call(Node, dbg, ln, []),
+ ?line ok = dbg:ln(),
+ ?line S = self(),
+ ?line {TraceSend, TraceCall} =
+ lists:partition(fun ({trace,RP,send,_,_}) when RP =:= RexPid -> true;
+ (_) -> false end,
+ flush()),
+ ?line [_|_] = TraceSend,
+ ?line [{trace,Pid,call,{dbg,ltp,[]}},
+ {trace,S,call,{dbg,ln,[]}}] = TraceCall,
+ ?line Node = node(Pid),
+ %%
+ ?line stop()
+ after
+ ?line stop_slave(Node),
+ ?line stop()
+ end,
+ ok.
+
+
+local_trace(suite) ->
+ [];
+local_trace(doc) ->
+ ["Tests tracing of local function calls."];
+local_trace(Config) when is_list(Config) ->
+ ?line {ok, _} = start(),
+ try
+ ?line S = self(),
+ ?line %% Split "<X.Y.Z>" into {X, Y, Z}
+ ?line "<"++L1 = L = pid_to_list(S),
+ ?line NoDot = fun ($.) -> false; (_) -> true end,
+ ?line {LX,"."++L2} = lists:splitwith(NoDot, L1),
+ ?line {LY,"."++L3} = lists:splitwith(NoDot, L2),
+ ?line ">"++L4 = lists:reverse(L3),
+ ?line LZ = lists:reverse(L4),
+ ?line X = 0 = list_to_integer(LX),
+ ?line Y = list_to_integer(LY),
+ ?line Z = list_to_integer(LZ),
+ ?line XYZ = {X, Y, Z},
+ ?line io:format("Self = ~w = ~w~n", [S,XYZ]),
+ ?line {ok, [{matched, _node, 1}]} = dbg:p(S,call),
+ ?line {ok, [{matched, _node, 1}]} = dbg:p(XYZ,call),
+ if Z =:= 0 ->
+ ?line {ok, [{matched, _node, 1}]} = dbg:p(Y,call);
+ true -> ok
+ end,
+ ?line {ok, [{matched, _node, 1}]} = dbg:p(L,call),
+ ?line {ok, _} = dbg:tpl(?MODULE,not_exported,[]),
+ ?line 4 = not_exported(2),
+ ?line [{trace,S,call,{?MODULE,not_exported,[2]}}] = flush(),
+ ?line {ok, _} = dbg:tp(?MODULE,exported,[]),
+ ?line 4 = ?MODULE:exported(2),
+ ?line [{trace,S,call,{?MODULE,exported,[2]}},
+ {trace,S,call,{?MODULE,not_exported,[2]}}] = flush(),
+ ?line {ok, _} = dbg:ctpl(?MODULE),
+ ?line 4 = ?MODULE:exported(2),
+ ?line [{trace,S,call,{?MODULE,exported,[2]}}] = flush(),
+ ?line {ok, _} = dbg:tpl(?MODULE,not_exported,[]),
+ ?line {ok, _} = dbg:ctp(?MODULE),
+ ?line 4 = ?MODULE:exported(2),
+ ?line [] = flush(),
+ ?line {ok, _} = dbg:tpl(?MODULE,not_exported,x),
+ ?line catch ?MODULE:exported(x),
+ ?line [{trace,S,call,{dbg_SUITE,not_exported,[x]}},
+ {trace,S,exception_from,
+ {dbg_SUITE,not_exported,1},
+ {error,badarith}}] = flush()
+ after
+ ?line stop()
+ end,
+ ok.
+
+saved_patterns(suite) ->
+ [];
+saved_patterns(doc) ->
+ ["Tests saving of match_spec's."];
+saved_patterns(Config) when is_list(Config) ->
+ ?line dbg:stop(),
+ ?line {ok,[{saved,1}]} =
+ dbg:tp(dbg,ctp,1,[{'_',[],[{message, blahonga}]}]),
+ ?line {ok,[{saved,2}]} =
+ dbg:tp(dbg,ctp,1,[{['_'],[],[{message, blahonga}]}]),
+ ?line Privdir=?config(priv_dir, Config),
+ ?line file:make_dir(Privdir),
+ ?line File = filename:join([Privdir, "blahonga.ms"]),
+ ?line dbg:wtp(File),
+ ?line dbg:stop(),
+ ?line dbg:ctp('_','_','_'),
+ ?line {ok, _} = start(),
+ try
+ ?line dbg:rtp(File),
+ ?line {ok,[{matched,_node,1},{saved,1}]} = dbg:tp(dbg,ltp,0,1),
+ ?line {ok, [{matched, _node, 1}]} = dbg:p(self(),call),
+ ?line dbg:ltp(),
+ ?line S = self(),
+ ?line [{trace,S,call,{dbg,ltp,[]},blahonga}] = flush()
+ after
+ ?line stop()
+ end,
+ ok.
+
+
+
+not_exported(N) ->
+ N * 2.
+
+exported(N) ->
+ not_exported(N).
+
+ip_port(suite) ->
+ [];
+ip_port(doc) ->
+ ["Test tracing to IP port"];
+ip_port(Config) when is_list(Config) ->
+ ?line stop(),
+ ?line Port = dbg:trace_port(ip, 0),
+ ?line {ok, _} = dbg:tracer(port, Port),
+ try
+ ?line {ok, [{matched, _node, 1}]} = dbg:p(self(),call),
+ ?line {ok, X} = dbg:tp(dbg, ltp,[{'_',[],[{message, {self}}]}]),
+ ?line {value, {saved, _Saved}} = lists:keysearch(saved, 1, X),
+ ?line {ok, Y} = dbg:tp(dbg, ln, [{'_',[],[{message, hej}]}]),
+ ?line {value, {saved, _}} = lists:keysearch(saved, 1, Y),
+ ?line ok = dbg:ltp(),
+ ?line ok = dbg:ln(),
+ ?line {ok, IpPort} = dbg:trace_port_control(get_listen_port),
+ ?line io:format("IpPort = ~p~n", [IpPort]),
+ ?line dbg:trace_client(ip, IpPort, {fun myhandler/2, self()}),
+ ?line S = self(),
+ ?line [{trace,S,call,{dbg,ltp,[]},S},
+ {trace,S,call,{dbg,ln,[]},hej}] = flush()
+ after
+ ?line stop()
+ end,
+ ok.
+
+
+
+ip_port_busy(suite) ->
+ [];
+ip_port_busy(doc) ->
+ ["Test that the dbg server does not hang if the tracer don't start ",
+ "(OTP-3592)"];
+ip_port_busy(Config) when is_list(Config) ->
+ ?line stop(),
+ ?line Tracer = dbg:trace_port(ip, 4745),
+ ?line Port = Tracer(),
+ ?line {error, Reason} = dbg:tracer(port, Tracer),
+ try
+ ?line io:format("Error reason = ~p~n", [Reason]),
+ ?line true = port_close(Port)
+ after
+ ?line dbg:stop()
+ end,
+ ?line ok.
+
+
+
+file_port(suite) ->
+ [];
+file_port(doc) ->
+ ["Test tracing to file port (simple)"];
+file_port(Config) when is_list(Config) ->
+ ?line stop(),
+ ?line {A,B,C} = erlang:now(),
+ ?line FTMP = atom_to_list(?MODULE) ++ integer_to_list(A) ++ "-" ++
+ integer_to_list(B) ++ "-" ++ integer_to_list(C),
+ ?line FName = filename:join([?config(data_dir, Config), FTMP]),
+ ?line Port = dbg:trace_port(file, FName),
+ ?line {ok, _} = dbg:tracer(port, Port),
+ try
+ ?line {ok, [{matched, _node, 1}]} = dbg:p(self(),call),
+ ?line {ok, X} = dbg:tp(dbg, ltp,[{'_',[],[{message, {self}}]}]),
+ ?line {value, {saved, _Saved}} = lists:keysearch(saved, 1, X),
+ ?line {ok, Y} = dbg:tp(dbg, ln, [{'_',[],[{message, hej}]}]),
+ ?line {value, {saved, _}} = lists:keysearch(saved, 1, Y),
+ ?line ok = dbg:ltp(),
+ ?line ok = dbg:ln(),
+ ?line stop(),
+ ?line dbg:trace_client(file, FName, {fun myhandler/2, self()}),
+ ?line S = self(),
+ ?line [{trace,S,call,{dbg,ltp,[]},S},
+ {trace,S,call,{dbg,ln,[]},hej},
+ end_of_trace] = flush()
+ after
+ ?line stop(),
+ ?line file:delete(FName)
+ end,
+ ok.
+
+file_port2(suite) ->
+ [];
+file_port2(doc) ->
+ ["Test tracing to file port with 'follow_file'"];
+file_port2(Config) when is_list(Config) ->
+ case os:type() of
+ vxworks ->
+ {skipped, "VxWorks NFS cache ruins it all."};
+ _ ->
+ ?line stop(),
+ ?line {A,B,C} = erlang:now(),
+ ?line FTMP = atom_to_list(?MODULE) ++ integer_to_list(A) ++
+ "-" ++ integer_to_list(B) ++ "-" ++ integer_to_list(C),
+ ?line FName = filename:join([?config(data_dir, Config), FTMP]),
+ %% Ok, lets try with flush and follow_file, not a chance on VxWorks
+ %% with NFS caching...
+ ?line Port2 = dbg:trace_port(file, FName),
+ ?line {ok, _} = dbg:tracer(port, Port2),
+ try
+ ?line {ok, [{matched, _node, 1}]} = dbg:p(self(),call),
+ ?line {ok, _} = dbg:tp(dbg, ltp,[{'_',[],[{message, {self}}]}]),
+ ?line {ok, _} = dbg:tp(dbg, ln, [{'_',[],[{message, hej}]}]),
+ ?line ok = dbg:ltp(),
+ ?line ok = dbg:flush_trace_port(),
+ ?line dbg:trace_client(follow_file, FName,
+ {fun myhandler/2, self()}),
+ ?line S = self(),
+ ?line [{trace,S,call,{dbg,ltp,[]},S}] = flush(),
+ ?line ok = dbg:ln(),
+ ?line ok = dbg:flush_trace_port(),
+ ?line receive after 1000 -> ok end, %% Polls every second...
+ ?line [{trace,S,call,{dbg,ln,[]},hej}] = flush(),
+ ?line stop(),
+ ?line [] = flush()
+ after
+ ?line stop(),
+ ?line file:delete(FName)
+ end,
+ ok
+ end.
+
+file_port_schedfix(suite) ->
+ [];
+file_port_schedfix(doc) ->
+ ["Test that the scheduling timestamp fix for trace flag 'running' works."];
+file_port_schedfix(Config) when is_list(Config) ->
+ ?line case (catch erlang:system_info(smp_support)) of
+ true ->
+ {skip, "No schedule fix on SMP"};
+ _ ->
+ try
+ file_port_schedfix1(Config)
+ after
+ dbg:stop()
+ end
+ end.
+file_port_schedfix1(Config) when is_list(Config) ->
+ ?line stop(),
+ ?line {A,B,C} = erlang:now(),
+ ?line FTMP = atom_to_list(?MODULE) ++ integer_to_list(A) ++
+ "-" ++ integer_to_list(B) ++ "-" ++ integer_to_list(C),
+ ?line FName = filename:join([?config(data_dir, Config), FTMP]),
+ %%
+ ?line Port = dbg:trace_port(file, {FName, wrap, ".wraplog", 8*1024, 4}),
+ ?line {ok, _} = dbg:tracer(port, Port),
+ ?line {ok,[{matched,_node,0}]} = dbg:p(new,[running,procs,send,timestamp]),
+ %%
+ %% Generate the trace data
+ %%
+ %% This starts 3 processes that sends a message to each other in a ring,
+ %% 4 laps. Prior to sending the message to the next in the ring, each
+ %% process send 8 messages to itself, just to generate some trace data,
+ %% and to lower the possibility that the trace log wraps just after
+ %% a schedule out message (which would not burden any process and hence
+ %% not show up in the result)
+ %%
+ %% The wrap file trace is used because it burns a lot of time when the
+ %% driver swaps files, a lot more than the regular file trace. The test
+ %% case is dimensioned so that the log fills two files and just starts
+ %% on the third (out of four wrap files). This gives two file swaps,
+ %% and there are three processes, so one process will NOT be burdened.
+ %% The criterion for trace success is then that the max process
+ %% execution time must not be more than twice the min process
+ %% execution time. Wallclock. A normal result is about 10 times more
+ %% without schedule in - schedule out compensation (OTP-3938).
+ %%
+ ?line ok = token_volleyball(3, 4, 8),
+ %%
+ ?line {ok,[{matched,_,_}]} = dbg:p(all, [clear]),
+ ?line stop(),
+ % Some debug code to run on all platforms, for finding the fault on genny
+ % Dont touch please /PaN
+ ?line io:format("Trace dump by PaN BEGIN~n"),
+ ?line dbg:trace_client(file,{FName, wrap, ".wraplog"},{fun(end_of_trace,Pid)-> Pid ! done; (Mesg,Pid) -> io:format("~w~n",[Mesg]),Pid end,self()}),
+ receive done -> ok end,
+ ?line io:format("Trace dump by PaN END~n"),
+ %%
+ %% Get the trace result
+ %%
+ ?line Tag = make_ref(),
+ ?line dbg:trace_client(file, {FName, wrap, ".wraplog"},
+ {fun schedstat_handler/2, {self(), Tag, []}}),
+ ?line Result =
+ receive
+ {Tag, D} ->
+ lists:map(
+ fun({Pid, {A1, B1, C1}}) ->
+ {Pid, C1/1000000 + B1 + A1*1000000}
+ end,
+ D)
+ end,
+ ?line ok = io:format("Result=~p", [Result]),
+% erlang:display({?MODULE, ?LINE, Result}),
+ %%
+ %% Analyze the result
+ %%
+ ?line {Min, Max} =
+ lists:foldl(
+ fun({_Pid, M}, {Mi, Ma}) ->
+ {if M < Mi -> M; true -> Mi end,
+ if M > Ma -> M; true -> Ma end}
+ end,
+ {void, 0},
+ Result),
+ % More PaN debug
+ ?line io:format("Min = ~f, Max = ~f~n",[Min,Max]),
+ %%
+ %% Cleanup
+ %%
+ ?line ToBeDeleted = filelib:wildcard(FName++"*"++".wraplog"),
+ ?line lists:map({file, delete}, ToBeDeleted),
+% io:format("ToBeDeleted=~p", [ToBeDeleted]),
+ %%
+ %% Present the result
+ %%
+ P = (Max / Min - 1) * 100,
+ BottomLine = lists:flatten(io_lib:format("~.2f %", [P])),
+ if P > 100 ->
+ Reason = {BottomLine, '>', "100%"},
+ erlang:display({file_port_schedfix, fail, Reason}),
+ test_server:fail(Reason);
+ true ->
+ {comment, BottomLine}
+ end.
+
+wrap_port(suite) ->
+ [];
+wrap_port(doc) ->
+ ["Test tracing to wrapping file port"];
+wrap_port(Config) when is_list(Config) ->
+ ?line Self = self(),
+ ?line stop(),
+ ?line {A,B,C} = erlang:now(),
+ ?line FTMP = atom_to_list(?MODULE) ++ integer_to_list(A) ++ "-" ++
+ integer_to_list(B) ++ "-" ++ integer_to_list(C) ++ "-",
+ ?line FName = filename:join([?config(data_dir, Config), FTMP]),
+ ?line FNameWildcard = FName++"*"++".trace",
+ %% WrapSize=0 and WrapCnt=11 will force the trace to wrap after
+ %% every trace message, and to contain only the last 10 entries
+ %% after trace stop since the last file will be empty waiting
+ %% for its first trace message.
+ ?line WrapSize = 0,
+ ?line WrapCnt = 11,
+ ?line WrapFilesSpec = {FName, wrap, ".trace", WrapSize, WrapCnt},
+ ?line wrap_port_init(WrapFilesSpec),
+ %% The number of iterations, N, is tested to place wrap the log,
+ %% giving a gap in the filename sequence at index 3.
+ %% This should be a difficult case for
+ %% the trace_client file sorting functionality.
+ N = 7,
+ ?line lists:foreach(
+ fun(Cnt) ->
+ ?MODULE:tracee1(Cnt),
+ ?MODULE:tracee2(Cnt)
+ end,
+ lists:seq(1, N)),
+ ?line stop(),
+ try
+ ?line Files1 = filelib:wildcard(FNameWildcard),
+ ?line io:format("~p~n", [Files1]),
+ ?line Tc1 = dbg:trace_client(file, WrapFilesSpec,
+ {fun myhandler/2, {wait_for_go,Self}}),
+ ?line Tref1 = erlang:monitor(process, Tc1),
+ Tc1 ! {go,Self},
+ ?line [{'DOWN',Tref1,_,_,normal},
+ end_of_trace
+ |Result] = lists:reverse(flush()),
+ ?line M = N - (WrapCnt-1) div 2,
+ ?line M = wrap_port_result(Result, Self, N),
+ %%
+ %% Start a new wrap log with the same name to verify that
+ %% all files are cleared at wrap log start. Only produce
+ %% two trace messages to also place the gap at index 3,
+ %% so the trace log will be misinterpreted.
+ %%
+ ?line wrap_port_init(WrapFilesSpec),
+ ?line Files2 = filelib:wildcard(FNameWildcard),
+ ?line io:format("~p~n", [Files2]),
+ ?line -1 = ?MODULE:tracee1(-1),
+ ?line -1 = ?MODULE:tracee2(-1),
+ ?line stop(),
+ ?line Files = filelib:wildcard(FNameWildcard),
+ ?line io:format("~p~n", [Files]),
+ ?line Tc2 = dbg:trace_client(file, WrapFilesSpec,
+ {fun myhandler/2, {wait_for_go,Self}}),
+ ?line Tref2 = erlang:monitor(process, Tc2),
+ Tc2 ! {go,Self},
+ ?line [{trace,Self,call,{?MODULE,tracee1,[-1]},Self},
+ {trace,Self,call,{?MODULE,tracee2,[-1]},hej},
+ end_of_trace,
+ {'DOWN',Tref2,_,_,normal}] = flush(),
+ %%
+ ?line lists:map(fun(F) -> file:delete(F) end, Files)
+ after
+ ?line stop()
+ end,
+ ok.
+
+wrap_port_init(WrapFilesSpec) ->
+ ?line Port = dbg:trace_port(file, WrapFilesSpec),
+ ?line {ok, _} = dbg:tracer(port, Port),
+ ?line {ok, [{matched, _node, 1}]} = dbg:p(self(),call),
+ ?line {ok, X} = dbg:tp(?MODULE, tracee1,[{'_',[],[{message, {self}}]}]),
+ ?line {value, {saved, _Saved}} = lists:keysearch(saved, 1, X),
+ ?line {ok, Y} = dbg:tp(?MODULE, tracee2, [{'_',[],[{message, hej}]}]),
+ ?line {value, {saved, _}} = lists:keysearch(saved, 1, Y),
+ ok.
+
+tracee1(X) ->
+ X.
+
+tracee2(X) ->
+ X.
+
+wrap_port_result([], _S, M) ->
+ M;
+wrap_port_result([{trace, S, call, {?MODULE, tracee2, [M]}, hej},
+ {trace, S, call, {?MODULE, tracee1, [M]}, S} | Tail],
+ S,
+ M) ->
+ wrap_port_result(Tail, S, M-1).
+
+
+wrap_port_time(suite) ->
+ [];
+wrap_port_time(doc) ->
+ ["Test tracing to time limited wrapping file port"];
+wrap_port_time(Config) when is_list(Config) ->
+ ?line stop(),
+ ?line {A,B,C} = erlang:now(),
+ ?line FTMP = atom_to_list(?MODULE) ++ integer_to_list(A) ++ "-" ++
+ integer_to_list(B) ++ "-" ++ integer_to_list(C) ++ "-",
+ ?line FName = filename:join([?config(data_dir, Config), FTMP]),
+ %% WrapTime=2 and WrapCnt=4 will force the trace to wrap after
+ %% every 2 seconds, and to contain between 3*2 and 4*2 seconds
+ %% of trace entries.
+ ?line WrapFilesSpec = {FName, wrap, ".trace", {time, 2000}, 4},
+ ?line Port = dbg:trace_port(file, WrapFilesSpec),
+ ?line {ok, _} = dbg:tracer(port, Port),
+ try
+ ?line {ok, [{matched, _node, 1}]} = dbg:p(self(),call),
+ ?line {ok, X} = dbg:tp(?MODULE, tracee1,[{'_',[],[{message, {self}}]}]),
+ ?line {value, {saved, _Saved1}} = lists:keysearch(saved, 1, X),
+ ?line {ok, Y} = dbg:tp(?MODULE, tracee2, [{'_',[],[{message, hej}]}]),
+ ?line {value, {saved, _Saved2}} = lists:keysearch(saved, 1, Y),
+ %% The delays in the iterations places two trace messages in each
+ %% trace file, but the last which is empty waiting for its first
+ %% trace message. The number of iterations is chosen so that
+ %% one trace file has been wasted, and therefore the first pair
+ %% of trace messages.
+ ?line lists:foreach(
+ fun(Cnt) ->
+ receive after 1000 -> ok end,
+ ?MODULE:tracee1(Cnt),
+ ?MODULE:tracee2(Cnt),
+ receive after 1100 -> ok end
+ end,
+ lists:seq(1, 4)),
+ ?line stop(),
+ ?line Files = filelib:wildcard(FName ++ "*" ++ ".trace"),
+ ?line io:format("~p~n", [Files]),
+ ?line dbg:trace_client(file, WrapFilesSpec, {fun myhandler/2, self()}),
+ ?line S = self(),
+ ?line [{trace, S, call, {?MODULE, tracee1, [2]}, S},
+ {trace, S, call, {?MODULE, tracee2, [2]}, hej},
+ {trace, S, call, {?MODULE, tracee1, [3]}, S},
+ {trace, S, call, {?MODULE, tracee2, [3]}, hej},
+ {trace, S, call, {?MODULE, tracee1, [4]}, S},
+ {trace, S, call, {?MODULE, tracee2, [4]}, hej},
+ end_of_trace] = flush(),
+ ?line lists:map(fun(F) -> file:delete(F) end, Files)
+ after
+ ?line stop()
+ end,
+ ok.
+
+with_seq_trace(suite) ->
+ [];
+with_seq_trace(doc) ->
+ ["Test ordinary tracing combined with seq_trace"];
+with_seq_trace(Config) when is_list(Config) ->
+ try
+ ?line {ok, Server} = start(),
+ ?line {ok, Tracer} = dbg:get_tracer(),
+ ?line {ok, X} = dbg:tp(dbg, get_tracer, [{[],[],
+ [{set_seq_token, send, true}]}]),
+ ?line {value, {saved, _}} = lists:keysearch(saved, 1, X),
+ ?line {ok, [{matched, _node, 1}]} = dbg:p(self(),call),
+ ?line seq_trace:set_system_tracer(Tracer),
+ ?line dbg:get_tracer(),
+ receive
+ after 1 ->
+ ok
+ end,
+ ?line S = self(),
+ ?line ThisNode = node(),
+ ?line [{trace,S,call,{dbg,get_tracer,[]}},
+ {seq_trace,0,{send,_,S,Server,{S,{get_tracer,ThisNode}}}},
+ {seq_trace,0,{send,_,Server,S,{dbg,{ok,Tracer}}}}] =
+ flush()
+ after
+ ?line stop()
+ end,
+ ok.
+
+dead_suspend(suite) ->
+ [];
+dead_suspend(doc) ->
+ ["Test that trace messages concerning a now dead process does "
+ "not crash dbg."];
+
+dead_suspend(Config) when is_list(Config) ->
+ ?line start(),
+ try
+ survived = run_dead_suspend()
+ after
+ ?line stop()
+ end.
+
+run_dead_suspend() ->
+ dbg:p(new, call),
+ dbg:tp(?MODULE, dummy, []),
+ spawn(?MODULE, dummy, []),
+ spawn(?MODULE, dummy, []),
+ spawn(?MODULE, dummy, []),
+ spawn(?MODULE, dummy, []),
+ spawn(?MODULE, dummy, []),
+ receive after 1000 -> ok end,
+ case whereis(dbg) of
+ undefined ->
+ died;
+ _ ->
+ survived
+ end.
+
+dummy() ->
+ ok.
+
+
+%%
+%% Support functions
+%%
+
+start_slave() ->
+ {A, B, C} = now(),
+ Name = "asdkxlkmd" ++ integer_to_list(A+B+C),
+ {ok, Node} = test_server:start_node(Name,slave,[]),
+ ok = wait_node(Node, 15),
+ Node.
+
+stop_slave(Node) ->
+ test_server:stop_node(Node).
+
+wait_node(_,0) ->
+ no;
+wait_node(Node, N) ->
+ case net_adm:ping(Node) of
+ pong ->
+ ok;
+ pang ->
+ receive
+ after 1000 ->
+ ok
+ end,
+ wait_node(Node, N - 1)
+ end.
+
+myhandler(Message, {wait_for_go,Pid}) ->
+ receive
+ {go,Pid} ->
+ myhandler(Message, Pid)
+ end;
+myhandler(Message, Relay) ->
+ Relay ! Message,
+ case Message of
+ end_of_trace ->
+ ok;
+ _ ->
+ Relay
+ end.
+
+flush() ->
+ flush([]).
+flush(Acc) ->
+ receive
+ X ->
+ flush(Acc ++ [X])
+ after 1000 ->
+ Acc
+ end.
+
+start() ->
+ stop(),
+ dbg:tracer(process, {fun myhandler/2, self()}).
+
+stop() ->
+ dbg:stop().
+
+
+
+schedstat_handler(TraceMsg, {Parent, Tag, Data} = State) ->
+ case TraceMsg of
+ {trace_ts, Pid, in, _, Ts} ->
+ NewData =
+ case lists:keysearch(Pid, 1, Data) of
+ {value, {Pid, Acc}} ->
+ [{Pid, Acc, Ts} | lists:keydelete(Pid, 1, Data)];
+ false ->
+ [{Pid, {0, 0, 0}, Ts} | Data];
+ Other ->
+ exit(Parent, {?MODULE, ?LINE, Other}),
+ erlang:display({?MODULE, ?LINE, Other}),
+ Data
+ end,
+ {Parent, Tag, NewData};
+ {trace_ts, Pid, out, _, {A3, B3, C3}} ->
+ NewData =
+ case lists:keysearch(Pid, 1, Data) of
+ {value, {Pid, {A1, B1, C1}, {A2, B2, C2}}} ->
+ [{Pid, {A3-A2+A1, B3-B2+B1, C3-C2+C1}} |
+ lists:keydelete(Pid, 1, Data)];
+ Other ->
+ exit(Parent, {?MODULE, ?LINE, Other}),
+ erlang:display({?MODULE, ?LINE, Other}),
+ Data
+ end,
+ {Parent, Tag, NewData};
+ {trace_ts, Pid, exit, normal, {A3, B3, C3}} ->
+ NewData =
+ case lists:keysearch(Pid, 1, Data) of
+ {value, {Pid, {A1, B1, C1}, {A2, B2, C2}}} ->
+ [{Pid, {A3-A2+A1, B3-B2+B1, C3-C2+C1}} |
+ lists:keydelete(Pid, 1, Data)];
+ {value, {Pid, _Acc}} ->
+ Data;
+ false ->
+ [{Pid, {0, 0, 0}} | Data];
+ Other ->
+ exit(Parent, {?MODULE, ?LINE, Other}),
+ erlang:display({?MODULE, ?LINE, Other}),
+ Data
+ end,
+ {Parent, Tag, NewData};
+ {trace_ts, _Pid, send, _Msg, _OtherPid, _Ts} ->
+ State;
+ end_of_trace ->
+ Parent ! {Tag, Data},
+ State
+ end.
+
+
+
+pass_token(Token, Next, Loops) ->
+ receive
+ {Token, 1} = Msg ->
+ sendloop(Loops),
+ Next ! Msg;
+ {Token, _Cnt} = Msg->
+ sendloop(Loops),
+ Next ! Msg,
+ pass_token(Token, Next, Loops)
+ end.
+
+pass_token(Token, Final, Cnt, Loops) ->
+ receive
+ {Token, start, Next} ->
+ sendloop(Loops),
+ Msg = {Token, Cnt},
+ Next ! Msg,
+ pass_token(Token, Final, Next, Cnt, Loops)
+ end.
+
+pass_token(Token, Final, Next, Cnt, Loops) ->
+ receive
+ {Token, 1} ->
+ sendloop(Loops),
+ Msg = {Token, done},
+ Final ! Msg;
+ {Token, Cnt} ->
+ sendloop(Loops),
+ NextCnt = Cnt-1,
+ Msg = {Token, NextCnt},
+ Next ! Msg,
+ pass_token(Token, Final, Next, NextCnt, Loops)
+ end.
+
+sendloop(Loops) ->
+ sendloop(make_ref(), Loops).
+
+sendloop(_Tag, 0) ->
+ ok;
+sendloop(Tag, Loops) ->
+ self() ! {Tag, Loops},
+ receive {Tag, Loops} -> ok end,
+ sendloop(Tag, Loops-1).
+
+token_volleyball(N, Cnt, Loops)
+ when is_integer(N), N >= 1, is_integer(Cnt), Cnt >= 1,
+ is_integer(Loops), Loops >= 0 ->
+ Self = self(),
+ Token = make_ref(),
+ Last = spawn_link(fun() -> pass_token(Token, Self, Cnt, Loops) end),
+ First = token_volleyball(Token, Last, N-1, Loops),
+ Last ! {Token, start, First},
+ receive {Token, done} -> ok end.
+
+token_volleyball(Token, Next, 1, Loops) ->
+ spawn_link(fun() -> pass_token(Token, Next, Loops) end);
+token_volleyball(Token, Next, N, Loops) ->
+ Pid = spawn_link(fun() -> pass_token(Token, Next, Loops) end),
+ token_volleyball(Token, Pid, N-1, Loops).
diff --git a/lib/runtime_tools/test/dbg_SUITE_data/dbg_test.erl b/lib/runtime_tools/test/dbg_SUITE_data/dbg_test.erl
new file mode 100644
index 0000000000..2edbf6f99a
--- /dev/null
+++ b/lib/runtime_tools/test/dbg_SUITE_data/dbg_test.erl
@@ -0,0 +1,87 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%%----------------------------------------------------------------------
+%%% Purpose : A priority queue.
+%%%----------------------------------------------------------------------
+%%% This module implements a priority queue as defined in
+%%% "Priority Queues and the STL" by Mark Nelson in Dr.Dobb's Journal, Jan 1996
+%%% see http://web2.airmail.net/markn/articles/pq_stl/priority.htm for more
+%%% information. (A heap implementation is planned aswell)
+%%%----------------------------------------------------------------------
+%%% The items of the queue is kept priority sorted, and because of that,
+%%% a push() operation costs more than a pop() operation (wich only
+%%% needs to return the top item of the queue(read: list)).
+%%%----------------------------------------------------------------------
+%%% The priority queue can be deceptively nice to use when creating for
+%%% example a Huffman coding tree.
+%%% See http://web2.airmail.net/markn/articles/pq_stl/priority.htm or
+%%% Dr.Dobb's Journal Jan, 96 for more information on this.
+%%%----------------------------------------------------------------------
+
+%%% Used here strictly as something for dbg_SUITE to run.
+
+-module(dbg_test).
+-export([test/1, loop/1]).
+-export([new/0, push/3, pop/1]).
+
+loop(Config) ->
+ test(Config),
+ receive
+ {dbg_test, stop} ->
+ ok;
+ Other ->
+ loop(Config)
+ end,
+ ok.
+
+test(Config) ->
+ Q1=new(),
+ Q2=push(Q1, "monkey", 3),
+ Q3=push(Q2, "banana", 4),
+ Q4=push(Q3, "jungle", 2),
+ Q5=push(Q4, "world", 5),
+ Q6=push(Q5, "universe",6),
+ Q7=push(Q6, "peanut", 1),
+% io:format("~p~n",[Q7]),
+ {Itm, Q8}=pop(Q7),
+ ok.
+
+%% Returns a new priority queue.
+new() ->
+ [].
+
+%% Pushes a new item with a set priority into the queue.
+push(Queue, Itm, Pri) ->
+ insert(Queue, Itm, Pri, []).
+
+%% Pops the item with the highest priority out of the queue.
+pop([{Itm, Pri}|Queue]) ->
+ {Itm, Queue}.
+
+%% --- -- -
+%% Support functions.
+insert([], Itm, Pri, NewQ) ->
+ lists:flatten([lists:reverse(NewQ)|[{Itm, Pri}]]);
+% Itm>QItm>NewQ>Queue
+insert([{QItm,QPri}|Queue], Itm, Pri, NewQ) when Pri>QPri->
+ A = [{Itm, Pri}|[{QItm, QPri}]],
+ lists:flatten([[A|NewQ]|Queue]);
+insert([QItm|Rest], Itm, Pri, NewQ) ->
+ insert(Rest, Itm, Pri, [QItm|NewQ]).
+%% --- -- -
diff --git a/lib/runtime_tools/test/dbg_SUITE_data/exref_td.erl b/lib/runtime_tools/test/dbg_SUITE_data/exref_td.erl
new file mode 100644
index 0000000000..85faf620aa
--- /dev/null
+++ b/lib/runtime_tools/test/dbg_SUITE_data/exref_td.erl
@@ -0,0 +1,50 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(exref_td).
+
+-export([a/1,b/1,c/1,d/1,e/1,my_analyse/1]).
+
+a("edcba") ->
+ true;
+a(_) ->
+ lists:reverse("abcde").
+
+b(_) ->
+ a(nil).
+
+c(_) ->
+ nonexistentmodule:f().
+
+d(_) ->
+ lists:nonexistentfunction().
+
+e(_) ->
+ [a(x),b(x),c(x)].
+
+localfuncnotcalled() ->
+ true.
+
+localfunccalledbyf(A) ->
+ A.
+
+f() ->
+ lists:filter(fun localfunccalledbyf/1,"aaabba"),
+ F = fun localfuncnotcalled/0.
+
+my_analyse(Graph) -> ok.
diff --git a/lib/runtime_tools/test/erts_alloc_config_SUITE.erl b/lib/runtime_tools/test/erts_alloc_config_SUITE.erl
new file mode 100644
index 0000000000..32483dbe73
--- /dev/null
+++ b/lib/runtime_tools/test/erts_alloc_config_SUITE.erl
@@ -0,0 +1,206 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(erts_alloc_config_SUITE).
+
+%-define(line_trace, 1).
+
+-include("test_server.hrl").
+
+%-compile(export_all).
+-export([all/1, init_per_testcase/2, fin_per_testcase/2]).
+
+%% Testcases
+-export([basic/1]).
+
+%% internal export
+-export([make_basic_config/1]).
+
+-define(DEFAULT_TIMEOUT, ?t:minutes(2)).
+
+all(doc) -> [];
+all(suite) -> [basic].
+
+init_per_testcase(Case, Config) when is_list(Config) ->
+ [{testcase, Case},
+ {watchdog, ?t:timetrap(?DEFAULT_TIMEOUT)},
+ {erl_flags_env, save_env()} | Config].
+
+fin_per_testcase(_Case, Config) when is_list(Config) ->
+ ?t:timetrap_cancel(?config(watchdog, Config)),
+ restore_env(?config(erl_flags_env, Config)),
+ ok.
+
+%%%
+%%% The test cases ------------------------------------------------------------
+%%%
+
+basic(doc) -> [];
+basic(suite) -> [];
+basic(Config) when is_list(Config) ->
+ ?line ErtsAllocConfig = privfile("generated", Config),
+
+ SbctMod = " +MBsbct 1024 +MHsbct 4096",
+
+ %% Make sure we have enabled allocators
+ ZFlgs = case os:getenv("ERL_ZFLAGS") of
+ FlgString when is_list(FlgString) ->
+ FlgString;
+ _ ->
+ ""
+ end ++ " +Mea max +Mea config",
+
+ ?line os:putenv("ERL_ZFLAGS", ZFlgs ++ SbctMod),
+
+ ?line {ok, Node1} = start_node(Config),
+ ?line ok = rpc:call(Node1, ?MODULE, make_basic_config, [ErtsAllocConfig]),
+ ?line stop_node(Node1),
+
+ ?line display_file(ErtsAllocConfig),
+
+ ?line ManualConfig = privfile("manual", Config),
+ ?line {ok, IOD} = file:open(ManualConfig, [write]),
+ ?line io:format(IOD, "~s", ["+MBsbct 2048"]),
+ ?line file:close(IOD),
+ ?line display_file(ManualConfig),
+
+ ?line os:putenv("ERL_ZFLAGS", ZFlgs),
+
+ ?line {ok, Node2} = start_node(Config,
+ "-args_file " ++ ErtsAllocConfig
+ ++ " -args_file " ++ ManualConfig),
+
+ ?line {_, _, _, Cfg} = rpc:call(Node2, erlang, system_info, [allocator]),
+
+ ?line stop_node(Node2),
+
+ ?line {value,{binary_alloc, BCfg}} = lists:keysearch(binary_alloc, 1, Cfg),
+ ?line {value,{sbct, 2097152}} = lists:keysearch(sbct, 1, BCfg),
+ ?line {value,{eheap_alloc, HCfg}} = lists:keysearch(eheap_alloc, 1, Cfg),
+ ?line {value,{sbct, 4194304}} = lists:keysearch(sbct, 1, HCfg),
+
+ ?line ok.
+
+make_basic_config(ErtsAllocConfig) ->
+ %% Save some different scenarios
+ Tester = self(),
+ SSBegun = make_ref(),
+ SSDone = make_ref(),
+ SSFun = fun (F) ->
+ receive
+ SSDone ->
+ ok = erts_alloc_config:save_scenario(),
+ Tester ! SSDone
+ after 500 ->
+ ok = erts_alloc_config:save_scenario(),
+ F(F)
+ end
+ end,
+ SS = spawn_link(fun () ->
+ ok = erts_alloc_config:save_scenario(),
+ Tester ! SSBegun,
+ SSFun(SSFun)
+ end),
+ receive SSBegun -> ok end,
+ Ref = make_ref(),
+ Tab = ets:new(?MODULE, [bag, public]),
+ Ps = lists:map(
+ fun (_) ->
+ spawn_link(
+ fun () ->
+ ets:insert(Tab,
+ {self(),
+ lists:seq(1, 1000)}),
+ receive after 1000 -> ok end,
+ Tester ! {Ref, self()}
+ end)
+ end,
+ lists:seq(1, 10000)),
+ lists:foreach(fun (P) -> receive {Ref, P} -> ok end end, Ps),
+ ets:delete(Tab),
+ SS ! SSDone,
+ receive SSDone -> ok end,
+
+ ok = erts_alloc_config:make_config(ErtsAllocConfig).
+
+
+
+%%
+%% Utils ----------------------------------------------------------------------
+%%
+
+display_file(FileName) ->
+ ?t:format("filename: ~s~n", [FileName]),
+ {ok, Bin} = file:read_file(FileName),
+ io:format("~s", [binary_to_list(Bin)]),
+ ?t:format("eof: ~s~n", [FileName]),
+ ok.
+
+mk_name(Config) when is_list(Config) ->
+ {A, B, C} = now(),
+ list_to_atom(atom_to_list(?MODULE)
+ ++ "-" ++ atom_to_list(?config(testcase, Config))
+ ++ "-" ++ integer_to_list(A)
+ ++ "-" ++ integer_to_list(B)
+ ++ "-" ++ integer_to_list(C)).
+
+start_node(Config) ->
+ start_node(Config, "").
+
+start_node(Config, Args) ->
+ ?line Pa = filename:dirname(code:which(?MODULE)),
+ ?line ?t:start_node(mk_name(Config),
+ slave,
+ [{args, "-pa " ++ Pa ++ " " ++ Args}]).
+
+stop_node(Node) ->
+ ?line true = ?t:stop_node(Node).
+
+privfile(Name, Config) ->
+ filename:join([?config(priv_dir, Config),
+ atom_to_list(?config(testcase, Config)) ++ "." ++ Name]).
+
+save_env() ->
+ {erl_flags,
+ os:getenv("ERL_AFLAGS"),
+ os:getenv("ERL_FLAGS"),
+ os:getenv("ERL_"++erlang:system_info(otp_release)++"_FLAGS"),
+ os:getenv("ERL_ZFLAGS")}.
+
+restore_env(EVar, false) when is_list(EVar) ->
+ restore_env(EVar, "");
+restore_env(EVar, "") when is_list(EVar) ->
+ case os:getenv(EVar) of
+ false -> ok;
+ "" -> ok;
+ " " -> ok;
+ _ -> os:putenv(EVar, " ")
+ end;
+restore_env(EVar, Value) when is_list(EVar), is_list(Value) ->
+ case os:getenv(EVar) of
+ Value -> ok;
+ _ -> os:putenv(EVar, Value)
+ end.
+
+restore_env({erl_flags, AFlgs, Flgs, RFlgs, ZFlgs}) ->
+ restore_env("ERL_AFLAGS", AFlgs),
+ restore_env("ERL_FLAGS", Flgs),
+ restore_env("ERL_"++erlang:system_info(otp_release)++"_FLAGS", RFlgs),
+ restore_env("ERL_ZFLAGS", ZFlgs),
+ ok.
diff --git a/lib/runtime_tools/test/inviso_SUITE.erl b/lib/runtime_tools/test/inviso_SUITE.erl
new file mode 100644
index 0000000000..1c5c887b62
--- /dev/null
+++ b/lib/runtime_tools/test/inviso_SUITE.erl
@@ -0,0 +1,2840 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%% Description:
+%% Test suite for inviso (basic parts, i.e not inviso tools). Note that
+%% inviso basic parts have modules in both the runtime_tools and
+%% inviso applications.
+%%
+%% Authors:
+%% Ann-Marie L�f, ann-marie.lof@st.se
+%% Lennart �hman, lennart.ohman@st.se
+%% -----------------------------------------------------------------------------
+
+-module(inviso_SUITE).
+-compile(export_all).
+
+-include("test_server.hrl").
+-include_lib("kernel/include/file.hrl").
+
+-define(l,?line).
+
+all(suite) ->
+ [
+ basic_dist_trace_1,
+ basic_dist_trace_2,
+ basic_dist_trace_3,
+ basic_dist_trace_ti_1,
+ basic_dist_trace_ti_2,
+ basic_dist_trace_ti_3,
+ suspend_dist_trace_ti_1,
+ suspend_dist_trace_ti_2,
+ meta_cleanfunc_dist_1,
+ basic_handlerfun_dist_1,
+ delete_log_dist_1,
+ autostart_dist_1,
+ autostart_dist_2,
+ autostart_dist_3,
+ running_alone_dist_1,
+ running_alone_dist_2,
+ running_alone_dist_3,
+ running_alone_dist_4,
+ running_alone_dist_5,
+ overload_dist_1,
+ overload_dist_2,
+ overload_dist_3,
+ overload_dist_4,
+ overload_dist_5,
+ subscribe_dist_1,
+ lfm_trace_dist_1,
+ lfm_trace_ti_dist_2,
+ handle_logfile_sort_wrapset,
+ fetch_log_dist_trace_1,
+ fetch_log_dist_trace_2,
+ fetch_log_dist_trace_3,
+ fetch_log_dist_error_1,
+ fetch_log_dist_error_2,
+ expand_regexp_dist_1,
+ only_loaded_dist_1
+ ].
+
+
+init_per_suite(Config) ->
+ %% No never know who skrewed up this node before this suite! :-)
+ erlang:trace_pattern({'_','_','_'},[],[local]),
+ erlang:trace_pattern({'_','_','_'},[],[global]),
+ erlang:trace(all,false,[all]),
+
+ ?l ok=application:start(runtime_tools),
+ Config.
+
+end_per_suite(_Config) ->
+ ?l ok=application:stop(runtime_tools).
+
+
+%% For each distributed testcase, we need two other distributed nodes to run the
+%% runtime components on. Since they are freshly started every time there is no
+%% need to clean them up first.
+init_per_testcase(_Case,Config) ->
+ ?l TH=test_server:timetrap(100000),
+ ?l {ok,Node1}=test_server:start_node(inviso1,peer,[]),
+ ?l {ok,Node2}=test_server:start_node(inviso2,peer,[]),
+ ?l SuiteDir=filename:dirname(code:which(?MODULE)),
+
+ %% Otherwise peer nodes will not find this module!
+ ?l true=rpc:call(Node1,code,add_patha,[SuiteDir]),
+ ?l true=rpc:call(Node2,code,add_patha,[SuiteDir]),
+
+ ?l start_side_effect_logger(node()),
+ ?l start_side_effect_logger(Node1),
+ ?l start_side_effect_logger(Node2),
+
+
+ %% SPECIAL FOR MY PRIVATE TEST ENVIROMENT
+% ?l rpc:call(Node1,code,add_patha,["/clearcase/otp/tools/runtime_tools/ebin"]),
+% ?l rpc:call(Node1,code,add_patha,["/clearcase/otp/tools/inviso/ebin"]),
+% ?l rpc:call(Node2,code,add_patha,["/clearcase/otp/tools/runtime_tools/ebin"]),
+% ?l rpc:call(Node2,code,add_patha,["/clearcase/otp/tools/inviso/ebin"]),
+
+% %% SPECIAL FOR MY PRIVATE TEST ENVIROMENT, windows.
+% ?l rpc:call(Node1,code,add_patha,["Z:/DATA/PROJECTS/inviso_project/runtime_tools/ebin"]),
+% ?l rpc:call(Node1,code,add_patha,["Z:/DATA/PROJECTS/inviso_project/inviso/ebin"]),
+% ?l rpc:call(Node2,code,add_patha,["Z:/DATA/PROJECTS/inviso_project/runtime_tools/ebin"]),
+% ?l rpc:call(Node2,code,add_patha,["Z:/DATA/PROJECTS/inviso_project/inviso/ebin"]),
+
+ ?l ok=rpc:call(Node1,application,start,[runtime_tools]),
+ ?l ok=rpc:call(Node2,application,start,[runtime_tools]),
+ ?l timer:sleep(100), % Problem with autostarted runtime.
+ %% The following is a test that the inviso_rt processes which are autostarted
+ %% are now gone.
+
+ ?l ok=poll(rpc,call,[Node1,erlang,whereis,[inviso_rt]],undefined,20),
+ ?l ok=poll(rpc,call,[Node2,erlang,whereis,[inviso_rt]],undefined,20),
+
+% ?l ok=poll(rpc,call,[Node1,supervisor,which_children,[runtime_tools_sup]],[],20),
+% ?l ok=poll(rpc,call,[Node2,supervisor,which_children,[runtime_tools_sup]],[],20),
+ NewConfig1=insert_remotenode_config(inviso1,Node1,Config),
+ NewConfig2=insert_remotenode_config(inviso2,Node2,NewConfig1),
+ insert_timetraphandle_config(TH,NewConfig2).
+%% -----------------------------------------------------------------------------
+
+fin_per_testcase(Case,Config) ->
+ ?l test_server:stop_node(get_remotenode_config(inviso1,Config)),
+ ?l test_server:stop_node(get_remotenode_config(inviso2,Config)),
+
+ case whereis(inviso_c) of
+ undefined -> % Should not exist.
+ true;
+ Pid when is_pid(Pid) -> % But if it exists...
+ exit(Pid,kill), % Remove it!
+ io:format("Had to kill the control component in fin_per_testcase,~p.~n",[Case])
+ end,
+ case whereis(inviso_rt) of
+ undefined -> % Should not exist.
+ true;
+ Pid2 when is_pid(Pid2) -> % But if it exists...
+ exit(Pid2,kill), % Remove it!
+ io:format("Had to kill local runtime component in fin_per_testcase,~p.~n",[Case])
+ end,
+ ?l process_killer([inviso_test_proc,
+ inviso_tab_proc,
+ inviso_collector_proc,
+ global_inviso_test_proc]),
+ ?l test_server:timetrap_cancel(get_timetraphandle_config(Config)),
+
+ NewConfig1=remove_remotenode_config(inviso1,Config),
+ NewConfig2=remove_remotenode_config(inviso2,NewConfig1),
+ remove_timetraphandle_config(NewConfig2).
+%% -----------------------------------------------------------------------------
+
+%% ==============================================================================
+%% Testcases.
+%% ==============================================================================
+
+%% TEST CASE: Basic, distributed, trace only.
+basic_dist_trace_1(suite) -> [];
+basic_dist_trace_1(doc) ->
+ ["Basic case, start of distributed tracing, using only trac."];
+basic_dist_trace_1(Config) when is_list(Config) ->
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ PrivDir=filename:join(?config(priv_dir,Config),""),
+ TracerDataList=lists:map(fun(N)->{N,{file,filename:join([PrivDir,
+ "tf1_"++
+ atom_to_list(N)
+ ])}} end,
+ Nodes),
+ start_and_init_tracing2(Nodes,[],TracerDataList,{ok,[{trace_log,ok}]}),
+ activate_local_tracing(Nodes),
+ deactivate_local_tracing(Nodes),
+ stop_tracing(Nodes),
+ stop(Nodes),
+ ok.
+%% -----------------------------------------------------------------------------
+
+
+%% TEST CASE: Basic, distributed, activate global tracing for functions in modules
+%% pointed out using a regexp. No tracing will be done.
+basic_dist_trace_2(suite) -> [];
+basic_dist_trace_2(doc) ->
+ [""];
+basic_dist_trace_2(Config) when is_list(Config) ->
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ PrivDir=filename:join(?config(priv_dir,Config),""),
+ TracerDataList=lists:map(fun(N)->{N,{file,filename:join([PrivDir,
+ "tf1a_"++
+ atom_to_list(N)
+ ])}} end,
+ Nodes),
+ start_and_init_tracing2(Nodes,[],TracerDataList,{ok,[{trace_log,ok}]}),
+ Funcs1=activate_global_tracing_regexp(Nodes),
+ deactivate_global_tracing_regexp(Nodes,Funcs1),
+ stop_tracing(Nodes),
+ stop(Nodes),
+ ok.
+%% -----------------------------------------------------------------------------
+
+%% TEST CASE: Basic, distributed, activate global tracing for functions in modules
+%% pointed out using a dir-regexp. No tracing will be done.
+basic_dist_trace_3(suite) -> [];
+basic_dist_trace_3(doc) ->
+ [""];
+basic_dist_trace_3(Config) when is_list(Config) ->
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ PrivDir=filename:join(?config(priv_dir,Config),""),
+ TracerDataList=lists:map(fun(N)->{N,{file,filename:join([PrivDir,
+ "tf1b_"++
+ atom_to_list(N)
+ ])}} end,
+ Nodes),
+ start_and_init_tracing2(Nodes,[],TracerDataList,{ok,[{trace_log,ok}]}),
+ Funcs1=activate_global_tracing_regexp_dir(Nodes),
+ deactivate_global_tracing_regexp_dir(Nodes,Funcs1),
+ stop_tracing(Nodes),
+ stop(Nodes),
+ ok.
+%% -----------------------------------------------------------------------------
+
+%% TEST CASE: Basic, distributed, trace and ti.
+basic_dist_trace_ti_1(suite) -> [];
+basic_dist_trace_ti_1(doc) ->
+ [""];
+basic_dist_trace_ti_1(Config) when is_list(Config) ->
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ PrivDir=filename:join(?config(priv_dir,Config),""),
+ TracerDataFun=
+ fun(N)->{N,[{trace,{file,filename:join([PrivDir,"tf2_"++atom_to_list(N)])}},
+ {ti,{file,filename:join([PrivDir,"tf2_"++atom_to_list(N)++".ti"])}}]}
+ end,
+ TracerDataList=lists:map(TracerDataFun,Nodes),
+ start_and_init_tracing2(Nodes,[],TracerDataList,{ok,[{trace_log,ok},{ti_log,ok}]}),
+ activate_local_tracing(Nodes),
+ activate_meta_tracing(Nodes),
+ ?l true=(is_pid(whereis(inviso_rt))),
+ ?l true=(is_pid(whereis(inviso_rt_meta))),
+ deactivate_meta_tracing(Nodes),
+ deactivate_local_tracing(Nodes),
+ stop_tracing(Nodes),
+ ?l true=(is_pid(whereis(inviso_rt))), % Shall still be running.
+ ?l ok=poll(erlang,whereis,[inviso_rt_meta],undefined,3),
+ stop(Nodes),
+ timer:sleep(200), % Give it time to terminate.
+ ?l ok=poll(erlang,whereis,[inviso_rt],undefined,3),% Shall be gone now.
+ ?l undefined=whereis(inviso_rt_meta), % Still gone.
+ ok.
+%% -----------------------------------------------------------------------------
+
+%% Test CASE: Testing that the tpm_tracer functionality works. That is appending
+%% {tracer,Tracer} to a meta match spec.
+basic_dist_trace_ti_2(suite) -> [];
+basic_dist_trace_ti_2(doc) ->
+ [""];
+basic_dist_trace_ti_2(Config) when is_list(Config) ->
+ case erlang:system_info(version) of
+ "5.4"++_ -> % Perhaps not perfect, but work now :-)
+ {skip,"Old emulator"};
+ _ ->
+ basic_dist_trace_ti_2_do(Config)
+ end.
+
+basic_dist_trace_ti_2_do(Config) ->
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ PrivDir=filename:join(?config(priv_dir,Config),""),
+ TracerDataFun=
+ fun(N)->{N,[{trace,{file,filename:join([PrivDir,"tf3_"++atom_to_list(N)])}},
+ {ti,{file,filename:join([PrivDir,"tf3_"++atom_to_list(N)++".ti"])}}]}
+ end,
+ TracerDataList=lists:map(TracerDataFun,Nodes),
+ start_and_init_tracing2(Nodes,[],TracerDataList,{ok,[{trace_log,ok},{ti_log,ok}]}),
+ activate_deactivate_meta_tracing_tracer(Nodes),
+ stop_tracing(Nodes),
+ stop(Nodes),
+ ok.
+%% -----------------------------------------------------------------------------
+
+%% TEST CASE: Basic, distributed, trace and ti, where we try to use ctp_all to
+%% check that all global and local patterns are removed but that meta patterns
+%% remain.
+%% This test also checks that if the meta tracer is terminated an error value
+%% is generated when trying to do meta tracing at that node.
+basic_dist_trace_ti_3(suite) -> [];
+basic_dist_trace_ti_3(doc) ->
+ [""];
+basic_dist_trace_ti_3(Config) when is_list(Config) ->
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ PrivDir=filename:join(?config(priv_dir,Config),""),
+ TracerDataFun=
+ fun(N)->{N,[{trace,{file,filename:join([PrivDir,"tf4_"++atom_to_list(N)])}},
+ {ti,{file,filename:join([PrivDir,"tf4_"++atom_to_list(N)++".ti"])}}]}
+ end,
+ TracerDataList=lists:map(TracerDataFun,Nodes),
+ start_and_init_tracing2(Nodes,[],TracerDataList,{ok,[{trace_log,ok},{ti_log,ok}]}),
+ activate_local_tracing(Nodes),
+ activate_global_tracing(Nodes),
+ activate_meta_tracing(Nodes),
+ ?l true=(is_pid(whereis(inviso_rt))),
+ ?l true=(is_pid(whereis(inviso_rt_meta))),
+ ?l {ok,NodeResults1}=inviso:ctp_all(Nodes), % Removes local and global patterns.
+ ?l true=check_noderesults(Nodes,ok,NodeResults1),
+ ?l true=check_on_nodes(Nodes,erlang,trace_info,[{code,which,1},traced],{traced,false}),
+ ?l true=check_on_nodes(Nodes,erlang,trace_info,[{code,get_path,0},traced],{traced,false}),
+ %% But meta patters shall remain.
+ ?l true=check_on_nodes(Nodes,
+ erlang,
+ trace_info,
+ [{lists,module_info,0},meta_match_spec],
+ fun({meta_match_spec,L})when length(L)>0 ->true end),
+ ?l true=check_on_nodes(Nodes,
+ erlang,
+ trace_info,
+ [{lists,module_info,0},meta],
+ fun({meta,P})when is_pid(P) ->
+ P=rpc:call(node(P),erlang,whereis,[inviso_rt_meta]),
+ true
+ end),
+ %% Now kill the meta tracer somewhere and try to activate meta tracing.
+ ?l [ANode|_]=Nodes,
+ ?l AMetaPid=rpc:call(ANode,erlang,whereis,[inviso_rt_meta]),
+ ?l rpc:call(ANode,erlang,exit,[AMetaPid,kill]),
+ ?l {ok,NodeResults2}=inviso:tpm(Nodes,math,pi,0,[],void),
+ ?l {value,{ANode,{error,_}}}=lists:keysearch(ANode,1,NodeResults2),
+
+ ?l stop_tracing(Nodes),
+ ?l stop(Nodes),
+ ok.
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% Test cases for SUSPEND
+%% -----------------------------------------------------------------------------
+
+%% TEST CASE: In this test case a trace with ti is started. Trace flags are set,
+%% trace patterns are set and meta trace patterns. We then check that the trace
+%% flags and the meta patterns are removed when tracing suspended.
+%% The suspension is cancelled and we check that it is possible to reactivate
+%% tracing by setting the process flags and meta patterns again.
+suspend_dist_trace_ti_1(suite) -> [];
+suspend_dist_trace_ti_1(doc) ->
+ [""];
+suspend_dist_trace_ti_1(Config) when is_list(Config) ->
+ ?l RemoteNodes=get_remotenodes_config(Config),
+ ?l Nodes=[node()|RemoteNodes],
+ ?l PrivDir=filename:join(?config(priv_dir,Config),""),
+ ?l TracerDataFun=
+ fun(N)->{N,[{trace,{file,filename:join([PrivDir,"tf_suspend1_"++atom_to_list(N)])}},
+ {ti,{file,filename:join([PrivDir,"tf_suspend1_"++atom_to_list(N)++".ti"])}}]}
+ end,
+ ?l TracerDataList=lists:map(TracerDataFun,Nodes),
+ start_and_init_tracing2(Nodes,[],TracerDataList,{ok,[{trace_log,ok},{ti_log,ok}]}),
+ activate_local_tracing(Nodes),
+ activate_meta_tracing(Nodes),
+ ?l true=(is_pid(whereis(inviso_rt))),
+ ?l true=(is_pid(whereis(inviso_rt_meta))),
+ %% Set some trace flags on some newly started test procs.
+ activate_traceflags(Nodes),
+
+ %% Now suspend the tracing on all nodes. That shall result in the removal
+ %% of trace flags and meta trace patterns, but not local trace patterns.
+ ?l {ok,NodeResults1}=inviso:suspend(Nodes,test),
+ ?l true=check_noderesults(Nodes,ok,NodeResults1),
+ %% Trace flags gone?
+ ?l TestProcs=lists:map(fun(N)->rpc:call(N,erlang,whereis,[inviso_test_proc]) end,Nodes),
+ ?l lists:foreach(fun(P)->
+ {flags,[]}=
+ rpc:call(node(P),erlang,trace_info,[P,flags])
+ end,
+ TestProcs),
+ %% Meta patterns shall be gone too, but local functions still there.
+ ?l lists:foreach(fun(N)->
+ {meta,false}=
+ rpc:call(N,
+ erlang,
+ trace_info,
+ [{math,module_info,1},meta]),
+ {traced,local}=
+ rpc:call(N,
+ erlang,
+ trace_info,
+ [{code,which,1},traced])
+ end,
+ Nodes),
+
+ %% Try to activate trace flags, trace patterns and meta tracing while
+ %% suspended. Should not succeed of course!
+ ?l ThisNode=node(),
+ ?l {ok,[{ThisNode,{error,suspended}}]}=
+ inviso:tf([ThisNode],inviso_test_proc,[call]),
+ ?l {ok,[{ThisNode,{error,suspended}}]}=
+ inviso:tpl([ThisNode],math,module_info,1,[]),
+ ?l {ok,[{ThisNode,{error,suspended}}]}=
+ inviso:init_tpm([ThisNode],
+ math,
+ module_info,
+ 1,
+ {?MODULE,tpm_init_func2}, % Does not exist on purpose.
+ {?MODULE,tpm_call_func2}, % Does not exist on purpose.
+ {?MODULE,tpm_return_func2}, % Does not exist on purpose.
+ {?MODULE,tpm_remove_func2}), % Does not exist on purpose.
+
+ %% Now we want to cancel suspension and see that we can reactivate tracing.
+ ?l {ok,NodeResults2}=inviso:cancel_suspension(Nodes),
+ ?l true=check_noderesults(Nodes,ok,NodeResults2),
+
+ ?l {ok,NodeResults3}=
+ inviso:init_tpm(math,
+ module_info,
+ 1,
+ {?MODULE,tpm_init_func2}, % Does not exist on purpose.
+ {?MODULE,tpm_call_func2}, % Does not exist on purpose.
+ {?MODULE,tpm_return_func2}, % Does not exist on purpose.
+ {?MODULE,tpm_remove_func2}), % Does not exist on purpose.
+ ?l true=check_noderesults(Nodes,ok,NodeResults3),
+ ?l {ok,NodeResults5}=
+ inviso:tpm_ms(math,module_info,1,ms1,[{'_',[],[{return_trace}]}]),
+ ?l true=check_noderesults(Nodes,{ok,1},NodeResults5),
+ ?l true=check_on_nodes(Nodes,
+ erlang,
+ trace_info,
+ [{math,module_info,1},meta_match_spec],
+ {meta_match_spec,[{'_',[],[{return_trace}]}]}),
+ ?l {ok,NodeResults6}=inviso:tf(Nodes,inviso_test_proc,[call]),
+ ?l true=check_noderesults(Nodes,{ok,[1]},NodeResults6),
+
+ %deactivate_meta_tracing(Nodes),
+ %deactivate_local_tracing(Nodes),
+ stop_tracing(Nodes),
+ ?l true=(is_pid(whereis(inviso_rt))), % Shall still be running.
+ ?l ok=poll(erlang,whereis,[inviso_rt_meta],undefined,3),
+ stop(Nodes),
+ ?l timer:sleep(200), % Give it time to terminate.
+ ?l ok=poll(erlang,whereis,[inviso_rt],undefined,3),% Shall be gone now.
+ ?l undefined=whereis(inviso_rt_meta), % Still gone.
+ ok.
+%% -----------------------------------------------------------------------------
+
+%% TEST CASE: In this test case a trace with ti is started. Trace flags are set,
+%% trace patterns are set and meta trace patterns. We then suspend tracing at
+%% all nodes, then stop tracing which shall be allowed. We then try to initiate
+%% tracing again which shall not be possible.
+suspend_dist_trace_ti_2(suite) -> [];
+suspend_dist_trace_ti_2(doc) ->
+ [""];
+suspend_dist_trace_ti_2(Config) when is_list(Config) ->
+ ?l RemoteNodes=get_remotenodes_config(Config),
+ ?l Nodes=[node()|RemoteNodes],
+ ?l PrivDir=filename:join(?config(priv_dir,Config),""),
+ ?l TracerDataFun=
+ fun(N)->{N,[{trace,{file,filename:join([PrivDir,"tf_suspend2_"++atom_to_list(N)])}},
+ {ti,{file,filename:join([PrivDir,"tf_suspend2_"++atom_to_list(N)++".ti"])}}]}
+ end,
+ ?l TracerDataList=lists:map(TracerDataFun,Nodes),
+ start_and_init_tracing2(Nodes,[],TracerDataList,{ok,[{trace_log,ok},{ti_log,ok}]}),
+ activate_local_tracing(Nodes),
+ activate_meta_tracing(Nodes),
+ ?l true=(is_pid(whereis(inviso_rt))),
+ ?l true=(is_pid(whereis(inviso_rt_meta))),
+ %% Set some trace flags on some newly started test procs.
+ activate_traceflags(Nodes),
+
+ %% Now suspend the tracing on all nodes. That shall result in the removal
+ %% of trace flags and meta trace patterns, but not local trace patterns.
+ ?l {ok,NodeResults1}=inviso:suspend(Nodes,test),
+ ?l true=check_noderesults(Nodes,ok,NodeResults1),
+
+ %% Now stop tracing.
+ ?l {ok,NodeResults3}=inviso:stop_tracing(Nodes),
+ ?l true=check_noderesults(Nodes,{ok,idle},NodeResults3),
+ %% Now try to initiate tracing again.
+ ThisNode=node(),
+ ?l {ok,[{ThisNode,{error,suspended}}]}=
+ inviso:init_tracing([ThisNode],
+ [{trace,{file,filename:join([PrivDir,"tf_suspend3_"++
+ atom_to_list(ThisNode)])}},
+ {ti,{file,{filename:join([PrivDir,"tf_suspend3_"++
+ atom_to_list(ThisNode)])}}}]),
+
+ %% Cancel the suspension and initiate tracing again.
+ ?l {ok,NodeResults2}=inviso:cancel_suspension(Nodes),
+ ?l true=check_noderesults(Nodes,ok,NodeResults2),
+ ?l TracerDataFun2=
+ fun(N)->{N,[{trace,{file,filename:join([PrivDir,"tf_suspend4_"++atom_to_list(N)])}},
+ {ti,{file,filename:join([PrivDir,"tf_suspend4_"++atom_to_list(N)++".ti"])}}]}
+ end,
+ ?l TracerDataList2=lists:map(TracerDataFun2,Nodes),
+ ?l {ok,NodeResults4}=inviso:init_tracing(TracerDataList2),
+ ?l true=check_noderesults(Nodes,{ok,[{trace_log,ok},{ti_log,ok}]},NodeResults4),
+ stop_tracing(Nodes),
+ ?l true=(is_pid(whereis(inviso_rt))), % Shall still be running.
+ stop(Nodes),
+ ?l timer:sleep(200), % Give it time to terminate.
+ ?l ok=poll(erlang,whereis,[inviso_rt],undefined,3),% Shall be gone now.
+ ok.
+%% -----------------------------------------------------------------------------
+
+
+
+%% TEST CASE: This test case tests that the clean function removes (prosumed)
+%% expired data from the internal public-loopdata structure in the inviso_rt_meta
+%% process.
+meta_cleanfunc_dist_1(Config) when is_list(Config) ->
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ PrivDir=filename:join(?config(priv_dir,Config),""),
+ TracerDataFun=
+ fun(N)->{N,[{trace,{file,filename:join([PrivDir,"mcf1_"++atom_to_list(N)])}},
+ {ti,{file,filename:join([PrivDir,"mcf1_"++atom_to_list(N)++".ti"])}}]}
+ end,
+ TracerDataList=lists:map(TracerDataFun,Nodes),
+ start_and_init_tracing2(Nodes,[],TracerDataList,{ok,[{trace_log,ok},{ti_log,ok}]}),
+ %% Now initialize meta tracing, but the call_func is a bit "fixed".
+ ?l {ok,NodeResults1}=
+ inviso:tpm(Nodes,math,module_info,1,[],
+ {?MODULE,meta_cleanfunc_initfunc_1},
+ {?MODULE,meta_cleanfunc_callfunc_1},
+ void,void),
+ ?l true=check_noderesults(Nodes,{ok,1},NodeResults1),
+ %% Nothing in the "our" part of the public loop data.
+ ?l true=check_on_nodes(Nodes,
+ inviso_rt_meta,get_state,[inviso_rt_meta],
+ fun({ok,_LD,{{_,[]},_}})->true end),
+ ?l lists:foreach(fun(N)->rpc:call(N,math,module_info,[exports]) end,Nodes),
+ %% Check that it has been added to the public loopdata structure.
+ ?l true=check_on_nodes(Nodes,
+ ?MODULE,poll,[inviso_rt_meta,
+ get_state,
+ [inviso_rt_meta],
+ fun({ok,_LD,{{_,[{meta_cleanfunc_test1,_Now}]},_}})->
+ true;
+ (_)->false
+ end,
+ 20],
+ ok),
+ %% While we wait for 60 seconds to pass, we test a few other things.
+ ?l {ok,NodeResults2}=
+ inviso:tpm(Nodes,?MODULE,slowfunction2,0,[{'_',[],[{return_trace}]}],
+ {?MODULE,meta_cleanfunc_initfunc_2},
+ {?MODULE,meta_cleanfunc_callfunc_2},
+ {?MODULE,meta_cleanfunc_returnfunc_2},
+ void),
+ ?l true=check_noderesults(Nodes,{ok,1},NodeResults2),
+ ?l lists:foreach(fun(N)->rpc:call(N,?MODULE,slowfunction,[]) end,Nodes),
+ %% Believe it or not but slowfunction is still running, in its own process,
+ %% we are therefore free now to examine the meta tracer.
+ ?l true=check_on_nodes(Nodes,
+ ?MODULE,poll,[inviso_rt_meta,
+ get_state,
+ [inviso_rt_meta],
+ fun({ok,_LD,{{[],Tuples},_}})->
+ {value,_}=
+ lists:keysearch(meta_cleanfunc_test2,
+ 1,
+ Tuples),
+ {value,_}=
+ lists:keysearch(meta_cleanfunc_test1,
+ 1,
+ Tuples),
+ true;
+ (_)->
+ false
+ end,
+ 20],
+ ok),
+ %% Now we wait for slowfunction to return and that the meta_cleanfunc_test2
+ %% to be removed from public loopdata strucuture.
+ ?l timer:sleep(10000),
+ %% The only thing remaining should be the meta_cleanfunc_test1 which will not
+ %% go away for less than that the clean functionality removes it.
+ ?l true=check_on_nodes(Nodes,
+ ?MODULE,poll,[inviso_rt_meta,
+ get_state,
+ [inviso_rt_meta],
+ fun({ok,_LD,{{_,[{meta_cleanfunc_test1,_Now}]},_}})->
+ true;
+ (_)->
+ false
+ end,
+ 20],
+ ok),
+ %% Wait for the clean function to clean meta_cleanfunc_test1 away.
+ ?l timer:sleep(51000), % Shall be gone after 5 seconds.
+ ?l true=check_on_nodes(Nodes,
+ ?MODULE,poll,[inviso_rt_meta,
+ get_state,
+ [inviso_rt_meta],
+ fun({ok,_LD,{{_,[]},_}})->true;
+ (_)->false
+ end,
+ 20],
+ ok),
+ stop_tracing(Nodes),
+ stop(Nodes),
+ ok.
+
+%% This function acts as tpm initialization function when we are going to test
+%% that the clean function works. Note that we here assume standard public loop
+%% datastructure.
+meta_cleanfunc_initfunc_1(_M,_F,_Arity,{E1,_E2}) ->
+ {ok,{E1,[]},void}.
+%% Function that is supposed to be called when the meta traced function is
+%% called.
+meta_cleanfunc_callfunc_1(_Pid,_Args,{{E1,E2},Global}) ->
+ {ok,{{E1,[{meta_cleanfunc_test1,now()}|E2]},Global},void}.
+
+meta_cleanfunc_initfunc_2(_M,_F,_Arity,PublLD) ->
+ {ok,PublLD,void}.
+meta_cleanfunc_callfunc_2(_Pid,_Args,{{E1,E2},Global}) ->
+ {ok,{{E1,[{meta_cleanfunc_test2,now()}|E2]},Global},void}.
+meta_cleanfunc_returnfunc_2(_Pid,_,{{E1,E2},Global}) ->
+ {value,_}=lists:keysearch(meta_cleanfunc_test2,1,E2),
+ {ok,{{E1,lists:keydelete(meta_cleanfunc_test2,1,E2)},Global},void}.
+
+slowfunction() ->
+ spawn(?MODULE,slowfunction1,[]).
+slowfunction1() ->
+ slowfunction2(). % Meta trace on this function call.
+slowfunction2() ->
+ timer:sleep(2000),
+ true.
+%% -----------------------------------------------------------------------------
+
+%% TEST CASE: Testing that a runtime component can be started instructing it
+%% to use a handler fun. Checks that the handler fun is called if a trace
+%% message comes in.
+basic_handlerfun_dist_1(suite) -> [];
+basic_handlerfun_dist_1(doc) ->
+ [""];
+basic_handlerfun_dist_1(Config) when is_list(Config) ->
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ ?l lists:foreach(fun(N)->rpc:call(N,ets,insert,[inviso_sideeffect_tab,{bhf1,0}]) end,
+ Nodes),
+ TracerDataFun=
+ fun(N)->{N,{fun basic_handlerfun_dist_1_fun/2,inviso_sideeffect_tab}} end,
+ TracerDataList=lists:map(TracerDataFun,Nodes),
+ start_and_init_tracing2(Nodes,[],TracerDataList,{ok,[{trace_log,ok}]}),
+ activate_local_tracing(Nodes),
+ activate_traceflags(Nodes),
+ ?l lists:foreach(fun(N)->[{bhf1,0}]=
+ rpc:call(N,ets,lookup,[inviso_sideeffect_tab,bhf1])
+ end,
+ Nodes),
+ ?l inviso_test_proc ! {apply,code,which,[lists]},
+ ok=poll(ets,lookup,[inviso_sideeffect_tab,bhf1],[{bhf1,1}],20),
+ deactivate_traceflags(Nodes),
+ deactivate_local_tracing(Nodes),
+ stop_tracing(Nodes),
+ timer:sleep(100),
+ ?l [{bhf1,1}]=ets:lookup(inviso_sideeffect_tab,bhf1),
+ stop(Nodes),
+ ok.
+
+%% Function used as handler fun for testcase above.
+basic_handlerfun_dist_1_fun(_Msg,TId) ->
+ ets:update_counter(TId,bhf1,1),
+ TId.
+%% -----------------------------------------------------------------------------
+
+%% TEST CASE: Here we test that delete_log removes the files at the involved
+%% runtime nodes. In this case we test that we remove logs according to last
+%% used tracer data.
+delete_log_dist_1(suite) -> [];
+delete_log_dist_1(doc) -> [""];
+delete_log_dist_1(Config) when is_list(Config) ->
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ PrivDir=filename:join(?config(priv_dir,Config),""),
+ TracerDataFun=
+ fun(N)->{N,[{trace,{file,filename:join([PrivDir,"dl1_"++atom_to_list(N)])}},
+ {ti,{file,filename:join([PrivDir,"dl1_"++atom_to_list(N)++".ti"])}}]}
+ end,
+ TracerDataList=lists:map(TracerDataFun,Nodes),
+ start_and_init_tracing2(Nodes,[],TracerDataList,{ok,[{trace_log,ok},{ti_log,ok}]}),
+ ?l Files=lists:map(fun({N,TD})->
+ ?l {value,{_,{_,TraceFile}}}=lists:keysearch(trace,1,TD),
+ ?l {value,{_,{_,TiFile}}}=lists:keysearch(ti,1,TD),
+ ?l {N,{TraceFile,TiFile}}
+ end,
+ TracerDataList),
+ io:format("The Files is:~w~n",[Files]),
+ ?l {ok,NodeResults1}=inviso:delete_log(Nodes), % Should not work!
+ ?l true=check_noderesults(Nodes,{error,tracing},NodeResults1),
+ stop_tracing(Nodes),
+ %% Files still here.
+ ?l lists:foreach(fun({N,{F1,F2}})->
+ ?l {ok,_}=rpc:call(N,file,read_file_info,[F1]),
+ ?l {ok,_}=rpc:call(N,file,read_file_info,[F2])
+ end,
+ Files),
+ ?l {ok,NodeResults2}=inviso:delete_log(Nodes),
+ ?l true=check_noderesults(Nodes,
+ fun({_N,{ok,LogInfos}})->
+ ?l {value,{_,[{ok,_FName1}]}}=
+ lists:keysearch(trace_log,1,LogInfos),
+ ?l {value,{_,[{ok,_FName2}]}}=
+ lists:keysearch(ti_log,1,LogInfos),
+ true
+ end,
+ NodeResults2),
+ %% The files shall be gone now.
+ ?l lists:foreach(fun({N,{F1,F2}})->
+ ?l {error,enoent}=rpc:call(N,file,read_file_info,[F1]),
+ ?l {error,enoent}=rpc:call(N,file,read_file_info,[F2])
+ end,
+ Files),
+ stop(Nodes),
+ ok.
+%% -----------------------------------------------------------------------------
+
+
+%% TEST CASE: Test of the autostart behaviour of the runtime component.
+%% Here we test that a runtime component is started according to the autostart.conf
+%% file. Note that the repeat parameter is set to 2.
+autostart_dist_1(suite) -> [];
+autostart_dist_1(doc) ->
+ [""];
+autostart_dist_1(Config) when is_list(Config) ->
+ RemoteNodes=get_remotenodes_config(Config),
+ PrivDir=filename:join(?config(priv_dir,Config),""),
+ AutoConfFile=filename:join(PrivDir,"autostart1.conf"),
+ [RNode|_]=RemoteNodes,
+ ?l ok=rpc:call(RNode,application,stop,[runtime_tools]),
+ ?l ok=rpc:call(RNode,application,set_env,[runtime_tools,
+ inviso_autostart_conf,
+ AutoConfFile]),
+ ?l {ok,FD}=file:open(AutoConfFile,[write]),
+ ?l ok=io:format(FD,"~w.~n~w.~n",[{repeat,2},{tag,c_ref}]),
+ ?l file:close(FD),
+ ?l ok=rpc:call(RNode,application,start,[runtime_tools]),
+ timer:sleep(1000),
+ ?l P1=rpc:call(RNode,erlang,whereis,[inviso_rt]),
+ ?l true=is_pid(P1),
+ ?l rpc:call(RNode,erlang,exit,[P1,kill]),
+ ?l ok=rpc:call(RNode,application,stop,[runtime_tools]),
+ ?l ok=rpc:call(RNode,application,start,[runtime_tools]),
+ timer:sleep(1000),
+ ?l P2=rpc:call(RNode,erlang,whereis,[inviso_rt]),
+ ?l true=is_pid(P2),
+ ?l rpc:call(RNode,erlang,exit,[P2,kill]),
+ ?l ok=rpc:call(RNode,application,stop,[runtime_tools]),
+ ?l ok=rpc:call(RNode,application,start,[runtime_tools]),
+ timer:sleep(1000),
+ ?l undefined=rpc:call(RNode,erlang,whereis,[inviso_rt]),
+ ok.
+%% -----------------------------------------------------------------------------
+
+%% TEST CASE: Test of autostart. Here we focus on that an autostarted
+%% runtime component actually follows the trace case command file and
+%% initiates tracing.
+autostart_dist_2(suite) -> [];
+autostart_dist_2(doc) ->
+ [""];
+autostart_dist_2(Config) when is_list(Config) ->
+ RemoteNodes=get_remotenodes_config(Config),
+ PrivDir=filename:join(?config(priv_dir,Config),""),
+ AutoConfFile=filename:join(PrivDir,"autostart2.conf"),
+ [RNode|_]=RemoteNodes,
+ ?l ok=rpc:call(RNode,application,stop,[runtime_tools]),
+ ?l ok=rpc:call(RNode,application,set_env,[runtime_tools,
+ inviso_autostart_conf,
+ AutoConfFile]),
+ ?l CmdFileName=filename:join(PrivDir,"autostart_cmd_as1"),
+ ?l {ok,FD}=file:open(CmdFileName,[write]),
+ ?l ok=io:format(FD,
+ "inviso:tpl(Nodes,M,F,Arity,[]).~n"
+ "inviso:tf(Nodes,inviso_test_proc,[call]).~n",
+ []),
+ ?l file:close(FD),
+ ?l TraceFileName=filename:join([PrivDir,"as1_"++atom_to_list(RNode)]),
+ ?l TiFileName=filename:join([PrivDir,"as1_"++atom_to_list(RNode)++".ti"]),
+ ?l inviso_as_lib:setup_autostart(RNode,
+ 2,
+ [],
+ [{trace,{file,TraceFileName}},
+ {ti,{file,TiFileName}}],
+ [[CmdFileName]],
+ [{'M',code},{'F',which},{'Arity',1}],
+ [{{inviso,tpl,5},{inviso_rt,tpl,{erlang,tl}}},
+ {{inviso,tf,3},{inviso_rt,tf,{erlang,tl}}}]),
+ ?l TestP=spawn(RNode,?MODULE,test_proc_init,[]),
+ ?l ok=rpc:call(RNode,application,start,[runtime_tools]),
+ ?l timer:sleep(1000),
+ ?l {ok,_}=file:read_file_info(TraceFileName),
+ ?l {ok,_}=file:read_file_info(TiFileName),
+ ?l true=is_pid(P=rpc:call(RNode,erlang,whereis,[inviso_rt])),
+ ?l ok=poll(rpc,call,[RNode,erlang,trace_info,[{code,which,1},traced]],{traced,local},10),
+ ?l {flags,[call]}=rpc:call(RNode,erlang,trace_info,[TestP,flags]),
+ ?l rpc:call(RNode,erlang,exit,[P,kill]),
+ ok.
+%% -----------------------------------------------------------------------------
+
+%% TEST CASE: Here we test that an autostarted runtime component with a dependency
+%% to a specific control component tries to connect to that control component
+%% during its start-up.
+autostart_dist_3(suite) -> [];
+autostart_dist_3(doc) ->
+ [""];
+autostart_dist_3(Config) when is_list(Config) ->
+ RemoteNodes=get_remotenodes_config(Config),
+ PrivDir=filename:join(?config(priv_dir,Config),""),
+ AutoConfFile=filename:join(PrivDir,"autostart3.conf"),
+ [RNode|_]=RemoteNodes,
+ ?l ok=rpc:call(RNode,application,stop,[runtime_tools]),
+ ?l ok=rpc:call(RNode,application,set_env,[runtime_tools,
+ inviso_autostart_conf,
+ AutoConfFile]),
+ ?l {ok,FD}=file:open(AutoConfFile,[write]),
+ ?l ok=io:format(FD,"~w.~n~w.~n~w.~n",
+ [{options,[{dependency,{infinity,node()}}]},{repeat,2},{tag,c_ref}]),
+ ?l file:close(FD),
+ %% Now start inviso at this node here for the runtime to connect.
+ ?l {ok,_Pid}=inviso:start(),
+ ?l ok=poll(erlang,whereis,[inviso_c],fun(P) when is_pid(P)->true;(_)->false end,10),
+ %% Make the runtime component start.
+ ?l ok=rpc:call(RNode,application,start,[runtime_tools]),
+ ?l ok=poll(rpc,call,[RNode,erlang,whereis,[inviso_rt]],
+ fun(P) when is_pid(P)->true;(_)->false end,10),
+ %% Check that the runtime component started.
+ ?l ok=poll(inviso,get_status,[[RNode]],{ok,[{RNode,{ok,{new,running}}}]},20),
+% ?l {ok,[{RNode,{ok,{new,running}}}]}=inviso:get_status([RNode]),
+ stop([RNode]),
+ ok.
+%% -----------------------------------------------------------------------------
+
+
+
+%% TEST CASE: Test of the dependency mechanism in the runtime component.
+%% Default behaviour is dependency=infinity, i.e the runtime components remains.
+%% We also test here that we can reconnect to the runtime.
+running_alone_dist_1(suite) -> [];
+running_alone_dist_1(doc) ->
+ [""];
+running_alone_dist_1(Config) when is_list(Config) ->
+ ?l {ok,_Pid1}=inviso:start(), % Start a control component.
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ ?l {ok,NodeResults1}=inviso:add_nodes(Nodes,a_ref,[]),
+ ?l true=check_noderesults(Nodes,{ok,new},NodeResults1),
+ ?l shutdown=inviso:stop(), % Stop the control component!
+ ?l undefined=whereis(inviso_c),
+ timer:sleep(3000), % How long shall we wait? :-)
+ ?l lists:foreach(fun(N)->true=is_pid(rpc:call(N,erlang,whereis,[inviso_rt])) end,
+ Nodes),
+ ?l {ok,_Pid2}=inviso:start(),
+ ?l {ok,NodeResults2}=inviso:add_nodes(Nodes,b_ref,[]),
+ ?l true=check_noderesults(Nodes,{ok,{adopted,new,running,a_ref}},NodeResults2),
+ stop(Nodes),
+ ok.
+%% -----------------------------------------------------------------------------
+
+%% TEST CASE: Test of the dependency mechanism in the runtime component.
+%% Test that the runtime components terminates after the specified 5000 ms.
+running_alone_dist_2(suite) -> [];
+running_alone_dist_2(doc) ->
+ [""];
+running_alone_dist_2(Config) when is_list(Config) ->
+ ?l {ok,_Pid1}=inviso:start(), % Start a control component.
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ ?l {ok,NodeResults1}=inviso:add_nodes(Nodes,a_ref,[{dependency,5000}]),
+ ?l true=check_noderesults(Nodes,{ok,new},NodeResults1),
+ ?l shutdown=inviso:stop(), % Stop the control component!
+ ?l undefined=whereis(inviso_c),
+ timer:sleep(2000),
+ ?l lists:foreach(fun(N)->true=is_pid(rpc:call(N,erlang,whereis,[inviso_rt])) end,
+ Nodes),
+ timer:sleep(4000), % Now they shall be dead!
+ ?l lists:foreach(fun(N)->undefined=rpc:call(N,erlang,whereis,[inviso_rt]) end,
+ Nodes),
+ ok.
+%% -----------------------------------------------------------------------------
+
+%% TEST CASE: Test of the dependency mechanism in the runtime component.
+%% Test that the runtime components terminates after the specified 5000 ms.
+running_alone_dist_3(suite) -> [];
+running_alone_dist_3(doc) ->
+ [""];
+running_alone_dist_3(Config) when is_list(Config) ->
+ ?l {ok,_Pid1}=inviso:start(), % Start a control component.
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ ?l {ok,NodeResults1}=inviso:add_nodes(Nodes,a_ref,[{dependency,1000}]),
+ ?l true=check_noderesults(Nodes,{ok,new},NodeResults1),
+ ?l {ok,NodeResults2}=inviso:change_options(Nodes,[{dependency,5000}]),
+ ?l true=check_noderesults(Nodes,ok,NodeResults2),
+ ?l shutdown=inviso:stop(), % Stop the control component!
+ ?l undefined=whereis(inviso_c),
+ timer:sleep(3000),
+ ?l lists:foreach(fun(N)->true=is_pid(rpc:call(N,erlang,whereis,[inviso_rt])) end,
+ Nodes),
+ timer:sleep(3000), % Now they shall be dead!
+ ?l lists:foreach(fun(N)->undefined=rpc:call(N,erlang,whereis,[inviso_rt]) end,
+ Nodes),
+ ok.
+%% -----------------------------------------------------------------------------
+
+%% TEST CASE: Test of the dependency mechanism in the runtime component.
+%% Test that the runtime components terminates after the specified 5000 ms,
+%% like we did in running_alone_dist_2. But now we also start tracing and checks
+%% that all inviso processes actually disappears when the time-out is reached.
+running_alone_dist_4(suite) -> [];
+running_alone_dist_4(doc) ->
+ [""];
+running_alone_dist_4(Config) when is_list(Config) ->
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ %% Start some tracing!
+ PrivDir=filename:join(?config(priv_dir,Config),""),
+ TracerDataFun=
+ fun(N)->{N,[{trace,{file,filename:join([PrivDir,"tf_ra4"++atom_to_list(N)])}},
+ {ti,{file,filename:join([PrivDir,"tf_ra4_"++atom_to_list(N)++".ti"])}}]}
+ end,
+ TracerDataList=lists:map(TracerDataFun,Nodes),
+ start_and_init_tracing2(Nodes,
+ [{dependency,5000}],
+ TracerDataList,
+ {ok,[{trace_log,ok},{ti_log,ok}]}),
+
+ ?l lists:foreach(fun(N)->true=is_pid(rpc:call(N,erlang,whereis,[inviso_rt])) end,
+ Nodes),
+ ?l lists:foreach(fun(N)->true=is_pid(rpc:call(N,erlang,whereis,[inviso_rt_meta])) end,
+ Nodes),
+ %% Stop control component and wait for the runtimes to terminate after
+ %% running alone timer has expired.
+ ?l shutdown=inviso:stop(), % Stop the control component!
+ ?l undefined=whereis(inviso_c),
+ timer:sleep(2000),
+ ?l lists:foreach(fun(N)->true=is_pid(rpc:call(N,erlang,whereis,[inviso_rt])) end,
+ Nodes),
+ ?l lists:foreach(fun(N)->true=is_pid(rpc:call(N,erlang,whereis,[inviso_rt_meta])) end,
+ Nodes),
+ timer:sleep(4000), % Now they shall be dead!
+ ?l lists:foreach(fun(N)->undefined=rpc:call(N,erlang,whereis,[inviso_rt]) end,
+ Nodes),
+ ?l lists:foreach(fun(N)->undefined=rpc:call(N,erlang,whereis,[inviso_rt_meta]) end,
+ Nodes),
+ ok.
+%% -----------------------------------------------------------------------------
+
+%% TEST CASE: Test of the dependency mechanism in the runtime component.
+%% Test that the runtime components terminates imeediately when the control
+%% component is stopped. Check that all processes are gone.
+running_alone_dist_5(suite) -> [];
+running_alone_dist_5(doc) ->
+ [""];
+running_alone_dist_5(Config) when is_list(Config) ->
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ %% Start some tracing!
+ PrivDir=filename:join(?config(priv_dir,Config),""),
+ TracerDataFun=
+ fun(N)->{N,[{trace,{file,filename:join([PrivDir,"tf_ra5"++atom_to_list(N)])}},
+ {ti,{file,filename:join([PrivDir,"tf_ra5_"++atom_to_list(N)++".ti"])}}]}
+ end,
+ TracerDataList=lists:map(TracerDataFun,Nodes),
+ start_and_init_tracing2(Nodes,
+ [{dependency,0}],
+ TracerDataList,
+ {ok,[{trace_log,ok},{ti_log,ok}]}),
+
+ ?l lists:foreach(fun(N)->true=is_pid(rpc:call(N,erlang,whereis,[inviso_rt])) end,
+ Nodes),
+ ?l lists:foreach(fun(N)->true=is_pid(rpc:call(N,erlang,whereis,[inviso_rt_meta])) end,
+ Nodes),
+ %% Stop control component and check that all runtime component processes have
+ %% terminate more or less immediately afterwards, since dependency==0.
+ ?l shutdown=inviso:stop(), % Stop the control component!
+ timer:sleep(100),
+ ?l undefined=whereis(inviso_c),
+ timer:sleep(500),
+ ?l lists:foreach(fun(N)->undefined=rpc:call(N,erlang,whereis,[inviso_rt]) end,
+ Nodes),
+ ?l lists:foreach(fun(N)->undefined=rpc:call(N,erlang,whereis,[inviso_rt_meta]) end,
+ Nodes),
+ ok.
+%% -----------------------------------------------------------------------------
+
+%% TEST CASE: Test of the overload protection mechanism. The mechanism checks
+%% for overload using the callback approximately at the interval specified.
+%% Check that it does not start protection until start of tracing.
+overload_dist_1(suite) -> [];
+overload_dist_1(doc) ->
+ [""];
+overload_dist_1(Config) when is_list(Config) ->
+ ?l {ok,_Pid1}=inviso:start(), % Start a control component.
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ ?l lists:foreach(fun(N)->true=rpc:call(N,ets,insert,[inviso_sideeffect_tab,{ovl1,0}]) end,
+ Nodes), % Initiate the counter.
+ ?l {ok,NodeResults1}=inviso:add_nodes(Nodes,
+ a_ref,
+ [{overload,{{?MODULE,overload1},500}}]),
+ ?l true=check_noderesults(Nodes,{ok,new},NodeResults1),
+ timer:sleep(1000), % Give the loadcheck time to perform.
+ ?l [{_,0}]=ets:lookup(inviso_sideeffect_tab,ovl1), % Nothing should have happened.
+
+ %% Overload check shall not start until we start tracing.
+ PrivDir=filename:join(?config(priv_dir,Config),""),
+ TracerDataList=lists:map(fun(N)->{N,[{trace,
+ {file,filename:join([PrivDir,
+ "tf_ovl1."++atom_to_list(N)
+ ])}}]}
+ end,
+ Nodes),
+ ?l {ok,NodeResults2}=inviso:init_tracing(TracerDataList),
+ ?l true=check_noderesults(Nodes,{ok,[{trace_log,ok}]},NodeResults2),
+ timer:sleep(1500), % Give the loadcheck time to perform.
+ ?l [{_,N}]=ets:lookup(inviso_sideeffect_tab,ovl1),
+ ?l true=(N>=2), % After 1,5 seconds, at least 2 checks.
+
+ %% Now change options and remove overload checking!
+ ?l {ok,NodeResults3}=inviso:change_options(Nodes,[overload]),
+ ?l true=check_noderesults(Nodes,ok,NodeResults3),
+ ?l [{_,N2}]=ets:lookup(inviso_sideeffect_tab,ovl1),
+ timer:sleep(1000),
+ ?l [{_,N2}]=ets:lookup(inviso_sideeffect_tab,ovl1), % No more loadchecks!
+
+ stop_tracing(Nodes),
+ stop(Nodes),
+ ok.
+%% -----------------------------------------------------------------------------
+
+%% TEST CASE: Test of the overload protection mechanism. In this case we focus
+%% in that the init and remove functions are carried out at change_options and
+%% when starting and stoping the runtime component.
+overload_dist_2(suite) -> [];
+overload_dist_2(doc) ->
+ [""];
+overload_dist_2(Config) when is_list(Config) ->
+ ?l {ok,_Pid1}=inviso:start(), % Start a control component.
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ ?l {ok,NodeResults1}=inviso:add_nodes(Nodes,
+ a_ref,
+ [{overload,{{?MODULE,overload2},
+ 500,
+ {?MODULE,overload2i,[]},
+ {?MODULE,overload2r,[]}}}]),
+ ?l true=check_noderesults(Nodes,{ok,new},NodeResults1),
+ ?l [{_,0}]=ets:lookup(inviso_sideeffect_tab,ovl2),
+
+ PrivDir=filename:join(?config(priv_dir,Config),""),
+ TracerDataList=lists:map(fun(N)->{N,[{trace,
+ {file,filename:join([PrivDir,
+ "tf_ovl2."++atom_to_list(N)
+ ])}}]}
+ end,
+ Nodes),
+ ?l {ok,NodeResults2}=inviso:init_tracing(TracerDataList),
+ ?l true=check_noderesults(Nodes,{ok,[{trace_log,ok}]},NodeResults2),
+ timer:sleep(1500), % Give the loadcheck time to perform.
+ ?l [{_,N}]=ets:lookup(inviso_sideeffect_tab,ovl2),
+ io:format("� is:~p~n",[N]),
+ ?l true=(N>=2), % After 1,5 seconds, at least 2 checks.
+ ?l {ok,NodeResults3}=inviso:change_options(Nodes,[{overload,{{?MODULE,overload3},
+ 500,
+ {?MODULE,overload3i,[]},
+ {?MODULE,overload3r,[]}}}]),
+ ?l true=check_noderesults(Nodes,ok,NodeResults3),
+ ?l []=ets:lookup(inviso_sideeffect_tab,ovl2),
+ timer:sleep(1500),
+ ?l [{_,N2}]=ets:lookup(inviso_sideeffect_tab,ovl3),
+ ?l true=(N2>=2), % After 1,5 seconds, at least 2 checks.
+ stop_tracing(Nodes),
+ ?l []=ets:lookup(inviso_sideeffect_tab,ovl3r), % Remove function shall not be called.
+ ?l [{_,N3}]=ets:lookup(inviso_sideeffect_tab,ovl3),
+ timer:sleep(1000), % Check that overloadchecking has stopped.
+ ?l [{_,N3}]=ets:lookup(inviso_sideeffect_tab,ovl3),
+ stop(Nodes),
+ ?l ok=poll(ets,lookup,[inviso_sideeffect_tab,ovl3r],[{ovl3r,done}],20),
+ ok.
+%% -----------------------------------------------------------------------------
+
+%% TEST CASE: Test of the overload protections mechanism. Here we focus on testing
+%% that if overload is reached tracing is really suspended.
+overload_dist_3(suite) -> [];
+overload_dist_3(doc) ->
+ [""];
+overload_dist_3(Config) when is_list(Config) ->
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ PrivDir=filename:join(?config(priv_dir,Config),""),
+ TracerDataList=
+ lists:map(fun(N)->{N,[{trace,{file,filename:join([PrivDir,
+ "tf_ovl3."++atom_to_list(N)])}},
+ {ti,{file,filename:join([PrivDir,
+ "tf_ovl3_ti."++atom_to_list(N)])}}]}
+ end,
+ Nodes),
+ ?l lists:foreach(fun(N)->
+ true=rpc:call(N,ets,insert,[inviso_sideeffect_tab,{ovl4,0}])
+ end,
+ Nodes),
+ start_and_init_tracing2(Nodes,
+ [{overload,{{?MODULE,overload4},500}}],
+ TracerDataList,
+ {ok,[{trace_log,ok},{ti_log,ok}]}),
+ activate_local_tracing(Nodes),
+ activate_meta_tracing(Nodes),
+ activate_traceflags(Nodes),
+ timer:sleep(600),
+ ?l [{_,N1}]=ets:lookup(inviso_sideeffect_tab,ovl4),
+ ?l true=(N1>=1), % Overload check has been done!
+ ?l Node=node(),
+ ?l {ok,[{Node,{ok,{tracing,running}}}]}=inviso:get_status([node()]),
+ ?l true=ets:insert(inviso_sideeffect_tab,{ovl4_suspend,true}),
+ timer:sleep(600),
+ ?l {ok,[{Node,{ok,{tracing,{suspended,test}}}}]}=inviso:get_status([node()]),
+ ?l [{_,N2}]=ets:lookup(inviso_sideeffect_tab,ovl4),
+ ?l {flags,[]}=erlang:trace_info(whereis(inviso_test_proc),flags),
+ ?l {meta,false}=erlang:trace_info({lists,module_info,0},meta),
+ ?l {traced,local}=erlang:trace_info({code,which,1},traced),
+ ?l true=(is_pid(whereis(inviso_rt_meta))),
+ ?l true=ets:delete(inviso_sideeffect_tab,ovl4_suspend),
+ timer:sleep(600),
+ ?l [{_,N2}]=ets:lookup(inviso_sideeffect_tab,ovl4), % No checking while suspended!
+ ?l {ok,[{Node,ok}]}=inviso:cancel_suspension([node()]),
+ ?l {ok,NodeResults1}=inviso:get_status(Nodes),
+ ?l true=check_noderesults(Nodes,{ok,{tracing,running}},NodeResults1),
+ timer:sleep(600),
+ ?l [{_,N3}]=ets:lookup(inviso_sideeffect_tab,ovl4),
+ ?l true=(N3>N2),
+ ?l deactivate_local_tracing(Nodes),
+ ?l stop_tracing(Nodes),
+ ?l stop(Nodes),
+ ok.
+%% -----------------------------------------------------------------------------
+
+%% TEST CASE. Test that the overload mechanism is triggered by to the runtime
+%% component incomming messages, and nothing else.
+overload_dist_4(suite) -> [];
+overload_dist_4(doc) ->
+ [""];
+overload_dist_4(Config) when is_list(Config) ->
+ ?l {ok,_Pid1}=inviso:start(), % Start a control component.
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ ?l {ok,NodeResults1}=inviso:add_nodes(Nodes,
+ a_ref,
+ [{overload,{{?MODULE,overload5},
+ infinity,
+ {?MODULE,overload5i,[]},
+ {?MODULE,overload5r,[]}}}]),
+ ?l true=check_noderesults(Nodes,{ok,new},NodeResults1),
+ ?l [{_,0}]=ets:lookup(inviso_sideeffect_tab,ovl5),
+
+ PrivDir=filename:join(?config(priv_dir,Config),""),
+ TracerDataList=lists:map(fun(N)->{N,[{trace,
+ {file,filename:join([PrivDir,
+ "tf_ovl4."++atom_to_list(N)
+ ])}}]}
+ end,
+ Nodes),
+ ?l {ok,NodeResults2}=inviso:init_tracing(TracerDataList),
+ ?l true=check_noderesults(Nodes,{ok,[{trace_log,ok}]},NodeResults2),
+ timer:sleep(2000), % Give the loadcheck time to perform.
+ ?l [{_,N}]=ets:lookup(inviso_sideeffect_tab,ovl5),
+ ?l true=(N==0), % And nothing shall have happend!
+ %% Now we send a message to the inviso_rt, then the load check function
+ %% shall be called.
+ ?l whereis(inviso_rt) ! test_of_loadcheck,
+ timer:sleep(200), % Make sure the inviso_rt gets scheduled.
+ ?l [{_,1}]=ets:lookup(inviso_sideeffect_tab,ovl5),
+ stop_tracing(Nodes),
+ ?l []=ets:lookup(inviso_sideeffect_tab,ovl5r), % Remove function shall not be called.
+ ?l [{_,N3}]=ets:lookup(inviso_sideeffect_tab,ovl5),
+ ?l whereis(inviso_rt) ! test_of_loadcheck,
+ timer:sleep(1000), % Check that overloadchecking has stopped.
+ ?l [{_,N3}]=ets:lookup(inviso_sideeffect_tab,ovl5),
+ stop(Nodes),
+ ?l ok=poll(ets,lookup,[inviso_sideeffect_tab,ovl5r],[{ovl5r,done}],20),
+ ok.
+%% -----------------------------------------------------------------------------
+
+%% TEST CASE. Test that the overload mechanism correctly calculates remaining time
+%% to next load check if a message comes into the runtime component "interupting"
+%% the waiting for loadcheck timeout. (Loadcheck timeout is implemented as an after
+%% in the receive).
+overload_dist_5(suite) -> [];
+overload_dist_5(doc) ->
+ [""];
+overload_dist_5(Config) when is_list(Config) ->
+ ?l {ok,_Pid1}=inviso:start(), % Start a control component.
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ ?l lists:foreach(fun(N)->true=rpc:call(N,ets,insert,[inviso_sideeffect_tab,{ovl6,0}]) end,
+ Nodes), % Initiate the counter.
+ ?l {ok,NodeResults1}=inviso:add_nodes(Nodes,
+ a_ref,
+ [{overload,{{?MODULE,overload6},1000}}]),
+ ?l true=check_noderesults(Nodes,{ok,new},NodeResults1),
+ %% Overload check shall not start until we start tracing.
+ PrivDir=filename:join(?config(priv_dir,Config),""),
+ TracerDataList=lists:map(fun(N)->{N,[{trace,
+ {file,filename:join([PrivDir,
+ "tf_ovl5."++atom_to_list(N)
+ ])}}]}
+ end,
+ Nodes),
+ ?l {ok,NodeResults2}=inviso:init_tracing(TracerDataList),
+ ?l true=check_noderesults(Nodes,{ok,[{trace_log,ok}]},NodeResults2),
+ ?l ok=poll(ets,lookup,[inviso_sideeffect_tab,ovl6],[{ovl6,2}],25),
+ %% Now we know that exactly 2 checks have been made. Try to Distract the runtime :-)
+ ?l inviso_rt:state(whereis(inviso_rt)), % Make it have to receive a message.
+ timer:sleep(500),
+ ?l [{_,2}]=ets:lookup(inviso_sideeffect_tab,ovl6), % Should still be 2.
+ timer:sleep(600),
+ ?l [{_,3}]=ets:lookup(inviso_sideeffect_tab,ovl6), % We expect yet one check.
+ timer:sleep(1100),
+ ?l [{_,4}]=ets:lookup(inviso_sideeffect_tab,ovl6),
+
+ stop_tracing(Nodes),
+ stop(Nodes),
+ ok.
+%% -----------------------------------------------------------------------------
+
+
+%% TEST CASE: Test of the subscription mechanism.
+subscribe_dist_1(Config) when is_list(Config) ->
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ PrivDir=filename:join(?config(priv_dir,Config),""),
+ Pid=spawn(?MODULE,inviso_msg_collector,[]),
+ CtrlPid=whereis(inviso_c),
+
+ ?l {ok,_Pid}=inviso:start(), % Start a control component.
+ ?l ok=inviso:subscribe(Pid),
+ ?l {ok,NodeResults1}=inviso:add_nodes(Nodes,a_ref,[]),
+ ?l true=check_noderesults(Nodes,{ok,new},NodeResults1),
+ ?l {ok,NodeResults2}=inviso:get_status(Nodes),
+ ?l true=check_noderesults(Nodes,{ok,{new,running}},NodeResults2),
+ check_msg_collector(Nodes,
+ fun({inviso_event,CP,_,{connected,N,{_Tag,{idle,running}}}})
+ when CP==CtrlPid ->
+ {true,N};
+ (_) ->
+ false
+ end,
+ 13),
+ TracerDataList=lists:map(fun(N)->{N,{file,
+ filename:join([PrivDir,
+ "tf_sub1"++atom_to_list(N)])}}
+ end,
+ Nodes),
+ ?l {ok,NodeResults3}=inviso:init_tracing(TracerDataList),
+ ?l true=check_noderesults(Nodes,{ok,[{trace_log,ok}]},NodeResults3),
+ check_msg_collector(Nodes,
+ fun({inviso_event,CP,_,{state_change,N,{tracing,running}}})
+ when CP==CtrlPid ->
+ {true,N};
+ (_) ->
+ false
+ end,
+ 13),
+ ?l {ok,NodeResults4}=inviso:suspend(Nodes,test),
+ ?l true=check_noderesults(Nodes,ok,NodeResults4),
+ check_msg_collector(Nodes,
+ fun({inviso_event,CP,_,{state_change,N,{tracing,{suspended,test}}}})
+ when CP==CtrlPid ->
+ {true,N};
+ (_) ->
+ false
+ end,
+ 13),
+ ?l [RNode|_]=RemoteNodes,
+ ?l RInvisoPid=rpc:call(RNode,erlang,whereis,[inviso_rt]),
+ ?l rpc:call(RNode,erlang,exit,[RInvisoPid,kill]),
+ check_msg_collector([RNode],
+ fun({inviso_event,CP,_,{disconnected,N,_Info}})
+ when CP==CtrlPid ->
+ {true,N};
+ (_) ->
+ false
+ end,
+ 11),
+
+ ?l {ok,_NodeResults5}=inviso:stop_tracing(Nodes),
+ ?l {ok,_NodeResults6}=inviso:stop_nodes(Nodes),
+ ?l shutdown=inviso:stop(),
+ ok.
+%% -----------------------------------------------------------------------------
+
+
+%% TEST CASE: fetch_log test of single straight trace_log file in distributed
+%% environment.
+fetch_log_dist_trace_1(suite) -> [];
+fetch_log_dist_trace_1(doc) ->
+ ["fetch_log test of single straight trace_log file in distributed"
+ "environment."];
+fetch_log_dist_trace_1(Config) when is_list(Config) ->
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ PrivDir=filename:join(?config(priv_dir,Config),""),
+ TracerDataList=lists:map(fun(N)->{N,[{trace,{file,filename:join([PrivDir,
+ "testfile1."++
+ atom_to_list(N)
+ ])}}]} end,
+ Nodes),
+ start_and_init_tracing2(Nodes,[],TracerDataList,{ok,[{trace_log,ok}]}),
+
+ %% Put some output in the logs.
+ ?l inviso:tp(Nodes,math,module_info,0,[]),
+ ?l inviso:tf(Nodes,all,[call]),
+ ?l lists:foreach(fun(N)->rpc:call(N,math,module_info,[]) end,Nodes),
+
+ stop_tracing(Nodes),
+ {H,M,S}=time(),
+ FetchToDir=filename:join([PrivDir,
+ "fetch_log_test1_"++integer_to_list(H)++"_"++
+ integer_to_list(M)++"_"++integer_to_list(S)]),
+ ?l ok=file:make_dir(FetchToDir),
+ ?l {ok,NodeResults}=inviso:fetch_log(RemoteNodes,FetchToDir,"p1"),
+ io:format("~p~n",[NodeResults]),
+ ?l true=check_noderesults(RemoteNodes,
+ fun({N,{complete,[{trace_log,[{ok,File}]},{ti_log,[]}]}}) ->
+ ?l File="p1testfile1."++atom_to_list(N),
+ true;
+ (_)->
+ false
+ end,
+ NodeResults),
+ ?l ON=filename:join(PrivDir,"testfile1."),
+ ?l FN=filename:join(FetchToDir,"p1testfile1."),
+ ?l lists:foreach(fun(N)->
+ {ok,#file_info{size=Size}}=
+ file:read_file_info(ON++atom_to_list(N)),
+ {ok,#file_info{size=Size}}=
+ file:read_file_info(FN++atom_to_list(N))
+ end,
+ RemoteNodes),
+ %% Now we wish to see that we get an incomplete if we try to fetch to a
+ %% directory that does not exist.
+ ?l FetchToErrorDir=filename:join([PrivDir,nonexistingingdir]),
+ ?l {ok,NodeResults2}=inviso:fetch_log(RemoteNodes,FetchToErrorDir,"p1"),
+ ?l io:format("NodeResults2:~w~n",[NodeResults2]),
+ ?l true=check_noderesults(RemoteNodes,
+ fun({_,{incomplete,_}}) ->
+ true;
+ (_)->
+ false
+ end,
+ NodeResults2),
+ stop(Nodes),
+ ok.
+%% -----------------------------------------------------------------------------
+
+fetch_log_dist_trace_2(suite) -> [];
+fetch_log_dist_trace_2(doc) ->
+ [""];
+fetch_log_dist_trace_2(Config) ->
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ PrivDir=filename:join(?config(priv_dir,Config),""),
+
+ {H,M,S}=time(),
+ ?l Name="wrap"++integer_to_list(H)++"_"++integer_to_list(M)++"_"++integer_to_list(S),
+ ?l BaseName=filename:join(PrivDir,Name),
+ Fun=fun(N)->{N,[{trace,{file,{BaseName++atom_to_list(N),wrap,".log",512,2}}},
+ {ti,{file,BaseName++"_ti_"++atom_to_list(N)++".ti"}}]}
+ end,
+ ?l TracerDataList=lists:map(Fun,Nodes),
+ start_and_init_tracing2(Nodes,[],TracerDataList,{ok,[{trace_log,ok},{ti_log,ok}]}),
+ fill_and_reach_two_wrapfiles(PrivDir,"^"++Name,Nodes),
+
+ stop_tracing(Nodes),
+ FetchToDir=filename:join([PrivDir,
+ "fetch_log_test2_"++integer_to_list(H)++"_"++
+ integer_to_list(M)++"_"++integer_to_list(S)]),
+ ?l ok=file:make_dir(FetchToDir),
+ ?l {ok,NodeResults}=inviso:fetch_log(RemoteNodes,FetchToDir,"p1"),
+ io:format("~p~n",[NodeResults]),
+ CheckFun=fun({N,{complete,[{trace_log,FileResults1},{ti_log,[{ok,TiFile}]}]}}) ->
+ Fun2=fun({ok,File}) ->
+ {match,1,_}=
+ regexp:first_match(File,
+ "^"++"p1"++Name++atom_to_list(N)),
+ true;
+ (_) ->
+ false
+ end,
+ ?l true=lists:all(Fun2,FileResults1),
+ ?l TiFile="p1"++Name++"_ti_"++atom_to_list(N)++".ti",
+ true;
+ (_)->
+ false
+ end,
+ ?l true=check_noderesults(RemoteNodes,CheckFun,NodeResults),
+ stop(Nodes),
+ ok.
+%% -----------------------------------------------------------------------------
+
+fetch_log_dist_trace_3(suite) -> [];
+fetch_log_dist_trace_3(doc) ->
+ [""];
+fetch_log_dist_trace_3(Config) ->
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ PrivDir=filename:join(?config(priv_dir,Config),""),
+
+ {H,M,S}=time(),
+ ?l Name="wrap2_"++integer_to_list(H)++"_"++integer_to_list(M)++"_"++integer_to_list(S),
+ ?l BaseName=filename:join(PrivDir,Name),
+ Fun=fun(N)->{N,[{trace,{file,{BaseName++atom_to_list(N),wrap,".log",512,2}}},
+ {ti,{file,BaseName++"_ti_"++atom_to_list(N)++".ti"}}]}
+ end,
+ ?l TracerDataList=lists:map(Fun,Nodes),
+ start_and_init_tracing2(Nodes,[],TracerDataList,{ok,[{trace_log,ok},{ti_log,ok}]}),
+ fill_and_reach_two_wrapfiles(PrivDir,"^"++Name,Nodes),
+
+ stop_tracing(Nodes),
+ FetchToDir=filename:join([PrivDir,
+ "fetch_log_test3_"++integer_to_list(H)++"_"++
+ integer_to_list(M)++"_"++integer_to_list(S)]),
+ ?l ok=file:make_dir(FetchToDir),
+ ?l {ok,NodeResults1}=inviso:list_logs(Nodes),
+ CheckFun=fun({N,{ok,[{trace_log,PrivDir2,[F1,F2]},{ti_log,PrivDir2,[F3]}]}})->
+ PrivDir2=PrivDir,
+ RegExp="^"++Name++atom_to_list(N)++"[0-9]+"++"\.log",
+ {match,1,_}=regexp:first_match(F1,RegExp),
+ {match,1,_}=regexp:first_match(F2,RegExp),
+ F3=Name++"_ti_"++atom_to_list(N)++".ti",
+ true;
+ (_) ->
+ false
+ end,
+ ?l true=check_noderesults(Nodes,CheckFun,NodeResults1),
+ ?l NodeFileSpecList=lists:map(fun({N,{ok,L}})->{N,L} end,
+ lists:keydelete(node(),1,NodeResults1)),
+ ?l {ok,NodeResults2}=inviso:fetch_log(NodeFileSpecList,FetchToDir,"p1"),
+io:format("~p~n",[NodeResults2]),
+ CheckFun2=fun({N,{complete,[{trace_log,FileResults1},{ti_log,[{ok,TiFile}]}]}}) ->
+ Fun2=fun({ok,File}) ->
+ {match,1,_}=
+ regexp:first_match(File,
+ "^"++"p1"++Name++atom_to_list(N)),
+ true;
+ (_) ->
+ false
+ end,
+ ?l true=lists:all(Fun2,FileResults1),
+ ?l TiFile="p1"++Name++"_ti_"++atom_to_list(N)++".ti",
+ true;
+ (_)->
+ false
+ end,
+ ?l true=check_noderesults(RemoteNodes,CheckFun2,NodeResults2),
+ stop(Nodes),
+ ok.
+%% -----------------------------------------------------------------------------
+
+fetch_log_dist_error_1(suite) -> [];
+fetch_log_dist_error_1(doc) ->
+ [""];
+fetch_log_dist_error_1(Config) when is_list(Config) ->
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ ?l {ok,_Pid}=inviso:start(), % Start a control component.
+ ?l {ok,NodeResults1}=inviso:add_nodes(Nodes,a_ref),
+ ?l true=check_noderesults(Nodes,{ok,new},NodeResults1),
+ ?l {ok,NodeResults2}=inviso:fetch_log(RemoteNodes,"foo","bar"),
+io:format("~p~n",[NodeResults2]),
+ ?l true=check_noderesults(RemoteNodes,
+ fun({_N,{error,no_tracerdata}})->true;
+ (_)->false
+ end,
+ NodeResults2),
+ stop(Nodes),
+ ok.
+%% -----------------------------------------------------------------------------
+
+fetch_log_dist_error_2(suite) -> [];
+fetch_log_dist_error_2(doc) ->
+ [""];
+fetch_log_dist_error_2(Config) when is_list(Config) ->
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ PrivDir=filename:join(?config(priv_dir,Config),""),
+ ?l {ok,_Pid}=inviso:start(), % Start a control component.
+ ?l {ok,NodeResults1}=inviso:add_nodes(Nodes,a_ref),
+ ?l true=check_noderesults(Nodes,{ok,new},NodeResults1),
+ ?l NodeLogList=lists:map(fun(N)->{N,[{trace_log,
+ PrivDir,
+ ["f1,fil","f2.fil"]},
+ {ti_log,
+ PrivDir,
+ ["f.ti"]}]}
+ end,
+ RemoteNodes),
+ ?l {ok,NodeResults2}=inviso:fetch_log(NodeLogList,"foo","bar"),
+ io:format("~p~n",[NodeResults2]),
+ ?l true=check_noderesults(RemoteNodes,
+ fun({_N,{incomplete,_}}) ->
+ true;
+ (_) ->
+ false
+ end,
+ NodeResults2),
+ ?l NodeTracerData=lists:map(fun(N)->{N,
+ [{trace,{file,filename:join(PrivDir,"foo")}},
+ {ti,{file,filename:join(PrivDir,"bar.ti")}}]}
+ end,
+ RemoteNodes),
+ {ok,NodeResults3}=inviso:fetch_log(NodeTracerData,"foo","bar"),
+ io:format("~p~n",[NodeResults3]),
+%% This should work this way. Now it says complete [], which is not entirely
+%% incorrect. But to follow the sematics of when fetching named files should
+%% say incomplete.
+%% Must do some rework to make that work. No real danger leaving it this way
+%% for now.
+% ?l true=check_noderesults(RemoteNodes,
+% fun({_N,{incomplete,_}}) ->
+% true;
+% (_) ->
+% false
+% end,
+% NodeResults3),
+ stop(Nodes),
+ ok.
+%% -----------------------------------------------------------------------------
+
+%% TEST CASE: This case tests that the log file merger merges files in the
+%% correct order, based on the timestamps.
+lfm_trace_dist_1(suite) -> [];
+lfm_trace_dist_1(doc) ->
+ [""];
+lfm_trace_dist_1(Config) when is_list(Config) ->
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ [RNode1,RNode2|_]=RemoteNodes,
+ PrivDir=filename:join(?config(priv_dir,Config),""),
+ TracerDataList=
+ lists:map(fun(N)->{N,{file,filename:join([PrivDir,"lfm1_"++atom_to_list(N)])}} end,
+ Nodes),
+ start_and_init_tracing2(Nodes,[],TracerDataList,{ok,[{trace_log,ok}]}),
+ activate_local_tracing(Nodes),
+ activate_traceflags(Nodes),
+
+ {inviso_test_proc,RNode2} ! {apply,code,which,[lists]},
+ timer:sleep(300),
+ {inviso_test_proc,RNode1} ! {apply,code,which,[lists]},
+ timer:sleep(300),
+ {inviso_test_proc,RNode1} ! {apply,code,which,[lists]},
+ timer:sleep(300),
+ inviso_test_proc ! {apply,code,which,[lists]},
+ timer:sleep(300),
+ {inviso_test_proc,RNode2} ! {apply,code,which,[lists]},
+ timer:sleep(300),
+ inviso_test_proc ! {apply,code,which,[lists]},
+
+ deactivate_traceflags(Nodes),
+ deactivate_local_tracing(Nodes),
+ stop_tracing(Nodes),
+ stop(Nodes),
+
+ DestFile=filename:join(PrivDir,"lfm1_out.txt"),
+ ?l {ok,6}=
+ inviso_lfm:merge([{node(),
+ [{trace_log,
+ [filename:join(PrivDir,"lfm1_"++atom_to_list(node()))]}]},
+ {RNode1,
+ [{trace_log,
+ [filename:join(PrivDir,"lfm1_"++atom_to_list(RNode1))]}]},
+ {RNode2,
+ [{trace_log,
+ [filename:join(PrivDir,"lfm1_"++atom_to_list(RNode2))]}]}],
+ DestFile),
+ ?l {ok,FD}=file:open(DestFile,[read]),
+ ?l S1=io:get_line(FD,""),
+ ?l true=lists:prefix(atom_to_list(RNode2),S1),
+ ?l S2=io:get_line(FD,""),
+ ?l true=lists:prefix(atom_to_list(RNode1),S2),
+ ?l S3=io:get_line(FD,""),
+ ?l true=lists:prefix(atom_to_list(RNode1),S3),
+ ?l S4=io:get_line(FD,""),
+ ?l true=lists:prefix(atom_to_list(node()),S4),
+ ?l S5=io:get_line(FD,""),
+ ?l true=lists:prefix(atom_to_list(RNode2),S5),
+ ?l S6=io:get_line(FD,""),
+ ?l true=lists:prefix(atom_to_list(node()),S6),
+ ?l file:close(FD),
+ ok.
+%% -----------------------------------------------------------------------------
+
+%% TEST CASE: Testing to the full extent that pid-mappings work with both
+%% local and global registration. Also checks that pidmappings can be removed
+%% and that consequently the mappings in the resulting merged file stops.
+lfm_trace_ti_dist_2(suite) -> [];
+lfm_trace_ti_dist_2(doc) ->
+ [""];
+lfm_trace_ti_dist_2(Config) when is_list(Config) ->
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ [RNode1,RNode2|_]=RemoteNodes,
+ PrivDir=filename:join(?config(priv_dir,Config),""),
+ TracerDataList=
+ lists:map(fun(N)->{N,[{trace,{file,filename:join(PrivDir,"lfm2_"++atom_to_list(N))}},
+ {ti,{file,filename:join(PrivDir,"lfm2_ti_"++atom_to_list(N))}}]}
+ end,
+ Nodes),
+ start_and_init_tracing2(Nodes,[],TracerDataList,{ok,[{trace_log,ok},{ti_log,ok}]}),
+ activate_local_tracing(Nodes),
+ activate_meta_tracing(Nodes),
+ activate_traceflags(Nodes),
+
+ {inviso_test_proc,RNode2} ! {apply,code,which,[lists]},
+ timer:sleep(300),
+ {inviso_test_proc,RNode1} ! {apply,code,which,[lists]},
+ timer:sleep(300),
+ {inviso_test_proc,RNode1} ! {apply,code,which,[lists]},
+ timer:sleep(300),
+ inviso_test_proc ! {apply,code,which,[lists]},
+ timer:sleep(300),
+
+ P2=spawn(RNode2,?MODULE,test_proc_loop,[]),
+ P1=spawn(RNode1,?MODULE,test_proc_loop,[]),
+ P0=spawn_link(?MODULE,test_proc_loop,[]),
+ ThisNode=node(),
+ ?l {ok,[{ThisNode,{ok,[1]}}]}=inviso:tf([node()],P0,[call,timestamp]),
+ ?l {ok,[{RNode1,{ok,[1]}}]}=inviso:tf([RNode1],P1,[call,timestamp]),
+ ?l {ok,[{RNode2,{ok,[1]}}]}=inviso:tf([RNode2],P2,[call,timestamp]),
+ P2 ! {apply,code,which,[lists]},
+ timer:sleep(300),
+ P1 ! {apply,code,which,[lists]},
+ timer:sleep(300),
+ P0 ! {apply,code,which,[lists]},
+ timer:sleep(300),
+
+ P3=spawn(RNode2,?MODULE,test_proc_loop,[]),
+ ?l yes=global:register_name(inviso_test_proc_globalname,P3),
+ ?l {ok,[{RNode2,{ok,[1]}}]}=inviso:tf([RNode2],P3,[call,timestamp]),
+ timer:sleep(300),
+ P3 ! {apply,code,which,[lists]},
+ timer:sleep(300),
+
+ P4=rpc:call(RNode1,erlang,whereis,[inviso_test_proc]),
+ ?l true=rpc:call(RNode1,erlang,unregister,[inviso_test_proc]),
+ timer:sleep(300),
+ P4 ! {apply,code,which,[lists]},
+ timer:sleep(300),
+
+ ?l true=rpc:call(RNode1,erlang,register,[inviso_test_proc,P4]),
+
+ ?l global:unregister_name(inviso_test_proc_globalname),
+ timer:sleep(300),
+ ?l P3 ! {apply,code,which,[lists]},
+ timer:sleep(300),
+
+ deactivate_traceflags(Nodes),
+ deactivate_local_tracing(Nodes),
+ stop_tracing(Nodes),
+ stop(Nodes),
+
+ DestFile=filename:join(PrivDir,"lfm2_out.txt"),
+ ?l {ok,10}=
+ inviso_lfm:merge([
+ {node(),
+ [{trace_log,
+ [filename:join(PrivDir,"lfm2_"++atom_to_list(node()))]},
+ {ti_log,
+ [filename:join(PrivDir,"lfm2_ti_"++atom_to_list(node()))]}]},
+ {RNode1,
+ [{trace_log,
+ [filename:join(PrivDir,"lfm2_"++atom_to_list(RNode1))]},
+ {ti_log,
+ [filename:join(PrivDir,"lfm2_ti_"++atom_to_list(RNode1))]}]},
+ {RNode2,
+ [{trace_log,
+ [filename:join(PrivDir,"lfm2_"++atom_to_list(RNode2))]},
+ {ti_log,
+ [filename:join(PrivDir,"lfm2_ti_"++atom_to_list(RNode2))]}]}
+ ],
+ DestFile),
+ ?l {ok,FD}=file:open(DestFile,[read]),
+ ?l S1=io:get_line(FD,""),
+io:format("S1 is:~p~n",[S1]),
+ ?l true=lists:prefix(atom_to_list(RNode2)++" [inviso_test_proc",S1),
+ ?l S2=io:get_line(FD,""),
+ ?l true=lists:prefix(atom_to_list(RNode1)++" [inviso_test_proc",S2),
+ ?l S3=io:get_line(FD,""),
+ ?l true=lists:prefix(atom_to_list(RNode1)++" [inviso_test_proc",S3),
+ ?l S4=io:get_line(FD,""),
+ ?l true=lists:prefix(atom_to_list(node())++" [inviso_test_proc",S4),
+ ?l S5=io:get_line(FD,""),
+ ?l true=lists:prefix(atom_to_list(RNode2)++" []",S5),
+ ?l S6=io:get_line(FD,""),
+ ?l true=lists:prefix(atom_to_list(RNode1)++" []",S6),
+ ?l S7=io:get_line(FD,""),
+ ?l true=lists:prefix(atom_to_list(node())++" []",S7),
+ ?l S8=io:get_line(FD,""),
+ ?l true=lists:prefix(atom_to_list(RNode2)++" [{global,inviso_test_proc_globalname}]",S8),
+ ?l S9=io:get_line(FD,""),
+ ?l true=lists:prefix(atom_to_list(RNode1)++" []",S9),
+ ?l S10=io:get_line(FD,""),
+ ?l true=lists:prefix(atom_to_list(RNode2)++" []",S10),
+ ?l file:close(FD),
+ ok.
+%% -----------------------------------------------------------------------------
+
+%% TEST CASE: This tests that the wrapset sorter works.
+handle_logfile_sort_wrapset(suite) -> [];
+handle_logfile_sort_wrapset(doc) ->
+ [""];
+handle_logfile_sort_wrapset(Config) when is_list(Config) ->
+ File0="prefix10.fil",
+ File1="prefix11.fil",
+ File2="prefix12.fil",
+ File3="prefix13.fil",
+ ?l [File0,File1,File2,File3]=
+ inviso_lfm_tpfreader:handle_logfile_sort_wrapset([File2,File1,File0,File3]),
+ File5="prefix15.fil",
+ ?l [File5,File0,File1,File2,File3]=
+ inviso_lfm_tpfreader:handle_logfile_sort_wrapset([File2,File5,File1,File0,File3]),
+ ok.
+%% -----------------------------------------------------------------------------
+
+%% TEST CASE: This case tests that the regexp mechanism in the inviso_rt_lib can
+%% find modules using regexps and that its only_loaded mechanism works.
+%% This test case can not be run when using cover because cover will make the
+%% modules no longer loaded from the path containing "runtime_tools".
+expand_regexp_dist_1(suite) -> [];
+expand_regexp_dist_1(doc) ->
+ [""];
+expand_regexp_dist_1(Config) when is_list(Config) ->
+ case ?t:is_cover() of
+ true ->
+ {skip,"Cover is running"};
+ false ->
+ expand_regexp_dist_1_nocover(Config)
+ end.
+
+expand_regexp_dist_1_nocover(Config) ->
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ [RNode1|_]=RemoteNodes,
+ ?l NodeResults1=inviso_rt_lib:expand_regexp(Nodes,"^inviso_rt.*",[]),
+ ?l L1=length(Nodes),
+ ?l L1=length(NodeResults1),
+ ?l true=lists:all(fun({_,Mods})->
+ ?l 3=length(Mods),
+ ?l true=lists:member(inviso_rt,Mods),
+ ?l true=lists:member(inviso_rt_lib,Mods),
+ ?l true=lists:member(inviso_rt_meta,Mods),
+ true;
+ (_) ->
+ false
+ end,
+ NodeResults1),
+ %% Check the dir-option. In the following inviso_tool_lib shall not be found.
+ ?l NodeResults2=inviso_rt_lib:expand_regexp(Nodes,"runtime_tools","invi.*lib.*",[]),
+?l io:format("NodeResults2:~w~n",[NodeResults2]),
+ ?l L1=length(NodeResults2), % Same number of nodes replying.
+ ?l true=lists:all(fun({_,Mods})->
+ 2=length(Mods),
+ true=lists:member(inviso_as_lib,Mods),
+ true=lists:member(inviso_rt_lib,Mods),
+ true;
+ (_) ->
+ false
+ end,
+ NodeResults2),
+ ?l [{RNode1,[]}]=
+ inviso_rt_lib:expand_regexp([RNode1],"^inviso_testmodule1.*",[only_loaded]),
+ ?l [{RNode1,[inviso_testmodule1_foo]}]=
+ inviso_rt_lib:expand_regexp([RNode1],"^inviso_testmodule1.*",[]),
+ ok.
+%% -----------------------------------------------------------------------------
+
+
+only_loaded_dist_1(suite) -> [];
+only_loaded_dist_1(doc) ->
+ [""];
+only_loaded_dist_1(Config) when is_list(Config) ->
+ RemoteNodes=get_remotenodes_config(Config),
+ Nodes=[node()|RemoteNodes],
+ [RNode1|_]=RemoteNodes,
+ PrivDir=filename:join(?config(priv_dir,Config),""),
+ TracerDataList=
+ lists:map(fun(N)->{N,[{trace,{file,filename:join(PrivDir,"ol_1_"++atom_to_list(N))}}]}
+ end,
+ Nodes),
+ start_and_init_tracing2(Nodes,[],TracerDataList,{ok,[{trace_log,ok}]}),
+ ?l false=rpc:call(RNode1,erlang,module_loaded,[inviso_testmodule1_foo]),
+ ?l {ok,[{RNode1,{ok,[0]}}]}=
+ inviso:tpl([RNode1],inviso_testmodule1_foo,'_','_',[],[only_loaded]),
+ ?l false=rpc:call(RNode1,erlang,module_loaded,[inviso_testmodule1_foo]),
+ ?l {ok,[{RNode1,{ok,[3]}}]}=
+ inviso:tpl([RNode1],inviso_testmodule1_foo,'_','_',[],[]),
+ stop_tracing(Nodes),
+ stop(Nodes),
+ ok.
+
+
+%% ==============================================================================
+%% Common functions setting up inviso.
+%% ==============================================================================
+
+%% Starts controlcomponent and adds runtime components on the nodes specified.
+%% Also initiates tracing on the nodes.
+start_and_init_tracing1(Nodes,Options,TracerData,Reply) when is_list(Nodes) ->
+ ?l {ok,_Pid}=inviso:start(), % Start a control component.
+ ?l {ok,NodeResults1}=inviso:add_nodes(Nodes,a_ref,Options),
+ io:format("~p~n",[NodeResults1]),
+ ?l true=check_noderesults(Nodes,{ok,new},NodeResults1),
+ ?l {ok,NodeResults2}=inviso:get_status(Nodes),
+ ?l true=check_noderesults(Nodes,{ok,{new,running}},NodeResults2),
+ ?l {ok,NodeResults3}=inviso:init_tracing(Nodes,TracerData),
+ ?l true=check_noderesults(Nodes,Reply,NodeResults3),
+ ok.
+start_and_init_tracing2(Nodes,Options,TracerDataList,Reply) ->
+ ?l {ok,_Pid}=inviso:start(), % Start a control component.
+ ?l {ok,NodeResults1}=inviso:add_nodes(Nodes,a_ref,Options),
+ io:format("~p~n",[NodeResults1]),
+ ?l true=check_noderesults(Nodes,{ok,new},NodeResults1),
+ ?l {ok,NodeResults2}=inviso:get_status(Nodes),
+ ?l true=check_noderesults(Nodes,{ok,{new,running}},NodeResults2),
+ ?l {ok,NodeResults4}=inviso:get_tracerdata(Nodes),
+ ?l true=check_noderesults(Nodes,{ok,no_tracerdata},NodeResults4),
+ ?l {ok,NodeResults3}=inviso:init_tracing(TracerDataList),
+ io:format("Tracerdatalist:~p~n",[TracerDataList]),
+ ?l true=check_noderesults(Nodes,Reply,NodeResults3),
+
+ ?l Fun1=fun({N,{ok,TD}}) when is_list(TD)->
+ ?l {value,{trace,Trace}}=lists:keysearch(trace,1,TD),
+ ?l {value,{N,TD2}}=lists:keysearch(N,1,TracerDataList),
+ ?l true=lists:member({trace,Trace},TD2),
+ %% Check that the trace file really exists.
+ ?l case Trace of % Trace={file,FilePortParameters}
+ {file,FileName1} when is_list(FileName1) ->
+ ?l {ok,_}=rpc:call(N,file,read_file_info,[FileName1]);
+ _ -> % This should be extended with more cases.
+ true
+ end,
+ ?l case lists:keysearch(ti,1,TD2) of
+ {value,{_,Ti}} -> % Ok, we have ti too.
+ ?l {value,{_,Ti}}=lists:keysearch(ti,1,TD),
+ ?l FileName2=element(2,Ti),
+ ?l {ok,_}=rpc:call(N,file,read_file_info,[FileName2]),
+ true;
+ false -> % No ti, we are done now.
+ true
+ end;
+ ({N,{ok,{file,FileName}}}) ->
+ ?l {value,{N,{file,FileName}}}=lists:keysearch(N,1,TracerDataList),
+ ?l {ok,_}=rpc:call(N,file,read_file_info,[FileName]),
+ true;
+ ({N,{ok,LogTD}}) -> % The case using a fun.
+ ?l {value,{N,LogTD}}=lists:keysearch(N,1,TracerDataList),
+ true
+ end,
+ ?l {ok,NodeResults5}=inviso:get_tracerdata(Nodes),
+ ?l true=check_noderesults(Nodes,Fun1,NodeResults5),
+ ok.
+%% ------------------------------------------------------------------------------
+
+%% Stops tracing on Nodes.
+stop_tracing(Nodes) when is_list(Nodes) ->
+ ?l {ok,NodeResults1}=inviso:stop_tracing(Nodes),
+ ?l true=check_noderesults(Nodes,{ok,idle},NodeResults1),
+ ?l {ok,NodeResults2}=inviso:get_status(Nodes),
+ ?l true=check_noderesults(Nodes,{ok,{idle,running}},NodeResults2),
+ %% The implementation says that the meta tracer shall be stopped when
+ %% tracing is stopped. Check that.
+ ?l lists:foreach(fun(N)->
+ ok=poll(erlang,whereis,[inviso_rt_meta],undefined,20)
+ end,
+ Nodes).
+%% ------------------------------------------------------------------------------
+
+%% Stops the runtime components on Nodes and stops the control component at this
+%% Erlang node.
+stop(Nodes) when is_list(Nodes) ->
+ ?l true=check_on_nodes(Nodes,erlang,whereis,[inviso_rt],fun(P) when is_pid(P)->true end),
+ ?l {ok,NodeResults}=inviso:stop_nodes(Nodes),
+ ?l true=check_noderesults(Nodes,ok,NodeResults),
+ ?l true=check_on_nodes(Nodes,erlang,whereis,[inviso_rt],fun(undefined)->true end),
+ ?l true=is_pid(whereis(inviso_c)),
+ ?l shutdown=inviso:stop(),
+ ?l ok=poll(erlang,whereis,[inviso_c],undefined,20).
+%% ------------------------------------------------------------------------------
+
+%% Help function activating local tracing.
+activate_local_tracing(Nodes) when is_list(Nodes) ->
+ ?l true=check_on_nodes(Nodes,
+ erlang,
+ trace_info,
+ [{code,which,1},traced],
+ {traced,false}),
+ ?l {ok,NodeResults}=inviso:tpl(Nodes,code,which,1,[]),
+ ?l true=check_noderesults(Nodes,fun({_,{ok,[1]}})->true end,NodeResults),
+ ?l true=check_on_nodes(Nodes,
+ erlang,
+ trace_info,
+ [{code,which,1},traced],
+ {traced,local}).
+%% ------------------------------------------------------------------------------
+
+%% Help function activating global tracing.
+activate_global_tracing(Nodes) when is_list(Nodes) ->
+ ?l true=check_on_nodes(Nodes,
+ erlang,
+ trace_info,
+ [{code,get_path,0},traced],
+ {traced,false}),
+ ?l {ok,NodeResults}=inviso:tp(Nodes,code,get_path,0,[]),
+ ?l true=check_noderesults(Nodes,fun({_,{ok,[1]}})->true end,NodeResults),
+ ?l true=check_on_nodes(Nodes,
+ erlang,
+ trace_info,
+ [{code,get_path,0},traced],
+ {traced,global}).
+%% ------------------------------------------------------------------------------
+
+
+%% Help function activating local tracing and using a regexp to point out modules.
+%% Returns the structure of modules and functions that were activated. Must be used
+%% when deactivating.
+activate_global_tracing_regexp(Nodes) when is_list(Nodes) ->
+ %% First find out which modules will be effected.
+ ?l Mods1=inviso_rt_lib:expand_regexp("application.*",[]),
+ ?l true=(length(Mods1)>1), % Should find more than one module!
+ ?l Funcs1=lists:foldl(fun(M,Acc)->[{M,M:module_info(exports)}|Acc] end,[],Mods1),
+ %% Check that these functions are not traced.
+ io:format("Modules:~w~n",[Mods1]),
+ ?l {ok,NodeResults}=inviso:tp(Nodes,"application.*",'_','_',[],[]),
+ io:format("Here 2~w~n",[NodeResults]),
+ ?l N=lists:foldl(fun({_,L1},A1)->lists:foldl(fun(_,A2)->A2+1 end,A1,L1) end,0,Funcs1),
+ ?l true=check_noderesults(Nodes,fun({_,{ok,L}})-> N==lists:sum(L) end,NodeResults),
+ io:format("Here 3~n",[]),
+ %% Check again!
+ ?l lists:foreach(fun({M,Funcs})->
+ lists:foreach(fun({F,Arity})->
+ true=check_on_nodes(Nodes,
+ erlang,
+ trace_info,
+ [{M,F,Arity},traced],
+ {traced,global})
+ end,
+ Funcs)
+ end,
+ Funcs1),
+ Funcs1.
+%% ------------------------------------------------------------------------------
+
+%% Help function as above but uses the dir feature as well.
+activate_global_tracing_regexp_dir(Nodes) when is_list(Nodes) ->
+ %% First find out which modules will be effected.
+ ?l Mods1=inviso_rt_lib:expand_regexp(".*kernel.*","application.*",[]),
+ ?l true=(length(Mods1)>1), % Should find more than one module!
+ ?l Funcs1=lists:foldl(fun(M,Acc)->[{M,M:module_info(exports)}|Acc] end,[],Mods1),
+ %% Check that these functions are not traced.
+ io:format("Modules:~w~n",[Mods1]),
+ ?l {ok,NodeResults}=inviso:tp(Nodes,{".*kernel.*","application.*"},'_','_',[],[]),
+ io:format("Here 2~w~n",[NodeResults]),
+ ?l N=lists:foldl(fun({_,L1},A1)->lists:foldl(fun(_,A2)->A2+1 end,A1,L1) end,0,Funcs1),
+ ?l true=check_noderesults(Nodes,fun({_,{ok,L}})-> N==lists:sum(L) end,NodeResults),
+ io:format("Here 3~n",[]),
+ %% Check again!
+ ?l lists:foreach(fun({M,Funcs})->
+ lists:foreach(fun({F,Arity})->
+ true=check_on_nodes(Nodes,
+ erlang,
+ trace_info,
+ [{M,F,Arity},traced],
+ {traced,global})
+ end,
+ Funcs)
+ end,
+ Funcs1),
+ Funcs1.
+%% ------------------------------------------------------------------------------
+
+deactivate_local_tracing(Nodes) when is_list(Nodes) ->
+ ?l true=check_on_nodes(Nodes,
+ erlang,
+ trace_info,
+ [{code,which,1},traced],
+ {traced,local}),
+ ?l {ok,NodeResults}=inviso:ctpl(Nodes,code,'_','_'),
+ ?l true=check_noderesults(Nodes,fun({_,{ok,[N]}})when is_integer(N)->true end,NodeResults),
+ ?l true=check_on_nodes(Nodes,
+ erlang,
+ trace_info,
+ [{code,which,1},traced],
+ {traced,false}).
+%% ------------------------------------------------------------------------------
+
+deactivate_global_tracing(Nodes) when is_list(Nodes) ->
+ ?l true=check_on_nodes(Nodes,
+ erlang,
+ trace_info,
+ [{code,get_path,0},traced],
+ {traced,global}),
+ ?l {ok,NodeResults}=inviso:ctp(Nodes,code,'_','_'),
+ ?l true=check_noderesults(Nodes,fun({_,{ok,[N]}})when is_integer(N)->true end,NodeResults),
+ ?l true=check_on_nodes(Nodes,
+ erlang,
+ trace_info,
+ [{code,get_path,0},traced],
+ {traced,false}).
+%% ------------------------------------------------------------------------------
+
+
+%% Function deactivating the functions activated by activate_global_tracing_regexp/1.
+deactivate_global_tracing_regexp(Nodes,Funcs1) ->
+ ?l lists:foreach(fun({M,Funcs})->
+ lists:foreach(fun({F,Arity})->
+ true=check_on_nodes(Nodes,
+ erlang,
+ trace_info,
+ [{M,F,Arity},traced],
+ {traced,global})
+ end,
+ Funcs)
+ end,
+ Funcs1),
+ ?l {ok,NodeResults}=inviso:ctp(Nodes,"application.*",'_','_'),
+ ?l N=lists:foldl(fun({_,L1},A1)->lists:foldl(fun(_,A2)->A2+1 end,A1,L1) end,0,Funcs1),
+ io:format("Noderesult from deactivate;~w~n",[NodeResults]),
+ ?l true=check_noderesults(Nodes,fun({_,{ok,L}})-> N==lists:sum(L) end,NodeResults),
+ ?l lists:foreach(fun({M,Funcs})->
+ lists:foreach(fun({F,Arity})->
+ true=check_on_nodes(Nodes,
+ erlang,
+ trace_info,
+ [{M,F,Arity},traced],
+ {traced,false})
+ end,
+ Funcs)
+ end,
+ Funcs1).
+%% ------------------------------------------------------------------------------
+
+%% Function deactivating the functions activated by activate_global_tracing_regexp_dir/1.
+deactivate_global_tracing_regexp_dir(Nodes,Funcs1) ->
+ ?l lists:foreach(fun({M,Funcs})->
+ lists:foreach(fun({F,Arity})->
+ true=check_on_nodes(Nodes,
+ erlang,
+ trace_info,
+ [{M,F,Arity},traced],
+ {traced,global})
+ end,
+ Funcs)
+ end,
+ Funcs1),
+ ?l {ok,NodeResults}=inviso:ctp(Nodes,{".*kernel.*","application.*"},'_','_'),
+ ?l N=lists:foldl(fun({_,L1},A1)->lists:foldl(fun(_,A2)->A2+1 end,A1,L1) end,0,Funcs1),
+ io:format("Noderesult from deactivate;~w~n",[NodeResults]),
+ ?l true=check_noderesults(Nodes,fun({_,{ok,L}})-> N==lists:sum(L) end,NodeResults),
+ ?l lists:foreach(fun({M,Funcs})->
+ lists:foreach(fun({F,Arity})->
+ true=check_on_nodes(Nodes,
+ erlang,
+ trace_info,
+ [{M,F,Arity},traced],
+ {traced,false})
+ end,
+ Funcs)
+ end,
+ Funcs1).
+%% ------------------------------------------------------------------------------
+
+%% Help function which starts the inviso_test_proc on all nodes and then sets
+%% the call flag on that process.
+activate_traceflags(Nodes) ->
+ ?l lists:foreach(fun(N)->spawn(N,?MODULE,test_proc_init,[]) end,Nodes),
+ ?l lists:foreach(fun(N)->
+ P=rpc:call(N,erlang,whereis,[inviso_test_proc]),
+ {flags,[]}=rpc:call(N,erlang,trace_info,[P,flags])
+ end,
+ Nodes),
+ ?l {ok,NodeResults}=inviso:tf(Nodes,inviso_test_proc,[call,timestamp]),
+ ?l true=check_noderesults(Nodes,{ok,[1]},NodeResults),
+ ?l lists:foreach(fun(N)->
+ P=rpc:call(N,erlang,whereis,[inviso_test_proc]),
+ {flags,Flags}=rpc:call(N,erlang,trace_info,[P,flags]),
+ true=lists:member(call,Flags),
+ true=lists:member(timestamp,Flags)
+ end,
+ Nodes),
+ %% Now try a globally registered process.
+ ?l [ANode|_]=Nodes,
+ ?l GPid=spawn(ANode,?MODULE,global_test_proc_init,[]),
+ ?l ok=poll(global,whereis_name,[global_inviso_test_proc],
+ fun(P) when is_pid(P)->true;(_)->false end,
+ 10),
+ ?l {ok,NodeResults2}=
+ inviso:tf(Nodes,{global,global_inviso_test_proc},[call,timestamp]),
+ ?l true=check_noderesults(Nodes,
+ fun({N,{ok,[1]}}) when N==ANode->true;
+ ({_,{ok,[0]}})->true;
+ (_)->false
+ end,
+ NodeResults2),
+ ?l {flags,Flags2}=rpc:call(ANode,erlang,trace_info,[GPid,flags]),
+ ?l 2=length(Flags2),
+ ?l true=lists:member(call,Flags2),
+ ?l true=lists:member(timestamp,Flags2),
+ true.
+%% ------------------------------------------------------------------------------
+
+deactivate_traceflags(Nodes) ->
+ ?l lists:foreach(fun(N)->
+ P=rpc:call(N,erlang,whereis,[inviso_test_proc]),
+ {flags,Flags}=rpc:call(N,erlang,trace_info,[P,flags]),
+ true=lists:member(call,Flags),
+ true=lists:member(timestamp,Flags)
+ end,
+ Nodes),
+ ?l {ok,NodeResults}=inviso:ctf(Nodes,inviso_test_proc,[call,timestamp]),
+ ?l true=check_noderesults(Nodes,{ok,[1]},NodeResults),
+ ?l lists:foreach(fun(N)->
+ P=rpc:call(N,erlang,whereis,[inviso_test_proc]),
+ {flags,[]}=rpc:call(N,erlang,trace_info,[P,flags])
+ end,
+ Nodes),
+ ?l GPid=global:whereis_name(global_inviso_test_proc),
+ ?l ANode=node(GPid),
+ ?l {flags,Flags2}=rpc:call(ANode,erlang,trace_info,[GPid,flags]),
+ ?l 2=length(Flags2),
+ ?l {ok,NodeResults2}=inviso:ctf(Nodes,{global,global_inviso_test_proc},[call,timestamp]),
+ ?l true=check_noderesults(Nodes,
+ fun({N,{ok,[1]}}) when N==ANode->true;
+ ({_,{ok,[0]}})->true;
+ (_)->false
+ end,
+ NodeResults2).
+%% ------------------------------------------------------------------------------
+
+
+activate_meta_tracing(Nodes) ->
+ ?l {ok,NodeResults1}=inviso:tpm_localnames(),
+ ?l true=check_noderesults(Nodes,{{ok,1},{ok,1}},NodeResults1),
+ ?l lists:foreach(fun(N)->P=rpc:call(N,erlang,whereis,[inviso_rt_meta]),
+ {meta,P}=rpc:call(N,erlang,trace_info,[{erlang,register,2},meta])
+ end,
+ Nodes),
+ ?l lists:foreach(fun(N)->P=rpc:call(N,erlang,whereis,[inviso_rt_meta]),
+ {meta,P}=rpc:call(N,erlang,trace_info,[{erlang,unregister,1},meta])
+ end,
+ Nodes),
+ ?l {ok,NodeResults2}=inviso:tpm_globalnames(),
+ ?l true=check_noderesults(Nodes,{{ok,1},{ok,1}},NodeResults2),
+ ?l lists:foreach(fun(N)->P=rpc:call(N,erlang,whereis,[inviso_rt_meta]),
+ {meta,P}=rpc:call(N,
+ erlang,
+ trace_info,
+ [{global,handle_call,3},meta])
+ end,
+ Nodes),
+ ?l lists:foreach(fun(N)->P=rpc:call(N,erlang,whereis,[inviso_rt_meta]),
+ {meta,P}=rpc:call(N,
+ erlang,
+ trace_info,
+ [{global,delete_global_name,2},meta])
+ end,
+ Nodes),
+
+ ?l lists:foreach(fun(N)->true=rpc:call(N,
+ ets,
+ insert,
+ [inviso_sideeffect_tab,{tpm_init_func1,0}]),
+ true=rpc:call(N,
+ ets,
+ insert,
+ [inviso_sideeffect_tab,{tpm_call_func1,0}]),
+ true=rpc:call(N,
+ ets,
+ insert,
+ [inviso_sideeffect_tab,{tpm_return_func1,0}])
+ end,
+ Nodes),
+ ?l {ok,NodeResults3}=
+ inviso:init_tpm(lists,
+ module_info,
+ 0,
+ {?MODULE,tpm_init_func1},
+ {?MODULE,tpm_call_func1},
+ {?MODULE,tpm_return_func1},
+ {?MODULE,tpm_remove_func1}),
+ ?l true=check_noderesults(Nodes,ok,NodeResults3),
+ ?l [{_,1}]=ets:lookup(inviso_sideeffect_tab,tpm_init_func1),
+ ?l {ok,NodeResults3a}=
+ inviso:init_tpm(lists,
+ module_info,
+ 0,
+ {?MODULE,tpm_init_func1},
+ {?MODULE,tpm_call_func1},
+ {?MODULE,tpm_return_func1},
+ {?MODULE,tpm_remove_func1}),
+ ?l true=check_noderesults(Nodes,{error,already_initiated},NodeResults3a),
+% %% Try more forbidden things. Wildcards not allowed in meta tracing!
+% ?l {ok,NodeResults3b}=inviso:tpm(Nodes,lists,'_',0,[{'_',[],[{return_trace}]}]),
+% io:format("The noderesults3b is:~w~n",[NodeResults3b]),
+% ?l true=check_noderesults(Nodes,{error,bad_mfa},NodeResults3b),
+ ?l {ok,NodeResults3c}=inviso:tpm(Nodes,lists,module_info,0,[{'_',[],[{return_trace}]}]),
+ ?l true=check_noderesults(Nodes,{ok,1},NodeResults3c),
+ ?l lists:foreach(fun(N)->P=rpc:call(N,erlang,whereis,[inviso_rt_meta]),
+ {meta,P}=rpc:call(N,erlang,trace_info,[{lists,module_info,0},meta])
+ end,
+ Nodes),
+ ?l lists:foreach(fun(N)->rpc:call(N,lists,module_info,[]) end,Nodes),
+ ?l ok=poll(ets,lookup,[inviso_sideeffect_tab,tpm_call_func1],[{tpm_call_func1,1}],20),
+ ?l ok=poll(ets,lookup,[inviso_sideeffect_tab,tpm_return_func1],[{tpm_return_func1,1}],20),
+ ?l lists:foreach(fun(N)->rpc:call(N,lists,module_info,[]) end,Nodes),
+ ?l ok=poll(ets,lookup,[inviso_sideeffect_tab,tpm_call_func1],[{tpm_call_func1,2}],20),
+ ?l ok=poll(ets,lookup,[inviso_sideeffect_tab,tpm_return_func1],[{tpm_return_func1,2}],20),
+
+ ?l {ok,NodeResults4}=
+ inviso:init_tpm(math,
+ module_info,
+ 1,
+ {?MODULE,tpm_init_func2}, % Does not exist on purpose.
+ {?MODULE,tpm_call_func2}, % Does not exist on purpose.
+ {?MODULE,tpm_return_func2}, % Does not exist on purpose.
+ {?MODULE,tpm_remove_func2}), % Does not exist on purpose.
+ ?l true=check_noderesults(Nodes,ok,NodeResults4),
+ ?l {ok,NodeResults5}=
+ inviso:tpm_ms(math,module_info,1,ms1,[{'_',[],[{return_trace}]}]),
+ ?l true=check_noderesults(Nodes,{ok,1},NodeResults5),
+ ?l lists:foreach(fun(N)->{meta_match_spec,[{'_',[],[{return_trace}]}]}=
+ rpc:call(N,erlang,trace_info,[{math,module_info,1},
+ meta_match_spec])
+ end,
+ Nodes),
+
+ ?l {ok,NodeResults6}=inviso:tpm_ms(math,module_info,1,ms2,[{[exports],[],[]}]),
+ ?l true=check_noderesults(Nodes,{ok,1},NodeResults6),
+ ?l lists:foreach(fun(N)->{meta_match_spec,[{[exports],[],[]},{'_',[],[{return_trace}]}]}=
+ rpc:call(N,erlang,trace_info,[{math,module_info,1},
+ meta_match_spec])
+ end,
+ Nodes),
+ ?l {ok,NodeResults7}=inviso:tpm_ms(math,module_info,1,ms3,[{[attributes],[],[]}]),
+ ?l true=check_noderesults(Nodes,{ok,1},NodeResults7),
+ ?l lists:foreach(fun(N)->{meta_match_spec,[{[attributes],[],[]},
+ {[exports],[],[]},
+ {'_',[],[{return_trace}]}]}=
+ rpc:call(N,erlang,trace_info,[{math,module_info,1},
+ meta_match_spec])
+ end,
+ Nodes),
+ ?l {ok,NodeResults8}=inviso:ctpm_ms(math,module_info,1,ms2),
+ ?l true=check_noderesults(Nodes,ok,NodeResults8),
+ ?l lists:foreach(fun(N)->{meta_match_spec,[{[attributes],[],[]},
+ {'_',[],[{return_trace}]}]}=
+ rpc:call(N,erlang,trace_info,[{math,module_info,1},
+ meta_match_spec])
+ end,
+ Nodes),
+ ?l io:format("whereis:~w~n",[lists:map(fun(N)->rpc:call(N,erlang,whereis,[inviso_rt_meta]) end,Nodes)]),
+ ?l {ok,NodeResults8}=inviso:ctpm_ms(math,module_info,1,ms3),
+ ?l io:format("whereis:~w~n",[lists:map(fun(N)->rpc:call(N,erlang,whereis,[inviso_rt_meta]) end,Nodes)]),
+ ?l {ok,NodeResults8}=inviso:ctpm_ms(math,module_info,1,ms1),
+ ?l lists:foreach(fun(N)->{meta_match_spec,false}=
+ rpc:call(N,erlang,trace_info,[{math,module_info,1},
+ meta_match_spec])
+ end,
+ Nodes),
+
+ %% Now try to do this with exception tracing instead.
+ %% Reset the side effect tables.
+ ?l lists:foreach(fun(N)->true=rpc:call(N,
+ ets,
+ insert,
+ [inviso_sideeffect_tab,{tpm_init_func1,0}]),
+ true=rpc:call(N,
+ ets,
+ insert,
+ [inviso_sideeffect_tab,{tpm_call_func1,0}]),
+ true=rpc:call(N,
+ ets,
+ insert,
+ [inviso_sideeffect_tab,{tpm_return_func1,0}])
+ end,
+ Nodes),
+ ?l {ok,NodeResults9}=
+ inviso:init_tpm(?MODULE,
+ failing_function,
+ 1,
+ {?MODULE,tpm_init_func1},
+ {?MODULE,tpm_call_func1},
+ {?MODULE,tpm_return_func1},
+ {?MODULE,tpm_remove_func1}),
+ ?l true=check_noderesults(Nodes,ok,NodeResults9),
+ ?l [{_,1}]=ets:lookup(inviso_sideeffect_tab,tpm_init_func1),
+ ?l {ok,NodeResults10}=inviso:tpm(Nodes,?MODULE,failing_function,1,[{'_',[],[{exception_trace}]}]),
+ ?l true=check_noderesults(Nodes,{ok,1},NodeResults10),
+ ?l lists:foreach(fun(N)->P=rpc:call(N,erlang,whereis,[inviso_rt_meta]),
+ {meta,P}=rpc:call(N,erlang,trace_info,[{?MODULE,failing_function,1},meta])
+ end,
+ Nodes),
+ ?l lists:foreach(fun(N)->rpc:call(N,?MODULE,failing_function,[nofailure]) end,Nodes),
+ ?l ok=poll(ets,lookup,[inviso_sideeffect_tab,tpm_call_func1],[{tpm_call_func1,1}],20),
+ ?l ok=poll(ets,lookup,[inviso_sideeffect_tab,tpm_return_func1],[{tpm_return_func1,1}],20),
+ ?l lists:foreach(fun(N)->rpc:call(N,?MODULE,failing_function,[failure]) end,Nodes),
+ ?l ok=poll(ets,lookup,[inviso_sideeffect_tab,tpm_call_func1],[{tpm_call_func1,2}],20),
+ ?l ok=poll(ets,lookup,[inviso_sideeffect_tab,tpm_return_func1],[{tpm_return_func1,3}],20),
+
+ ok.
+%% ------------------------------------------------------------------------------
+
+%% This function is for testing that appending the tracer to a trace action term
+%% works.
+activate_deactivate_meta_tracing_tracer(Nodes) ->
+ ?l {ok,NodeResults}=
+ inviso:tpm_tracer(Nodes,lists,module_info,0,[{'_',[],[{trace,[all],[call]}]}],void),
+ ?l true=check_noderesults(Nodes,{ok,1},NodeResults),
+ ?l lists:foreach(fun(N)->P=rpc:call(N,erlang,whereis,[inviso_rt_meta]),
+ {meta,P}=rpc:call(N,erlang,trace_info,[{lists,module_info,0},meta]),
+ {meta_match_spec,[{'_',[],[{trace,[all],Enable}]}]}=
+ rpc:call(N,erlang,trace_info,[{lists,module_info,0},
+ meta_match_spec]),
+ true=list_search(Enable,fun({{tracer,P}}) when is_port(P)->true;
+ (_) -> false
+ end)
+ end,
+ Nodes),
+ ?l {ok,NodeResults2}=
+ inviso:ctpm(Nodes,lists,module_info,0),
+ ?l true=check_noderesults(Nodes,ok,NodeResults2),
+ ?l lists:foreach(fun(N)->{meta,false}=
+ rpc:call(N,erlang,trace_info,[{lists,module_info,0},meta])
+ end,
+ Nodes),
+ ok.
+%% ------------------------------------------------------------------------------
+
+deactivate_meta_tracing(Nodes) ->
+ ?l lists:foreach(fun(N)->{meta,P}=
+ rpc:call(N,erlang,trace_info,[{erlang,register,2},meta]),
+ true=is_pid(P)
+ end,
+ Nodes),
+ ?l lists:foreach(fun(N)->{meta,P}=
+ rpc:call(N,erlang,trace_info,[{erlang,unregister,1},meta]),
+ true=is_pid(P)
+ end,
+ Nodes),
+ ?l {ok,NodeResults1}=inviso:ctpm_localnames(),
+ ?l lists:foreach(fun(N)->{meta,false}=
+ rpc:call(N,erlang,trace_info,[{erlang,register,2},meta]) end,
+ Nodes),
+ ?l lists:foreach(fun(N)->{meta,false}=
+ rpc:call(N,erlang,trace_info,[{erlang,unregister,1},meta])
+ end,
+ Nodes),
+ ?l true=check_noderesults(Nodes,{ok,ok},NodeResults1),
+
+ ?l lists:foreach(fun(N)->P=rpc:call(N,erlang,whereis,[inviso_rt_meta]),
+ {meta,P}=rpc:call(N,
+ erlang,
+ trace_info,
+ [{global,handle_call,3},meta])
+ end,
+ Nodes),
+ ?l lists:foreach(fun(N)->P=rpc:call(N,erlang,whereis,[inviso_rt_meta]),
+ {meta,P}=rpc:call(N,
+ erlang,
+ trace_info,
+ [{global,delete_global_name,2},meta])
+ end,
+ Nodes),
+ ?l {ok,NodeResults1b}=inviso:ctpm_globalnames(),
+ ?l true=check_noderesults(Nodes,{ok,ok},NodeResults1b),
+ ?l lists:foreach(fun(N)->
+ {meta,false}=rpc:call(N,
+ erlang,
+ trace_info,
+ [{global,handle_call,3},meta])
+ end,
+ Nodes),
+ ?l lists:foreach(fun(N)->
+ {meta,false}=rpc:call(N,
+ erlang,
+ trace_info,
+ [{global,delete_global_name,2},meta])
+ end,
+ Nodes),
+
+ ?l lists:foreach(fun(N)->{meta,P}=
+ rpc:call(N,erlang,trace_info,[{lists,module_info,0},meta]),
+ true=is_pid(P)
+ end,
+ Nodes),
+ ?l {ok,NodeResults2}=inviso:ctpm(lists,module_info,0),
+ ?l true=check_noderesults(Nodes,ok,NodeResults2),
+ ?l lists:foreach(fun(N)->{meta,false}=
+ rpc:call(N,erlang,trace_info,[{lists,module_info,0},meta]) end,
+ Nodes),
+ ?l [{_,0}]=ets:lookup(inviso_sideeffect_tab,tpm_init_func1),
+ ?l {ok,NodeResults3}=inviso:ctpm(math,module_info,1),
+ ?l true=check_noderesults(Nodes,ok,NodeResults3),
+ ok.
+%% ------------------------------------------------------------------------------
+
+%% Functions acting as callbacks for testing the meta tracing mechanisms.
+tpm_init_func1(_M,_F,_Arity,PublLD) ->
+ ets:update_counter(inviso_sideeffect_tab,tpm_init_func1,1),
+ {ok,PublLD,void}.
+tpm_call_func1(_Pid,{call,_Args,_TS},PublLD) ->
+ ets:update_counter(inviso_sideeffect_tab,tpm_call_func1,1),
+ {ok,PublLD,void}.
+tpm_return_func1(_Pid,{return_from,_ReturnVal,_TS},PublLD) ->
+ ets:update_counter(inviso_sideeffect_tab,tpm_return_func1,1),
+ {ok,PublLD,void};
+tpm_return_func1(_Pid,{exception_from,_ReturnVal,_TS},PublLD) ->
+ ets:update_counter(inviso_sideeffect_tab,tpm_return_func1,1),
+ ets:update_counter(inviso_sideeffect_tab,tpm_return_func1,1),
+ {ok,PublLD,void}.
+tpm_remove_func1(_M,_F,_Arity,PublLD) ->
+ ets:update_counter(inviso_sideeffect_tab,tpm_init_func1,-1),
+ {ok,PublLD}.
+%% ------------------------------------------------------------------------------
+
+
+%% Help function which traces on a function and makes function calls until there
+%% are two files in the wrap-set.
+fill_and_reach_two_wrapfiles(PrivDir,RegExp,Nodes) ->
+ ?l lists:foreach(fun(N)->spawn(N,?MODULE,test_proc_init,[]) end,Nodes),
+ ?l {ok,NodeResults1}=inviso:tpl(Nodes,?MODULE,test_function,0,[]),
+ ?l true=check_noderesults(Nodes,{ok,[1]},NodeResults1),
+ ?l {ok,NodeResults2}=inviso:tf(Nodes,inviso_test_proc,[call]),
+ ?l true=check_noderesults(Nodes,{ok,[1]},NodeResults2),
+ fill_and_reach_two_wrapfiles_2(PrivDir,RegExp,Nodes),
+ ?l {ok,NodeResults3}=inviso:ctf(Nodes,inviso_test_proc,[call]),
+ ?l true=check_noderesults(Nodes,{ok,[1]},NodeResults3),
+ ?l {ok,NodeResults4}=inviso:ctpl(Nodes,?MODULE,test_function,0),
+ ?l true=check_noderesults(Nodes,{ok,[1]},NodeResults4),
+ ok.
+
+fill_and_reach_two_wrapfiles_2(PrivDir,RegExp,[Node|Rest]) ->
+ ?l ok=rpc:call(Node,?MODULE,fill_and_reach_two_wrapfiles_3,[PrivDir,RegExp]),
+ fill_and_reach_two_wrapfiles_2(PrivDir,RegExp,Rest);
+fill_and_reach_two_wrapfiles_2(_,_,[]) ->
+ ok.
+
+fill_and_reach_two_wrapfiles_3(Dir,RegExp) ->
+ ok=send_to_test_proc({apply,?MODULE,test_function,[]},
+ fun reach_two_wraps_stopfun/1,
+ {Dir,RegExp++atom_to_list(node())},
+ 100).
+
+%% Help function intended to be used as fun in a send_to_test_proc/4 call.
+%% The function lists the content of Dir and looks for occurancies of String.
+%% If two files containing the string String are found, 'done' is returned.
+%% Otherwise 'continue'.
+reach_two_wraps_stopfun({Dir,RegExp}) ->
+ case file:list_dir(Dir) of
+ {ok,FileNames} ->
+ case how_many_files_regexp(FileNames,RegExp,0) of
+ {ok,2} ->
+ done;
+ _ ->
+ continue
+ end;
+ {error,_Reason} ->
+ error
+ end.
+%% ------------------------------------------------------------------------------
+
+%% ------------------------------------------------------------------------------
+%% Help function for the overload tests. These functions are used as callbacks.
+%% ------------------------------------------------------------------------------
+
+overload1(_) ->
+ ets:update_counter(inviso_sideeffect_tab,ovl1,1),
+ ok.
+%% This function is used when timeout occurs inside the runtime component.
+%% That is it is time to check for overload.
+overload2({timeout,overload2i_data}) ->
+ ets:update_counter(inviso_sideeffect_tab,ovl2,1),
+ ok.
+overload2i() ->
+ ets:insert(inviso_sideeffect_tab,{ovl2,0}),
+ {ok,overload2i_data}.
+overload2r(overload2i_data) ->
+ ets:delete(inviso_sideeffect_tab,ovl2).
+
+%% This function is used when timeout occurs inside the runtime component.
+%% That is it is time to check for overload.
+overload3({timeout,overload3i_data}) ->
+ ets:update_counter(inviso_sideeffect_tab,ovl3,1),
+ ok;
+overload3(_) -> % Must handle garbage too.
+ ignore.
+overload3i() ->
+ ets:insert(inviso_sideeffect_tab,{ovl3,0}),
+ {ok,overload3i_data}.
+overload3r(overload3i_data) ->
+ ets:insert(inviso_sideeffect_tab,{ovl3r,done}),
+ ets:delete(inviso_sideeffect_tab,ovl3).
+
+overload4(_) ->
+ case ets:lookup(inviso_sideeffect_tab,ovl4_suspend) of
+ [] -> % We are supposed to be running.
+ ets:update_counter(inviso_sideeffect_tab,ovl4,1),
+ ok;
+ [_] ->
+ {suspend,test}
+ end.
+
+%% This function is used when overload check is done by icomming message.
+overload5({msg,{test_of_loadcheck,overload5i_data}}) ->
+ ets:update_counter(inviso_sideeffect_tab,ovl5,1),
+ ok;
+overload5(_) ->
+ ignore.
+overload5i() ->
+ ets:insert(inviso_sideeffect_tab,{ovl5,0}),
+ {ok,overload5i_data}.
+overload5r(overload5i_data) ->
+ ets:delete(inviso_sideeffect_tab,ovl5),
+ ets:insert(inviso_sideeffect_tab,{ovl5r,done});
+overload5r(X) ->
+ erlang:display({'***',overload5r,X}).
+
+overload6(_) ->
+ ets:update_counter(inviso_sideeffect_tab,ovl6,1),
+ ok.
+%% ------------------------------------------------------------------------------
+
+%% ------------------------------------------------------------------------------
+%% Help function for the subscription tests. These function implements a collector
+%% process which will subscribe to inviso_events from the control component.
+%% ------------------------------------------------------------------------------
+
+%% Function which can be used to check if an inviso_event has arrived. The function
+%% takes a fun which tests the messages.
+check_msg_collector([],_,_) ->
+ true;
+check_msg_collector(_,_,0) ->
+ false;
+check_msg_collector(Nodes,Fun,T) ->
+ Ref=make_ref(),
+ inviso_collector_proc ! {fetch_message,self(),Ref,Fun},
+ receive
+ {inviso,Ref,{true,Node}} ->
+ check_msg_collector(lists:delete(Node,Nodes),Fun,T-1);
+ {inviso,Ref,false} ->
+ timer:sleep(100),
+ check_msg_collector(Nodes,Fun,T-1)
+ end.
+
+%% Spawn on this function to get a subscriber.
+inviso_msg_collector() ->
+ register(inviso_collector_proc,self()),
+ inviso_msg_collector_loop([]).
+
+inviso_msg_collector_loop(Msgs) ->
+ receive
+ {fetch_message,From,Ref,Fun} ->
+ {NewMsgs,Reply}=inviso_msg_collector_selector(Msgs,Fun,[]),
+ From ! {inviso,Ref,Reply},
+ inviso_msg_collector_loop(NewMsgs);
+ Msg ->
+ inviso_msg_collector_loop([Msg|Msgs])
+ end.
+
+inviso_msg_collector_selector([M|Rest],Fun,Accum) ->
+ case Fun(M) of
+ {true,X} ->
+ {Rest++Accum,{true,X}};
+ _ ->
+ inviso_msg_collector_selector(Rest,Fun,[M|Accum])
+ end;
+inviso_msg_collector_selector([],_,Accum) ->
+ {Accum,false}.
+%% ------------------------------------------------------------------------------
+
+
+%% ==============================================================================
+%% Help functions
+%% ==============================================================================
+
+list_search([E|Rest],Fun) ->
+ case Fun(E) of
+ true ->
+ true;
+ false ->
+ list_search(Rest,Fun)
+ end;
+list_search([],_Fun) ->
+ false.
+%% ------------------------------------------------------------------------------
+
+%% Help function checking that there is a Result for each node in Nodes.
+%% Returns 'true' if successful.
+check_noderesults(Nodes,Fun,[{Node,Result}|Rest]) when is_function(Fun) ->
+ case Fun({Node,Result}) of
+ true ->
+ case lists:member(Node,Nodes) of
+ true ->
+ check_noderesults(lists:delete(Node,Nodes),Fun,Rest);
+ false -> % Not good.
+ unknown_node_in_returnvalue
+ end;
+ _ ->
+ illegal_result
+ end;
+check_noderesults(Nodes,Result,[{Node,Result}|Rest]) ->
+ case lists:member(Node,Nodes) of
+ true ->
+ check_noderesults(lists:delete(Node,Nodes),Result,Rest);
+ false -> % Not good.
+ unknown_node_in_returnvalue
+ end;
+check_noderesults([],_,[]) ->
+ true;
+check_noderesults(X,Y,Z) ->
+ io:format("Bad arguments to check noderesults:~w~n~w~n~w~n",[X,Y,Z]),
+ false.
+%% ------------------------------------------------------------------------------
+
+%% Help function doing rpc on all nodes in Nodes calling M:F. Returns 'true' if
+%% successful.
+check_on_nodes([Node|Rest],M,F,Args,Result) when Node==node() ->
+ if
+ is_function(Result) ->
+ ?l true=Result(apply(M,F,Args));
+ true ->
+ ?l Result=apply(M,F,Args)
+ end,
+ check_on_nodes(Rest,M,F,Args,Result);
+check_on_nodes([Node|Rest],M,F,Args,Result) ->
+ if
+ is_function(Result) ->
+ ?l true=Result(rpc:call(Node,M,F,Args));
+ true ->
+ ?l Result=rpc:call(Node,M,F,Args)
+ end,
+ check_on_nodes(Rest,M,F,Args,Result);
+check_on_nodes([],_,_,_,_) ->
+ true.
+%% ------------------------------------------------------------------------------
+
+%% Help function which given a list of files searches through it and returns
+%% how many satisfies the RegExp.
+%% Returns {ok,N}.
+how_many_files_regexp([],_,N) ->
+ {ok,N};
+how_many_files_regexp([FName|Rest],RegExp,N) ->
+ case regexp:first_match(FName,RegExp) of
+ {match,1,_} ->
+ how_many_files_regexp(Rest,RegExp,N+1);
+ nomatch ->
+ how_many_files_regexp(Rest,RegExp,N);
+ {error,Reason} ->
+ test_server:fail(Reason)
+ end.
+%% ------------------------------------------------------------------------------
+
+%% Help function killing a bunch of registered processes.
+process_killer([RegName|Rest]) ->
+ case whereis(RegName) of
+ undefined ->
+ case global:whereis_name(RegName) of
+ undefined ->
+ process_killer(Rest);
+ P when is_pid(P) ->
+ if
+ node()==node(P) ->
+ exit(P,kill);
+ true ->
+ true
+ end,
+ process_killer(Rest)
+ end;
+ P when is_pid(P) ->
+ exit(P,kill),
+ process_killer(Rest)
+ end;
+process_killer([]) ->
+ true.
+%% ------------------------------------------------------------------------------
+
+%% Help function which waits for a function call to become Result. This is useful
+%% if what we are waiting for can happend independantly of indications we have
+%% access to.
+poll(_,_,_,_,0) ->
+ error;
+poll(M,F,Args,Result,Times) ->
+ try apply(M,F,Args) of
+ What when is_function(Result) ->
+ case Result(What) of
+ true ->
+ ok;
+ _ ->
+ timer:sleep(100),
+ poll(M,F,Args,Result,Times-1)
+ end;
+ Result ->
+ ok;
+ _ ->
+ timer:sleep(100),
+ poll(M,F,Args,Result,Times-1)
+ catch
+ error:Reason ->
+ io:format("Apply in suite-function poll/5 failed, ~w~n",[Reason]),
+ timer:sleep(100),
+ poll(M,F,Args,Result,Times-1)
+ end.
+%% ------------------------------------------------------------------------------
+
+insert_remotenode_config(Name,Node,Config) ->
+ [{remotenode,{Name,Node}}|Config].
+%% ------------------------------------------------------------------------------
+
+insert_timetraphandle_config(Handle,Config) ->
+ [{timetraphandle,Handle}|Config].
+%% ------------------------------------------------------------------------------
+
+get_remotenode_config(Name, [{remotenode, {Name, Node}}| _Cs]) ->
+ Node;
+get_remotenode_config(Name, [_ | Cs]) ->
+ get_remotenode_config(Name, Cs);
+get_remotenode_config(Name, []) ->
+ exit({no_remotenode, Name}).
+
+%% ------------------------------------------------------------------------------
+
+get_timetraphandle_config(Config) ->
+ {value,{_,Handle}}=lists:keysearch(timetraphandle,1,Config),
+ Handle.
+%% ------------------------------------------------------------------------------
+
+get_remotenodes_config([{remotenode,{_Name,Node}}|Config]) ->
+ [Node|get_remotenodes_config(Config)];
+get_remotenodes_config([_|Config]) ->
+ get_remotenodes_config(Config);
+get_remotenodes_config([]) ->
+ [].
+%% ------------------------------------------------------------------------------
+
+remove_remotenode_config(Name, [{remotenode, {Name, _}} | Cs]) ->
+ Cs;
+remove_remotenode_config(Name, [C | Cs]) ->
+ [C | remove_remotenode_config(Name, Cs)];
+remove_remotenode_config(_Name, []) ->
+ [].
+
+%% ------------------------------------------------------------------------------
+
+remove_timetraphandle_config(Config) ->
+ lists:keydelete(timetraphandle,1,Config).
+%% ------------------------------------------------------------------------------
+
+%% This function can be meta traced in order to check that exception_trace works.
+%% Must be exported.
+failing_function(nofailure) ->
+ true;
+failing_function(failure) ->
+ exit(failure).
+%% ------------------------------------------------------------------------------
+
+%% ==============================================================================
+%% Code for a test process which can be started.
+%% ==============================================================================
+
+test_proc_init() ->
+ register(inviso_test_proc,self()),
+ test_proc_loop().
+
+test_proc_loop() ->
+ receive
+ {apply,M,F,Args} ->
+ apply(M,F,Args),
+ test_proc_loop();
+ X ->
+ io:format("Got ~w~n",[X]),
+ test_proc_loop()
+ end.
+
+global_test_proc_init() ->
+ global:register_name(global_inviso_test_proc,self()),
+ test_proc_loop().
+%% ------------------------------------------------------------------------------
+
+send_to_test_proc(_,_,_,0) ->
+ error;
+send_to_test_proc(Msg,Fun,FunArg,N) ->
+ inviso_test_proc ! Msg,
+ case Fun(FunArg) of
+ done ->
+ ok;
+ error ->
+ test_server:fail(send_to_test_proc);
+ _ ->
+ send_to_test_proc(Msg,Fun,FunArg,N-1)
+ end.
+%% ------------------------------------------------------------------------------
+
+
+%% This function is here to be traced on by the inviso_test_proc. Must be exported.
+test_function() ->
+ 1+1.
+%% ------------------------------------------------------------------------------
+
+
+%% ==============================================================================
+%% Code for a test side effect table process.
+%% ==============================================================================
+
+%% The side effect logger is a process owning a public ETS table. The idea is that
+%% various callback functions can write in the table when called. In that way
+%% correct calling of the call-backs can be verified.
+start_side_effect_logger(Node) ->
+ ?l true=is_pid(spawn(Node,?MODULE,side_effect_logger_proc,[])),
+ ?l ok=poll(rpc,call,[Node,ets,lookup,[inviso_sideeffect_tab,foo]],[],20).
+
+%% This one must be exported.
+side_effect_logger_proc() ->
+ register(inviso_tab_proc,self()), % So we can kill it later.
+ ets:new(inviso_sideeffect_tab,[public,named_table]),
+ side_effect_logger_proc_2().
+
+side_effect_logger_proc_2() ->
+ receive
+ _X -> % This process is not expecting anything!
+ side_effect_logger_proc_2()
+ end.
+%% ------------------------------------------------------------------------------
diff --git a/lib/runtime_tools/test/inviso_testmodule1_foo.erl b/lib/runtime_tools/test/inviso_testmodule1_foo.erl
new file mode 100644
index 0000000000..a7a22cad39
--- /dev/null
+++ b/lib/runtime_tools/test/inviso_testmodule1_foo.erl
@@ -0,0 +1,9 @@
+-module(inviso_testmodule1_foo).
+
+-compile(export_all).
+
+%% The purpose of this module is simply to have a module that is
+%% guaranteed not loaded.
+
+foo() ->
+ true.
diff --git a/lib/runtime_tools/test/runtime_tools.cover b/lib/runtime_tools/test/runtime_tools.cover
new file mode 100644
index 0000000000..2d62ebe6ac
--- /dev/null
+++ b/lib/runtime_tools/test/runtime_tools.cover
@@ -0,0 +1 @@
+{exclude,[observer_backend]}.
diff --git a/lib/runtime_tools/test/runtime_tools.spec b/lib/runtime_tools/test/runtime_tools.spec
new file mode 100644
index 0000000000..a60a533ce2
--- /dev/null
+++ b/lib/runtime_tools/test/runtime_tools.spec
@@ -0,0 +1 @@
+{topcase, {dir, "../runtime_tools_test"}}.
diff --git a/lib/runtime_tools/test/runtime_tools_SUITE.erl b/lib/runtime_tools/test/runtime_tools_SUITE.erl
new file mode 100644
index 0000000000..84e255e126
--- /dev/null
+++ b/lib/runtime_tools/test/runtime_tools_SUITE.erl
@@ -0,0 +1,50 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(runtime_tools_SUITE).
+-include("test_server.hrl").
+
+%% Test server specific exports
+-export([all/1]).
+-export([init_per_testcase/2, end_per_testcase/2]).
+
+%% Test cases
+-export([app_file/1]).
+
+%% Default timetrap timeout (set in init_per_testcase)
+-define(default_timeout, ?t:minutes(1)).
+
+init_per_testcase(_Case, Config) ->
+ Dog = test_server:timetrap(?default_timeout),
+ [{watchdog, Dog} | Config].
+
+end_per_testcase(_Case, Config) ->
+ Dog = ?config(watchdog, Config),
+ ?t:timetrap_cancel(Dog),
+ ok.
+
+all(suite) ->
+ [app_file].
+
+app_file(suite) ->
+ [];
+app_file(doc) ->
+ ["Testing .app file"];
+app_file(Config) when is_list(Config) ->
+ ?line ok = ?t:app_test(runtime_tools),
+ ok.