aboutsummaryrefslogtreecommitdiffstats
path: root/lib/common_test
diff options
context:
space:
mode:
Diffstat (limited to 'lib/common_test')
-rw-r--r--lib/common_test/src/ct_hooks.erl7
-rw-r--r--lib/common_test/src/ct_logs.erl82
-rw-r--r--lib/common_test/src/ct_netconfc.erl94
-rw-r--r--lib/common_test/src/ct_run.erl2
-rw-r--r--lib/common_test/src/ct_util.erl69
-rw-r--r--lib/common_test/src/cth_log_redirect.erl141
-rw-r--r--lib/common_test/test/Makefile1
-rw-r--r--lib/common_test/test/ct_gen_conn_SUITE_data/proto.erl25
-rw-r--r--lib/common_test/test/ct_hooks_SUITE.erl49
-rw-r--r--lib/common_test/test/ct_hooks_SUITE_data/cth/tests/cth_log_SUITE.erl124
-rw-r--r--lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl13
-rw-r--r--lib/common_test/test/ct_pre_post_test_io_SUITE.erl252
-rw-r--r--lib/common_test/test/ct_pre_post_test_io_SUITE_data/cth_ctrl.erl104
-rw-r--r--lib/common_test/test/ct_pre_post_test_io_SUITE_data/dummy_SUITE.erl132
-rw-r--r--lib/common_test/test/ct_test_support.erl35
-rw-r--r--lib/common_test/test/ct_verbosity_SUITE.erl26
16 files changed, 1067 insertions, 89 deletions
diff --git a/lib/common_test/src/ct_hooks.erl b/lib/common_test/src/ct_hooks.erl
index 3d87a82e24..e845e9e908 100644
--- a/lib/common_test/src/ct_hooks.erl
+++ b/lib/common_test/src/ct_hooks.erl
@@ -50,9 +50,8 @@
-spec init(State :: term()) -> ok |
{fail, Reason :: term()}.
init(Opts) ->
- call(get_new_hooks(Opts, undefined) ++ get_builtin_hooks(Opts),
+ call(get_builtin_hooks(Opts) ++ get_new_hooks(Opts, undefined),
ok, init, []).
-
%% @doc Called after all suites are done.
-spec terminate(Hooks :: term()) ->
@@ -276,8 +275,10 @@ get_new_hooks(Config, Fun) ->
end, get_new_hooks(Config)).
get_new_hooks(Config) when is_list(Config) ->
- lists:flatmap(fun({?config_name, HookConfigs}) ->
+ lists:flatmap(fun({?config_name, HookConfigs}) when is_list(HookConfigs) ->
HookConfigs;
+ ({?config_name, HookConfig}) when is_atom(HookConfig) ->
+ [HookConfig];
(_) ->
[]
end, Config);
diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl
index f5355bfefe..1a6e4d31a8 100644
--- a/lib/common_test/src/ct_logs.erl
+++ b/lib/common_test/src/ct_logs.erl
@@ -61,6 +61,7 @@
-define(index_name, "index.html").
-define(totals_name, "totals.info").
-define(log_cache_name, "ct_log_cache").
+-define(misc_io_log, "misc_io.log.html").
-define(table_color1,"#ADD8E6").
-define(table_color2,"#E4F0FE").
@@ -446,6 +447,8 @@ tc_print(Category,Importance,Format,Args) ->
ct_util:get_verbosity('$unspecified');
{error,bad_invocation} ->
?MAX_VERBOSITY;
+ {error,_Failure} ->
+ ?MAX_VERBOSITY;
Val ->
Val
end,
@@ -521,7 +524,7 @@ int_footer() ->
div_header(Class) ->
div_header(Class,"User").
div_header(Class,Printer) ->
- "<div class=\"" ++ atom_to_list(Class) ++ "\"><b>*** " ++ Printer ++
+ "\n<div class=\"" ++ atom_to_list(Class) ++ "\"><b>*** " ++ Printer ++
" " ++ log_timestamp(now()) ++ " ***</b>".
div_footer() ->
"</div>".
@@ -615,6 +618,34 @@ logger(Parent, Mode, Verbosity) ->
end
end
end,
+
+ test_server_io:start_link(),
+ MiscIoName = filename:join(Dir, ?misc_io_log),
+ {ok,MiscIoFd} = file:open(MiscIoName,
+ [write,{encoding,utf8}]),
+ test_server_io:set_fd(unexpected_io, MiscIoFd),
+
+ {MiscIoHeader,MiscIoFooter} =
+ case get_ts_html_wrapper("Pre/post-test I/O log", Dir, false,
+ Dir, undefined, utf8) of
+ {basic_html,UH,UF} ->
+ {UH,UF};
+ {xhtml,UH,UF} ->
+ {UH,UF}
+ end,
+ io:put_chars(MiscIoFd,
+ [MiscIoHeader,
+ "<a name=\"pretest\"></a>\n",
+ xhtml("<br>\n<h2>Pre-test Log</h2>",
+ "<br />\n<h3>PRE-TEST LOG</h3>"),
+ "\n<pre>\n"]),
+ MiscIoDivider =
+ "\n<a name=\"posttest\"></a>\n"++
+ xhtml("</pre>\n<br><h2>Post-test Log</h2>\n<pre>\n",
+ "</pre>\n<br />\n<h3>POST-TEST LOG</h3>\n<pre>\n"),
+ ct_util:set_testdata_async({misc_io_log,{filename:absname(MiscIoName),
+ MiscIoDivider,MiscIoFooter}}),
+
ct_event:notify(#event{name=start_logging,node=node(),
data=AbsDir}),
make_all_runs_index(start),
@@ -625,7 +656,7 @@ logger(Parent, Mode, Verbosity) ->
end,
file:set_cwd(Dir),
make_last_run_index(Time),
- CtLogFd = open_ctlog(),
+ CtLogFd = open_ctlog(?misc_io_log),
io:format(CtLogFd,int_header()++int_footer(),
[log_timestamp(now()),"Common Test Logger started"]),
Parent ! {started,self(),{Time,filename:absname("")}},
@@ -690,14 +721,15 @@ logger_loop(State) ->
false ->
%% Group leader is dead, so write to the
%% CtLog or unexpected_io log instead
- unexpected_io(Pid,Category,List,State),
+ unexpected_io(Pid,Category,Importance,
+ List,State),
logger_loop(State)
end;
{ct_log,_Fd,TCGLs} ->
%% If category is ct_internal then write
%% to ct_log, else write to unexpected_io
%% log
- unexpected_io(Pid,Category,List,State),
+ unexpected_io(Pid,Category,Importance,List,State),
logger_loop(State#logger_state{
tc_groupleaders = TCGLs})
end;
@@ -798,7 +830,7 @@ print_to_log(sync, FromPid, Category, TCGL, List, State) ->
IoFun = create_io_fun(FromPid, State),
io:format(TCGL,"~ts", [lists:foldl(IoFun, [], List)]);
true ->
- unexpected_io(FromPid,Category,List,State)
+ unexpected_io(FromPid,Category,?MAX_IMPORTANCE,List,State)
end,
State;
@@ -814,7 +846,8 @@ print_to_log(async, FromPid, Category, TCGL, List, State) ->
end;
true ->
fun() ->
- unexpected_io(FromPid,Category,List,State)
+ unexpected_io(FromPid,Category,?MAX_IMPORTANCE,
+ List,State)
end
end,
case State#logger_state.async_print_jobs of
@@ -918,7 +951,7 @@ set_evmgr_gl(GL) ->
EvMgrPid -> group_leader(GL,EvMgrPid)
end.
-open_ctlog() ->
+open_ctlog(MiscIoName) ->
{ok,Fd} = file:open(?ct_log_name,[write,{encoding,utf8}]),
io:format(Fd, header("Common Test Framework Log", {[],[1,2],[]}), []),
case file:consult(ct_run:variables_file_name("../")) of
@@ -933,10 +966,21 @@ open_ctlog() ->
"No configuration found for test!!\n",
[Variables,Reason])
end,
+ io:format(Fd,
+ xhtml("<br><br><h2>Pre/post-test I/O Log</h2>\n",
+ "<br /><br />\n<h4>PRE/POST TEST I/O LOG</h4>\n"), []),
+ io:format(Fd,
+ "\n<ul>\n"
+ "<li><a href=\"~ts#pretest\">"
+ "View I/O logged before the test run</a></li>\n"
+ "<li><a href=\"~ts#posttest\">"
+ "View I/O logged after the test run</a></li>\n</ul>\n",
+ [MiscIoName,MiscIoName]),
+
print_style(Fd,undefined),
io:format(Fd,
- xhtml("<br><br><h2>Progress Log</h2>\n<pre>\n",
- "<br /><br /><h4>PROGRESS LOG</h4>\n<pre>\n"), []),
+ xhtml("<br><h2>Progress Log</h2>\n<pre>\n",
+ "<br />\n<h4>PROGRESS LOG</h4>\n<pre>\n"), []),
Fd.
print_style(Fd,undefined) ->
@@ -2852,6 +2896,9 @@ make_relative1(DirTs, CwdTs) ->
%%% @doc
%%%
get_ts_html_wrapper(TestName, PrintLabel, Cwd, TableCols, Encoding) ->
+ get_ts_html_wrapper(TestName, undefined, PrintLabel, Cwd, TableCols, Encoding).
+
+get_ts_html_wrapper(TestName, Logdir, PrintLabel, Cwd, TableCols, Encoding) ->
TestName1 = if is_list(TestName) ->
lists:flatten(TestName);
true ->
@@ -2872,7 +2919,12 @@ get_ts_html_wrapper(TestName, PrintLabel, Cwd, TableCols, Encoding) ->
end
end,
CTPath = code:lib_dir(common_test),
- {ok,CtLogdir} = get_log_dir(true),
+
+ {ok,CtLogdir} =
+ if Logdir == undefined -> get_log_dir(true);
+ true -> {ok,Logdir}
+ end,
+
AllRuns = make_relative(filename:join(filename:dirname(CtLogdir),
?all_runs_name), Cwd),
TestIndex = make_relative(filename:join(filename:dirname(CtLogdir),
@@ -3066,10 +3118,12 @@ html_encoding(latin1) ->
html_encoding(utf8) ->
"utf-8".
-unexpected_io(Pid,ct_internal,List,#logger_state{ct_log_fd=Fd}=State) ->
+unexpected_io(Pid,ct_internal,_Importance,List,State) ->
IoFun = create_io_fun(Pid,State),
- io:format(Fd, "~ts", [lists:foldl(IoFun, [], List)]);
-unexpected_io(Pid,_Category,List,State) ->
+ io:format(State#logger_state.ct_log_fd, "~ts",
+ [lists:foldl(IoFun, [], List)]);
+unexpected_io(Pid,_Category,_Importance,List,State) ->
IoFun = create_io_fun(Pid,State),
Data = io_lib:format("~ts", [lists:foldl(IoFun, [], List)]),
- test_server_io:print_unexpected(Data).
+ test_server_io:print_unexpected(Data),
+ ok.
diff --git a/lib/common_test/src/ct_netconfc.erl b/lib/common_test/src/ct_netconfc.erl
index e094ee877a..7f10e1db09 100644
--- a/lib/common_test/src/ct_netconfc.erl
+++ b/lib/common_test/src/ct_netconfc.erl
@@ -247,7 +247,11 @@
-define(is_timeout(T), (is_integer(T) orelse T==infinity)).
-define(is_filter(F),
- (is_atom(F) orelse (is_tuple(F) andalso is_atom(element(1,F))))).
+ (?is_simple_xml(F)
+ orelse (F==[])
+ orelse (is_list(F) andalso ?is_simple_xml(hd(F))))).
+-define(is_simple_xml(Xml),
+ (is_atom(Xml) orelse (is_tuple(Xml) andalso is_atom(element(1,Xml))))).
-define(is_string(S), (is_list(S) andalso is_integer(hd(S)))).
%%----------------------------------------------------------------------
@@ -540,22 +544,51 @@ get_capabilities(Client) ->
get_capabilities(Client, Timeout) ->
call(Client, get_capabilities, Timeout).
-%% @private
+%%----------------------------------------------------------------------
+%% @spec send(Client, SimpleXml) -> Result
+%% @equiv send(Client, SimpleXml, infinity)
send(Client, SimpleXml) ->
send(Client, SimpleXml, ?DEFAULT_TIMEOUT).
-%% @private
+
+%%----------------------------------------------------------------------
+-spec send(Client, SimpleXml, Timeout) -> Result when
+ Client :: client(),
+ SimpleXml :: simple_xml(),
+ Timeout :: timeout(),
+ Result :: ok | {error,error_reason()}.
+%% @doc Send an XML document to the server.
+%%
+%% The given XML document is sent as is to the server. This function
+%% can be used for sending XML documents that can not be expressed by
+%% other interface functions in this module.
send(Client, SimpleXml, Timeout) ->
call(Client,{send, Timeout, SimpleXml}).
-%% @private
+%%----------------------------------------------------------------------
+%% @spec send_rpc(Client, SimpleXml) -> Result
+%% @equiv send_rpc(Client, SimpleXml, infinity)
send_rpc(Client, SimpleXml) ->
send_rpc(Client, SimpleXml, ?DEFAULT_TIMEOUT).
-%% @private
+
+%%----------------------------------------------------------------------
+-spec send_rpc(Client, SimpleXml, Timeout) -> Result when
+ Client :: client(),
+ SimpleXml :: simple_xml(),
+ Timeout :: timeout(),
+ Result :: ok | {error,error_reason()}.
+%% @doc Send a Netconf <code>rpc</code> request to the server.
+%%
+%% The given XML document is wrapped in a valid Netconf
+%% <code>rpc</code> request and sent to the server. The
+%% <code>message-id</code> and namespace attributes are added to the
+%% <code>rpc</code> element.
+%%
+%% This function can be used for sending <code>rpc</code> requests
+%% that can not be expressed by other interface functions in this
+%% module.
send_rpc(Client, SimpleXml, Timeout) ->
call(Client,{send_rpc, SimpleXml, Timeout}).
-
-
%%----------------------------------------------------------------------
%% @spec lock(Client, Target) -> Result
%% @equiv lock(Client, Target, infinity)
@@ -761,7 +794,7 @@ create_subscription(Client,Timeout)
when ?is_timeout(Timeout) ->
create_subscription(Client,?DEFAULT_STREAM,Timeout);
create_subscription(Client,Stream)
- when is_list(Stream) ->
+ when ?is_string(Stream) ->
create_subscription(Client,Stream,?DEFAULT_TIMEOUT);
create_subscription(Client,Filter)
when ?is_filter(Filter) ->
@@ -769,14 +802,14 @@ create_subscription(Client,Filter)
?DEFAULT_TIMEOUT).
create_subscription(Client,Stream,Timeout)
- when is_list(Stream) andalso
+ when ?is_string(Stream) andalso
?is_timeout(Timeout) ->
call(Client,{send_rpc_op,{create_subscription,self()},
[Stream,undefined,undefined,undefined],
Timeout});
create_subscription(Client,StartTime,StopTime)
- when is_list(StartTime) andalso
- is_list(StopTime) ->
+ when ?is_string(StartTime) andalso
+ ?is_string(StopTime) ->
create_subscription(Client,?DEFAULT_STREAM,StartTime,StopTime,
?DEFAULT_TIMEOUT);
create_subscription(Client,Filter,Timeout)
@@ -784,28 +817,28 @@ create_subscription(Client,Filter,Timeout)
?is_timeout(Timeout) ->
create_subscription(Client,?DEFAULT_STREAM,Filter,Timeout);
create_subscription(Client,Stream,Filter)
- when is_list(Stream) andalso
+ when ?is_string(Stream) andalso
?is_filter(Filter) ->
create_subscription(Client,Stream,Filter,?DEFAULT_TIMEOUT).
create_subscription(Client,StartTime,StopTime,Timeout)
- when is_list(StartTime) andalso
- is_list(StopTime) andalso
+ when ?is_string(StartTime) andalso
+ ?is_string(StopTime) andalso
?is_timeout(Timeout) ->
create_subscription(Client,?DEFAULT_STREAM,StartTime,StopTime,Timeout);
create_subscription(Client,Stream,StartTime,StopTime)
- when is_list(Stream) andalso
- is_list(StartTime) andalso
- is_list(StopTime) ->
+ when ?is_string(Stream) andalso
+ ?is_string(StartTime) andalso
+ ?is_string(StopTime) ->
create_subscription(Client,Stream,StartTime,StopTime,?DEFAULT_TIMEOUT);
create_subscription(Client,Filter,StartTime,StopTime)
when ?is_filter(Filter) andalso
- is_list(StartTime) andalso
- is_list(StopTime) ->
+ ?is_string(StartTime) andalso
+ ?is_string(StopTime) ->
create_subscription(Client,?DEFAULT_STREAM,Filter,
StartTime,StopTime,?DEFAULT_TIMEOUT);
create_subscription(Client,Stream,Filter,Timeout)
- when is_list(Stream) andalso
+ when ?is_string(Stream) andalso
?is_filter(Filter) andalso
?is_timeout(Timeout) ->
call(Client,{send_rpc_op,{create_subscription,self()},
@@ -813,18 +846,18 @@ create_subscription(Client,Stream,Filter,Timeout)
Timeout}).
create_subscription(Client,Stream,StartTime,StopTime,Timeout)
- when is_list(Stream) andalso
- is_list(StartTime) andalso
- is_list(StopTime) andalso
+ when ?is_string(Stream) andalso
+ ?is_string(StartTime) andalso
+ ?is_string(StopTime) andalso
?is_timeout(Timeout) ->
call(Client,{send_rpc_op,{create_subscription,self()},
[Stream,undefined,StartTime,StopTime],
Timeout});
create_subscription(Client,Stream,Filter,StartTime,StopTime)
- when is_list(Stream) andalso
+ when ?is_string(Stream) andalso
?is_filter(Filter) andalso
- is_list(StartTime) andalso
- is_list(StopTime) ->
+ ?is_string(StartTime) andalso
+ ?is_string(StopTime) ->
create_subscription(Client,Stream,Filter,StartTime,StopTime,?DEFAULT_TIMEOUT).
%%----------------------------------------------------------------------
@@ -832,7 +865,7 @@ create_subscription(Client,Stream,Filter,StartTime,StopTime)
Result when
Client :: client(),
Stream :: stream_name(),
- Filter :: simple_xml(),
+ Filter :: simple_xml() | [simple_xml()],
StartTime :: xs_datetime(),
StopTime :: xs_datetime(),
Timeout :: timeout(),
@@ -855,8 +888,7 @@ create_subscription(Client,Stream,Filter,StartTime,StopTime)
%% possible events is of interest. The format of this parameter is
%% the same as that of the filter parameter in the NETCONF protocol
%% operations. If not present, all events not precluded by other
-%% parameters will be sent. See section 3.6 for more information on
-%% filters.</dd>
+%% parameters will be sent.</dd>
%%
%% <dt>StartTime:</dt>
%% <dd>An optional parameter used to trigger the replay feature and
@@ -1241,8 +1273,10 @@ filter(undefined) ->
[];
filter({xpath,Filter}) when ?is_string(Filter) ->
[{filter,[{type,"xpath"},{select, Filter}],[]}];
+filter(Filter) when is_list(Filter) ->
+ [{filter,[{type,"subtree"}],Filter}];
filter(Filter) ->
- [{filter,[{type,"subtree"}],[Filter]}].
+ filter([Filter]).
maybe_element(_,undefined) ->
[];
diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl
index 266ca73417..7c797be03e 100644
--- a/lib/common_test/src/ct_run.erl
+++ b/lib/common_test/src/ct_run.erl
@@ -1883,7 +1883,7 @@ verify_suites(TestSuites) ->
atom_to_list(
Suite)),
io:format(user,
- "Suite ~w not found"
+ "Suite ~w not found "
"in directory ~ts~n",
[Suite,TestDir]),
{Found,[{DS,[Name]}|NotFound]}
diff --git a/lib/common_test/src/ct_util.erl b/lib/common_test/src/ct_util.erl
index 68e76c2396..bcc4caa62e 100644
--- a/lib/common_test/src/ct_util.erl
+++ b/lib/common_test/src/ct_util.erl
@@ -187,6 +187,7 @@ do_start(Parent, Mode, LogDir, Verbosity) ->
false ->
ok
end,
+
{StartTime,TestLogDir} = ct_logs:init(Mode, Verbosity),
ct_event:notify(#event{name=test_start,
@@ -198,12 +199,26 @@ do_start(Parent, Mode, LogDir, Verbosity) ->
ok ->
Parent ! {self(),started};
{fail,CTHReason} ->
- ct_logs:tc_print('Suite Callback',CTHReason,[]),
+ ErrorInfo = if is_atom(CTHReason) ->
+ io_lib:format("{~p,~p}",
+ [CTHReason,
+ erlang:get_stacktrace()]);
+ true ->
+ CTHReason
+ end,
+ ct_logs:tc_print('Suite Callback',ErrorInfo,[]),
self() ! {{stop,{self(),{user_error,CTHReason}}},
{Parent,make_ref()}}
catch
_:CTHReason ->
- ct_logs:tc_print('Suite Callback',CTHReason,[]),
+ ErrorInfo = if is_atom(CTHReason) ->
+ io_lib:format("{~p,~p}",
+ [CTHReason,
+ erlang:get_stacktrace()]);
+ true ->
+ CTHReason
+ end,
+ ct_logs:tc_print('Suite Callback',ErrorInfo,[]),
self() ! {{stop,{self(),{user_error,CTHReason}}},
{Parent,make_ref()}}
end,
@@ -286,14 +301,23 @@ get_start_dir() ->
%% handle verbosity outside ct_util_server (let the client read
%% the verbosity table) to avoid possible deadlock situations
set_verbosity(Elem = {_Category,_Level}) ->
- ets:insert(?verbosity_table, Elem),
- ok.
+ try ets:insert(?verbosity_table, Elem) of
+ _ ->
+ ok
+ catch
+ _:Reason ->
+ {error,Reason}
+ end.
+
get_verbosity(Category) ->
- case ets:lookup(?verbosity_table, Category) of
+ try ets:lookup(?verbosity_table, Category) of
[{Category,Level}] ->
Level;
_ ->
undefined
+ catch
+ _:Reason ->
+ {error,Reason}
end.
loop(Mode,TestData,StartDir) ->
@@ -383,19 +407,38 @@ loop(Mode,TestData,StartDir) ->
return(From,StartDir),
loop(From,TestData,StartDir);
{{stop,Info},From} ->
+ test_server_io:reset_state(),
+ {MiscIoName,MiscIoDivider,MiscIoFooter} =
+ proplists:get_value(misc_io_log,TestData),
+ {ok,MiscIoFd} = file:open(MiscIoName,
+ [append,{encoding,utf8}]),
+ io:put_chars(MiscIoFd, MiscIoDivider),
+ test_server_io:set_fd(unexpected_io, MiscIoFd),
+
Time = calendar:local_time(),
ct_event:sync_notify(#event{name=test_done,
node=node(),
data=Time}),
- Callbacks = ets:lookup_element(?suite_table,
- ct_hooks,
- #suite_data.value),
+ Callbacks =
+ try ets:lookup_element(?suite_table,
+ ct_hooks,
+ #suite_data.value) of
+ CTHMods -> CTHMods
+ catch
+ %% this is because ct_util failed in init
+ error:badarg -> []
+ end,
ct_hooks:terminate(Callbacks),
close_connections(ets:tab2list(?conn_table)),
ets:delete(?conn_table),
ets:delete(?board_table),
ets:delete(?suite_table),
ets:delete(?verbosity_table),
+
+ io:put_chars(MiscIoFd, "\n</pre>\n"++MiscIoFooter),
+ test_server_io:stop([unexpected_io]),
+ test_server_io:finish(),
+
ct_logs:close(Info, StartDir),
ct_event:stop(),
ct_config:stop(),
@@ -670,8 +713,14 @@ reset_silent_connections() ->
%%% @see ct
stop(Info) ->
case whereis(ct_util_server) of
- undefined -> ok;
- _ -> call({stop,Info})
+ undefined ->
+ ok;
+ CtUtilPid ->
+ Ref = monitor(process, CtUtilPid),
+ call({stop,Info}),
+ receive
+ {'DOWN',Ref,_,_,_} -> ok
+ end
end.
%%%-----------------------------------------------------------------
diff --git a/lib/common_test/src/cth_log_redirect.erl b/lib/common_test/src/cth_log_redirect.erl
index 958b7a94c7..11af1aa346 100644
--- a/lib/common_test/src/cth_log_redirect.erl
+++ b/lib/common_test/src/cth_log_redirect.erl
@@ -25,16 +25,29 @@
%% CTH Callbacks
--export([id/1, init/2, post_init_per_group/4, pre_end_per_group/3,
- post_end_per_testcase/4]).
+-export([id/1, init/2,
+ pre_init_per_suite/3, pre_end_per_suite/3, post_end_per_suite/4,
+ pre_init_per_group/3, post_init_per_group/4,
+ pre_end_per_group/3, post_end_per_group/4,
+ pre_init_per_testcase/3, post_end_per_testcase/4]).
%% Event handler Callbacks
-export([init/1,
handle_event/2, handle_call/2, handle_info/2,
terminate/1]).
+%% Other
+-export([handle_remote_events/1]).
+
-include("ct.hrl").
+-record(eh_state, {log_func,
+ curr_suite,
+ curr_group,
+ curr_func,
+ parallel_tcs = false,
+ handle_remote_events = false}).
+
id(_Opts) ->
?MODULE.
@@ -42,36 +55,62 @@ init(?MODULE, _Opts) ->
error_logger:add_report_handler(?MODULE),
tc_log_async.
+pre_init_per_suite(Suite, Config, State) ->
+ set_curr_func({Suite,init_per_suite}, Config),
+ {Config, State}.
+
+pre_end_per_suite(Suite, Config, State) ->
+ set_curr_func({Suite,end_per_suite}, Config),
+ {Config, State}.
+
+post_end_per_suite(_Suite, Config, Return, State) ->
+ set_curr_func(undefined, Config),
+ {Return, State}.
+
+pre_init_per_group(Group, Config, State) ->
+ set_curr_func({group,Group,init_per_group}, Config),
+ {Config, State}.
+
post_init_per_group(Group, Config, Result, tc_log_async) ->
case lists:member(parallel,proplists:get_value(
tc_group_properties,Config,[])) of
true ->
- {Result, {set_log_func(ct_log),Group}};
+ {Result, {set_log_func(tc_log),Group}};
false ->
{Result, tc_log_async}
end;
post_init_per_group(_Group, _Config, Result, State) ->
{Result, State}.
+pre_init_per_testcase(TC, Config, State) ->
+ set_curr_func(TC, Config),
+ {Config, State}.
+
post_end_per_testcase(_TC, _Config, Result, State) ->
%% Make sure that the event queue is flushed
%% before ending this test case.
gen_event:call(error_logger, ?MODULE, flush, 300000),
{Result, State}.
-pre_end_per_group(Group, Config, {ct_log, Group}) ->
+pre_end_per_group(Group, Config, {tc_log, Group}) ->
+ set_curr_func({group,Group,end_per_group}, Config),
{Config, set_log_func(tc_log_async)};
-pre_end_per_group(_Group, Config, State) ->
+pre_end_per_group(Group, Config, State) ->
+ set_curr_func({group,Group,end_per_group}, Config),
{Config, State}.
+post_end_per_group(_Group, Config, Return, State) ->
+ set_curr_func({group,undefined}, Config),
+ {Return, State}.
%% Copied and modified from sasl_report_tty_h.erl
init(_Type) ->
- {ok, tc_log_async}.
+ {ok, #eh_state{log_func = tc_log_async}}.
-handle_event({_Type, GL, _Msg}, State) when node(GL) /= node() ->
+handle_event({_Type,GL,_Msg}, #eh_state{handle_remote_events = false} = State)
+ when node(GL) /= node() ->
{ok, State};
-handle_event(Event, LogFunc) ->
+handle_event(Event, #eh_state{log_func = LogFunc} = State) ->
case lists:keyfind(sasl, 1, application:which_applications()) of
false ->
sasl_not_started;
@@ -80,7 +119,8 @@ handle_event(Event, LogFunc) ->
SReport = sasl_report:format_report(group_leader(), ErrLogType,
tag_event(Event)),
if is_list(SReport) ->
- ct_logs:LogFunc(sasl, ?STD_IMPORTANCE, "System", SReport, []);
+ SaslHeader = format_header(State),
+ ct_logs:LogFunc(sasl, ?STD_IMPORTANCE, SaslHeader, SReport, []);
true -> %% Report is an atom if no logging is to be done
ignore
end
@@ -88,20 +128,50 @@ handle_event(Event, LogFunc) ->
EReport = error_logger_tty_h:write_event(
tag_event(Event),io_lib),
if is_list(EReport) ->
- ct_logs:LogFunc(error_logger, ?STD_IMPORTANCE, "System", EReport, []);
+ ErrHeader = format_header(State),
+ ct_logs:LogFunc(error_logger, ?STD_IMPORTANCE, ErrHeader, EReport, []);
true -> %% Report is an atom if no logging is to be done
ignore
end,
- {ok, LogFunc}.
+ {ok, State}.
handle_info(_,State) -> {ok, State}.
handle_call(flush,State) ->
{ok, ok, State};
-handle_call({set_logfunc,NewLogFunc},_) ->
- {ok, NewLogFunc, NewLogFunc};
-handle_call(_Query, _State) -> {error, bad_query}.
+
+handle_call({set_curr_func,{group,Group,Conf},Config}, State) ->
+ Parallel = case proplists:get_value(tc_group_properties, Config) of
+ undefined -> false;
+ Props -> lists:member(parallel, Props)
+ end,
+ {ok, ok, State#eh_state{curr_group = Group,
+ curr_func = Conf,
+ parallel_tcs = Parallel}};
+handle_call({set_curr_func,{group,undefined},_Config}, State) ->
+ {ok, ok, State#eh_state{curr_group = undefined,
+ curr_func = undefined,
+ parallel_tcs = false}};
+handle_call({set_curr_func,{Suite,Conf},_Config}, State) ->
+ {ok, ok, State#eh_state{curr_suite = Suite,
+ curr_func = Conf,
+ parallel_tcs = false}};
+handle_call({set_curr_func,undefined,_Config}, State) ->
+ {ok, ok, State#eh_state{curr_suite = undefined,
+ curr_func = undefined,
+ parallel_tcs = false}};
+handle_call({set_curr_func,TC,_Config}, State) ->
+ {ok, ok, State#eh_state{curr_func = TC}};
+
+handle_call({set_logfunc,NewLogFunc}, State) ->
+ {ok, NewLogFunc, State#eh_state{log_func = NewLogFunc}};
+
+handle_call({handle_remote_events,Bool}, State) ->
+ {ok, ok, State#eh_state{handle_remote_events = Bool}};
+
+handle_call(_Query, _State) ->
+ {error, bad_query}.
terminate(_State) ->
error_logger:delete_report_handler(?MODULE),
@@ -110,5 +180,48 @@ terminate(_State) ->
tag_event(Event) ->
{calendar:local_time(), Event}.
+set_curr_func(CurrFunc, Config) ->
+ gen_event:call(error_logger, ?MODULE, {set_curr_func, CurrFunc, Config}).
+
set_log_func(Func) ->
gen_event:call(error_logger, ?MODULE, {set_logfunc, Func}).
+
+handle_remote_events(Bool) ->
+ gen_event:call(error_logger, ?MODULE, {handle_remote_events, Bool}).
+
+%%%-----------------------------------------------------------------
+
+format_header(#eh_state{curr_suite = undefined,
+ curr_group = undefined,
+ curr_func = undefined}) ->
+ io_lib:format("System report", []);
+
+format_header(#eh_state{curr_suite = Suite,
+ curr_group = undefined,
+ curr_func = undefined}) ->
+ io_lib:format("System report during ~w", [Suite]);
+
+format_header(#eh_state{curr_suite = Suite,
+ curr_group = undefined,
+ curr_func = TcOrConf}) ->
+ io_lib:format("System report during ~w:~w/1",
+ [Suite,TcOrConf]);
+
+format_header(#eh_state{curr_suite = Suite,
+ curr_group = Group,
+ curr_func = Conf}) when Conf == init_per_group;
+ Conf == end_per_group ->
+ io_lib:format("System report during ~w:~w/2 for ~w",
+ [Suite,Conf,Group]);
+
+format_header(#eh_state{curr_suite = Suite,
+ curr_group = Group,
+ parallel_tcs = true}) ->
+ io_lib:format("System report during ~w in ~w",
+ [Group,Suite]);
+
+format_header(#eh_state{curr_suite = Suite,
+ curr_group = Group,
+ curr_func = TC}) ->
+ io_lib:format("System report during ~w:~w/1 in ~w",
+ [Suite,TC,Group]).
diff --git a/lib/common_test/test/Makefile b/lib/common_test/test/Makefile
index 9d2edcd653..085f19d023 100644
--- a/lib/common_test/test/Makefile
+++ b/lib/common_test/test/Makefile
@@ -51,6 +51,7 @@ MODULES= \
ct_master_SUITE \
ct_misc_1_SUITE \
ct_hooks_SUITE \
+ ct_pre_post_test_io_SUITE \
ct_netconfc_SUITE \
ct_basic_html_SUITE \
ct_auto_compile_SUITE \
diff --git a/lib/common_test/test/ct_gen_conn_SUITE_data/proto.erl b/lib/common_test/test/ct_gen_conn_SUITE_data/proto.erl
index 8fcd35e0a4..1d08ce167b 100644
--- a/lib/common_test/test/ct_gen_conn_SUITE_data/proto.erl
+++ b/lib/common_test/test/ct_gen_conn_SUITE_data/proto.erl
@@ -1,10 +1,21 @@
-%%% @author Peter Andersson <[email protected]>
-%%% @copyright (C) 2013, Peter Andersson
-%%% @doc
-%%%
-%%% @end
-%%% Created : 24 May 2013 by Peter Andersson <[email protected]>
-
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2012. 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(proto).
-compile(export_all).
diff --git a/lib/common_test/test/ct_hooks_SUITE.erl b/lib/common_test/test/ct_hooks_SUITE.erl
index 796a0832d7..596bfe3ff0 100644
--- a/lib/common_test/test/ct_hooks_SUITE.erl
+++ b/lib/common_test/test/ct_hooks_SUITE.erl
@@ -84,7 +84,7 @@ all(suite) ->
skip_post_suite_cth, recover_post_suite_cth, update_config_cth,
state_update_cth, options_cth, same_id_cth,
fail_n_skip_with_minimal_cth, prio_cth, no_config,
- data_dir
+ data_dir, cth_log
]
).
@@ -222,7 +222,32 @@ data_dir(Config) when is_list(Config) ->
do_test(data_dir, "ct_data_dir_SUITE.erl",
[verify_data_dir_cth],Config).
-
+cth_log(Config) when is_list(Config) ->
+ %% test that cth_log_redirect writes properly to
+ %% unexpected I/O log
+ StartOpts = do_test(cth_log, "cth_log_SUITE.erl", [], Config),
+ Logdir = proplists:get_value(logdir, StartOpts),
+ UnexpIoLogs =
+ filelib:wildcard(
+ filename:join(Logdir,
+ "ct_run*/cth.tests*/run*/unexpected_io.log.html")),
+ lists:foreach(
+ fun(UnexpIoLog) ->
+ {ok,Bin} = file:read_file(UnexpIoLog),
+ Ts = string:tokens(binary_to_list(Bin),[$\n]),
+ Matches = lists:foldl(fun([$=,$E,$R,$R,$O,$R|_], N) ->
+ N+1;
+ ([$L,$o,$g,$g,$e,$r|_], N) ->
+ N+1;
+ (_, N) -> N
+ end, 0, Ts),
+ ct:pal("~p matches in ~tp", [Matches,UnexpIoLog]),
+ if Matches > 10 -> ok;
+ true -> exit({no_unexpected_io_found,UnexpIoLog})
+ end
+ end, UnexpIoLogs),
+ ok.
+
%%%-----------------------------------------------------------------
%%% HELP FUNCTIONS
@@ -251,7 +276,8 @@ do_test(Tag, SuiteWildCard, CTHs, Config, Res, EC) ->
Opts),
TestEvents = events_to_check(Tag, EC),
- ok = ct_test_support:verify_events(TestEvents, Events, Config).
+ ok = ct_test_support:verify_events(TestEvents, Events, Config),
+ Opts.
setup(Test, Config) ->
Opts0 = ct_test_support:get_opts(Config),
@@ -1187,6 +1213,23 @@ test_events(data_dir) ->
{?eh,stop_logging,[]}
];
+test_events(cth_log) ->
+ [{?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,tc_start,{cth_log_SUITE,init_per_suite}},
+
+ {parallel,
+ [{?eh,tc_start,{ct_framework,{init_per_group,g1,[parallel]}}},
+ {?eh,tc_done,{ct_framework,{init_per_group,g1,[parallel]},ok}},
+ {?eh,test_stats,{30,0,{0,0}}},
+ {?eh,tc_start,{ct_framework,{end_per_group,g1,[parallel]}}},
+ {?eh,tc_done,{ct_framework,{end_per_group,g1,[parallel]},ok}}]},
+
+ {?eh,tc_done,{cth_log_SUITE,end_per_suite,ok}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+ ];
+
test_events(ok) ->
ok.
diff --git a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/cth_log_SUITE.erl b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/cth_log_SUITE.erl
new file mode 100644
index 0000000000..18dd07e87e
--- /dev/null
+++ b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/cth_log_SUITE.erl
@@ -0,0 +1,124 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(cth_log_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+%%--------------------------------------------------------------------
+%% @spec suite() -> Info
+%% Info = [tuple()]
+%% @end
+%%--------------------------------------------------------------------
+suite() ->
+ [{timetrap,{seconds,30}}].
+
+%%--------------------------------------------------------------------
+%% @spec init_per_suite(Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ Gen = spawn(fun() -> gen() end),
+ [{gen,Gen}|Config].
+
+%%--------------------------------------------------------------------
+%% @spec end_per_suite(Config0) -> void() | {save_config,Config1}
+%% Config0 = Config1 = [tuple()]
+%% @end
+%%--------------------------------------------------------------------
+end_per_suite(Config) ->
+ Gen = proplists:get_value(gen, Config),
+ exit(Gen, kill),
+ timer:sleep(100),
+ ok.
+
+%%--------------------------------------------------------------------
+%% @spec init_per_testcase(TestCase, Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% TestCase = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+init_per_testcase(_TestCase, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% @spec end_per_testcase(TestCase, Config0) ->
+%% void() | {save_config,Config1} | {fail,Reason}
+%% TestCase = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+end_per_testcase(_TestCase, _Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% @spec groups() -> [Group]
+%% Group = {GroupName,Properties,GroupsAndTestCases}
+%% GroupName = atom()
+%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}]
+%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase]
+%% TestCase = atom()
+%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}}
+%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail |
+%% repeat_until_any_ok | repeat_until_any_fail
+%% N = integer() | forever
+%% @end
+%%--------------------------------------------------------------------
+groups() ->
+ [{g1,[parallel,{repeat,10}],[tc1,tc2,tc3]}].
+
+%%--------------------------------------------------------------------
+%% @spec all() -> GroupsAndTestCases | {skip,Reason}
+%% GroupsAndTestCases = [{group,GroupName} | TestCase]
+%% GroupName = atom()
+%% TestCase = atom()
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+all() ->
+ [{group,g1}].
+
+tc1(_) ->
+ ct:sleep(100),
+ ok.
+tc2(_) ->
+ ct:sleep(100),
+ ok.
+tc3(_) ->
+ ct:sleep(100),
+ ok.
+
+%%%-----------------------------------------------------------------
+
+gen() ->
+ gen_loop(1).
+
+gen_loop(N) ->
+ ct:log("Logger iteration: ~p", [N]),
+ error_logger:error_report(N),
+ ct:sleep(200),
+ gen_loop(N+1).
diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl
index 0535eb924b..6ee7fdd6f6 100644
--- a/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl
+++ b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl
@@ -886,6 +886,19 @@ create_subscription(Config) ->
?NS:expect_do_reply('close-session',close,ok),
?ok = ct_netconfc:close_session(Client8),
+ %% Multiple filters
+ {ok,Client9} = open_success(DataDir),
+ ?NS:expect_reply({'create-subscription',[stream,filter]},ok),
+ MultiFilters = [{event,[{xmlns,"http://my.namespaces.com/event"}],
+ [{eventClass,["fault"]},
+ {severity,["critical"]}]},
+ {event,[{xmlns,"http://my.namespaces.com/event"}],
+ [{eventClass,["fault"]},
+ {severity,["major"]}]}],
+ ?ok = ct_netconfc:create_subscription(Client9,MultiFilters),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client9),
+
ok.
receive_event(Config) ->
diff --git a/lib/common_test/test/ct_pre_post_test_io_SUITE.erl b/lib/common_test/test/ct_pre_post_test_io_SUITE.erl
new file mode 100644
index 0000000000..84341a0b99
--- /dev/null
+++ b/lib/common_test/test/ct_pre_post_test_io_SUITE.erl
@@ -0,0 +1,252 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2012. 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%
+%%
+
+%%%-------------------------------------------------------------------
+%%% File: ct_pre_post_test_io_SUITE
+%%%
+%%% Description:
+%%%
+%%% Test that ct:log/2 printouts and error/progress reports that happen
+%%% before or after the test run are saved in the pre/post test IO log.
+%%%-------------------------------------------------------------------
+-module(ct_pre_post_test_io_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("common_test/include/ct_event.hrl").
+
+-define(eh, ct_test_support_eh).
+
+%%--------------------------------------------------------------------
+%% TEST SERVER CALLBACK FUNCTIONS
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+%% Description: Since Common Test starts another Test Server
+%% instance, the tests need to be performed on a separate node (or
+%% there will be clashes with logging processes etc).
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ DataDir = ?config(data_dir, Config),
+ CTH = filename:join(DataDir, "cth_ctrl.erl"),
+ ct:pal("Compiling ~p: ~p",
+ [CTH,compile:file(CTH,[{outdir,DataDir},debug_info])]),
+ ct_test_support:init_per_suite([{path_dirs,[DataDir]},
+ {start_sasl,true} | Config]).
+
+end_per_suite(Config) ->
+ ct_test_support:end_per_suite(Config).
+
+init_per_testcase(TestCase, Config) ->
+ ct_test_support:init_per_testcase(TestCase, Config).
+
+end_per_testcase(TestCase, Config) ->
+ ct_test_support:end_per_testcase(TestCase, Config).
+
+suite() -> [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [
+ pre_post_io
+ ].
+
+%%--------------------------------------------------------------------
+%% TEST CASES
+%%--------------------------------------------------------------------
+
+%%%-----------------------------------------------------------------
+%%%
+pre_post_io(Config) ->
+ TC = pre_post_io,
+ DataDir = ?config(data_dir, Config),
+ Suite = filename:join(DataDir, "dummy_SUITE"),
+ {Opts,ERPid} = setup([{suite,Suite},{label,TC},{ct_hooks,[cth_ctrl]}],
+ Config),
+
+ %%!--------------------------------------------------------------------
+ %%! Note that error reports will not start showing up in the pre-test
+ %%! io log until handle_remote_events has been set to true (see below).
+ %%! The reason is that the error logger has its group leader on the
+ %%! test_server node (not the ct node) and cth_log_redirect ignores
+ %%! events with remote destination until told otherwise.
+ %%!--------------------------------------------------------------------
+
+ spawn(fun() ->
+ %% --- test run 1 ---
+ ct:sleep(3000),
+ ct_test_support:ct_rpc({cth_log_redirect,
+ handle_remote_events,
+ [true]}, Config),
+ ct:sleep(2000),
+ io:format(user, "Starting test run!~n", []),
+ ct_test_support:ct_rpc({cth_ctrl,proceed,[]}, Config),
+ ct:sleep(6000),
+ io:format(user, "Finishing off!~n", []),
+ ct_test_support:ct_rpc({cth_ctrl,proceed,[]}, Config),
+ %% --- test run 2 ---
+ ct:sleep(3000),
+ ct_test_support:ct_rpc({cth_log_redirect,
+ handle_remote_events,
+ [true]}, Config),
+ ct:sleep(2000),
+ io:format(user, "Starting test run!~n", []),
+ ct_test_support:ct_rpc({cth_ctrl,proceed,[]}, Config),
+ ct:sleep(6000),
+ io:format(user, "Finishing off!~n", []),
+ ct_test_support:ct_rpc({cth_ctrl,proceed,[]}, Config)
+ end),
+ ct_test_support:run(Opts, Config),
+ Events = ct_test_support:get_events(ERPid, Config),
+ ct_test_support:log_events(TC,
+ reformat(Events, ?eh),
+ ?config(priv_dir, Config),
+ Opts),
+ TestEvents = events_to_check(TC),
+ ok = ct_test_support:verify_events(TestEvents, Events, Config),
+
+ LogDirs = lists:flatmap(fun({_EH,#event{name=start_logging,data=Dir}}) ->
+ [Dir];
+ (_) ->
+ []
+ end, Events),
+ PrePostIoFiles =
+ [filename:join(LogDir, "misc_io.log.html") || LogDir <- LogDirs],
+ lists:foreach(
+ fun(PrePostIoFile) ->
+ ct:log("Reading Pre/Post Test IO Log file: ~ts", [PrePostIoFile]),
+ {ok,Bin} = file:read_file(PrePostIoFile),
+ Ts = string:tokens(binary_to_list(Bin),[$\n]),
+ PrePostIOEntries =
+ lists:foldl(fun([$L,$o,$g,$g,$e,$r|_],
+ {pre,PreLogN,PreErrN,0,0}) ->
+ {pre,PreLogN+1,PreErrN,0,0};
+ ([$=,$E,$R,$R,$O,$R|_],
+ {pre,PreLogN,PreErrN,0,0}) ->
+ {pre,PreLogN,PreErrN+1,0,0};
+ ([_,_,_,_,$P,$O,$S,$T,$-,$T,$E,$S,$T|_],
+ {pre,PreLogN,PreErrN,0,0}) ->
+ {post,PreLogN,PreErrN,0,0};
+ ([$L,$o,$g,$g,$e,$r|_],
+ {post,PreLogN,PreErrN,PostLogN,PostErrN}) ->
+ {post,PreLogN,PreErrN,PostLogN+1,PostErrN};
+ ([$=,$E,$R,$R,$O,$R|_],
+ {post,PreLogN,PreErrN,PostLogN,PostErrN}) ->
+ {post,PreLogN,PreErrN,PostLogN,PostErrN+1};
+ (_, Counters) ->
+ Counters
+ end, {pre,0,0,0,0}, Ts),
+ [_|Counters] = tuple_to_list(PrePostIOEntries),
+ ct:log("Entries in the Pre/Post Test IO Log: ~p", [Counters]),
+ case [C || C <- Counters, C < 2] of
+ [] ->
+ ok;
+ _ ->
+ exit("Not enough entries in the Pre/Post Test IO Log!")
+ end
+ end, PrePostIoFiles),
+
+ UnexpIoFiles =
+ [filelib:wildcard(
+ filename:join(LogDir,
+ "*dummy_SUITE.logs/run.*/"
+ "unexpected_io.log.html")) || LogDir <- LogDirs],
+ lists:foreach(
+ fun(UnexpIoFile) ->
+ ct:log("Reading Unexpected IO Log file: ~ts", [UnexpIoFile]),
+ {ok,Bin} = file:read_file(UnexpIoFile),
+ Ts = string:tokens(binary_to_list(Bin),[$\n]),
+ UnexpIOEntries =
+ lists:foldl(fun([$L,$o,$g,$g,$e,$r|_], [LogN,ErrN]) ->
+ [LogN+1,ErrN];
+ ([$=,$E,$R,$R,$O,$R|_], [LogN,ErrN]) ->
+ [LogN,ErrN+1];
+ (_, Counters) -> Counters
+ end, [0,0], Ts),
+ ct:log("Entries in the Unexpected IO Log: ~p", [UnexpIOEntries]),
+ case [N || N <- UnexpIOEntries, N < 2] of
+ [] ->
+ ok;
+ _ ->
+ exit("Not enough entries in the Unexpected IO Log!")
+ end
+ end, UnexpIoFiles),
+ ok.
+
+%%%-----------------------------------------------------------------
+%%% HELP FUNCTIONS
+%%%-----------------------------------------------------------------
+
+setup(Test, Config) ->
+ Opts0 = ct_test_support:get_opts(Config),
+ Level = ?config(trace_level, Config),
+ EvHArgs = [{cbm,ct_test_support},{trace_level,Level}],
+ Opts = Opts0 ++ [{event_handler,{?eh,EvHArgs}}|Test],
+ ERPid = ct_test_support:start_event_receiver(Config),
+ {Opts,ERPid}.
+
+reformat(Events, EH) ->
+ ct_test_support:reformat(Events, EH).
+
+%%%-----------------------------------------------------------------
+%%% TEST EVENTS
+%%%-----------------------------------------------------------------
+
+events_to_check(pre_post_io) ->
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,start_info,{1,1,7}},
+ {?eh,tc_start,{dummy_SUITE,init_per_suite}},
+ {?eh,tc_done,{dummy_SUITE,init_per_suite,ok}},
+ {parallel,
+ [{?eh,tc_start,{dummy_SUITE,{init_per_group,g1,[parallel]}}},
+ {?eh,tc_done,
+ {dummy_SUITE,{init_per_group,g1,[parallel]},ok}},
+ {?eh,tc_start,{dummy_SUITE,tc1}},
+ {?eh,tc_start,{dummy_SUITE,tc2}},
+ {?eh,tc_start,{dummy_SUITE,tc3}},
+ {?eh,tc_done,{dummy_SUITE,tc2,ok}},
+ {?eh,tc_done,{dummy_SUITE,tc1,ok}},
+ {?eh,tc_done,{dummy_SUITE,tc3,ok}},
+ {?eh,test_stats,{1,0,{0,0}}},
+ {?eh,test_stats,{2,0,{0,0}}},
+ {?eh,test_stats,{3,0,{0,0}}},
+ {?eh,tc_start,{dummy_SUITE,{end_per_group,g1,[parallel]}}},
+ {?eh,tc_done,{dummy_SUITE,{end_per_group,g1,[parallel]},ok}}]},
+ {?eh,tc_start,{dummy_SUITE,tc1}},
+ {?eh,tc_done,{dummy_SUITE,tc1,ok}},
+ {?eh,test_stats,{4,0,{0,0}}},
+ {?eh,tc_start,{dummy_SUITE,tc2}},
+ {?eh,tc_done,{dummy_SUITE,tc2,ok}},
+ {?eh,test_stats,{5,0,{0,0}}},
+ [{?eh,tc_start,{dummy_SUITE,{init_per_group,g2,[]}}},
+ {?eh,tc_done,{dummy_SUITE,{init_per_group,g2,[]},ok}},
+ {?eh,tc_start,{dummy_SUITE,tc4}},
+ {?eh,tc_done,{dummy_SUITE,tc4,ok}},
+ {?eh,test_stats,{6,0,{0,0}}},
+ {?eh,tc_start,{dummy_SUITE,tc5}},
+ {?eh,tc_done,{dummy_SUITE,tc5,ok}},
+ {?eh,test_stats,{7,0,{0,0}}},
+ {?eh,tc_start,{dummy_SUITE,{end_per_group,g2,[]}}},
+ {?eh,tc_done,{dummy_SUITE,{end_per_group,g2,[]},ok}}],
+ {?eh,tc_start,{dummy_SUITE,end_per_suite}},
+ {?eh,tc_done,{dummy_SUITE,end_per_suite,ok}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}].
diff --git a/lib/common_test/test/ct_pre_post_test_io_SUITE_data/cth_ctrl.erl b/lib/common_test/test/ct_pre_post_test_io_SUITE_data/cth_ctrl.erl
new file mode 100644
index 0000000000..a9ea7b14dd
--- /dev/null
+++ b/lib/common_test/test/ct_pre_post_test_io_SUITE_data/cth_ctrl.erl
@@ -0,0 +1,104 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2012. 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(cth_ctrl).
+
+-export([proceed/0,
+ init/2, terminate/1]).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+
+proceed() ->
+ ?MODULE ! proceed.
+
+%%--------------------------------------------------------------------
+%% Hook functions
+%%--------------------------------------------------------------------
+init(_Id, _Opts) ->
+ case lists:keyfind(sasl, 1, application:which_applications()) of
+ false ->
+ exit(sasl_not_started);
+ _Else ->
+ ok
+ end,
+ WhoAmI = self(),
+ DispPid = spawn_link(fun() -> dispatcher(WhoAmI) end),
+ register(?MODULE, DispPid),
+ io:format(user,
+ "~n~n+++ Startup of ~w on ~p finished, "
+ "call ~w:proceed() to run tests...~n",
+ [?MODULE,node(),?MODULE]),
+ start_external_logger(cth_logger),
+ receive
+ {?MODULE,proceed} -> ok
+ after
+ 10000 ->
+ ok
+ end,
+ {ok,[],ct_last}.
+
+terminate(_State) ->
+ io:format(user,
+ "~n~n+++ Tests finished, call ~w:proceed() to shut down...~n",
+ [?MODULE]),
+ receive
+ {?MODULE,proceed} -> ok
+ after
+ 10000 ->
+ ok
+ end,
+ stop_external_logger(cth_logger),
+ stop_dispatcher(),
+ ok.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+
+start_external_logger(Name) ->
+ case whereis(Name) of
+ undefined -> ok;
+ Pid -> exit(Pid, kill)
+ end,
+ spawn(fun() -> init_logger(Name) end).
+
+stop_external_logger(Name) ->
+ catch exit(whereis(Name), kill).
+
+init_logger(Name) ->
+ register(Name, self()),
+ logger_loop(1).
+
+logger_loop(N) ->
+ ct:log("Logger iteration: ~p", [N]),
+ error_logger:error_report(N),
+ timer:sleep(250),
+ logger_loop(N+1).
+
+%%%-----------------------------------------------------------------
+
+dispatcher(SendTo) ->
+ receive Msg -> SendTo ! {?MODULE,Msg} end,
+ dispatcher(SendTo).
+
+stop_dispatcher() ->
+ catch exit(whereis(?MODULE), kill).
+
+
diff --git a/lib/common_test/test/ct_pre_post_test_io_SUITE_data/dummy_SUITE.erl b/lib/common_test/test/ct_pre_post_test_io_SUITE_data/dummy_SUITE.erl
new file mode 100644
index 0000000000..ac9c4efd31
--- /dev/null
+++ b/lib/common_test/test/ct_pre_post_test_io_SUITE_data/dummy_SUITE.erl
@@ -0,0 +1,132 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2012. 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(dummy_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+%%--------------------------------------------------------------------
+%% @spec suite() -> Info
+%% Info = [tuple()]
+%% @end
+%%--------------------------------------------------------------------
+suite() ->
+ [{timetrap,{seconds,30}}].
+
+%%--------------------------------------------------------------------
+%% @spec init_per_suite(Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% @spec end_per_suite(Config0) -> void() | {save_config,Config1}
+%% Config0 = Config1 = [tuple()]
+%% @end
+%%--------------------------------------------------------------------
+end_per_suite(_Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% @spec init_per_group(GroupName, Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% GroupName = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+init_per_group(_GroupName, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% @spec end_per_group(GroupName, Config0) ->
+%% void() | {save_config,Config1}
+%% GroupName = atom()
+%% Config0 = Config1 = [tuple()]
+%% @end
+%%--------------------------------------------------------------------
+end_per_group(_GroupName, _Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% @spec init_per_testcase(TestCase, Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% TestCase = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+init_per_testcase(_TestCase, Config) ->
+ ct:sleep(500),
+ Config.
+
+%%--------------------------------------------------------------------
+%% @spec end_per_testcase(TestCase, Config0) ->
+%% void() | {save_config,Config1} | {fail,Reason}
+%% TestCase = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+end_per_testcase(_TestCase, _Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% @spec groups() -> [Group]
+%% Group = {GroupName,Properties,GroupsAndTestCases}
+%% GroupName = atom()
+%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}]
+%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase]
+%% TestCase = atom()
+%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}}
+%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail |
+%% repeat_until_any_ok | repeat_until_any_fail
+%% N = integer() | forever
+%% @end
+%%--------------------------------------------------------------------
+groups() ->
+ [{g1,[parallel],[tc1,tc2,tc3]},
+ {g2,[],[tc4,tc5]}].
+
+%%--------------------------------------------------------------------
+%% @spec all() -> GroupsAndTestCases | {skip,Reason}
+%% GroupsAndTestCases = [{group,GroupName} | TestCase]
+%% GroupName = atom()
+%% TestCase = atom()
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+all() ->
+ [{group,g1},tc1,tc2,{group,g2}].
+
+tc1(_C) ->
+ ok.
+tc2(_C) ->
+ ok.
+tc3(_C) ->
+ ok.
+tc4(_C) ->
+ ok.
+tc5(_C) ->
+ ok.
diff --git a/lib/common_test/test/ct_test_support.erl b/lib/common_test/test/ct_test_support.erl
index 6bcac12326..67e430f821 100644
--- a/lib/common_test/test/ct_test_support.erl
+++ b/lib/common_test/test/ct_test_support.erl
@@ -36,7 +36,9 @@
verify_events/3, verify_events/4, reformat/2, log_events/4,
join_abs_dirs/2]).
--export([ct_test_halt/1]).
+-export([start_slave/3, slave_stop/1]).
+
+-export([ct_test_halt/1, ct_rpc/2]).
-include_lib("kernel/include/file.hrl").
@@ -63,13 +65,16 @@ init_per_suite(Config, Level) ->
_ ->
ok
end,
-
start_slave(Config, Level).
-start_slave(Config,Level) ->
+start_slave(Config, Level) ->
+ start_slave(ct, Config, Level).
+
+start_slave(NodeName, Config, Level) ->
[_,Host] = string:tokens(atom_to_list(node()), "@"),
- test_server:format(0, "Trying to start ~s~n", ["ct@"++Host]),
- case slave:start(Host, ct, []) of
+ test_server:format(0, "Trying to start ~s~n",
+ [atom_to_list(NodeName)++"@"++Host]),
+ case slave:start(Host, NodeName, []) of
{error,Reason} ->
test_server:fail(Reason);
{ok,CTNode} ->
@@ -77,7 +82,7 @@ start_slave(Config,Level) ->
IsCover = test_server:is_cover(),
if IsCover ->
cover:start(CTNode);
- true->
+ true ->
ok
end,
@@ -97,6 +102,14 @@ start_slave(Config,Level) ->
test_server:format(Level, "Dirs added to code path (on ~w):~n",
[CTNode]),
[io:format("~s~n", [D]) || D <- PathDirs],
+
+ case proplists:get_value(start_sasl, Config) of
+ true ->
+ rpc:call(CTNode, application, start, [sasl]),
+ test_server:format(Level, "SASL started on ~w~n", [CTNode]);
+ _ ->
+ ok
+ end,
TraceFile = filename:join(DataDir, "ct.trace"),
case file:read_file_info(TraceFile) of
@@ -372,6 +385,16 @@ wait_for_ct_stop(Retries, CTNode) ->
end.
%%%-----------------------------------------------------------------
+%%% ct_rpc/1
+ct_rpc({M,F,A}, Config) ->
+ CTNode = proplists:get_value(ct_node, Config),
+ Level = proplists:get_value(trace_level, Config),
+ test_server:format(Level, "~nCalling ~w:~w(~p) on ~p...",
+ [M,F,A, CTNode]),
+ rpc:call(CTNode, M, F, A).
+
+
+%%%-----------------------------------------------------------------
%%% EVENT HANDLING
handle_event(EH, Event) ->
diff --git a/lib/common_test/test/ct_verbosity_SUITE.erl b/lib/common_test/test/ct_verbosity_SUITE.erl
index 32488b1db9..1aa71953ec 100644
--- a/lib/common_test/test/ct_verbosity_SUITE.erl
+++ b/lib/common_test/test/ct_verbosity_SUITE.erl
@@ -53,9 +53,19 @@ init_per_suite(Config) ->
end_per_suite(Config) ->
ct_test_support:end_per_suite(Config).
+init_per_testcase(no_crashing, Config) ->
+ Opts = ct_test_support:start_slave(ctX, Config, 50),
+ XNode = proplists:get_value(ct_node, Opts),
+ ct:pal("Node ~p started!", [XNode]),
+ [{xnode,XNode} | Config];
init_per_testcase(TestCase, Config) ->
ct_test_support:init_per_testcase(TestCase, Config).
+end_per_testcase(no_crashing, Config) ->
+ XNode = proplists:get_value(xnode, Config),
+ ct_test_support:slave_stop(XNode),
+ ct:pal("Node ~p stopped!", [XNode]),
+ ok;
end_per_testcase(TestCase, Config) ->
ct_test_support:end_per_testcase(TestCase, Config).
@@ -72,7 +82,8 @@ all() ->
combine_categories,
testspec_only,
merge_with_testspec,
- possible_deadlock
+ possible_deadlock,
+ no_crashing
].
%%--------------------------------------------------------------------
@@ -189,6 +200,19 @@ possible_deadlock(Config) ->
%%%-----------------------------------------------------------------
+%%%
+no_crashing(Config) ->
+ XNode = proplists:get_value(xnode, Config),
+ ok = rpc:call(XNode, ct, print, ["hello",[]]),
+ ok = rpc:call(XNode, ct, pal, ["hello",[]]),
+ ok = rpc:call(XNode, ct, log, ["hello",[]]),
+ Data = io_lib:format("hello", []),
+ {badrpc,{'EXIT',{noproc,_}}} =
+ (catch rpc:call(XNode, test_server_io, print_unexpected, [Data])),
+ ok.
+
+
+%%%-----------------------------------------------------------------
%%% HELP FUNCTIONS
%%%-----------------------------------------------------------------