aboutsummaryrefslogtreecommitdiffstats
path: root/lib/common_test/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/common_test/src')
-rw-r--r--lib/common_test/src/ct.erl37
-rw-r--r--lib/common_test/src/ct_config.erl5
-rw-r--r--lib/common_test/src/ct_config_plain.erl2
-rw-r--r--lib/common_test/src/ct_conn_log_h.erl4
-rw-r--r--lib/common_test/src/ct_default_gl.erl1
-rw-r--r--lib/common_test/src/ct_event.erl1
-rw-r--r--lib/common_test/src/ct_framework.erl7
-rw-r--r--lib/common_test/src/ct_ftp.erl2
-rw-r--r--lib/common_test/src/ct_gen_conn.erl9
-rw-r--r--lib/common_test/src/ct_hooks_lock.erl1
-rw-r--r--lib/common_test/src/ct_logs.erl103
-rw-r--r--lib/common_test/src/ct_master.erl4
-rw-r--r--lib/common_test/src/ct_master_event.erl1
-rw-r--r--lib/common_test/src/ct_master_logs.erl19
-rw-r--r--lib/common_test/src/ct_netconfc.erl2
-rw-r--r--lib/common_test/src/ct_release_test.erl2
-rw-r--r--lib/common_test/src/ct_repeat.erl2
-rw-r--r--lib/common_test/src/ct_run.erl22
-rw-r--r--lib/common_test/src/ct_slave.erl56
-rw-r--r--lib/common_test/src/ct_ssh.erl3
-rw-r--r--lib/common_test/src/ct_telnet_client.erl1
-rw-r--r--lib/common_test/src/ct_testspec.erl7
-rw-r--r--lib/common_test/src/ct_util.erl86
-rw-r--r--lib/common_test/src/ct_webtool.erl1
-rw-r--r--lib/common_test/src/ct_webtool_sup.erl1
-rw-r--r--lib/common_test/src/cth_log_redirect.erl5
-rw-r--r--lib/common_test/src/cth_surefire.erl11
-rw-r--r--lib/common_test/src/test_server.erl33
-rw-r--r--lib/common_test/src/test_server_ctrl.erl66
-rw-r--r--lib/common_test/src/test_server_gl.erl1
-rw-r--r--lib/common_test/src/test_server_io.erl9
-rw-r--r--lib/common_test/src/test_server_node.erl11
-rw-r--r--lib/common_test/src/test_server_sup.erl3
-rw-r--r--lib/common_test/src/unix_telnet.erl3
-rw-r--r--lib/common_test/src/vts.erl2
35 files changed, 372 insertions, 151 deletions
diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl
index a12c0c9101..69e371a30f 100644
--- a/lib/common_test/src/ct.erl
+++ b/lib/common_test/src/ct.erl
@@ -89,6 +89,8 @@
-export([get_target_name/1]).
-export([parse_table/1, listenv/1]).
+-export([remaining_test_procs/0]).
+
%%----------------------------------------------------------------------
%% Exported types
%%----------------------------------------------------------------------
@@ -1030,7 +1032,7 @@ make_and_load(Dir, Suite) ->
EnvInclude =
case os:getenv("CT_INCLUDE_PATH") of
false -> [];
- CtInclPath -> string:tokens(CtInclPath, [$:,$ ,$,])
+ CtInclPath -> string:lexemes(CtInclPath, [$:,$ ,$,])
end,
StartInclude =
case init:get_argument(include) of
@@ -1474,3 +1476,36 @@ continue() ->
%%% in order to let the test case proceed.</p>
continue(TestCase) ->
test_server:continue(TestCase).
+
+
+%%%-----------------------------------------------------------------
+%%% @spec remaining_test_procs() -> {TestProcs,SharedGL,OtherGLs}
+%%% TestProcs = [{pid(),GL}]
+%%% GL = SharedGL = pid()
+%%% OtherGLs = [pid()]
+%%%
+%%% @doc <p>This function will return the identity of test- and group
+%%% leader processes that are still running at the time of this call.
+%%% TestProcs are processes in the system that have a Common Test IO
+%%% process as group leader. SharedGL is the central Common Test
+%%% IO process, responsible for printing to log files for configuration
+%%% functions and sequentially executing test cases. OtherGLs are
+%%% Common Test IO processes that print to log files for test cases
+%%% in parallel test case groups.</p>
+%%% <p>The process information returned by this function may be
+%%% used to locate and terminate remaining processes after tests have
+%%% finished executing. The function would typically by called from
+%%% Common Test Hook functions.</p>
+%%% <p>Note that processes that execute configuration functions or
+%%% test cases are never included in TestProcs. It is therefore safe
+%%% to use post configuration hook functions (such as post_end_per_suite,
+%%% post_end_per_group, post_end_per_testcase) to terminate all processes
+%%% in TestProcs that have the current group leader process as its group
+%%% leader.</p>
+%%% <p>Note also that the shared group leader (SharedGL) must never be
+%%% terminated by the user, only by Common Test. Group leader processes
+%%% for parallel test case groups (OtherGLs) may however be terminated
+%%% in post_end_per_group hook functions.</p>
+%%%
+remaining_test_procs() ->
+ ct_util:remaining_test_procs().
diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl
index d48ae830bb..6c87b11f8d 100644
--- a/lib/common_test/src/ct_config.erl
+++ b/lib/common_test/src/ct_config.erl
@@ -81,6 +81,7 @@ start(Mode) ->
do_start(Parent) ->
process_flag(trap_exit,true),
+ ct_util:mark_process(),
register(ct_config_server,self()),
ct_util:create_table(?attr_table,bag,#ct_conf.key),
{ok,StartDir} = file:get_cwd(),
@@ -659,7 +660,7 @@ decrypt_config_file(EncryptFileName, TargetFileName, {key,Key}) ->
get_crypt_key_from_file(File) ->
case file:read_file(File) of
{ok,Bin} ->
- case catch string:tokens(binary_to_list(Bin), [$\n,$\r]) of
+ case catch string:lexemes(binary_to_list(Bin), [$\n,$\r]) of
[Key] ->
Key;
_ ->
@@ -693,7 +694,7 @@ get_crypt_key_from_file() ->
noent ->
Result;
_ ->
- case catch string:tokens(binary_to_list(Result), [$\n,$\r]) of
+ case catch string:lexemes(binary_to_list(Result), [$\n,$\r]) of
[Key] ->
io:format("~nCrypt key file: ~ts~n", [FullName]),
Key;
diff --git a/lib/common_test/src/ct_config_plain.erl b/lib/common_test/src/ct_config_plain.erl
index e72b55971b..e77381d7cf 100644
--- a/lib/common_test/src/ct_config_plain.erl
+++ b/lib/common_test/src/ct_config_plain.erl
@@ -106,7 +106,7 @@ read_config_terms1({done,{eof,EL},_}, L, _, _) ->
read_config_terms1({done,{error,Info,EL},_}, L, _, _) ->
{error,{Info,{L,EL}}};
read_config_terms1({more,_}, L, Terms, Rest) ->
- case string:tokens(Rest, [$\n,$\r,$\t]) of
+ case string:lexemes(Rest, [$\n,$\r,$\t]) of
[] ->
lists:reverse(Terms);
_ ->
diff --git a/lib/common_test/src/ct_conn_log_h.erl b/lib/common_test/src/ct_conn_log_h.erl
index cf0a228e1b..3e83020f45 100644
--- a/lib/common_test/src/ct_conn_log_h.erl
+++ b/lib/common_test/src/ct_conn_log_h.erl
@@ -224,7 +224,7 @@ now_to_time({_,_,MicroS}=Now) ->
{calendar:now_to_local_time(Now),MicroS}.
pretty_head({{{Y,Mo,D},{H,Mi,S}},MicroS},ConnMod,Text0) ->
- Text = string:to_upper(atom_to_list(ConnMod) ++ Text0),
+ Text = string:uppercase(atom_to_list(ConnMod) ++ Text0),
io_lib:format("= ~s ==== ~s-~s-~w::~s:~s:~s,~s ",
[Text,t(D),month(Mo),Y,t(H),t(Mi),t(S),
micro2milli(MicroS)]).
@@ -275,7 +275,7 @@ pad0(N,Str) ->
lists:duplicate(N-M,$0) ++ Str.
pad_char_end(N,Str,Char) ->
- case length(lists:flatten(Str)) of
+ case string:length(Str) of
M when M<N -> Str ++ lists:duplicate(N-M,Char);
_ -> Str
end.
diff --git a/lib/common_test/src/ct_default_gl.erl b/lib/common_test/src/ct_default_gl.erl
index d1b52e5f4f..9ae430c546 100644
--- a/lib/common_test/src/ct_default_gl.erl
+++ b/lib/common_test/src/ct_default_gl.erl
@@ -55,6 +55,7 @@ stop() ->
init([ParentGL]) ->
register(?MODULE, self()),
+ ct_util:mark_process(),
{ok,#{parent_gl_pid => ParentGL,
parent_gl_monitor => erlang:monitor(process,ParentGL)}}.
diff --git a/lib/common_test/src/ct_event.erl b/lib/common_test/src/ct_event.erl
index 1a0ee4f3cd..8b5bba7600 100644
--- a/lib/common_test/src/ct_event.erl
+++ b/lib/common_test/src/ct_event.erl
@@ -137,6 +137,7 @@ is_alive() ->
%% this function is called to initialize the event handler.
%%--------------------------------------------------------------------
init(RecvPids) ->
+ ct_util:mark_process(),
%% RecvPids = [{RecvTag,Pid}]
{ok,#state{receivers=RecvPids}}.
diff --git a/lib/common_test/src/ct_framework.erl b/lib/common_test/src/ct_framework.erl
index 6066470233..7f7e9ae6f9 100644
--- a/lib/common_test/src/ct_framework.erl
+++ b/lib/common_test/src/ct_framework.erl
@@ -921,9 +921,10 @@ error_notification(Mod,Func,_Args,{Error,Loc}) ->
end,
ErrorStr = case ErrorSpec of
{badmatch,Descr} ->
- Descr1 = lists:flatten(io_lib:format("~tP",[Descr,10])),
- if length(Descr1) > 50 ->
- Descr2 = string:substr(Descr1,1,50),
+ Descr1 = io_lib:format("~tP",[Descr,10]),
+ DescrLength = string:length(Descr1),
+ if DescrLength > 50 ->
+ Descr2 = string:slice(Descr1,0,50),
io_lib:format("{badmatch,~ts...}",[Descr2]);
true ->
io_lib:format("{badmatch,~ts}",[Descr1])
diff --git a/lib/common_test/src/ct_ftp.erl b/lib/common_test/src/ct_ftp.erl
index 8effb06e7e..ee4a6a6c45 100644
--- a/lib/common_test/src/ct_ftp.erl
+++ b/lib/common_test/src/ct_ftp.erl
@@ -285,7 +285,7 @@ init(KeyOrName,{IP,Port},{Username,Password}) ->
{ok,FtpPid} ->
log(heading(init,KeyOrName),
"Opened ftp connection:\nIP: ~tp\nUsername: ~tp\nPassword: ~p\n",
- [IP,Username,lists:duplicate(length(Password),$*)]),
+ [IP,Username,lists:duplicate(string:length(Password),$*)]),
{ok,FtpPid,#state{ftp_pid=FtpPid,target_name=KeyOrName}};
Error ->
Error
diff --git a/lib/common_test/src/ct_gen_conn.erl b/lib/common_test/src/ct_gen_conn.erl
index badb7c52ae..456bfd8bd1 100644
--- a/lib/common_test/src/ct_gen_conn.erl
+++ b/lib/common_test/src/ct_gen_conn.erl
@@ -186,9 +186,11 @@ end_log() ->
do_within_time(Fun,Timeout) ->
Self = self(),
Silent = get(silent),
- TmpPid = spawn_link(fun() -> put(silent,Silent),
- R = Fun(),
- Self ! {self(),R}
+ TmpPid = spawn_link(fun() ->
+ ct_util:mark_process(),
+ put(silent,Silent),
+ R = Fun(),
+ Self ! {self(),R}
end),
ConnPid = get(conn_pid),
receive
@@ -301,6 +303,7 @@ return({To,Ref},Result) ->
init_gen(Parent,Opts) ->
process_flag(trap_exit,true),
+ ct_util:mark_process(),
put(silent,false),
try (Opts#gen_opts.callback):init(Opts#gen_opts.name,
Opts#gen_opts.address,
diff --git a/lib/common_test/src/ct_hooks_lock.erl b/lib/common_test/src/ct_hooks_lock.erl
index fea298e535..a82be288e1 100644
--- a/lib/common_test/src/ct_hooks_lock.erl
+++ b/lib/common_test/src/ct_hooks_lock.erl
@@ -78,6 +78,7 @@ release() ->
%% @doc Initiates the server
init(Id) ->
+ ct_util:mark_process(),
{ok, #state{ id = Id }}.
%% @doc Handling call messages
diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl
index ba7660fe6a..9861b1e521 100644
--- a/lib/common_test/src/ct_logs.erl
+++ b/lib/common_test/src/ct_logs.erl
@@ -666,6 +666,7 @@ log_timestamp({MS,S,US}) ->
logger(Parent, Mode, Verbosity) ->
register(?MODULE,self()),
+ ct_util:mark_process(),
%%! Below is a temporary workaround for the limitation of
%%! max one test run per second.
%%! --->
@@ -1004,6 +1005,7 @@ print_to_log(async, FromPid, Category, TCGL, Content, EscChars, State) ->
if FromPid /= TCGL ->
IoFun = create_io_fun(FromPid, CtLogFd, EscChars),
fun() ->
+ ct_util:mark_process(),
test_server:permit_io(TCGL, self()),
%% Since asynchronous io gets can get buffered if
@@ -1035,6 +1037,7 @@ print_to_log(async, FromPid, Category, TCGL, Content, EscChars, State) ->
end;
true ->
fun() ->
+ ct_util:mark_process(),
unexpected_io(FromPid, Category, ?MAX_IMPORTANCE,
Content, CtLogFd, EscChars)
end
@@ -1188,26 +1191,23 @@ print_style(Fd, IoFormat, StyleSheet) ->
case file:read_file(StyleSheet) of
{ok,Bin} ->
Str = b2s(Bin,encoding(StyleSheet)),
- Pos0 = case string:str(Str,"<style>") of
- 0 -> string:str(Str,"<STYLE>");
- N0 -> N0
- end,
- Pos1 = case string:str(Str,"</style>") of
- 0 -> string:str(Str,"</STYLE>");
- N1 -> N1
- end,
- if (Pos0 == 0) and (Pos1 /= 0) ->
- print_style_error(Fd, IoFormat,
- StyleSheet, missing_style_start_tag);
- (Pos0 /= 0) and (Pos1 == 0) ->
- print_style_error(Fd, IoFormat,
- StyleSheet,missing_style_end_tag);
- Pos0 /= 0 ->
- Style = string:sub_string(Str,Pos0,Pos1+7),
- IoFormat(Fd,"~ts\n",[Style]);
- Pos0 == 0 ->
- IoFormat(Fd,"<style>\n~ts</style>\n",[Str])
- end;
+ case re:run(Str,"<style>.*</style>",
+ [dotall,caseless,{capture,all,list}]) of
+ nomatch ->
+ case re:run(Str,"</?style>",[caseless,{capture,all,list}]) of
+ nomatch ->
+ IoFormat(Fd,"<style>\n~ts</style>\n",[Str]);
+ {match,["</"++_]} ->
+ print_style_error(Fd, IoFormat,
+ StyleSheet,
+ missing_style_start_tag);
+ {match,[_]} ->
+ print_style_error(Fd, IoFormat,
+ StyleSheet,missing_style_end_tag)
+ end;
+ {match,[Style]} ->
+ IoFormat(Fd,"~ts\n",[Style])
+ end;
{error,Reason} ->
print_style_error(Fd,IoFormat,StyleSheet,Reason)
end.
@@ -1414,9 +1414,9 @@ make_one_index_entry1(SuiteName, Link, Label, Success, Fail, UserSkip, AutoSkip,
{Lbl,Timestamp,Node,AllInfo} =
case All of
{true,OldRuns} ->
- [_Prefix,NodeOrDate|_] = string:tokens(Link,"."),
- Node1 = case string:chr(NodeOrDate,$@) of
- 0 -> "-";
+ [_Prefix,NodeOrDate|_] = string:lexemes(Link,"."),
+ Node1 = case string:find(NodeOrDate,[$@]) of
+ nomatch -> "-";
_ -> NodeOrDate
end,
@@ -1523,7 +1523,7 @@ not_built(BaseName,_LogDir,_All,Missing) ->
%% Top.ObjDir | Top.ObjDir.suites | Top.ObjDir.Suite |
%% Top.ObjDir.Suite.cases | Top.ObjDir.Suite.Case
Failed =
- case string:tokens(BaseName,".") of
+ case string:lexemes(BaseName,".") of
[T,O] when is_list(T) -> % all under Top.ObjDir
locate_info({T,O},all,Missing);
[T,O,"suites"] ->
@@ -2051,9 +2051,9 @@ sort_all_runs(Dirs) ->
%% "YYYY-MM-DD_HH.MM.SS"
lists:sort(fun(Dir1,Dir2) ->
[SS1,MM1,HH1,Date1|_] =
- lists:reverse(string:tokens(Dir1,[$.,$_])),
+ lists:reverse(string:lexemes(Dir1,[$.,$_])),
[SS2,MM2,HH2,Date2|_] =
- lists:reverse(string:tokens(Dir2,[$.,$_])),
+ lists:reverse(string:lexemes(Dir2,[$.,$_])),
{Date1,HH1,MM1,SS1} > {Date2,HH2,MM2,SS2}
end, Dirs).
@@ -2063,9 +2063,9 @@ sort_ct_runs(Dirs) ->
lists:sort(
fun(Dir1,Dir2) ->
[SS1,MM1,DateHH1 | _] =
- lists:reverse(string:tokens(filename:dirname(Dir1),[$.])),
+ lists:reverse(string:lexemes(filename:dirname(Dir1),[$.])),
[SS2,MM2,DateHH2 | _] =
- lists:reverse(string:tokens(filename:dirname(Dir2),[$.])),
+ lists:reverse(string:lexemes(filename:dirname(Dir2),[$.])),
{DateHH1,MM1,SS1} =< {DateHH2,MM2,SS2}
end, Dirs).
@@ -2211,27 +2211,15 @@ runentry(Dir, Totals={Node,Label,Logs,
0 -> "-";
N -> integer_to_list(N)
end,
- StripExt =
- fun(File) ->
- string:sub_string(File,1,
- length(File)-
- length(?logdir_ext)) ++ ", "
- end,
- Polish = fun(S) -> case lists:reverse(S) of
- [32,$,|Rev] -> lists:reverse(Rev);
- [$,|Rev] -> lists:reverse(Rev);
- _ -> S
- end
- end,
- TestNames = Polish(lists:flatten(lists:map(StripExt,Logs))),
+
+ RootNames = lists:map(fun(F) -> filename:rootname(F,?logdir_ext) end, Logs),
+ TestNames = lists:flatten(lists:join(", ", RootNames)),
TestNamesTrunc =
- if TestNames=="" ->
- "";
- length(TestNames) < ?testname_width ->
+ if length(TestNames) < ?testname_width ->
TestNames;
true ->
- Trunc = Polish(string:substr(TestNames,1,
- ?testname_width-3)),
+ Trunc = string:trim(string:slice(TestNames,0,?testname_width-3),
+ trailing,",\s"),
lists:flatten(io_lib:format("~ts...",[Trunc]))
end,
TotMissingStr =
@@ -2374,7 +2362,7 @@ force_rename(From,To,Number) ->
timestamp(Dir) ->
- TsR = lists:reverse(string:tokens(Dir,".-_")),
+ TsR = lists:reverse(string:lexemes(Dir,".-_")),
[S,Min,H,D,M,Y] = [list_to_integer(N) || N <- lists:sublist(TsR,6)],
format_time({{Y,M,D},{H,Min,S}}).
@@ -2923,7 +2911,7 @@ cache_vsn() ->
VSNfile = filename:join([EbinDir,"..","vsn.mk"]),
case file:read_file(VSNfile) of
{ok,Bin} ->
- [_,VSN] = string:tokens(binary_to_list(Bin),[$=,$\n,$ ]),
+ [_,VSN] = string:lexemes(binary_to_list(Bin),[$=,$\n,$ ]),
VSN;
_ ->
undefined
@@ -3017,6 +3005,7 @@ simulate() ->
S = self(),
Pid = spawn(fun() ->
register(?MODULE,self()),
+ ct_util:mark_process(),
S ! {self(),started},
simulate_logger_loop()
end),
@@ -3144,8 +3133,8 @@ locate_priv_file(FileName) ->
filename:join(get(ct_run_dir), FileName);
_ ->
%% executed on other process than ct_logs
- {ok,RunDir} = get_log_dir(true),
- filename:join(RunDir, FileName)
+ {ok,LogDir} = get_log_dir(true),
+ filename:join(LogDir, FileName)
end,
case filelib:is_file(PrivResultFile) of
true ->
@@ -3227,6 +3216,10 @@ get_ts_html_wrapper(TestName, Logdir, PrintLabel, Cwd, TableCols, Encoding) ->
?all_runs_name), Cwd),
TestIndex = make_relative(filename:join(filename:dirname(CtLogdir),
?index_name), Cwd),
+ LatestTest = make_relative(filename:join(filename:dirname(CtLogdir),
+ ?suitelog_name++".latest.html"),
+ Cwd),
+
case Basic of
true ->
TileFile = filename:join(filename:join(CTPath,"priv"),"tile1.jpg"),
@@ -3253,7 +3246,9 @@ get_ts_html_wrapper(TestName, Logdir, PrintLabel, Cwd, TableCols, Encoding) ->
"<a href=\"", uri(AllRuns),
"\">Test run history\n</a> | ",
"<a href=\"", uri(TestIndex),
- "\">Top level test index\n</a>\n</p>\n",
+ "\">Top level test index\n</a> | ",
+ "<a href=\"", uri(LatestTest),
+ "\">Latest test result</a>\n</p>\n",
Copyright,"</center>\n</body>\n</html>\n"]};
_ ->
Copyright =
@@ -3300,7 +3295,9 @@ get_ts_html_wrapper(TestName, Logdir, PrintLabel, Cwd, TableCols, Encoding) ->
"<a href=\"", uri(AllRuns),
"\">Test run history\n</a> | ",
"<a href=\"", uri(TestIndex),
- "\">Top level test index\n</a>\n</p>\n",
+ "\">Top level test index\n</a> | ",
+ "<a href=\"", uri(LatestTest),
+ "\">Latest test result</a>\n</p>\n",
Copyright,"</center>\n</body>\n</html>\n"]}
end.
@@ -3320,7 +3317,7 @@ insert_javascript({tablesorter,TableName,
end, [{"CTDateSorter",DateCols},
{"CTTextSorter",TextCols},
{"CTValSorter",ValCols}]))),
- Headers1 = string:substr(Headers, 1, length(Headers)-2),
+ Headers1 = string:trim(Headers, trailing, ",\n"),
["<script type=\"text/javascript\">\n",
"// Parser for date format, e.g: Wed Jul 4 2012 11:24:15\n",
diff --git a/lib/common_test/src/ct_master.erl b/lib/common_test/src/ct_master.erl
index 6e6d1879c2..e2ea525cdd 100644
--- a/lib/common_test/src/ct_master.erl
+++ b/lib/common_test/src/ct_master.erl
@@ -346,6 +346,7 @@ init_master(Parent,NodeOptsList,EvHandlers,MasterLogDir,LogDirs,
case whereis(ct_master) of
undefined ->
register(ct_master,self()),
+ ct_util:mark_process(),
ok;
_Pid ->
io:format("~nWarning: ct_master already running!~n"),
@@ -690,6 +691,7 @@ refresh_logs([],Refreshed) ->
init_node_ctrl(MasterPid,Cookie,Opts) ->
%% make sure tests proceed even if connection to master is lost
process_flag(trap_exit, true),
+ ct_util:mark_process(),
MasterNode = node(MasterPid),
group_leader(whereis(user),self()),
io:format("~n********** node_ctrl process ~w started on ~w **********~n",
@@ -807,7 +809,7 @@ filter_accessible(InitOptions, Inaccessible)->
start_nodes(InitOptions)->
lists:foreach(fun({NodeName, Options})->
- [NodeS,HostS]=string:tokens(atom_to_list(NodeName), "@"),
+ [NodeS,HostS]=string:lexemes(atom_to_list(NodeName), "@"),
Node=list_to_atom(NodeS),
Host=list_to_atom(HostS),
HasNodeStart = lists:keymember(node_start, 1, Options),
diff --git a/lib/common_test/src/ct_master_event.erl b/lib/common_test/src/ct_master_event.erl
index d535d1274e..bd4d1efc92 100644
--- a/lib/common_test/src/ct_master_event.erl
+++ b/lib/common_test/src/ct_master_event.erl
@@ -116,6 +116,7 @@ sync_notify(Event) ->
%% this function is called to initialize the event handler.
%%--------------------------------------------------------------------
init(_) ->
+ ct_util:mark_process(),
ct_master_logs:log("CT Master Event Handler started","",[]),
{ok,#state{}}.
diff --git a/lib/common_test/src/ct_master_logs.erl b/lib/common_test/src/ct_master_logs.erl
index d8ecd641ed..1308720823 100644
--- a/lib/common_test/src/ct_master_logs.erl
+++ b/lib/common_test/src/ct_master_logs.erl
@@ -88,6 +88,7 @@ stop() ->
init(Parent,LogDir,Nodes) ->
register(?MODULE,self()),
+ ct_util:mark_process(),
Time = calendar:local_time(),
RunDir = make_dirname(Time),
RunDirAbs = filename:join(LogDir,RunDir),
@@ -297,7 +298,7 @@ sort_all_runs(Dirs) ->
%% "YYYY-MM-DD_HH.MM.SS"
KeyList =
lists:map(fun(Dir) ->
- case lists:reverse(string:tokens(Dir,[$.,$_])) of
+ case lists:reverse(string:lexemes(Dir,[$.,$_])) of
[SS,MM,HH,Date|_] ->
{{Date,HH,MM,SS},Dir};
_Other ->
@@ -312,18 +313,8 @@ runentry(Dir) ->
{MasterStr,NodesStr} =
case read_details_file(Dir) of
{Master,Nodes} when is_list(Nodes) ->
- [_,Host] = string:tokens(atom_to_list(Master),"@"),
- NodesList =
- lists:reverse(lists:map(fun(N) ->
- atom_to_list(N) ++ ", "
- end,Nodes)),
- case NodesList of
- [N|NListR] ->
- N1 = string:sub_string(N,1,length(N)-2),
- {Host,lists:flatten(lists:reverse([N1|NListR]))};
- [] ->
- {Host,""}
- end;
+ [_,Host] = string:lexemes(atom_to_list(Master),"@"),
+ {Host,lists:concat(lists:join(", ",Nodes))};
_Error ->
{"unknown",""}
end,
@@ -348,7 +339,7 @@ all_runs_header() ->
xhtml("", "</tr></thead>\n<tbody>\n")]].
timestamp(Dir) ->
- [S,Min,H,D,M,Y|_] = lists:reverse(string:tokens(Dir,".-_")),
+ [S,Min,H,D,M,Y|_] = lists:reverse(string:lexemes(Dir,".-_")),
[S1,Min1,H1,D1,M1,Y1] = [list_to_integer(N) || N <- [S,Min,H,D,M,Y]],
format_time({{Y1,M1,D1},{H1,Min1,S1}}).
diff --git a/lib/common_test/src/ct_netconfc.erl b/lib/common_test/src/ct_netconfc.erl
index 2c4b97df20..29188a648e 100644
--- a/lib/common_test/src/ct_netconfc.erl
+++ b/lib/common_test/src/ct_netconfc.erl
@@ -1467,7 +1467,7 @@ decode_data(Other) ->
{error,{unexpected_rpc_reply,Other}}.
get_qualified_name(Tag) ->
- case string:tokens(atom_to_list(Tag),":") of
+ case string:lexemes(atom_to_list(Tag),":") of
[TagStr] -> {[],TagStr};
[PrefixStr,TagStr] -> {PrefixStr,TagStr}
end.
diff --git a/lib/common_test/src/ct_release_test.erl b/lib/common_test/src/ct_release_test.erl
index 551a7e06d7..4af9afb45b 100644
--- a/lib/common_test/src/ct_release_test.erl
+++ b/lib/common_test/src/ct_release_test.erl
@@ -817,7 +817,7 @@ get_runtime_deps([App|Apps],StartApps,Acc,Visited) ->
RuntimeDeps =
lists:flatmap(
fun(Str) ->
- [RuntimeAppStr,_] = string:tokens(Str,"-"),
+ [RuntimeAppStr,_] = string:lexemes(Str,"-"),
RuntimeApp = list_to_atom(RuntimeAppStr),
case {lists:keymember(RuntimeApp,1,Acc),
lists:member(RuntimeApp,StartApps)} of
diff --git a/lib/common_test/src/ct_repeat.erl b/lib/common_test/src/ct_repeat.erl
index c043c9846c..177ef37d1f 100644
--- a/lib/common_test/src/ct_repeat.erl
+++ b/lib/common_test/src/ct_repeat.erl
@@ -70,6 +70,7 @@ loop_test(If,Args) when is_list(Args) ->
CtrlPid = self(),
spawn(
fun() ->
+ ct_util:mark_process(),
stop_after(CtrlPid,Secs,ForceStop)
end)
end,
@@ -134,6 +135,7 @@ spawn_tester(script,Ctrl,Args) ->
spawn_tester(func,Ctrl,Opts) ->
Tester = fun() ->
+ ct_util:mark_process(),
case catch ct_run:run_test2(Opts) of
{'EXIT',Reason} ->
exit(Reason);
diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl
index 14f28f9ca3..8c401cf3f5 100644
--- a/lib/common_test/src/ct_run.erl
+++ b/lib/common_test/src/ct_run.erl
@@ -147,7 +147,7 @@ script_start(Args) ->
CTVsn =
case filename:basename(code:lib_dir(common_test)) of
CTBase when is_list(CTBase) ->
- case string:tokens(CTBase, "-") of
+ case string:lexemes(CTBase, "-") of
["common_test",Vsn] -> " v"++Vsn;
_ -> ""
end
@@ -250,6 +250,8 @@ finish(Tracing, ExitStatus, Args) ->
end.
script_start1(Parent, Args) ->
+ %% tag this process
+ ct_util:mark_process(),
%% read general start flags
Label = get_start_opt(label, fun([Lbl]) -> Lbl end, Args),
Profile = get_start_opt(profile, fun([Prof]) -> Prof end, Args),
@@ -315,7 +317,7 @@ script_start1(Parent, Args) ->
{undefined,InclDirs};
CtInclPath ->
AllInclDirs =
- string:tokens(CtInclPath,[$:,$ ,$,]) ++ InclDirs,
+ string:lexemes(CtInclPath,[$:,$ ,$,]) ++ InclDirs,
application:set_env(common_test, include, AllInclDirs),
{undefined,AllInclDirs}
end;
@@ -956,7 +958,10 @@ run_test(StartOpts) when is_list(StartOpts) ->
-spec run_test1_fun(_) -> fun(() -> no_return()).
run_test1_fun(StartOpts) ->
- fun() -> run_test1(StartOpts) end.
+ fun() ->
+ ct_util:mark_process(),
+ run_test1(StartOpts)
+ end.
run_test1(StartOpts) when is_list(StartOpts) ->
case proplists:get_value(refresh_logs, StartOpts) of
@@ -1096,7 +1101,7 @@ run_test2(StartOpts) ->
application:set_env(common_test, include, InclDirs),
{undefined,InclDirs};
CtInclPath ->
- InclDirs1 = string:tokens(CtInclPath, [$:,$ ,$,]),
+ InclDirs1 = string:lexemes(CtInclPath, [$:,$ ,$,]),
AllInclDirs = InclDirs1++InclDirs,
application:set_env(common_test, include, AllInclDirs),
{undefined,AllInclDirs}
@@ -1447,7 +1452,10 @@ run_testspec(TestSpec) ->
-spec run_testspec1_fun(_) -> fun(() -> no_return()).
run_testspec1_fun(TestSpec) ->
- fun() -> run_testspec1(TestSpec) end.
+ fun() ->
+ ct_util:mark_process(),
+ run_testspec1(TestSpec)
+ end.
run_testspec1(TestSpec) ->
{ok,Cwd} = file:get_cwd(),
@@ -1482,7 +1490,7 @@ run_testspec2(TestSpec) ->
false ->
Opts#opts.include;
CtInclPath ->
- EnvInclude = string:tokens(CtInclPath, [$:,$ ,$,]),
+ EnvInclude = string:lexemes(CtInclPath, [$:,$ ,$,]),
EnvInclude++Opts#opts.include
end,
application:set_env(common_test, include, AllInclude),
@@ -1906,10 +1914,12 @@ possibly_spawn(true, Tests, Skip, Opts) ->
CTUtilSrv = whereis(ct_util_server),
Supervisor =
fun() ->
+ ct_util:mark_process(),
process_flag(trap_exit, true),
link(CTUtilSrv),
TestRun =
fun() ->
+ ct_util:mark_process(),
TestResult = (catch do_run_test(Tests, Skip, Opts)),
case TestResult of
{EType,_} = Error when EType == user_error;
diff --git a/lib/common_test/src/ct_slave.erl b/lib/common_test/src/ct_slave.erl
index 4188bd7c3b..0c16ad5980 100644
--- a/lib/common_test/src/ct_slave.erl
+++ b/lib/common_test/src/ct_slave.erl
@@ -38,7 +38,8 @@
-record(options, {username, password, boot_timeout, init_timeout,
startup_timeout, startup_functions, monitor_master,
- kill_if_fail, erl_flags, env, ssh_port, ssh_opts}).
+ kill_if_fail, erl_flags, env, ssh_port, ssh_opts,
+ stop_timeout}).
%%%-----------------------------------------------------------------
%%% @spec start(Node) -> Result
@@ -198,6 +199,7 @@ start(Host, Node, Opts) ->
end
end.
+%%%-----------------------------------------------------------------
%%% @spec stop(Node) -> Result
%%% Node = atom()
%%% Result = {ok, NodeName} |
@@ -205,16 +207,41 @@ start(Host, Node, Opts) ->
%%% Reason = not_started |
%%% not_connected |
%%% stop_timeout
-
%%% NodeName = atom()
%%% @doc Stops the running Erlang node with name <code>Node</code> on
%%% the localhost.
stop(Node) ->
stop(gethostname(), Node).
-%%% @spec stop(Host, Node) -> Result
+%%%-----------------------------------------------------------------
+%%% @spec stop(HostOrNode, NodeOrOpts) -> Result
+%%% HostOrNode = atom()
+%%% NodeOrOpts = atom() | list()
+%%% Result = {ok, NodeName} |
+%%% {error, Reason, NodeName}
+%%% Reason = not_started |
+%%% not_connected |
+%%% stop_timeout
+%%% NodeName = atom()
+%%% @doc Stops the running Erlang node with default options on a specified
+%%% host, or on the local host with specified options. That is,
+%%% the call is interpreted as <code>stop(Host, Node)</code> when the
+%%% second argument is atom-valued and <code>stop(Node, Opts)</code>
+%%% when it's list-valued.
+%%% @see stop/3
+stop(_HostOrNode = Node, _NodeOrOpts = Opts) %% match to satiate edoc
+ when is_list(Opts) ->
+ stop(gethostname(), Node, Opts);
+
+stop(Host, Node) ->
+ stop(Host, Node, []).
+
+%%% @spec stop(Host, Node, Opts) -> Result
%%% Host = atom()
%%% Node = atom()
+%%% Opts = [OptTuples]
+%%% OptTuples = {stop_timeout, StopTimeout}
+%%% StopTimeout = integer()
%%% Result = {ok, NodeName} |
%%% {error, Reason, NodeName}
%%% Reason = not_started |
@@ -222,12 +249,19 @@ stop(Node) ->
%%% stop_timeout
%%% NodeName = atom()
%%% @doc Stops the running Erlang node with name <code>Node</code> on
-%%% host <code>Host</code>.
-stop(Host, Node) ->
+%%% host <code>Host</code> as specified by options <code>Opts</code>.
+%%%
+%%% <p>Option <code>stop_timeout</code> specifies, in seconds,
+%%% the time to wait until the node is disconnected.
+%%% Defaults to 5 seconds. If this timeout occurs,
+%%% the result <code>{error, stop_timeout, NodeName}</code> is returned.
+%%%
+stop(Host, Node, Opts) ->
ENode = enodename(Host, Node),
case is_started(ENode) of
{true, connected}->
- do_stop(ENode);
+ OptionsRec = fetch_options(Opts),
+ do_stop(ENode, OptionsRec);
{true, not_connected}->
{error, not_connected, ENode};
false->
@@ -257,11 +291,13 @@ fetch_options(Options) ->
EnvVars = get_option_value(env, Options, []),
SSHPort = get_option_value(ssh_port, Options, []),
SSHOpts = get_option_value(ssh_opts, Options, []),
+ StopTimeout = get_option_value(stop_timeout, Options, 5),
#options{username=UserName, password=Password,
boot_timeout=BootTimeout, init_timeout=InitTimeout,
startup_timeout=StartupTimeout, startup_functions=StartupFunctions,
monitor_master=Monitor, kill_if_fail=KillIfFail,
- erl_flags=ErlFlags, env=EnvVars, ssh_port=SSHPort, ssh_opts=SSHOpts}.
+ erl_flags=ErlFlags, env=EnvVars, ssh_port=SSHPort, ssh_opts=SSHOpts,
+ stop_timeout=StopTimeout}.
% send a message when slave node is started
% @hidden
@@ -282,6 +318,7 @@ monitor_master(MasterNode) ->
% code of the masterdeath-waiter process
monitor_master_int(MasterNode) ->
+ ct_util:mark_process(),
erlang:monitor_node(MasterNode, true),
receive
{nodedown, MasterNode}->
@@ -461,6 +498,8 @@ wait_for_node_alive(Node, N) ->
% call init:stop on a remote node
do_stop(ENode) ->
+ do_stop(ENode, fetch_options([])).
+do_stop(ENode, Options) ->
{Cover,MainCoverNode} =
case test_server:is_cover() of
true ->
@@ -471,7 +510,8 @@ do_stop(ENode) ->
{false,undefined}
end,
spawn(ENode, init, stop, []),
- case wait_for_node_dead(ENode, 5) of
+ StopTimeout = Options#options.stop_timeout,
+ case wait_for_node_dead(ENode, StopTimeout) of
{ok,ENode} ->
if Cover ->
%% To avoid that cover is started again if a node
diff --git a/lib/common_test/src/ct_ssh.erl b/lib/common_test/src/ct_ssh.erl
index ca62357e1c..491d56dfc1 100644
--- a/lib/common_test/src/ct_ssh.erl
+++ b/lib/common_test/src/ct_ssh.erl
@@ -996,7 +996,8 @@ init(KeyOrName, {ConnType,Addr,Port}, AllOpts) ->
try_log(heading(init,KeyOrName),
"Opened ~w connection:\n"
"Host: ~tp (~p)\nUser: ~tp\nPassword: ~p\n",
- [ConnType,Addr,Port,User,lists:duplicate(length(Password),$*)]),
+ [ConnType,Addr,Port,User,
+ lists:duplicate(string:length(Password),$*)]),
{ok,SSHRef,#state{ssh_ref=SSHRef, conn_type=ConnType,
target=KeyOrName}}
end.
diff --git a/lib/common_test/src/ct_telnet_client.erl b/lib/common_test/src/ct_telnet_client.erl
index c8d217cd2a..76e4b9ea70 100644
--- a/lib/common_test/src/ct_telnet_client.erl
+++ b/lib/common_test/src/ct_telnet_client.erl
@@ -118,6 +118,7 @@ get_data(Pid) ->
%%%-----------------------------------------------------------------
%%% Internal functions
init(Parent, Server, Port, Timeout, KeepAlive, NoDelay, ConnName) ->
+ ct_util:mark_process(),
case gen_tcp:connect(Server, Port, [list,{packet,0},{nodelay,NoDelay}], Timeout) of
{ok,Sock} ->
dbg("~tp connected to: ~tp (port: ~w, keep_alive: ~w)\n",
diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl
index bb445bb0d2..bd3755722f 100644
--- a/lib/common_test/src/ct_testspec.erl
+++ b/lib/common_test/src/ct_testspec.erl
@@ -1425,7 +1425,12 @@ skip_groups1(Suite,Groups,Cmt,Suites0) ->
GrAndCases1 = GrAndCases0 ++ SkipGroups,
insert_in_order({Suite,GrAndCases1},Suites0,replace);
false ->
- insert_in_order({Suite,SkipGroups},Suites0,replace)
+ case Suites0 of
+ [{all,_}=All|Skips]->
+ [All|Skips++[{Suite,SkipGroups}]];
+ _ ->
+ insert_in_order({Suite,SkipGroups},Suites0,replace)
+ end
end.
skip_cases(Node,Dir,Suite,Cases,Cmt,Tests,false) when is_list(Cases) ->
diff --git a/lib/common_test/src/ct_util.erl b/lib/common_test/src/ct_util.erl
index abf131f4df..10a06d5c88 100644
--- a/lib/common_test/src/ct_util.erl
+++ b/lib/common_test/src/ct_util.erl
@@ -65,6 +65,9 @@
-export([warn_duplicates/1]).
+-export([mark_process/0, mark_process/1, is_marked/1, is_marked/2,
+ remaining_test_procs/0]).
+
-export([get_profile_data/0, get_profile_data/1,
get_profile_data/2, open_url/3]).
@@ -126,6 +129,7 @@ start(Mode, LogDir, Verbosity) ->
do_start(Parent, Mode, LogDir, Verbosity) ->
process_flag(trap_exit,true),
register(ct_util_server,self()),
+ mark_process(),
create_table(?conn_table,#conn.handle),
create_table(?board_table,2),
create_table(?suite_table,#suite_data.key),
@@ -795,25 +799,25 @@ parse_table(Data) ->
{Heading,Lines}.
get_headings(["|" ++ Headings | Rest]) ->
- {remove_space(string:tokens(Headings, "|"),[]), Rest};
+ {remove_space(string:lexemes(Headings, "|"),[]), Rest};
get_headings([_ | Rest]) ->
get_headings(Rest);
get_headings([]) ->
{{},[]}.
parse_row(["|" ++ _ = Row | T], Rows, NumCols) when NumCols > 1 ->
- case string:tokens(Row, "|") of
+ case string:lexemes(Row, "|") of
Values when length(Values) =:= NumCols ->
parse_row(T,[remove_space(Values,[])|Rows], NumCols);
Values when length(Values) < NumCols ->
parse_row([Row ++"\n"++ hd(T) | tl(T)], Rows, NumCols)
end;
-parse_row(["|" ++ _ = Row | T], Rows, 1 = NumCols) ->
- case string:rchr(Row, $|) of
- 1 ->
+parse_row(["|" ++ X = Row | T], Rows, 1 = NumCols) ->
+ case string:find(X, [$|]) of
+ nomatch ->
parse_row([Row ++"\n"++hd(T) | tl(T)], Rows, NumCols);
_Else ->
- parse_row(T, [remove_space(string:tokens(Row,"|"),[])|Rows],
+ parse_row(T, [remove_space(string:lexemes(Row,"|"),[])|Rows],
NumCols)
end;
parse_row([_Skip | T], Rows, NumCols) ->
@@ -822,7 +826,7 @@ parse_row([], Rows, _NumCols) ->
lists:reverse(Rows).
remove_space([Str|Rest],Acc) ->
- remove_space(Rest,[string:strip(string:strip(Str),both,$')|Acc]);
+ remove_space(Rest,[string:trim(string:trim(Str,both,[$\s]),both,[$'])|Acc]);
remove_space([],Acc) ->
list_to_tuple(lists:reverse(Acc)).
@@ -832,7 +836,7 @@ remove_space([],Acc) ->
%%%
%%% @doc
is_test_dir(Dir) ->
- lists:last(string:tokens(filename:basename(Dir), "_")) == "test".
+ lists:last(string:lexemes(filename:basename(Dir), "_")) == "test".
%%%-----------------------------------------------------------------
%%% @spec
@@ -934,6 +938,70 @@ warn_duplicates(Suites) ->
%%% @spec
%%%
%%% @doc
+mark_process() ->
+ mark_process(system).
+
+mark_process(Type) ->
+ put(ct_process_type, Type).
+
+is_marked(Pid) ->
+ is_marked(Pid, system).
+
+is_marked(Pid, Type) ->
+ case process_info(Pid, dictionary) of
+ {dictionary,List} ->
+ Type == proplists:get_value(ct_process_type, List);
+ undefined ->
+ false
+ end.
+
+remaining_test_procs() ->
+ Procs = processes(),
+ {SharedGL,OtherGLs,Procs2} =
+ lists:foldl(
+ fun(Pid, ProcTypes = {Shared,Other,Procs1}) ->
+ case is_marked(Pid, group_leader) of
+ true ->
+ if not is_pid(Shared) ->
+ case test_server_io:get_gl(true) of
+ Pid ->
+ {Pid,Other,
+ lists:delete(Pid,Procs1)};
+ _ ->
+ {Shared,[Pid|Other],Procs1}
+ end;
+ true -> % SharedGL already found
+ {Shared,[Pid|Other],Procs1}
+ end;
+ false ->
+ case is_marked(Pid) of
+ true ->
+ {Shared,Other,lists:delete(Pid,Procs1)};
+ false ->
+ ProcTypes
+ end
+ end
+ end, {undefined,[],Procs}, Procs),
+
+ AllGLs = [SharedGL | OtherGLs],
+ TestProcs =
+ lists:flatmap(fun(Pid) ->
+ case process_info(Pid, group_leader) of
+ {group_leader,GL} ->
+ case lists:member(GL, AllGLs) of
+ true -> [{Pid,GL}];
+ false -> []
+ end;
+ undefined ->
+ []
+ end
+ end, Procs2),
+ {TestProcs, SharedGL, OtherGLs}.
+
+%%%-----------------------------------------------------------------
+%%% @spec
+%%%
+%%% @doc
get_profile_data() ->
get_profile_data(all).
@@ -1077,7 +1145,7 @@ open_url(iexplore, Args, URL) ->
_ = case win32reg:values(R) of
{ok, Paths} ->
Path = proplists:get_value(default, Paths),
- [Cmd | _] = string:tokens(Path, "%"),
+ [Cmd | _] = string:lexemes(Path, "%"),
Cmd1 = Cmd ++ " " ++ Args ++ " " ++ URL,
io:format(?def_gl, "~nOpening ~ts with command:~n ~ts~n", [URL,Cmd1]),
open_port({spawn,Cmd1}, []);
diff --git a/lib/common_test/src/ct_webtool.erl b/lib/common_test/src/ct_webtool.erl
index 9016aca899..82aa78fc4b 100644
--- a/lib/common_test/src/ct_webtool.erl
+++ b/lib/common_test/src/ct_webtool.erl
@@ -343,6 +343,7 @@ code_change(_,State,_)->
% Start the gen_server
%----------------------------------------------------------------------
init({Path,Config})->
+ ct_util:mark_process(),
case filelib:is_dir(Path) of
true ->
{ok, Table} = get_tool_files_data(),
diff --git a/lib/common_test/src/ct_webtool_sup.erl b/lib/common_test/src/ct_webtool_sup.erl
index c02ec69d04..6c6dbde0a6 100644
--- a/lib/common_test/src/ct_webtool_sup.erl
+++ b/lib/common_test/src/ct_webtool_sup.erl
@@ -46,6 +46,7 @@ stop(Pid)->
%% {error, Reason}
%%----------------------------------------------------------------------
init(_StartArgs) ->
+ ct_util:mark_process(),
%%Child1 =
%%Child2 ={webcover_backend,{webcover_backend,start_link,[]},permanent,2000,worker,[webcover_backend]},
%%{ok,{{simple_one_for_one,5,10},[Child1]}}.
diff --git a/lib/common_test/src/cth_log_redirect.erl b/lib/common_test/src/cth_log_redirect.erl
index 8b29d0f96d..b05f0bd28b 100644
--- a/lib/common_test/src/cth_log_redirect.erl
+++ b/lib/common_test/src/cth_log_redirect.erl
@@ -56,6 +56,7 @@ id(_Opts) ->
?MODULE.
init(?MODULE, _Opts) ->
+ ct_util:mark_process(),
error_logger:add_report_handler(?MODULE),
tc_log_async.
@@ -121,8 +122,8 @@ handle_event({_Type,GL,_Msg}, #eh_state{handle_remote_events = false} = State)
when node(GL) /= node() ->
{ok, State};
handle_event(Event, #eh_state{log_func = LogFunc} = State) ->
- case lists:keyfind(sasl, 1, application:which_applications()) of
- false ->
+ case whereis(sasl_sup) of
+ undefined ->
sasl_not_started;
_Else ->
{ok, ErrLogType} = application:get_env(sasl, errlog_type),
diff --git a/lib/common_test/src/cth_surefire.erl b/lib/common_test/src/cth_surefire.erl
index da68bd105e..4407ff56c1 100644
--- a/lib/common_test/src/cth_surefire.erl
+++ b/lib/common_test/src/cth_surefire.erl
@@ -184,15 +184,14 @@ end_tc(Name, _Config, _Res, State = #state{ curr_suite = Suite,
Log =
case Log0 of
"" ->
- LowerSuiteName = string:to_lower(atom_to_list(Suite)),
+ LowerSuiteName = string:lowercase(atom_to_list(Suite)),
filename:join(CurrLogDir,LowerSuiteName++"."++Name++".html");
_ ->
Log0
end,
Url = make_url(UrlBase,Log),
ClassName = atom_to_list(Suite),
- PGroup = string:join([ atom_to_list(Group)||
- Group <- lists:reverse(Groups)],"."),
+ PGroup = lists:concat(lists:join(".",lists:reverse(Groups))),
TimeTakes = io_lib:format("~f",[timer:now_diff(?now,TS) / 1000000]),
State#state{ test_cases = [#testcase{ log = Log,
url = Url,
@@ -317,9 +316,9 @@ make_url(undefined,_) ->
make_url(_,[]) ->
undefined;
make_url(UrlBase0,Log) ->
- UrlBase = string:strip(UrlBase0,right,$/),
+ UrlBase = string:trim(UrlBase0,trailing,[$/]),
RelativeLog = get_relative_log_url(Log),
- string:join([UrlBase,RelativeLog],"/").
+ lists:flatten(lists:join($/,[UrlBase,RelativeLog])).
get_test_root(Log) ->
LogParts = filename:split(Log),
@@ -329,7 +328,7 @@ get_relative_log_url(Log) ->
LogParts = filename:split(Log),
Start = length(LogParts)-?log_depth,
Length = ?log_depth+1,
- string:join(lists:sublist(LogParts,Start,Length),"/").
+ lists:flatten(lists:join($/,lists:sublist(LogParts,Start,Length))).
count_tcs([#testcase{name=ConfCase}|TCs],Ok,F,S)
when ConfCase=="init_per_suite";
diff --git a/lib/common_test/src/test_server.erl b/lib/common_test/src/test_server.erl
index dc6b7a536c..7250041e13 100644
--- a/lib/common_test/src/test_server.erl
+++ b/lib/common_test/src/test_server.erl
@@ -415,6 +415,7 @@ run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData) ->
St = #st{ref=Ref,pid=Pid,mf={Mod,Func},last_known_loc=unknown,
status=starting,ret_val=[],comment="",timeout=infinity,
config=hd(Args)},
+ ct_util:mark_process(),
run_test_case_msgloop(St).
%% Ugly bug (pre R5A):
@@ -462,11 +463,12 @@ run_test_case_msgloop(#st{ref=Ref,pid=Pid,end_conf_pid=EndConfPid0}=St0) ->
%% it as a comment, potentially replacing user data
Error = lists:flatten(io_lib:format("Aborted: ~tp",
[Reason])),
- Error1 = lists:flatten([string:strip(S,left) ||
- S <- string:tokens(Error,
- [$\n])]),
- Comment = if length(Error1) > 63 ->
- string:substr(Error1,1,60) ++ "...";
+ Error1 = lists:flatten([string:trim(S,leading,"\s") ||
+ S <- string:lexemes(Error,
+ [$\n])]),
+ ErrorLength = string:length(Error1),
+ Comment = if ErrorLength > 63 ->
+ string:slice(Error1,0,60) ++ "...";
true ->
Error1
end,
@@ -784,6 +786,7 @@ spawn_fw_call(Mod,IPTC={init_per_testcase,Func},CurrConf,Pid,
Why,Loc,SendTo) ->
FwCall =
fun() ->
+ ct_util:mark_process(),
Skip = {skip,{failed,{Mod,init_per_testcase,Why}}},
%% if init_per_testcase fails, the test case
%% should be skipped
@@ -814,6 +817,7 @@ spawn_fw_call(Mod,EPTC={end_per_testcase,Func},EndConf,Pid,
Why,_Loc,SendTo) ->
FwCall =
fun() ->
+ ct_util:mark_process(),
{RetVal,Report} =
case proplists:get_value(tc_status, EndConf) of
undefined ->
@@ -863,6 +867,7 @@ spawn_fw_call(Mod,EPTC={end_per_testcase,Func},EndConf,Pid,
spawn_fw_call(FwMod,FwFunc,_,_Pid,{framework_error,FwError},_,SendTo) ->
FwCall =
fun() ->
+ ct_util:mark_process(),
test_server_sup:framework_call(report, [framework_error,
{{FwMod,FwFunc},
FwError}]),
@@ -879,6 +884,7 @@ spawn_fw_call(FwMod,FwFunc,_,_Pid,{framework_error,FwError},_,SendTo) ->
spawn_link(FwCall);
spawn_fw_call(Mod,Func,CurrConf,Pid,Error,Loc,SendTo) ->
+ ct_util:mark_process(),
{Func1,EndTCFunc} = case Func of
CF when CF == init_per_suite; CF == end_per_suite;
CF == init_per_group; CF == end_per_group ->
@@ -917,6 +923,7 @@ start_job_proxy() ->
%% The io_reply_proxy is not the most satisfying solution but it works...
io_reply_proxy(ReplyTo) ->
+ ct_util:mark_process(),
receive
IoReply when is_tuple(IoReply),
element(1, IoReply) == io_reply ->
@@ -926,6 +933,7 @@ io_reply_proxy(ReplyTo) ->
end.
job_proxy_msgloop() ->
+ ct_util:mark_process(),
receive
%%
@@ -1803,6 +1811,7 @@ break(CBM, TestCase, Comment) ->
spawn_break_process(Pid, PName) ->
spawn(fun() ->
register(PName, self()),
+ ct_util:mark_process(),
receive
continue -> continue(Pid);
cancel -> ok
@@ -2000,6 +2009,7 @@ time_ms_apply(Func, TCPid, MultAndScale) ->
user_timetrap_supervisor(Func, Spawner, TCPid, GL, T0, MultAndScale) ->
process_flag(trap_exit, true),
+ ct_util:mark_process(),
Spawner ! {self(),infinity},
MonRef = monitor(process, TCPid),
UserTTSup = self(),
@@ -2570,6 +2580,7 @@ run_on_shielded_node(Fun, CArgs) when is_function(Fun), is_list(CArgs) ->
-spec start_job_proxy_fun(_, _) -> fun(() -> no_return()).
start_job_proxy_fun(Master, Fun) ->
fun () ->
+ ct_util:mark_process(),
_ = start_job_proxy(),
receive
Ref ->
@@ -2697,9 +2708,9 @@ is_cover() ->
is_debug() ->
case catch erlang:system_info(debug_compiled) of
{'EXIT', _} ->
- case string:str(erlang:system_info(system_version), "debug") of
- Int when is_integer(Int), Int > 0 -> true;
- _ -> false
+ case string:find(erlang:system_info(system_version), "debug") of
+ nomatch -> false;
+ _ -> true
end;
Res ->
Res
@@ -2735,9 +2746,9 @@ has_superfluous_schedulers() ->
%% We might want to do more tests on a commercial platform, for instance
%% ensuring that all applications have documentation).
is_commercial() ->
- case string:str(erlang:system_info(system_version), "source") of
- Int when is_integer(Int), Int > 0 -> false;
- _ -> true
+ case string:find(erlang:system_info(system_version), "source") of
+ nomatch -> true;
+ _ -> false
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/common_test/src/test_server_ctrl.erl b/lib/common_test/src/test_server_ctrl.erl
index 71978c7267..3a454a1e84 100644
--- a/lib/common_test/src/test_server_ctrl.erl
+++ b/lib/common_test/src/test_server_ctrl.erl
@@ -89,6 +89,7 @@
-define(logdir_ext, ".logs").
-define(data_dir_suffix, "_data/").
-define(suitelog_name, "suite.log").
+-define(suitelog_latest_name, "suite.log.latest").
-define(coverlog_name, "cover.html").
-define(raw_coverlog_name, "cover.log").
-define(cross_coverlog_name, "cross_cover.html").
@@ -1126,6 +1127,7 @@ init_tester(Mod, Func, Args, Dir, Name, {_,_,MinLev}=Levels,
RejectIoReqs, CreatePrivDir, TCCallback, ExtraTools) ->
process_flag(trap_exit, true),
_ = test_server_io:start_link(),
+ put(app, common_test),
put(test_server_name, Name),
put(test_server_dir, Dir),
put(test_server_total_time, 0),
@@ -1150,6 +1152,12 @@ init_tester(Mod, Func, Args, Dir, Name, {_,_,MinLev}=Levels,
end,
%% before first print, read and set logging options
+ FWLogDir =
+ case test_server_sup:framework_call(get_log_dir, [], []) of
+ {ok,FwDir} -> FwDir;
+ _ -> filename:dirname(Dir)
+ end,
+ put(test_server_framework_logdir, FWLogDir),
LogOpts = test_server_sup:framework_call(get_logopts, [], []),
put(test_server_logopts, LogOpts),
@@ -1464,13 +1472,14 @@ get_suites([], Mods) ->
lists:reverse(Mods).
add_mod(Mod, Mods) ->
- case string:rstr(atom_to_list(Mod), "_SUITE") of
- 0 -> false;
- _ -> % test suite
+ case lists:reverse(atom_to_list(Mod)) of
+ "ETIUS_" ++ _ -> % test suite
case lists:member(Mod, Mods) of
true -> false;
false -> true
- end
+ end;
+ _ ->
+ false
end.
@@ -1711,6 +1720,12 @@ start_log_file() ->
test_server_io:set_fd(html, Html),
test_server_io:set_fd(unexpected_io, Unexpected),
+ %% we must assume the redirection file (to the latest suite index) can
+ %% be stored on the level above the log directory of the current test
+ TopDir = filename:dirname(get(test_server_framework_logdir)),
+ RedirectLink = filename:join(TopDir, ?suitelog_latest_name ++ ?html_ext),
+ make_html_link(RedirectLink, HtmlName, redirect),
+
make_html_link(filename:absname(?last_test ++ ?html_ext),
HtmlName, filename:basename(Dir)),
LinkName = filename:join(Dir, ?last_link),
@@ -1739,11 +1754,18 @@ make_html_link(LinkName, Target, Explanation) ->
false ->
"file:" ++ uri_encode(Target)
end,
- H = [html_header(Explanation),
- "<h1>Last test</h1>\n"
- "<a href=\"",Href,"\">",Explanation,"</a>\n"
- "</body>\n</html>\n"],
+ H = if Explanation == redirect ->
+ Meta = ["<meta http-equiv=\"refresh\" "
+ "content=\"0; url=", Href, "\" />\n"],
+ [html_header("redirect", Meta), "</html>\n"];
+ true ->
+ [html_header(Explanation),
+ "<h1>Last test</h1>\n"
+ "<a href=\"",Href,"\">",Explanation,"</a>\n"
+ "</body>\n</html>\n"]
+ end,
ok = write_html_file(LinkName, H).
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% start_minor_log_file(Mod, Func, ParallelTC) -> AbsName
@@ -2611,7 +2633,7 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0,
NumStr ->
%% Ex: "123 456 789" or "123,456,789" -> {123,456,789}
list_to_tuple([list_to_integer(NS) ||
- NS <- string:tokens(NumStr, [$ ,$:,$,])])
+ NS <- string:lexemes(NumStr, [$ ,$:,$,])])
end,
{shuffle_cases(Ref, Cs0, UseSeed),{shuffle,UseSeed}}
end;
@@ -3704,6 +3726,7 @@ run_test_case(Ref, Num, Mod, Func, Args, RunInit, TimetrapData, Mode) ->
spawn_link(
fun() ->
process_flag(trap_exit, true),
+ ct_util:mark_process(),
_ = [put(Key, Val) || {Key,Val} <- Dictionary],
set_io_buffering({tc,Main}),
run_test_case1(Ref, Num, Mod, Func, Args, RunInit,
@@ -3979,11 +4002,12 @@ progress(skip, CaseNum, Mod, Func, GrName, Loc, Reason, Time,
true -> "~w"
end, [Time]),
ReasonStr = escape_chars(reason_to_string(Reason1)),
- ReasonStr1 = lists:flatten([string:strip(S,left) ||
- S <- string:tokens(ReasonStr,[$\n])]),
+ ReasonStr1 = lists:flatten([string:trim(S,leading,"\s") ||
+ S <- string:lexemes(ReasonStr,[$\n])]),
+ ReasonLength = string:length(ReasonStr1),
ReasonStr2 =
- if length(ReasonStr1) > 80 ->
- string:substr(ReasonStr1, 1, 77) ++ "...";
+ if ReasonLength > 80 ->
+ string:slice(ReasonStr1, 0, 77) ++ "...";
true ->
ReasonStr1
end,
@@ -4067,11 +4091,12 @@ progress(failed, CaseNum, Mod, Func, GrName, unknown, Reason, Time,
true -> "~w"
end, [Time]),
ErrorReason = escape_chars(lists:flatten(io_lib:format("~tp", [Reason]))),
- ErrorReason1 = lists:flatten([string:strip(S,left) ||
- S <- string:tokens(ErrorReason,[$\n])]),
+ ErrorReason1 = lists:flatten([string:trim(S,leading,"\s") ||
+ S <- string:lexemes(ErrorReason,[$\n])]),
+ ErrorReasonLength = string:length(ErrorReason1),
ErrorReason2 =
- if length(ErrorReason1) > 63 ->
- string:substr(ErrorReason1, 1, 60) ++ "...";
+ if ErrorReasonLength > 63 ->
+ string:slice(ErrorReason1, 0, 60) ++ "...";
true ->
ErrorReason1
end,
@@ -5655,6 +5680,13 @@ html_header(Title) ->
"<body bgcolor=\"white\" text=\"black\" "
"link=\"blue\" vlink=\"purple\" alink=\"red\">\n"].
+html_header(Title, Meta) ->
+ ["<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n"
+ "<!-- autogenerated by '", atom_to_list(?MODULE), "'. -->\n"
+ "<html>\n"
+ "<head>\n"
+ "<title>", Title, "</title>\n"] ++ Meta ++ ["</head>\n"].
+
open_html_file(File) ->
open_utf8_file(File).
diff --git a/lib/common_test/src/test_server_gl.erl b/lib/common_test/src/test_server_gl.erl
index ce7682d101..24dd5cd54c 100644
--- a/lib/common_test/src/test_server_gl.erl
+++ b/lib/common_test/src/test_server_gl.erl
@@ -132,6 +132,7 @@ set_props(GL, PropList) ->
%%% Internal functions.
init([TSIO]) ->
+ ct_util:mark_process(group_leader),
EscChars = case application:get_env(test_server, esc_chars) of
{ok,ECBool} -> ECBool;
_ -> true
diff --git a/lib/common_test/src/test_server_io.erl b/lib/common_test/src/test_server_io.erl
index 062e3bd8ff..ef31521950 100644
--- a/lib/common_test/src/test_server_io.erl
+++ b/lib/common_test/src/test_server_io.erl
@@ -184,6 +184,7 @@ reset_state() ->
init([]) ->
process_flag(trap_exit, true),
+ ct_util:mark_process(),
Empty = gb_trees:empty(),
{ok,Shared} = test_server_gl:start_link(self()),
{ok,#st{fds=Empty,shared_gl=Shared,gls=gb_sets:empty(),
@@ -262,7 +263,7 @@ handle_call(reset_state, From, #st{phase=stopping,pending_ops=Ops}=St) ->
{Result,NewSt1}
end,
{noreply,St#st{pending_ops=[{From,Op}|Ops]}};
-handle_call(reset_state, _From, #st{fds=Fds,tags=Tags,gls=Gls,
+handle_call(reset_state, _From, #st{fds=Fds,tags=Tags,shared_gl=Shared0,gls=Gls,
offline_buffer=OfflineBuff}) ->
%% close open log files
lists:foreach(fun(Tag) ->
@@ -273,6 +274,7 @@ handle_call(reset_state, _From, #st{fds=Fds,tags=Tags,gls=Gls,
file:close(Fd)
end
end, Tags),
+ test_server_gl:stop(Shared0),
GlList = gb_sets:to_list(Gls),
_ = [test_server_gl:stop(GL) || GL <- GlList],
timer:sleep(100),
@@ -320,7 +322,7 @@ handle_call(finish, From, St) ->
handle_info({'EXIT',Pid,normal}, #st{gls=Gls0,stopping=From}=St) ->
Gls = gb_sets:delete_any(Pid, Gls0),
- case gb_sets:is_empty(Gls) andalso stopping =/= undefined of
+ case gb_sets:is_empty(Gls) andalso From =/= undefined of
true ->
%% No more group leaders left.
gen_server:reply(From, ok),
@@ -329,6 +331,9 @@ handle_info({'EXIT',Pid,normal}, #st{gls=Gls0,stopping=From}=St) ->
%% Wait for more group leaders to finish.
{noreply,St#st{gls=Gls,phase=stopping}}
end;
+handle_info({'EXIT',Pid,killed}, #st{gls=Gls0}=St) ->
+ %% forced termination of group leader
+ {noreply,St#st{gls=gb_sets:delete_any(Pid, Gls0)}};
handle_info({'EXIT',_Pid,Reason}, _St) ->
exit(Reason);
handle_info(stop_group_leaders, #st{gls=Gls}=St) ->
diff --git a/lib/common_test/src/test_server_node.erl b/lib/common_test/src/test_server_node.erl
index c0d7e12721..b2d4f199c3 100644
--- a/lib/common_test/src/test_server_node.erl
+++ b/lib/common_test/src/test_server_node.erl
@@ -315,9 +315,11 @@ start_node_peer(SlaveName, OptList, From, TI) ->
Prog0 = start_node_get_option_value(erl, OptList, default),
Prog = quote_progname(pick_erl_program(Prog0)),
Args =
- case string:str(SuppliedArgs,"-setcookie") of
- 0 -> "-setcookie " ++ TI#target_info.cookie ++ " " ++ SuppliedArgs;
- _ -> SuppliedArgs
+ case string:find(SuppliedArgs,"-setcookie") of
+ nomatch ->
+ "-setcookie " ++ TI#target_info.cookie ++ " " ++ SuppliedArgs;
+ _ ->
+ SuppliedArgs
end,
Cmd = lists:concat([Prog,
" -detached ",
@@ -612,7 +614,7 @@ pick_erl_program(L) ->
%% emulator and flags as the test node. The return from lib:progname()
%% could then typically be '/<full_path_to>/cerl -gcov').
quote_progname(Progname) ->
- do_quote_progname(string:tokens(Progname," ")).
+ do_quote_progname(string:lexemes(Progname," ")).
do_quote_progname([Prog]) ->
"\""++Prog++"\"";
@@ -747,6 +749,7 @@ unpack(Bin) ->
id(I) -> I.
print_data(Port) ->
+ ct_util:mark_process(),
receive
{Port, {data, Bytes}} ->
io:put_chars(Bytes),
diff --git a/lib/common_test/src/test_server_sup.erl b/lib/common_test/src/test_server_sup.erl
index 21f4be22fe..6ddbf1ad27 100644
--- a/lib/common_test/src/test_server_sup.erl
+++ b/lib/common_test/src/test_server_sup.erl
@@ -56,6 +56,7 @@ timetrap(Timeout0, Scale, Pid) ->
timetrap(Timeout0, ReportTVal, Scale, Pid) ->
process_flag(priority, max),
+ ct_util:mark_process(),
Timeout = if not Scale -> Timeout0;
true -> test_server:timetrap_scale_factor() * Timeout0
end,
@@ -773,6 +774,7 @@ framework_call(Callback,Func,Args,DefaultReturn) ->
false ->
ok
end,
+ ct_util:mark_process(),
try apply(Mod,Func,Args) of
Result ->
Result
@@ -850,6 +852,7 @@ util_start() ->
undefined ->
spawn_link(fun() ->
register(?MODULE, self()),
+ put(app, common_test),
util_loop(#util_state{starter=Starter})
end),
ok;
diff --git a/lib/common_test/src/unix_telnet.erl b/lib/common_test/src/unix_telnet.erl
index 8ac467014c..5992f26e6d 100644
--- a/lib/common_test/src/unix_telnet.erl
+++ b/lib/common_test/src/unix_telnet.erl
@@ -121,7 +121,8 @@ connect1(Name,Ip,Port,Timeout,KeepAlive,TCPNoDelay,Username,Password) ->
prompt,?prx,[]) of
{ok,{prompt,?password},_} ->
ok = ct_telnet_client:send_data(Pid,Password),
- Stars = lists:duplicate(length(Password),$*),
+ Stars =
+ lists:duplicate(string:length(Password),$*),
log(Name,send,"Password: ~s",[Stars]),
% ok = ct_telnet_client:send_data(Pid,""),
case ct_telnet:silent_teln_expect(Name,Pid,[],
diff --git a/lib/common_test/src/vts.erl b/lib/common_test/src/vts.erl
index 99a109cfe8..83fcde2f48 100644
--- a/lib/common_test/src/vts.erl
+++ b/lib/common_test/src/vts.erl
@@ -157,6 +157,7 @@ test_info(_VtsPid,Type,Data) ->
init(Parent) ->
register(?MODULE,self()),
process_flag(trap_exit,true),
+ ct_util:mark_process(),
Parent ! {self(),started},
{ok,Cwd} = file:get_cwd(),
InitState = #state{start_dir=Cwd},
@@ -284,6 +285,7 @@ run_test1(State=#state{tests=Tests,current_log_dir=LogDir,
logopts=LogOpts}) ->
Self=self(),
RunTest = fun() ->
+ ct_util:mark_process(),
case ct_run:do_run(Tests,[],LogDir,LogOpts) of
{error,_Reason} ->
aborted();