aboutsummaryrefslogtreecommitdiffstats
path: root/lib/test_server/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/test_server/src')
-rw-r--r--lib/test_server/src/Makefile1
-rw-r--r--lib/test_server/src/configure.in3
-rw-r--r--lib/test_server/src/erl2html2.erl5
-rw-r--r--lib/test_server/src/test_server.app.src1
-rw-r--r--lib/test_server/src/test_server.erl51
-rw-r--r--lib/test_server/src/test_server_ctrl.erl181
-rw-r--r--lib/test_server/src/test_server_gl.erl4
-rw-r--r--lib/test_server/src/test_server_h.erl148
-rw-r--r--lib/test_server/src/test_server_internal.hrl10
-rw-r--r--lib/test_server/src/test_server_io.erl218
-rw-r--r--lib/test_server/src/test_server_node.erl67
-rw-r--r--lib/test_server/src/ts.erl26
-rw-r--r--lib/test_server/src/ts_benchmark.erl7
-rw-r--r--lib/test_server/src/ts_erl_config.erl24
-rw-r--r--lib/test_server/src/ts_install_cth.erl6
-rw-r--r--lib/test_server/src/ts_lib.erl12
16 files changed, 352 insertions, 412 deletions
diff --git a/lib/test_server/src/Makefile b/lib/test_server/src/Makefile
index ebc5f5b71b..ab4dd4d95d 100644
--- a/lib/test_server/src/Makefile
+++ b/lib/test_server/src/Makefile
@@ -45,7 +45,6 @@ MODULES= test_server_ctrl \
test_server_node \
test_server \
test_server_sup \
- test_server_h \
erl2html2
TS_MODULES= \
diff --git a/lib/test_server/src/configure.in b/lib/test_server/src/configure.in
index 785bab395c..3815027721 100644
--- a/lib/test_server/src/configure.in
+++ b/lib/test_server/src/configure.in
@@ -2,7 +2,7 @@ dnl Process this file with autoconf to produce a configure script for Erlang.
dnl
dnl %CopyrightBegin%
dnl
-dnl Copyright Ericsson AB 1997-2012. All Rights Reserved.
+dnl Copyright Ericsson AB 1997-2013. All Rights Reserved.
dnl
dnl The contents of this file are subject to the Erlang Public License,
dnl Version 1.1, (the "License"); you may not use this file except in
@@ -276,6 +276,7 @@ AC_CHECK_FUNC(gethostbyname, , AC_CHECK_LIB(nsl, main, [LIBS="$LIBS -lnsl"]))
dnl Checks for library functions.
AC_CHECK_FUNCS(strerror)
AC_CHECK_FUNCS(vsnprintf)
+AC_CHECK_FUNCS(usleep)
# First check if the library is available, then if we can choose between
# two versions of gethostbyname
diff --git a/lib/test_server/src/erl2html2.erl b/lib/test_server/src/erl2html2.erl
index 9c0ca64173..d0f40c47a7 100644
--- a/lib/test_server/src/erl2html2.erl
+++ b/lib/test_server/src/erl2html2.erl
@@ -1,3 +1,4 @@
+%% -*- coding: utf-8 -*-
%%
%% %CopyrightBegin%
%%
@@ -126,7 +127,7 @@ build_html(SFd,DFd,Encoding,Functions) ->
build_html(SFd,DFd,Encoding,file:read_line(SFd),1,Functions,false).
build_html(SFd,DFd,Encoding,{ok,Str},L,[{F,A,L}|Functions],_IsFuncDef) ->
- FALink = http_uri:encode(F++"-"++integer_to_list(A)),
+ FALink = test_server_ctrl:uri_encode(F++"-"++integer_to_list(A),utf8),
file:write(DFd,["<a name=\"",to_raw_list(FALink,Encoding),"\"/>"]),
build_html(SFd,DFd,Encoding,{ok,Str},L,Functions,true);
build_html(SFd,DFd,Encoding,{ok,Str},L,[{clause,L}|Functions],_IsFuncDef) ->
@@ -214,7 +215,7 @@ html_encoding(utf8) ->
%%% from the source.
%%%
%%% Example: if the encoding of the file is utf8, and we have a string
-%%% containing "�" = [229], then we need to convert this to [195,165]
+%%% containing "å" = [229], then we need to convert this to [195,165]
%%% before writing. Note that this conversion is only necessary
%%% because the destination file is not (necessarily) opened with utf8
%%% encoding - it is opened with default encoding in order to allow
diff --git a/lib/test_server/src/test_server.app.src b/lib/test_server/src/test_server.app.src
index 163f370a47..42e78ed279 100644
--- a/lib/test_server/src/test_server.app.src
+++ b/lib/test_server/src/test_server.app.src
@@ -23,7 +23,6 @@
erl2html2,
test_server_ctrl,
test_server,
- test_server_h,
test_server_io,
test_server_node,
test_server_sup
diff --git a/lib/test_server/src/test_server.erl b/lib/test_server/src/test_server.erl
index 4c39c604a2..6ddb2b615f 100644
--- a/lib/test_server/src/test_server.erl
+++ b/lib/test_server/src/test_server.erl
@@ -218,17 +218,14 @@ do_cover_compile1([]) ->
%% Analyse = {details,Dir} | details | {overview,void()} | overview
%% Modules = [atom()], the modules to analyse
%%
-%% Cover analysis. If this is a remote target, analyse_to_file can not be used.
-%% In that case the analyse level 'line' is used instead if Analyse==details.
+%% Cover analysis. If Analyse=={details,Dir} analyse_to_file is used.
%%
-%% If this is a local target, the test directory is given
-%% (Analyse=={details,Dir}) and analyse_to_file can be used directly.
+%% If Analyse=={overview,Dir} analyse_to_file is not used, only an
+%% overview containing the number of covered/not covered lines in each
+%% module.
%%
-%% If Analyse==overview | {overview,Dir} analyse_to_file is not used, only
-%% an overview containing the number of covered/not covered lines in each module.
-%%
-%% Also, if a Dir exists, cover data will be exported to a file called
-%% all.coverdata in that directory.
+%% Also, cover data will be exported to a file called all.coverdata in
+%% the given directory.
%%
%% Finally, if Stop==true, then cover will be stopped after the
%% analysis is completed. Stopping cover causes the original (non
@@ -261,24 +258,13 @@ cover_analyse(Analyse,Modules,Stop) ->
Error ->
fun(_) -> Error end
end;
- details ->
- fun(M) ->
- case cover:analyse(M,line) of
- {ok,Lines} ->
- {lines,Lines};
- Error ->
- Error
- end
- end;
{overview,Dir} ->
case cover:export(filename:join(Dir,"all.coverdata")) of
ok ->
fun(_) -> undefined end;
Error ->
fun(_) -> Error end
- end;
- overview ->
- fun(_) -> undefined end
+ end
end,
R = pmap(
fun(M) ->
@@ -403,7 +389,6 @@ run_test_case_apply({CaseNum,Mod,Func,Args,Name,
os:putenv("VALGRIND_LOGFILE_INFIX",atom_to_list(Mod)++"."++
atom_to_list(Func)++"-")
end,
- test_server_h:testcase({Mod,Func,1}),
ProcBef = erlang:system_info(process_count),
Result = run_test_case_apply(Mod, Func, Args, Name, RunInit,
TimetrapData),
@@ -512,10 +497,10 @@ run_test_case_msgloop(#st{ref=Ref,pid=Pid,end_conf_pid=EndConfPid0}=St0) ->
end,
run_test_case_msgloop(St);
{sync_apply,From,MFA} ->
- sync_local_or_remote_apply(false,From,MFA),
+ do_sync_apply(false,From,MFA),
run_test_case_msgloop(St0);
{sync_apply_proxy,Proxy,From,MFA} ->
- sync_local_or_remote_apply(Proxy,From,MFA),
+ do_sync_apply(Proxy,From,MFA),
run_test_case_msgloop(St0);
{comment,NewComment0} ->
NewComment1 = test_server_ctrl:to_string(NewComment0),
@@ -730,6 +715,16 @@ end_conf_timeout(_, _) ->
call_end_conf(Mod,Func,TCPid,TCExitReason,Loc,Conf,TVal) ->
Starter = self(),
Data = {Mod,Func,TCPid,TCExitReason,Loc},
+ case erlang:function_exported(Mod,end_per_testcase,2) of
+ false ->
+ spawn_link(fun() ->
+ Starter ! {self(),{call_end_conf,Data,ok}}
+ end);
+ true ->
+ do_call_end_conf(Starter,Mod,Func,Data,Conf,TVal)
+ end.
+
+do_call_end_conf(Starter,Mod,Func,Data,Conf,TVal) ->
EndConfProc =
fun() ->
process_flag(trap_exit,true), % to catch timetraps
@@ -767,7 +762,8 @@ call_end_conf(Mod,Func,TCPid,TCExitReason,Loc,Conf,TVal) ->
end,
spawn_link(EndConfProc).
-spawn_fw_call(Mod,{init_per_testcase,Func},CurrConf,Pid,{timetrap_timeout,TVal}=Why,
+spawn_fw_call(Mod,{init_per_testcase,Func},CurrConf,Pid,
+ {timetrap_timeout,TVal}=Why,
Loc,SendTo) ->
FwCall =
fun() ->
@@ -2599,10 +2595,9 @@ purify_format(Format, Args) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
-%% Generic send functions for communication with host
+%% Apply given function and reply to caller or proxy.
%%
-sync_local_or_remote_apply(Proxy, From, {M,F,A}) ->
- %% i'm a local target
+do_sync_apply(Proxy, From, {M,F,A}) ->
Result = apply(M, F, A),
if is_pid(Proxy) -> Proxy ! {sync_result_proxy,From,Result};
true -> From ! {sync_result,Result}
diff --git a/lib/test_server/src/test_server_ctrl.erl b/lib/test_server/src/test_server_ctrl.erl
index 5d4d392166..d0f31af198 100644
--- a/lib/test_server/src/test_server_ctrl.erl
+++ b/lib/test_server/src/test_server_ctrl.erl
@@ -251,8 +251,6 @@ parse_cmd_line(['MODULE',Mod|Cmds], SpecList, Names, Param, Trc, Cov, TCCB) ->
parse_cmd_line(['CASE',Mod,Case|Cmds], SpecList, Names, Param, Trc, Cov, TCCB) ->
parse_cmd_line(Cmds,[{topcase,{Mod,Case}}|SpecList],[atom_to_list(Mod)|Names],
Param, Trc, Cov, TCCB);
-parse_cmd_line(['PARAMETERS',Param|Cmds], SpecList, Names, _Param, Trc, Cov, TCCB) ->
- parse_cmd_line(Cmds, SpecList, Names, Param, Trc, Cov, TCCB);
parse_cmd_line(['TRACE',Trc|Cmds], SpecList, Names, Param, _Trc, Cov, TCCB) ->
parse_cmd_line(Cmds, SpecList, Names, Param, Trc, Cov, TCCB);
parse_cmd_line(['COVER',App,CF,Analyse|Cmds], SpecList, Names, Param, Trc, _Cov, TCCB) ->
@@ -284,19 +282,23 @@ cast_to_list(X) -> lists:flatten(io_lib:format("~w", [X])).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% START INTERFACE
-start() ->
- start(local).
+%% Kept for backwards compatibility
+start(_) ->
+ start().
+start_link(_) ->
+ start_link().
+
-start(Param) ->
- case gen_server:start({local,?MODULE}, ?MODULE, [Param], []) of
+start() ->
+ case gen_server:start({local,?MODULE}, ?MODULE, [], []) of
{ok, Pid} ->
{ok, Pid};
Other ->
Other
end.
-start_link(Param) ->
- case gen_server:start_link({local,?MODULE}, ?MODULE, [Param], []) of
+start_link() ->
+ case gen_server:start_link({local,?MODULE}, ?MODULE, [], []) of
{ok, Pid} ->
{ok, Pid};
Other ->
@@ -463,24 +465,11 @@ controller_call(Arg, Timeout) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% init([Mode])
-%% Mode = lazy | error_logger
-%% StateFile = string()
-%% ReadMode = ignore_errors | halt_on_errors
+%% init([])
%%
%% init() is the init function of the test_server's gen_server.
-%% When Mode=error_logger: The init function of the test_server's gen_event
-%% event handler used as a replacement error_logger when running test_suites.
-%%
-%% The init function reads the test server state file, to see what test
-%% suites were running when the test server was last running, and which
-%% flags that were in effect. If no state file is found, or there are
-%% errors in it, defaults are used.
%%
-%% Mode 'lazy' ignores (and resets to []) any jobs in the state file
-%%
-
-init([_]) ->
+init([]) ->
case os:getenv("TEST_SERVER_CALL_TRACE") of
false ->
ok;
@@ -490,12 +479,6 @@ init([_]) ->
test_server_sup:call_trace(TraceSpec)
end,
process_flag(trap_exit, true),
- case lists:keysearch(sasl, 1, application:which_applications()) of
- {value,_} ->
- test_server_h:install();
- false ->
- ok
- end,
%% copy format_exception setting from init arg to application environment
case init:get_argument(test_server_format_exception) of
{ok,[[TSFE]]} ->
@@ -505,7 +488,6 @@ init([_]) ->
end,
test_server_sup:cleanup_crash_dumps(),
State = #state{jobs=[],finish=false},
- put(test_server_free_targets,[]),
TI0 = test_server:init_target_info(),
TargetHost = test_server_sup:hoststr(),
TI = TI0#target_info{host=TargetHost,
@@ -529,7 +511,7 @@ naming() ->
%% is completed.
%%
handle_call(kill_slavenodes, _From, State) ->
- Nodes = test_server_node:kill_nodes(State#state.target_info),
+ Nodes = test_server_node:kill_nodes(),
{reply, Nodes, State};
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -950,11 +932,11 @@ handle_call({wait_for_node, Node}, From, State) ->
%% - the node is really stopped by test_server when this returns.
handle_call({stop_node, Name}, _From, State) ->
- R = test_server_node:stop_node(Name, State#state.target_info),
+ R = test_server_node:stop_node(Name),
{reply, R, State};
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% handle_call({stop_node,Name}, _, State) -> ok | {error,Reason}
+%% handle_call({is_release_available,Name}, _, State) -> ok | {error,Reason}
%%
%% Tests if the release is available.
@@ -993,9 +975,7 @@ handle_cast({node_started,Node}, State) ->
%%
%% Handles exit messages from linked processes. Only test suites are
%% expected to be linked. When a test suite terminates, it is removed
-%% from the job queue. If a target client terminates it means that we
-%% lost contact with target. The test_server_ctrl process is
-%% terminated, and teminate/2 will do the cleanup
+%% from the job queue.
handle_info(report_idle, State) ->
Finish = State#state.finish,
@@ -1047,35 +1027,12 @@ handle_info({'EXIT',Pid,Reason}, State) ->
end
end;
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% handle_info({tcp,Sock,Bin}, State)
-%%
-%% Message from remote main target process
-%% Only valid message is 'job_proc_killed', which indicates
-%% that a process running a test suite was killed
-
-handle_info({tcp,_MainSock,<<1,Request/binary>>}, State) ->
- case binary_to_term(Request) of
- {job_proc_killed,Name,Reason} ->
- %% The only purpose of this is to inform the user about what
- %% happened on target.
- %% The local job proc will soon be killed by the closed socket or
- %% because the job is finished. Then the above clause ('EXIT') will
- %% handle the problem.
- io:format("Suite ~ts was killed on remote target with reason"
- " ~p\n", [Name,Reason]);
- _ ->
- ignore
- end,
- {noreply,State};
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% handle_info({tcp_closed,Sock}, State)
%%
%% A Socket was closed. This indicates that a node died.
%% This can be
-%% *Target node (if remote)
%% *Slave or peer node started by a test suite
%% *Trace controll node
@@ -1084,7 +1041,7 @@ handle_info({tcp_closed,Sock}, State=#state{trc=Sock}) ->
%%! Maybe print something???
{noreply,State#state{trc=false}};
handle_info({tcp_closed,Sock}, State) ->
- test_server_node:nodedown(Sock, State#state.target_info),
+ test_server_node:nodedown(Sock),
{noreply,State};
handle_info(_, State) ->
%% dummy; accept all, do nothing.
@@ -1095,7 +1052,7 @@ handle_info(_, State) ->
%% Reason = term()
%%
%% Cleans up when the test_server is terminating. Kills the running
-%% test suites (if any) and terminates the remote target (if is exists)
+%% test suites (if any) and any possible remainting slave node
terminate(_Reason, State) ->
case State#state.trc of
@@ -1103,13 +1060,7 @@ terminate(_Reason, State) ->
Sock -> test_server_node:stop_tracer_node(Sock)
end,
kill_all_jobs(State#state.jobs),
- test_server_node:stop(State#state.target_info),
- case lists:keysearch(sasl, 1, application:which_applications()) of
- {value,_} ->
- test_server_h:restore();
- _ ->
- ok
- end,
+ test_server_node:kill_nodes(),
ok.
kill_all_jobs([{_Name,JobPid}|Jobs]) ->
@@ -1220,7 +1171,13 @@ init_tester(Mod, Func, Args, Dir, Name, {_,_,MinLev}=Levels,
"<td>~.3fs</td><td><b>~ts</b></td><td>~w Ok, ~w Failed~ts of ~w</td></tr>\n"
"</tfoot>\n",
[Time,SuccessStr,OkN,FailedN,SkipStr,OkN+FailedN+SkippedN]),
- test_server_io:stop().
+
+ test_server_io:stop([major,html,unexpected_io]),
+ {UnexpectedIoName,UnexpectedIoFooter} = get(test_server_unexpected_footer),
+ {ok,UnexpectedIoFd} = open_html_file(UnexpectedIoName, [append]),
+ io:put_chars(UnexpectedIoFd, "\n</pre>\n"++UnexpectedIoFooter),
+ file:close(UnexpectedIoFd),
+ ok.
report_severe_error(Reason) ->
test_server_sup:framework_call(report, [severe_error,Reason]).
@@ -1625,7 +1582,7 @@ do_test_cases(TopCases, SkipCases,
print(major, "=started ~s",
[lists:flatten(timestamp_get(""))]),
- put(test_server_html_footer, Footer),
+ test_server_io:set_footer(Footer),
run_test_cases(TestSpec, Config, TimetrapData)
end;
@@ -1679,15 +1636,13 @@ start_log_file() ->
FilenameMode),
ok = write_file(?last_file, TestDir1 ++ "\n", FilenameMode),
put(test_server_log_dir_base,TestDir1),
+
MajorName = filename:join(TestDir1, ?suitelog_name),
HtmlName = MajorName ++ ?html_ext,
UnexpectedName = filename:join(TestDir1, ?unexpected_io_log),
+
{ok,Major} = open_utf8_file(MajorName),
{ok,Html} = open_html_file(HtmlName),
- {ok,Unexpected} = open_html_file(UnexpectedName),
- test_server_io:set_fd(major, Major),
- test_server_io:set_fd(html, Html),
- test_server_io:set_fd(unexpected_io, Unexpected),
{UnexpHeader,UnexpFooter} =
case test_server_sup:framework_call(get_html_wrapper,
@@ -1700,8 +1655,17 @@ start_log_file() ->
{xhtml,UH,UF} ->
{UH,UF}
end,
- io:put_chars(Unexpected, UnexpHeader++"\n<pre>\n"),
- put(test_server_unexpected_footer,UnexpFooter),
+
+ {ok,Unexpected} = open_html_file(UnexpectedName),
+ io:put_chars(Unexpected, [UnexpHeader,
+ xhtml("<br>\n<h2>Unexpected I/O</h2>",
+ "<br />\n<h3>Unexpected I/O</h3>"),
+ "\n<pre>\n"]),
+ put(test_server_unexpected_footer,{UnexpectedName,UnexpFooter}),
+
+ test_server_io:set_fd(major, Major),
+ test_server_io:set_fd(html, Html),
+ test_server_io:set_fd(unexpected_io, Unexpected),
make_html_link(filename:absname(?last_test ++ ?html_ext),
HtmlName, filename:basename(Dir)),
@@ -2071,7 +2035,6 @@ do_add_end_per_suite_and_skip(LastMod, LastRef, Mod, FwMod) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% run_test_cases(TestSpec, Config, TimetrapData) -> exit(Result)
%%
-%% If remote target, a socket connection is established.
%% Runs the specified tests, then displays/logs the summary.
run_test_cases(TestSpec, Config, TimetrapData) ->
@@ -2137,8 +2100,7 @@ run_test_cases(TestSpec, Config, TimetrapData) ->
%% return a new configuration.
%%
%% {make,Ref,{Mod,Func,Args}} Mod:Func is a make function, and it is called
-%% with the given arguments. This function will *always* be called on the host
-%% - not on target.
+%% with the given arguments.
%%
%% {Mod,Case} This is a normal test case. Determine the correct
%% configuration, and insert {Mod,Case,Config} as head of the list,
@@ -3462,20 +3424,16 @@ handle_io_and_exits(Main, CurrPid, CaseNum, Mod, Func, Cases) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% run_test_case(Ref, Num, Mod, Func, Args, RunInit,
-%% Where, TimetrapData, Mode) -> RetVal
+%% TimetrapData, Mode) -> RetVal
%%
%% Creates the minor log file and inserts some test case specific headers
-%% and footers into the log files. If a remote target is used, the test
-%% suite (binary) and the content of data_dir is sent. Then the test case
-%% is executed and the result is printed to the log files (also info
-%% about lingering processes & slave nodes in the system is presented).
+%% and footers into the log files. Then the test case is executed and the
+%% result is printed to the log files (also info about lingering processes
+%% & slave nodes in the system is presented).
%%
%% RunInit decides if the per test case init is to be run (true for all
%% but conf cases).
%%
-%% Where specifies if the test case should run on target or on the host.
-%% (Note that 'make' test cases always run on host).
-%%
%% Mode specifies if the test case should be executed by a dedicated,
%% parallel, process rather than sequentially by the main process. If
%% the former, the new process is spawned and the dictionary of the main
@@ -4110,11 +4068,6 @@ do_format_exception(Reason={Error,Stack}) ->
%% DetectedFail = [{File,Line}]
%% ProcessesBefore = ProcessesAfter = integer()
%%
-%% Where indicates if the test should run on target or always on the host.
-%%
-%% If test is to be run on target, and target is remote the request is
-%% sent over socket to target, and test_server runs the case and sends the
-%% result back over the socket. Else test_server runs the case directly on host.
run_test_case_apply(CaseNum, Mod, Func, Args, Name, RunInit,
TimetrapData) ->
@@ -4351,8 +4304,7 @@ update_config(Config, []) ->
%% Cases is treated according to this table, then
%% FinMFA is placed in the BasicCaseList. InitMFA
%% and FinMFA are make/unmake functions. If InitMFA
-%% fails, Cases are not run. InitMFA and FinMFA are
-%% always run on the host - not on target.
+%% fails, Cases are not run.
%%
%% When a function is called, above, it means that the function is invoked
%% and the return is expected to be:
@@ -4702,7 +4654,7 @@ keep_name(Props) ->
(_) -> false end, Props).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% Target node handling functions %%
+%% Node handling functions %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -5348,6 +5300,9 @@ html_header(Title) ->
open_html_file(File) ->
open_utf8_file(File).
+open_html_file(File,Opts) ->
+ open_utf8_file(File,Opts).
+
write_html_file(File,Content) ->
write_file(File,Content,utf8).
@@ -5356,6 +5311,9 @@ write_html_file(File,Content) ->
open_utf8_file(File) ->
file:open(File,[write,{encoding,utf8}]).
+open_utf8_file(File,Opts) ->
+ file:open(File,[{encoding,utf8}|Opts]).
+
%% Write a file with specified encoding
write_file(File,Content,latin1) ->
file:write_file(File,Content);
@@ -5375,31 +5333,28 @@ uri_encode(File,Encoding) ->
Components = filename:split(File),
filename:join([uri_encode_comp(C,Encoding) || C <- Components]).
-uri_encode_comp("/",_) ->
- "/";
-uri_encode_comp(Chars,utf8) ->
- http_uri:encode(Chars);
-uri_encode_comp(Chars,latin1) ->
- do_uri_encode(Chars).
-
-%% Encode a file reference to a latin1 filename so it can be inserted
-%% in a utf8 encoded HTML file.
-%% This does the same as http_uri:encode/1, except it also encodes all
-%% characters >127 - i.e. latin1 but not ASCII.
-do_uri_encode([Char|Chars]) ->
- case Char>127 orelse sets:is_element(Char, reserved()) of
+%% Encode the reference to a "filename of the given encoding" so it
+%% can be inserted in a utf8 encoded HTML file.
+%% This does almost the same as http_uri:encode/1, except
+%% 1. it does not convert @, : and / (in order to preserve nodename and c:/)
+%% 2. if the file name is in latin1, it also encodes all
+%% characters >127 - i.e. latin1 but not ASCII.
+uri_encode_comp([Char|Chars],Encoding) ->
+ Reserved = sets:is_element(Char, reserved()),
+ case (Char>127 andalso Encoding==latin1) orelse Reserved of
true ->
- [ $% | http_util:integer_to_hexlist(Char)] ++ do_uri_encode(Chars);
+ [ $% | http_util:integer_to_hexlist(Char)] ++
+ uri_encode_comp(Chars,Encoding);
false ->
- [Char | do_uri_encode(Chars)]
+ [Char | uri_encode_comp(Chars,Encoding)]
end;
-do_uri_encode([]) ->
+uri_encode_comp([],_) ->
[].
%% Copied from http_uri.erl, but slightly modified
-%% (not converting @ and :)
+%% (not converting @, : and /)
reserved() ->
- sets:from_list([$;, $&, $=, $+, $,, $/, $?,
+ sets:from_list([$;, $&, $=, $+, $,, $?,
$#, $[, $], $<, $>, $\", ${, $}, $|,
$\\, $', $^, $%, $ ]).
diff --git a/lib/test_server/src/test_server_gl.erl b/lib/test_server/src/test_server_gl.erl
index 766a4537a2..1f7317c51d 100644
--- a/lib/test_server/src/test_server_gl.erl
+++ b/lib/test_server/src/test_server_gl.erl
@@ -166,7 +166,7 @@ handle_info({'DOWN',Ref,process,_,Reason}=D, #st{minor_monitor=Ref}=St) ->
Data = io_lib:format("=== WARNING === TC: ~w\n"
"Got down from minor Fd ~w: ~w\n\n",
[St#st.tc,St#st.minor,D]),
- test_server_io:print(xxxFrom, unexpected_io, Data)
+ test_server_io:print_unexpected(Data)
end,
{noreply,St#st{minor=none,minor_monitor=none}};
handle_info({permit_io,Pid}, #st{permit_io=P}=St) ->
@@ -197,7 +197,7 @@ handle_info({io_request,From,ReplyAs,Req}=IoReq, St) ->
From ! {io_reply,ReplyAs,ok}
catch
_:_ ->
- {io_reply,ReplyAs,{error,arguments}}
+ From ! {io_reply,ReplyAs,{error,arguments}}
end,
{noreply,St};
handle_info({structured_io,ClientPid,{Detail,Str}}, St) ->
diff --git a/lib/test_server/src/test_server_h.erl b/lib/test_server/src/test_server_h.erl
deleted file mode 100644
index 24063ddb10..0000000000
--- a/lib/test_server/src/test_server_h.erl
+++ /dev/null
@@ -1,148 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2013. 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(test_server_h).
--behaviour(gen_event).
-
-%% API
--export([install/0, restore/0]).
--export([testcase/1]).
-
-%% gen_event callbacks
--export([init/1, handle_event/2, handle_call/2,
- handle_info/2, terminate/2, code_change/3]).
-
--record(state, {kernel, sasl, testcase}).
-
-%%====================================================================
-%% API
-%%====================================================================
-
-install() ->
- case gen_event:add_handler(error_logger, ?MODULE, []) of
- ok ->
- error_logger:delete_report_handler(sasl_report_tty_h),
- gen_event:delete_handler(error_logger, error_logger_tty_h, []),
- ok;
- Error ->
- Error
- end.
-
-restore() ->
- gen_event:add_handler(error_logger, error_logger_tty_h, []),
- error_logger:add_report_handler(sasl_report_tty_h, all),
- gen_event:delete_handler(error_logger, ?MODULE, []).
-
-testcase(Testcase) ->
- gen_event:call(error_logger, ?MODULE, {set_testcase, Testcase}, 10*60*1000).
-
-%%====================================================================
-%% gen_event callbacks
-%%====================================================================
-
-init([]) ->
-
- %% error_logger_tty_h initialization
- User = set_group_leader(),
-
- %% sasl_report_tty_h initialization
- Type = all,
-
- {ok, #state{kernel={User, []}, sasl=Type}}.
-
-set_group_leader() ->
- case whereis(user) of
- User when is_pid(User) ->
- link(User),
- group_leader(User, self()),
- User;
- _ ->
- false
- end.
-
-handle_event({_Type, GL, _Msg}, State) when node(GL)/=node() ->
- {ok, State};
-handle_event({Tag, _GL, {_Pid, Type, _Report}} = Event, State) ->
- SASL = lists:keyfind(sasl, 1, application:which_applications()),
- case report_receiver(Tag, Type) of
- sasl when SASL /= false ->
- {ok,ErrLogType} = application:get_env(sasl, errlog_type),
- SReport = sasl_report:format_report(group_leader(), ErrLogType,
- tag_event(Event)),
- if is_list(SReport) ->
- tag(State#state.testcase),
- sasl_report_tty_h:handle_event(Event,
- State#state.sasl);
- true -> %% Report is an atom if no logging is to be done
- ignore
- end;
- sasl -> %% SASL not running
- ignore;
- kernel ->
- tag(State#state.testcase),
- error_logger_tty_h:handle_event(Event, State#state.kernel);
- none ->
- ignore
- end,
- {ok, State};
-handle_event(_Event, State) ->
- {ok, State}.
-
-handle_call({set_testcase, Testcase}, State) ->
- {ok, ok, State#state{testcase=Testcase}};
-handle_call(_Query, _State) ->
- {error, bad_query}.
-
-handle_info({emulator,GL,_Chars}=Event, State) when node(GL)==node() ->
- tag(State#state.testcase),
- error_logger_tty_h:handle_info(Event, State#state.kernel),
- {ok, State};
-handle_info(_Msg, State) ->
- {ok, State}.
-
-terminate(_Reason, _State) ->
- ok.
-
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
-report_receiver(error_report, supervisor_report) -> sasl;
-report_receiver(error_report, crash_report) -> sasl;
-report_receiver(info_report, progress) -> sasl;
-report_receiver(error, _) -> kernel;
-report_receiver(error_report, _) -> kernel;
-report_receiver(warning_msg, _) -> kernel;
-report_receiver(warning_report, _) -> kernel;
-report_receiver(info, _) -> kernel;
-report_receiver(info_msg, _) -> kernel;
-report_receiver(info_report,Tuple)
- when is_tuple(Tuple) andalso
- (element(1,Tuple)==ct_connection orelse
- element(1,Tuple)==conn_log) ->
- none;
-report_receiver(info_report, _) -> kernel;
-report_receiver(_, _) -> none.
-
-tag({M,F,A}) when is_atom(M), is_atom(F), is_integer(A) ->
- io:format(user, "~n=TESTCASE: ~w:~w/~w", [M,F,A]);
-tag(Testcase) ->
- io:format(user, "~n=TESTCASE: ~p", [Testcase]).
-
-tag_event(Event) ->
- {calendar:local_time(), Event}.
diff --git a/lib/test_server/src/test_server_internal.hrl b/lib/test_server/src/test_server_internal.hrl
index 9a11182725..4e734a330b 100644
--- a/lib/test_server/src/test_server_internal.hrl
+++ b/lib/test_server/src/test_server_internal.hrl
@@ -21,8 +21,7 @@
-define(MAIN_PORT,3289).
-define(ACCEPT_TIMEOUT,20000).
-%% Target information generated by test_server:init_target_info/0 and
-%% test_server_ctrl:contact_main_target/2
+%% Target information generated by test_server:init_target_info/0
%% Once initiated, this information will never change!!
-record(target_info, {os_family, % atom(); win32 | unix
os_type, % result of os:type()
@@ -36,21 +35,16 @@
username, % string()
cookie, % string(); Cookie for target node
naming, % string(); "-name" | "-sname"
- master, % string(); Was used for OSE's master
+ master}). % string(); Was used for OSE's master
% node for main target and slave nodes.
% For other platforms the target node
% itself is master for slave nodes
- %% The following are only used for remote targets
- slave_targets=[]}).% list() of atom(); all available
- % targets for starting slavenodes
-
%% Temporary information generated by test_server_ctrl:read_parameters/X
%% This information is used when starting the main target, and for
%% initiating the #target_info record.
-record(par, {type,
target,
- slave_targets=[],
naming,
master,
cookie}).
diff --git a/lib/test_server/src/test_server_io.erl b/lib/test_server/src/test_server_io.erl
index 242c08f765..62af3d5b28 100644
--- a/lib/test_server/src/test_server_io.erl
+++ b/lib/test_server/src/test_server_io.erl
@@ -29,38 +29,54 @@
%%
-module(test_server_io).
--export([start_link/0,stop/0,get_gl/1,set_fd/2,
- start_transaction/0,end_transaction/0,print_buffered/1,print/3,
- set_footer/1,set_job_name/1,set_gl_props/1]).
+-export([start_link/0,stop/1,get_gl/1,set_fd/2,
+ start_transaction/0,end_transaction/0,
+ print_buffered/1,print/3,print_unexpected/1,
+ set_footer/1,set_job_name/1,set_gl_props/1,
+ reset_state/0,finish/0]).
-export([init/1,handle_call/3,handle_info/2,terminate/2]).
--record(st, {fds, %Singleton fds (gb_tree)
- shared_gl :: pid(), %Shared group leader
- gls, %Group leaders (gb_set)
- io_buffering=false, %I/O buffering
- buffered, %Buffered I/O requests
- html_footer, %HTML footer
- job_name, %Name of current job.
- gl_props, %Properties for GL.
- stopping
+-record(st, {fds, % Singleton fds (gb_tree)
+ tags=[], % Known tag types
+ shared_gl :: pid(), % Shared group leader
+ gls, % Group leaders (gb_set)
+ io_buffering=false, % I/O buffering
+ buffered, % Buffered I/O requests
+ html_footer, % HTML footer
+ job_name, % Name of current job.
+ gl_props, % Properties for GL
+ phase, % Indicates current mode
+ offline_buffer, % Buffer I/O during startup
+ stopping, % Reply to when process stopped
+ pending_ops % Perform when process idle
}).
start_link() ->
- case gen_server:start_link({local,?MODULE}, ?MODULE, [], []) of
- {ok,Pid} ->
- {ok,Pid};
- Other ->
- Other
+ case whereis(?MODULE) of
+ undefined ->
+ case gen_server:start_link({local,?MODULE}, ?MODULE, [], []) of
+ {ok,Pid} ->
+ {ok,Pid};
+ Other ->
+ Other
+ end;
+ Pid ->
+ %% already running, reset the state
+ reset_state(),
+ {ok,Pid}
end.
-stop() ->
+stop(FilesToClose) ->
OldGL = group_leader(),
group_leader(self(), self()),
- req(stop),
+ req({stop,FilesToClose}),
group_leader(OldGL, self()),
ok.
+finish() ->
+ req(finish).
+
%% get_gl(Shared) -> Pid
%% Shared = boolean()
%% Pid = pid()
@@ -124,6 +140,14 @@ print(From, Tag, Msg) ->
print_buffered(Pid) ->
req({print_buffered,Pid}).
+%% print_unexpected(Msg)
+%% Msg = string or iolist
+%%
+%% Print the given string in the unexpected_io log.
+
+print_unexpected(Msg) ->
+ print(xxxFrom,unexpected_io,Msg).
+
%% set_footer(IoData)
%%
%% Set a footer for the file associated with the 'html' tag.
@@ -133,19 +157,27 @@ set_footer(Footer) ->
req({set_footer,Footer}).
%% set_job_name(Name)
+%%
%% Set a name for the currently running job. The name will be used
%% when printing to 'stdout'.
%%
+
set_job_name(Name) ->
req({set_job_name,Name}).
%% set_gl_props(PropList)
+%%
%% Set properties for group leader processes. When a group_leader process
%% is created, test_server_gl:set_props(PropList) will be called.
set_gl_props(PropList) ->
req({set_gl_props,PropList}).
+%% reset_state
+%%
+%% Reset the initial state
+reset_state() ->
+ req(reset_state).
%%% Internal functions.
@@ -158,7 +190,10 @@ init([]) ->
buffered=Empty,
html_footer="</body>\n</html>\n",
job_name="<name not set>",
- gl_props=[]}}.
+ gl_props=[],
+ phase=starting,
+ offline_buffer=[],
+ pending_ops=[]}}.
req(Req) ->
gen_server:call(?MODULE, Req, infinity).
@@ -169,9 +204,24 @@ handle_call({get_gl,false}, _From, #st{gls=Gls,gl_props=Props}=St) ->
{reply,Pid,St#st{gls=gb_sets:insert(Pid, Gls)}};
handle_call({get_gl,true}, _From, #st{shared_gl=Shared}=St) ->
{reply,Shared,St};
-handle_call({set_fd,Tag,Fd}, _From, #st{fds=Fds0}=St) ->
+handle_call({set_fd,Tag,Fd}, _From, #st{fds=Fds0,tags=Tags0,
+ offline_buffer=OfflineBuff}=St) ->
Fds = gb_trees:enter(Tag, Fd, Fds0),
- {reply,ok,St#st{fds=Fds}};
+ St1 = St#st{fds=Fds,tags=[Tag|lists:delete(Tag, Tags0)]},
+ OfflineBuff1 =
+ if OfflineBuff == [] ->
+ [];
+ true ->
+ %% Fd ready, print anything buffered for associated Tag
+ lists:filtermap(fun({T,From,Str}) when T == Tag ->
+ output(From, Tag, Str, St1),
+ false;
+ (_) ->
+ true
+ end, lists:reverse(OfflineBuff))
+ end,
+ {reply,ok,St1#st{phase=started,
+ offline_buffer=lists:reverse(OfflineBuff1)}};
handle_call({start_transaction,Pid}, _From, #st{io_buffering=Buffer0,
buffered=Buf0}=St) ->
Buf = case gb_trees:is_defined(Pid, Buf0) of
@@ -204,13 +254,67 @@ handle_call({set_job_name,Name}, _From, St) ->
handle_call({set_gl_props,Props}, _From, #st{shared_gl=Shared}=St) ->
test_server_gl:set_props(Shared, Props),
{reply,ok,St#st{gl_props=Props}};
-handle_call(stop, From, #st{shared_gl=SGL,gls=Gls0}=St0) ->
- St = St0#st{gls=gb_sets:insert(SGL, Gls0),stopping=From},
+handle_call(reset_state, From, #st{phase=stopping,pending_ops=Ops}=St) ->
+ %% can't reset during stopping phase, save op for later
+ Op = fun(NewSt) ->
+ {_,Result,NewSt1} = handle_call(reset_state, From, NewSt),
+ {Result,NewSt1}
+ end,
+ {noreply,St#st{pending_ops=[{From,Op}|Ops]}};
+handle_call(reset_state, _From, #st{fds=Fds,tags=Tags,gls=Gls,
+ offline_buffer=OfflineBuff}) ->
+ %% close open log files
+ lists:foreach(fun(Tag) ->
+ case gb_trees:lookup(Tag, Fds) of
+ none ->
+ ok;
+ {value,Fd} ->
+ file:close(Fd)
+ end
+ end, Tags),
+ GlList = gb_sets:to_list(Gls),
+ [test_server_gl:stop(GL) || GL <- GlList],
+ timer:sleep(100),
+ case lists:filter(fun(GlPid) -> is_process_alive(GlPid) end, GlList) of
+ [] ->
+ ok;
+ _ ->
+ timer:sleep(2000),
+ [exit(GL, kill) || GL <- GlList]
+ end,
+ Empty = gb_trees:empty(),
+ {ok,Shared} = test_server_gl:start_link(),
+ {reply,ok,#st{fds=Empty,shared_gl=Shared,gls=gb_sets:empty(),
+ io_buffering=gb_sets:empty(),
+ buffered=Empty,
+ html_footer="</body>\n</html>\n",
+ job_name="<name not set>",
+ gl_props=[],
+ phase=starting,
+ offline_buffer=OfflineBuff,
+ pending_ops=[]}};
+handle_call({stop,FdTags}, From, #st{fds=Fds0,tags=Tags0,
+ shared_gl=SGL,gls=Gls0}=St0) ->
+ St = St0#st{gls=gb_sets:insert(SGL, Gls0),phase=stopping,stopping=From},
gc(St),
+ %% close open log files
+ {Fds1,Tags1} = lists:foldl(fun(Tag, {Fds,Tags}) ->
+ case gb_trees:lookup(Tag, Fds) of
+ none ->
+ {Fds,Tags};
+ {value,Fd} ->
+ file:close(Fd),
+ {gb_trees:delete(Tag, Fds),
+ lists:delete(Tag, Tags)}
+ end
+ end, {Fds0,Tags0}, FdTags),
%% Give the users of the surviving group leaders some
%% time to finish.
- erlang:send_after(2000, self(), stop_group_leaders),
- {noreply,St}.
+ erlang:send_after(1000, self(), stop_group_leaders),
+ {noreply,St#st{fds=Fds1,tags=Tags1}};
+handle_call(finish, From, St) ->
+ gen_server:reply(From, ok),
+ {stop,normal,St}.
handle_info({'EXIT',Pid,normal}, #st{gls=Gls0,stopping=From}=St) ->
Gls = gb_sets:delete_any(Pid, Gls0),
@@ -218,22 +322,40 @@ handle_info({'EXIT',Pid,normal}, #st{gls=Gls0,stopping=From}=St) ->
true ->
%% No more group leaders left.
gen_server:reply(From, ok),
- {stop,normal,St#st{gls=Gls,stopping=undefined}};
+ {noreply,St#st{gls=Gls,phase=stopping,stopping=undefined}};
false ->
%% Wait for more group leaders to finish.
- {noreply,St#st{gls=Gls}}
+ {noreply,St#st{gls=Gls,phase=stopping}}
end;
handle_info({'EXIT',_Pid,Reason}, _St) ->
exit(Reason);
handle_info(stop_group_leaders, #st{gls=Gls}=St) ->
%% Stop the remaining group leaders.
- [test_server_gl:stop(GL) || GL <- gb_sets:to_list(Gls)],
- erlang:send_after(2000, self(), kill_group_leaders),
+ GlPids = gb_sets:to_list(Gls),
+ [test_server_gl:stop(GL) || GL <- GlPids],
+ timer:sleep(100),
+ Wait =
+ case lists:filter(fun(GlPid) -> is_process_alive(GlPid) end, GlPids) of
+ [] -> 0;
+ _ -> 2000
+ end,
+ erlang:send_after(Wait, self(), kill_group_leaders),
{noreply,St};
-handle_info(kill_group_leaders, #st{gls=Gls,stopping=From}=St) ->
+handle_info(kill_group_leaders, #st{gls=Gls,stopping=From,
+ pending_ops=Ops}=St) ->
[exit(GL, kill) || GL <- gb_sets:to_list(Gls)],
- gen_server:reply(From, ok),
- {stop,normal,St};
+ if From /= undefined ->
+ gen_server:reply(From, ok);
+ true -> % reply has been sent already
+ ok
+ end,
+ %% we're idle, check if any ops are pending
+ St1 = lists:foldr(fun({ReplyTo,Op},NewSt) ->
+ {Result,NewSt1} = Op(NewSt),
+ gen_server:reply(ReplyTo, Result),
+ NewSt1
+ end, St#st{phase=idle,pending_ops=[]}, Ops),
+ {noreply,St1};
handle_info(Other, St) ->
io:format("Ignoring: ~p\n", [Other]),
{noreply,St}.
@@ -241,11 +363,19 @@ handle_info(Other, St) ->
terminate(_, _) ->
ok.
-output(From, Tag, Str, #st{io_buffering=Buffered,buffered=Buf0}=St) ->
+output(From, Tag, Str, #st{io_buffering=Buffered,buffered=Buf0,
+ phase=Phase,offline_buffer=OfflineBuff}=St) ->
case gb_sets:is_member(From, Buffered) of
false ->
- do_output(Tag, Str, St),
- St;
+ case do_output(Tag, Str, Phase, St) of
+ buffer when length(OfflineBuff)>500 ->
+ %% something's wrong, clear buffer
+ St#st{offline_buffer=[]};
+ buffer ->
+ St#st{offline_buffer=[{Tag,From,Str}|OfflineBuff]};
+ _ ->
+ St
+ end;
true ->
Q0 = gb_trees:get(From, Buf0),
Q = queue:in({Tag,Str}, Q0),
@@ -253,17 +383,19 @@ output(From, Tag, Str, #st{io_buffering=Buffered,buffered=Buf0}=St) ->
St#st{buffered=Buf}
end.
-do_output(stdout, Str, #st{job_name=undefined}) ->
+do_output(stdout, Str, _, #st{job_name=undefined}) ->
io:put_chars(Str);
-do_output(stdout, Str0, #st{job_name=Name}) ->
+do_output(stdout, Str0, _, #st{job_name=Name}) ->
Str = io_lib:format("Testing ~ts: ~ts\n", [Name,Str0]),
io:put_chars(Str);
-do_output(Tag, Str, #st{fds=Fds}=St) ->
+do_output(Tag, Str, Phase, #st{fds=Fds}=St) ->
case gb_trees:lookup(Tag, Fds) of
+ none when Phase /= started ->
+ buffer;
none ->
S = io_lib:format("\n*** ERROR: ~w, line ~w: No known '~p' log file\n",
[?MODULE,?LINE,Tag]),
- do_output(stdout, [S,Str], St);
+ do_output(stdout, [S,Str], Phase, St);
{value,Fd} ->
try
io:put_chars(Fd, Str),
@@ -275,14 +407,14 @@ do_output(Tag, Str, #st{fds=Fds}=St) ->
S = io_lib:format("\n*** ERROR: ~w, line ~w: Error writing to "
"log file '~p': ~p\n",
[?MODULE,?LINE,Tag,Error]),
- do_output(stdout, [S,Str], St)
+ do_output(stdout, [S,Str], Phase, St)
end
end.
finalise_table(Fd, #st{html_footer=Footer}) ->
case file:position(Fd, {cur,0}) of
{ok,Pos} ->
- %% We are writing to a seekable file. Finalise so
+ %% We are writing to a seekable file. Finalise so
%% we get complete valid (and viewable) HTML code.
%% Then rewind to overwrite the finalising code.
io:put_chars(Fd, ["\n</table>\n",Footer]),
@@ -301,7 +433,7 @@ do_print_buffered(Q0, St) ->
eot ->
Q;
{Tag,Str} ->
- do_output(Tag, Str, St),
+ do_output(Tag, Str, undefined, St),
do_print_buffered(Q, St)
end.
diff --git a/lib/test_server/src/test_server_node.erl b/lib/test_server/src/test_server_node.erl
index 54a49b31ca..619fd463de 100644
--- a/lib/test_server/src/test_server_node.erl
+++ b/lib/test_server/src/test_server_node.erl
@@ -26,10 +26,9 @@
%% Test Controller interface
-export([is_release_available/1]).
--export([stop/1]).
-export([start_tracer_node/2,trace_nodes/2,stop_tracer_node/1]).
--export([start_node/5, stop_node/2]).
--export([kill_nodes/1, nodedown/2]).
+-export([start_node/5, stop_node/1]).
+-export([kill_nodes/0, nodedown/1]).
%% Internal export
-export([node_started/1,trc/1,handle_debug/4]).
@@ -57,23 +56,12 @@ is_release_available(Rel) ->
false
end.
-stop(TI) ->
- kill_nodes(TI).
-
-nodedown(Sock, TI) ->
+nodedown(Sock) ->
Match = #slave_info{name='$1',socket=Sock,client='$2',_='_'},
case ets:match(slave_tab,Match) of
- [[Node,Client]] -> % Slave node died
+ [[Node,_Client]] -> % Slave node died
gen_tcp:close(Sock),
ets:delete(slave_tab,Node),
- close_target_client(Client),
- HostAtom = test_server_sup:hostatom(Node),
- case lists:member(HostAtom,TI#target_info.slave_targets) of
- true ->
- put(test_server_free_targets,
- get(test_server_free_targets) ++ [HostAtom]);
- false -> ok
- end,
slave_died;
[] ->
ok
@@ -300,9 +288,11 @@ start_node(_SlaveName, _Type, _Options, _From, _TI) ->
%%
%% Peer nodes are always started on the same host as test_server_ctrl
-%% Socket communication is used in case target and controller is
-%% not the same node (target must not know the controller node
-%% via erlang distribution)
+%%
+%% (Socket communication is used since in early days the test target
+%% and the test server controller node could be on different hosts and
+%% the target could not know the controller node via erlang
+%% distribution)
%%
start_node_peer(SlaveName, OptList, From, TI) ->
SuppliedArgs = start_node_get_option_value(args, OptList, []),
@@ -403,8 +393,6 @@ do_start_node_slave(Host0, SlaveName, Args, Prog, Cleanup) ->
_ -> cast_to_list(Host0)
end,
Cmd = Prog ++ " " ++ Args,
- %% Can use slave.erl here because I'm both controller and target
- %% so I will ping the new node anyway
case slave:start(Host, SlaveName, Args, no_link, Prog) of
{ok,Nodename} ->
case Cleanup of
@@ -545,62 +533,37 @@ start_node_get_option_value(Key, List, Default) ->
%% stop_node(Name) -> ok | {error,Reason}
%%
%% Clean up - test_server will stop this node
-stop_node(Name, TI) ->
+stop_node(Name) ->
case ets:lookup(slave_tab,Name) of
- [#slave_info{client=Client}] ->
+ [#slave_info{}] ->
ets:delete(slave_tab,Name),
- HostAtom = test_server_sup:hostatom(Name),
- case lists:member(HostAtom,TI#target_info.slave_targets) of
- true ->
- put(test_server_free_targets,
- get(test_server_free_targets) ++ [HostAtom]);
- false -> ok
- end,
- close_target_client(Client),
ok;
[] ->
{error, not_a_slavenode}
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% kill_nodes(TI) -> ok
+%% kill_nodes() -> ok
%%
%% Brutally kill all slavenodes that were not stopped by test_server
-kill_nodes(TI) ->
+kill_nodes() ->
case ets:match_object(slave_tab,'_') of
[] -> [];
List ->
- lists:map(fun(SI) -> kill_node(SI,TI) end, List)
+ lists:map(fun(SI) -> kill_node(SI) end, List)
end.
-kill_node(SI,TI) ->
+kill_node(SI) ->
Name = SI#slave_info.name,
ets:delete(slave_tab,Name),
- HostAtom = test_server_sup:hostatom(Name),
- case lists:member(HostAtom,TI#target_info.slave_targets) of
- true ->
- put(test_server_free_targets,
- get(test_server_free_targets) ++ [HostAtom]);
- false -> ok
- end,
case SI#slave_info.socket of
undefined ->
catch rpc:call(Name,erlang,halt,[]);
Sock ->
gen_tcp:close(Sock)
end,
- close_target_client(SI#slave_info.client),
Name.
-
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%% Platform specific code
-
-close_target_client(undefined) ->
- ok.
-
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% cast_to_list(X) -> string()
%%% X = list() | atom() | void()
diff --git a/lib/test_server/src/ts.erl b/lib/test_server/src/ts.erl
index 4e5dc1b759..8e71c69d35 100644
--- a/lib/test_server/src/ts.erl
+++ b/lib/test_server/src/ts.erl
@@ -28,6 +28,7 @@
tests/0, tests/1,
install/0, install/1,
bench/0, bench/1, bench/2, benchmarks/0,
+ smoke_test/0, smoke_test/1,smoke_test/2, smoke_tests/0,
estone/0, estone/1,
cross_cover_analyse/1,
compile_testcases/0, compile_testcases/1,
@@ -174,6 +175,13 @@ help(installed) ->
" ts:bench(Spec) - Runs all benchmarks in the given spec file.\n"
" The spec file is actually ../*_test/Spec_bench.spec\n\n"
" ts:bench can take the same Options argument as ts:run.\n"
+ "Smoke test functions:\n"
+ " ts:smoke_tests() - Get all available families of smoke tests\n"
+ " ts:smoke_test() - Runs all smoke tests\n"
+ " ts:smoke_test(Spec)\n"
+ " - Runs all smoke tests in the given spec file.\n"
+ " The spec file is actually ../*_test/Spec_smoke.spec\n\n"
+ " ts:smoke_test can take the same Options argument as ts:run.\n"
"\n"
"Installation (already done):\n"
],
@@ -258,6 +266,7 @@ run(List, Opts) when is_list(List), is_list(Opts) ->
%% Runs one test spec with Options
run(Testspec, Config) when is_atom(Testspec), is_list(Config) ->
Options=check_test_get_opts(Testspec, Config),
+ IsSmoke=proplists:get_value(smoke,Config),
File=atom_to_list(Testspec),
WhatToDo =
case Testspec of
@@ -293,6 +302,8 @@ run(Testspec, Config) when is_atom(Testspec), is_list(Config) ->
case WhatToDo of
skip ->
create_skip_spec(Testspec, tests(Testspec));
+ test when IsSmoke ->
+ File++"_smoke.spec";
test ->
File++".spec"
end,
@@ -507,7 +518,22 @@ bench(Specs, Opts) ->
benchmarks() ->
ts_benchmark:benchmarks().
+smoke_test() ->
+ smoke_test([]).
+smoke_test(Opts) when is_list(Opts) ->
+ smoke_test(smoke_tests(),Opts);
+smoke_test(Spec) ->
+ smoke_test([Spec],[]).
+
+smoke_test(Spec, Opts) when is_atom(Spec) ->
+ smoke_test([Spec],Opts);
+smoke_test(Specs, Opts) ->
+ run(Specs, [{smoke,true}|Opts]).
+
+smoke_tests() ->
+ {ok, Cwd} = file:get_cwd(),
+ ts_lib:specialized_specs(Cwd,"smoke").
%%
%% estone/0, estone/1
diff --git a/lib/test_server/src/ts_benchmark.erl b/lib/test_server/src/ts_benchmark.erl
index 516d22fd2d..bd6abc3372 100644
--- a/lib/test_server/src/ts_benchmark.erl
+++ b/lib/test_server/src/ts_benchmark.erl
@@ -30,12 +30,7 @@
benchmarks() ->
{ok, Cwd} = file:get_cwd(),
- Benches = filelib:wildcard(
- filename:join([Cwd,"..","*_test","*_bench.spec"])),
- [begin
- Base = filename:basename(N),
- list_to_atom(string:substr(Base,1,string:rstr(Base,"_")-1))
- end || N <- Benches].
+ ts_lib:specialized_specs(Cwd,"bench").
run(Specs, Opts, Vars) ->
{ok, Cwd} = file:get_cwd(),
diff --git a/lib/test_server/src/ts_erl_config.erl b/lib/test_server/src/ts_erl_config.erl
index 73abe86e11..8cd6a333d8 100644
--- a/lib/test_server/src/ts_erl_config.erl
+++ b/lib/test_server/src/ts_erl_config.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2012. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2013. 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
@@ -32,7 +32,7 @@ variables(Base0, OsType) ->
Base2 = get_app_vars(fun erl_interface/2, Base1, OsType),
Base3 = get_app_vars(fun ic/2, Base2, OsType),
Base4 = get_app_vars(fun jinterface/2, Base3, OsType),
- Base5 = dl_vars(Base4, OsType),
+ Base5 = dl_vars(Base4, Base3, OsType),
Base6 = emu_vars(Base5),
Base7 = get_app_vars(fun ssl/2, Base6, OsType),
Base8 = erts_lib(Base7, OsType),
@@ -60,7 +60,7 @@ get_app_vars(AppFun, Vars, OsType) ->
exit({unexpected_internal_error, Garbage})
end.
-dl_vars(Vars, _) ->
+dl_vars(Vars, Base3, OsType) ->
ShlibRules0 = ".SUFFIXES:\n" ++
".SUFFIXES: @dll@ @obj@ .c\n\n" ++
".c@dll@:\n" ++
@@ -68,7 +68,23 @@ dl_vars(Vars, _) ->
"\t@SHLIB_LD@ @CROSSLDFLAGS@ @SHLIB_LDFLAGS@ $(SHLIB_EXTRA_LDFLAGS) -o $@ $*@obj@ @SHLIB_LDLIBS@ $(SHLIB_EXTRA_LDLIBS)",
ShlibRules = ts_lib:subst(ShlibRules0, Vars),
- [{'SHLIB_RULES', ShlibRules}|Vars].
+ case get_app_vars2(fun jinterface/2, Base3, OsType) of
+ {App, not_found} ->
+ [{'SHLIB_RULES', ShlibRules}, {App, "not_found"}|Vars];
+ _ ->
+ [{'SHLIB_RULES', ShlibRules}|Vars]
+ end.
+get_app_vars2(AppFun, Vars, OsType) ->
+ case catch AppFun(Vars,OsType) of
+ Res when is_list(Res) ->
+ {jinterface, ok};
+ {cannot_find_app, App} ->
+ {App, not_found};
+ {'EXIT', Reason} ->
+ exit(Reason);
+ Garbage ->
+ exit({unexpected_internal_error, Garbage})
+ end.
erts_lib_name(multi_threaded, {win32, V}) ->
link_library("erts_MD" ++ case is_debug_build() of
diff --git a/lib/test_server/src/ts_install_cth.erl b/lib/test_server/src/ts_install_cth.erl
index 9b6e10e7e2..7746bbed6f 100644
--- a/lib/test_server/src/ts_install_cth.erl
+++ b/lib/test_server/src/ts_install_cth.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2013. 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
@@ -103,7 +103,9 @@ pre_init_per_suite(_Suite,Config,State) ->
end,
{add_node_name(Config, State), State}
- catch Error:Reason ->
+ catch error:{badmatch,{error,enoent}} ->
+ {add_node_name(Config, State), State};
+ Error:Reason ->
Stack = erlang:get_stacktrace(),
ct:pal("~p failed! ~p:{~p,~p}",[?MODULE,Error,Reason,Stack]),
{{fail,{?MODULE,{Error,Reason, Stack}}},State}
diff --git a/lib/test_server/src/ts_lib.erl b/lib/test_server/src/ts_lib.erl
index a00f607fc1..52bb346043 100644
--- a/lib/test_server/src/ts_lib.erl
+++ b/lib/test_server/src/ts_lib.erl
@@ -27,6 +27,7 @@
erlang_type/1,
initial_capital/1,
specs/1, suites/2,
+ specialized_specs/2,
subst_file/3, subst/2, print_data/1,
make_non_erlang/2,
maybe_atom_to_list/1, progress/4,
@@ -91,13 +92,22 @@ initial_capital([C|Rest]) when $a =< C, C =< $z ->
initial_capital(String) ->
String.
+specialized_specs(Dir,PostFix) ->
+ Specs = filelib:wildcard(filename:join([filename:dirname(Dir),
+ "*_test", "*_"++PostFix++".spec"])),
+ sort_tests([begin
+ Base = filename:basename(Name),
+ list_to_atom(string:substr(Base,1,string:rstr(Base,"_")-1))
+ end || Name <- Specs]).
+
specs(Dir) ->
Specs = filelib:wildcard(filename:join([filename:dirname(Dir),
"*_test", "*.{dyn,}spec"])),
- % Filter away all spec which end with _bench.spec
+ % Filter away all spec which end with {_bench,_smoke}.spec
NoBench = fun(SpecName) ->
case lists:reverse(SpecName) of
"ceps.hcneb_"++_ -> false;
+ "ceps.ekoms_"++_ -> false;
_ -> true
end
end,