diff options
Diffstat (limited to 'lib/common_test/src')
44 files changed, 2565 insertions, 982 deletions
diff --git a/lib/common_test/src/Makefile b/lib/common_test/src/Makefile index 449cba6c83..987345c679 100644 --- a/lib/common_test/src/Makefile +++ b/lib/common_test/src/Makefile @@ -1,18 +1,19 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 2003-2013. All Rights Reserved. +# Copyright Ericsson AB 2003-2014. 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/. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # -# 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. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # # %CopyrightEnd% # @@ -77,7 +78,8 @@ MODULES= \ ct_conn_log_h \ cth_conn_log \ ct_groups \ - ct_property_test + ct_property_test \ + ct_release_test TARGET_MODULES= $(MODULES:%=$(EBIN)/%) BEAM_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) diff --git a/lib/common_test/src/common_test.app.src b/lib/common_test/src/common_test.app.src index 580d5dbd7b..d847907d75 100644 --- a/lib/common_test/src/common_test.app.src +++ b/lib/common_test/src/common_test.app.src @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2009-2014. 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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% @@ -63,9 +64,10 @@ ct_master_logs]}, {applications, [kernel,stdlib]}, {env, []}, - {runtime_dependencies,["xmerl-1.3.7","webtool-0.8.10","tools-2.6.14", - "test_server-3.7.1","stdlib-2.0","ssh-3.0.1", - "snmp-4.25.1","sasl-2.4","runtime_tools-1.8.14", - "kernel-3.0","inets-5.10","erts-6.0", - "debugger-4.0","crypto-3.3","compiler-5.0"]}]}. + {runtime_dependencies,["xmerl-1.3.8","tools-2.8", + "test_server-3.9","stdlib-2.5","ssh-4.0", + "snmp-5.1.2","sasl-2.4.2","runtime_tools-1.8.16", + "kernel-4.0","inets-6.0","erts-7.0", + "debugger-4.1","crypto-3.6","compiler-6.0", + "observer-2.1"]}]}. diff --git a/lib/common_test/src/common_test.appup.src b/lib/common_test/src/common_test.appup.src index 4dfd9f1b0d..a657e4a3a6 100644 --- a/lib/common_test/src/common_test.appup.src +++ b/lib/common_test/src/common_test.appup.src @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2014. 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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% {"%VSN%", diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl index 9d8fce2789..22941668f2 100644 --- a/lib/common_test/src/ct.erl +++ b/lib/common_test/src/ct.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2003-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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -63,7 +64,8 @@ -export([require/1, require/2, get_config/1, get_config/2, get_config/3, reload_config/1, - log/1, log/2, log/3, log/4, + escape_chars/1, escape_chars/2, + log/1, log/2, log/3, log/4, log/5, print/1, print/2, print/3, print/4, pal/1, pal/2, pal/3, pal/4, capture_start/0, capture_stop/0, capture_get/0, capture_get/1, @@ -79,6 +81,7 @@ %% Other interface functions -export([get_status/0, abort_current_testcase/1, get_event_mgr_ref/0, + get_testspec_terms/0, get_testspec_terms/1, encrypt_config_file/2, encrypt_config_file/3, decrypt_config_file/2, decrypt_config_file/3]). @@ -158,9 +161,9 @@ run(TestDirs) -> %%% {repeat,N} | {duration,DurTime} | {until,StopTime} | %%% {force_stop,ForceStop} | {decrypt,DecryptKeyOrFile} | %%% {refresh_logs,LogDir} | {logopts,LogOpts} | -%%% {verbosity,VLevels} | {basic_html,Bool} | -%%% {ct_hooks, CTHs} | {enable_builtin_hooks,Bool} | -%%% {release_shell,Bool} +%%% {verbosity,VLevels} | {basic_html,Bool} | +%%% {esc_chars,Bool} | {ct_hooks, CTHs} | +%%% {enable_builtin_hooks,Bool} | {release_shell,Bool} %%% TestDirs = [string()] | string() %%% Suites = [string()] | [atom()] | string() | atom() %%% Cases = [atom()] | atom() @@ -463,44 +466,132 @@ reload_config(Required)-> ct_config:reload_config(Required). %%%----------------------------------------------------------------- +%%% @spec get_testspec_terms() -> TestSpecTerms | undefined +%%% TestSpecTerms = [{Tag,Value}] +%%% Value = [term()] +%%% +%%% @doc Get a list of all test specification terms used to +%%% configure and run this test. +%%% +get_testspec_terms() -> + case ct_util:get_testdata(testspec) of + undefined -> + undefined; + CurrSpecRec -> + ct_testspec:testspec_rec2list(CurrSpecRec) + end. + +%%%----------------------------------------------------------------- +%%% @spec get_testspec_terms(Tags) -> TestSpecTerms | undefined +%%% Tags = [Tag] | Tag +%%% Tag = atom() +%%% TestSpecTerms = [{Tag,Value}] | {Tag,Value} +%%% Value = [{Node,term()}] | [term()] +%%% Node = atom() +%%% +%%% @doc Read one or more terms from the test specification used +%%% to configure and run this test. Tag is any valid test specification +%%% tag, such as e.g. <c>label</c>, <c>config</c>, <c>logdir</c>. +%%% User specific terms are also available to read if the +%%% <c>allow_user_terms</c> option has been set. Note that all value tuples +%%% returned, except user terms, will have the node name as first element. +%%% Note also that in order to read test terms, use <c>Tag = tests</c> +%%% (rather than <c>suites</c>, <c>groups</c> or <c>cases</c>). Value is +%%% then the list of *all* tests on the form: +%%% <c>[{Node,Dir,[{TestSpec,GroupsAndCases1},...]},...], where +%%% GroupsAndCases = [{Group,[Case]}] | [Case]</c>. +get_testspec_terms(Tags) -> + case ct_util:get_testdata(testspec) of + undefined -> + undefined; + CurrSpecRec -> + ct_testspec:testspec_rec2list(Tags, CurrSpecRec) + end. + + +%%%----------------------------------------------------------------- +%%% @spec escape_chars(IoList1) -> IoList2 | {error,Reason} +%%% IoList1 = iolist() +%%% IoList2 = iolist() +%%% +%%% @doc Escape special characters to be printed in html log +%%% +escape_chars(IoList) -> + ct_logs:escape_chars(IoList). + +%%%----------------------------------------------------------------- +%%% @spec escape_chars(Format, Args) -> IoList | {error,Reason} +%%% Format = string() +%%% Args = list() +%%% +%%% @doc Escape special characters to be printed in html log +%%% +escape_chars(Format, Args) -> + try io_lib:format(Format, Args) of + IoList -> + ct_logs:escape_chars(IoList) + catch + _:Reason -> + {error,Reason} + end. + +%%%----------------------------------------------------------------- %%% @spec log(Format) -> ok -%%% @equiv log(default,50,Format,[]) +%%% @equiv log(default,50,Format,[],[]) log(Format) -> - log(default,?STD_IMPORTANCE,Format,[]). + log(default,?STD_IMPORTANCE,Format,[],[]). %%%----------------------------------------------------------------- %%% @spec log(X1,X2) -> ok %%% X1 = Category | Importance | Format %%% X2 = Format | Args -%%% @equiv log(Category,Importance,Format,Args) +%%% @equiv log(Category,Importance,Format,Args,[]) log(X1,X2) -> {Category,Importance,Format,Args} = if is_atom(X1) -> {X1,?STD_IMPORTANCE,X2,[]}; is_integer(X1) -> {default,X1,X2,[]}; is_list(X1) -> {default,?STD_IMPORTANCE,X1,X2} end, - log(Category,Importance,Format,Args). + log(Category,Importance,Format,Args,[]). %%%----------------------------------------------------------------- %%% @spec log(X1,X2,X3) -> ok +%%% X1 = Category | Importance | Format +%%% X2 = Importance | Format | Args +%%% X3 = Format | Args | Opts +%%% @equiv log(Category,Importance,Format,Args,Opts) +log(X1,X2,X3) -> + {Category,Importance,Format,Args,Opts} = + if is_atom(X1), is_integer(X2) -> {X1,X2,X3,[],[]}; + is_atom(X1), is_list(X2) -> {X1,?STD_IMPORTANCE,X2,X3,[]}; + is_integer(X1) -> {default,X1,X2,X3,[]}; + is_list(X1), is_list(X2) -> {default,?STD_IMPORTANCE,X1,X2,X3} + end, + log(Category,Importance,Format,Args,Opts). + +%%%----------------------------------------------------------------- +%%% @spec log(X1,X2,X3,X4) -> ok %%% X1 = Category | Importance %%% X2 = Importance | Format %%% X3 = Format | Args -%%% @equiv log(Category,Importance,Format,Args) -log(X1,X2,X3) -> - {Category,Importance,Format,Args} = - if is_atom(X1), is_integer(X2) -> {X1,X2,X3,[]}; - is_atom(X1), is_list(X2) -> {X1,?STD_IMPORTANCE,X2,X3}; - is_integer(X1) -> {default,X1,X2,X3} +%%% X4 = Args | Opts +%%% @equiv log(Category,Importance,Format,Args,Opts) +log(X1,X2,X3,X4) -> + {Category,Importance,Format,Args,Opts} = + if is_atom(X1), is_integer(X2) -> {X1,X2,X3,X4,[]}; + is_atom(X1), is_list(X2) -> {X1,?STD_IMPORTANCE,X2,X3,X4}; + is_integer(X1) -> {default,X1,X2,X3,X4} end, - log(Category,Importance,Format,Args). + log(Category,Importance,Format,Args,Opts). %%%----------------------------------------------------------------- -%%% @spec log(Category,Importance,Format,Args) -> ok +%%% @spec log(Category,Importance,Format,Args,Opts) -> ok %%% Category = atom() %%% Importance = integer() %%% Format = string() %%% Args = list() +%%% Opts = [Opt] +%%% Opt = esc_chars | no_css %%% %%% @doc Printout from a test case to the log file. %%% @@ -512,8 +603,8 @@ log(X1,X2,X3) -> %%% and default value for <c>Args</c> is <c>[]</c>.</p> %%% <p>Please see the User's Guide for details on <c>Category</c> %%% and <c>Importance</c>.</p> -log(Category,Importance,Format,Args) -> - ct_logs:tc_log(Category,Importance,Format,Args). +log(Category,Importance,Format,Args,Opts) -> + ct_logs:tc_log(Category,Importance,Format,Args,Opts). %%%----------------------------------------------------------------- @@ -684,7 +775,7 @@ capture_get([]) -> test_server:capture_get(). %%%----------------------------------------------------------------- -%%% @spec fail(Reason) -> void() +%%% @spec fail(Reason) -> ok %%% Reason = term() %%% %%% @doc Terminate a test case with the given error @@ -702,7 +793,7 @@ fail(Reason) -> end. %%%----------------------------------------------------------------- -%%% @spec fail(Format, Args) -> void() +%%% @spec fail(Format, Args) -> ok %%% Format = string() %%% Args = list() %%% @@ -728,7 +819,7 @@ fail(Format, Args) -> end. %%%----------------------------------------------------------------- -%%% @spec comment(Comment) -> void() +%%% @spec comment(Comment) -> ok %%% Comment = term() %%% %%% @doc Print the given <c>Comment</c> in the comment field in @@ -751,7 +842,7 @@ comment(Comment) -> send_html_comment(lists:flatten(Formatted)). %%%----------------------------------------------------------------- -%%% @spec comment(Format, Args) -> void() +%%% @spec comment(Format, Args) -> ok %%% Format = string() %%% Args = list() %%% diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl index 5c80a299f8..251204aa75 100644 --- a/lib/common_test/src/ct_config.erl +++ b/lib/common_test/src/ct_config.erl @@ -3,16 +3,17 @@ %% %% 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 -%% 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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %%---------------------------------------------------------------------- @@ -693,8 +694,7 @@ make_crypto_key(String) -> {[K1,K2,K3],IVec}. random_bytes(N) -> - {A,B,C} = now(), - random:seed(A, B, C), + random:seed(os:timestamp()), random_bytes_1(N, []). random_bytes_1(0, Acc) -> Acc; diff --git a/lib/common_test/src/ct_config_plain.erl b/lib/common_test/src/ct_config_plain.erl index c6547f0a40..810dec7c76 100644 --- a/lib/common_test/src/ct_config_plain.erl +++ b/lib/common_test/src/ct_config_plain.erl @@ -3,16 +3,17 @@ %% %% 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 -%% 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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %%---------------------------------------------------------------------- diff --git a/lib/common_test/src/ct_config_xml.erl b/lib/common_test/src/ct_config_xml.erl index 6e0a016161..593ae3de52 100644 --- a/lib/common_test/src/ct_config_xml.erl +++ b/lib/common_test/src/ct_config_xml.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2010-2011. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %%---------------------------------------------------------------------- diff --git a/lib/common_test/src/ct_conn_log_h.erl b/lib/common_test/src/ct_conn_log_h.erl index cff02a46d9..034906a3ba 100644 --- a/lib/common_test/src/ct_conn_log_h.erl +++ b/lib/common_test/src/ct_conn_log_h.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2012-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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -34,6 +35,8 @@ -define(WIDTH,80). +-define(now, os:timestamp()). + %%%----------------------------------------------------------------- %%% Callbacks init({GL,ConnLogs}) -> @@ -72,14 +75,14 @@ handle_event({_Type, GL, _Msg}, State) when node(GL) /= node() -> handle_event({_Type,GL,{Pid,{ct_connection,Mod,Action,ConnName},Report}}, State) -> Info = conn_info(Pid,#conn_log{name=ConnName,action=Action,module=Mod}), - write_report(now(),Info,Report,GL,State), + write_report(?now,Info,Report,GL,State), {ok, State}; handle_event({_Type,GL,{Pid,Info=#conn_log{},Report}}, State) -> - write_report(now(),conn_info(Pid,Info),Report,GL,State), + write_report(?now,conn_info(Pid,Info),Report,GL,State), {ok, State}; handle_event({error_report,GL,{Pid,_,[{ct_connection,ConnName}|R]}}, State) -> %% Error reports from connection - write_error(now(),conn_info(Pid,#conn_log{name=ConnName}),R,GL,State), + write_error(?now,conn_info(Pid,#conn_log{name=ConnName}),R,GL,State), {ok, State}; handle_event(_What, State) -> {ok, State}. @@ -101,40 +104,63 @@ terminate(_,#state{logs=Logs}) -> %%%----------------------------------------------------------------- %%% Writing reports write_report(_Time,#conn_log{header=false,module=ConnMod}=Info,Data,GL,State) -> - {LogType,Fd} = get_log(Info,GL,State), - io:format(Fd,"~n~ts",[format_data(ConnMod,LogType,Data)]); + case get_log(Info,GL,State) of + {silent,_,_} -> + ok; + {LogType,Dest,Fd} -> + Str = if LogType == html, Dest == gl -> ["$tc_html","~n~ts"]; + true -> "~n~ts" + end, + io:format(Fd,Str,[format_data(ConnMod,LogType,Data)]) + end; write_report(Time,#conn_log{module=ConnMod}=Info,Data,GL,State) -> - {LogType,Fd} = get_log(Info,GL,State), - io:format(Fd,"~n~ts~ts~ts",[format_head(ConnMod,LogType,Time), - format_title(LogType,Info), - format_data(ConnMod,LogType,Data)]). + case get_log(Info,GL,State) of + {silent,_,_} -> + ok; + {LogType,Dest,Fd} -> + case format_data(ConnMod,LogType,Data) of + [] when Info#conn_log.action==send; Info#conn_log.action==recv -> + ok; + FormattedData -> + Str = if LogType == html, Dest == gl -> + ["$tc_html","~n~ts~ts~ts"]; + true -> + "~n~ts~ts~ts" + end, + io:format(Fd,Str,[format_head(ConnMod,LogType,Time), + format_title(LogType,Info), + FormattedData]) + end + end. write_error(Time,#conn_log{module=ConnMod}=Info,Report,GL,State) -> case get_log(Info,GL,State) of - {html,_} -> + {LogType,_,_} when LogType==html; LogType==silent -> %% The error will anyway be written in the html log by the %% sasl error handler, so don't write it again. ok; - {LogType,Fd} -> - io:format(Fd,"~n~ts~ts~ts", - [format_head(ConnMod,LogType,Time," ERROR"), - format_title(LogType,Info), - format_error(LogType,Report)]) + {LogType,Dest,Fd} -> + Str = if LogType == html, Dest == gl -> ["$tc_html","~n~ts~ts~ts"]; + true -> "~n~ts~ts~ts" + end, + io:format(Fd,Str,[format_head(ConnMod,LogType,Time," ERROR"), + format_title(LogType,Info), + format_error(LogType,Report)]) end. get_log(Info,GL,State) -> case proplists:get_value(GL,State#state.logs) of undefined -> - {html,State#state.default_gl}; + {html,gl,State#state.default_gl}; ConnLogs -> case proplists:get_value(Info#conn_log.module,ConnLogs) of {html,_} -> - {html,GL}; + {html,gl,GL}; {LogType,Fds} -> - {LogType,get_fd(Info,Fds)}; + {LogType,file,get_fd(Info,Fds)}; undefined -> - {html,GL} + {html,gl,GL} end end. diff --git a/lib/common_test/src/ct_cover.erl b/lib/common_test/src/ct_cover.erl index b630a51835..8e5ce9b245 100644 --- a/lib/common_test/src/ct_cover.erl +++ b/lib/common_test/src/ct_cover.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2006-2012. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% diff --git a/lib/common_test/src/ct_event.erl b/lib/common_test/src/ct_event.erl index c1c1d943b9..01beabaa73 100644 --- a/lib/common_test/src/ct_event.erl +++ b/lib/common_test/src/ct_event.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2006-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. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -259,8 +260,8 @@ handle_info(_Info, State) -> {ok, State}. %%-------------------------------------------------------------------- -%% Function: terminate(Reason, State) -> void() -%% Description:Whenever an event handler is deleted from an event manager, +%% Function: terminate(Reason, State) -> ok +%% Description: Whenever an event handler is deleted from an event manager, %% this function is called. It should be the opposite of Module:init/1 and %% do any necessary cleaning up. %%-------------------------------------------------------------------- diff --git a/lib/common_test/src/ct_framework.erl b/lib/common_test/src/ct_framework.erl index 498950c9d1..19a3a51b88 100644 --- a/lib/common_test/src/ct_framework.erl +++ b/lib/common_test/src/ct_framework.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2004-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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -27,7 +28,7 @@ -export([init_tc/3, end_tc/3, end_tc/4, get_suite/2, get_all_cases/1]). -export([report/2, warn/1, error_notification/4]). --export([get_logopts/0, format_comment/1, get_html_wrapper/4]). +-export([get_log_dir/0, get_logopts/0, format_comment/1, get_html_wrapper/4]). -export([error_in_suite/1, init_per_suite/1, end_per_suite/1, init_per_group/2, end_per_group/2]). @@ -51,9 +52,23 @@ %%% %%% @doc Test server framework callback, called by the test_server %%% when a new test case is started. -init_tc(Mod,Func,Config) -> +init_tc(Mod,EPTC={end_per_testcase,_},[Config]) -> %% in case Mod == ct_framework, lookup the suite name Suite = get_suite_name(Mod, Config), + case ct_hooks:init_tc(Suite,EPTC,Config) of + NewConfig when is_list(NewConfig) -> + {ok,[NewConfig]}; + Other-> + Other + end; + +init_tc(Mod,Func0,Args) -> + %% in case Mod == ct_framework, lookup the suite name + Suite = get_suite_name(Mod, Args), + {Func,HookFunc} = case Func0 of + {init_per_testcase,F} -> {F,Func0}; + _ -> {Func0,Func0} + end, %% check if previous testcase was interpreted and has left %% a "dead" trace window behind - if so, kill it @@ -85,7 +100,7 @@ init_tc(Mod,Func,Config) -> end, [create]), case ct_util:read_suite_data({seq,Suite,Func}) of undefined -> - init_tc1(Mod,Suite,Func,Config); + init_tc1(Mod,Suite,Func,HookFunc,Args); Seq when is_atom(Seq) -> case ct_util:read_suite_data({seq,Suite,Seq}) of [Func|TCs] -> % this is the 1st case in Seq @@ -101,26 +116,27 @@ init_tc(Mod,Func,Config) -> _ -> ok end, - init_tc1(Mod,Suite,Func,Config); + init_tc1(Mod,Suite,Func,HookFunc,Args); {failed,Seq,BadFunc} -> {auto_skip,{sequence_failed,Seq,BadFunc}} end end - end. + end. -init_tc1(?MODULE,_,error_in_suite,[Config0]) when is_list(Config0) -> +init_tc1(?MODULE,_,error_in_suite,_,[Config0]) when is_list(Config0) -> ct_logs:init_tc(false), ct_event:notify(#event{name=tc_start, node=node(), data={?MODULE,error_in_suite}}), - case ?val(error, Config0) of + ct_suite_init(?MODULE,error_in_suite,[],Config0), + case ?val(error,Config0) of undefined -> {fail,"unknown_error_in_suite"}; Reason -> {fail,Reason} end; -init_tc1(Mod,Suite,Func,[Config0]) when is_list(Config0) -> +init_tc1(Mod,Suite,Func,HookFunc,[Config0]) when is_list(Config0) -> Config1 = case ct_util:read_suite_data(last_saved_config) of {{Suite,LastFunc},SavedConfig} -> % last testcase @@ -154,11 +170,13 @@ init_tc1(Mod,Suite,Func,[Config0]) when is_list(Config0) -> %% testcase info function (these should only survive the %% testcase, not the whole suite) FuncSpec = group_or_func(Func,Config0), - if is_tuple(FuncSpec) -> % group - ok; - true -> - ct_config:delete_default_config(testcase) - end, + HookFunc1 = + if is_tuple(FuncSpec) -> % group + FuncSpec; + true -> + ct_config:delete_default_config(testcase), + HookFunc + end, Initialize = fun() -> ct_logs:init_tc(false), ct_event:notify(#event{name=tc_start, @@ -182,14 +200,15 @@ init_tc1(Mod,Suite,Func,[Config0]) when is_list(Config0) -> Initialize(), {fail,Reason}; _ -> - init_tc2(Mod,Suite,Func,SuiteInfo,MergeResult,Config) + init_tc2(Mod,Suite,Func,HookFunc1, + SuiteInfo,MergeResult,Config) end end; -init_tc1(_Mod,_Suite,_Func,Args) -> +init_tc1(_Mod,_Suite,_Func,_HookFunc,Args) -> {ok,Args}. -init_tc2(Mod,Suite,Func,SuiteInfo,MergeResult,Config) -> +init_tc2(Mod,Suite,Func,HookFunc,SuiteInfo,MergeResult,Config) -> %% timetrap must be handled before require MergedInfo = timetrap_first(MergeResult, [], []), %% tell logger to use specified style sheet @@ -236,7 +255,7 @@ init_tc2(Mod,Suite,Func,SuiteInfo,MergeResult,Config) -> {ok,PostInitHook,Config1} -> case get('$test_server_framework_test') of undefined -> - ct_suite_init(Suite, FuncSpec, PostInitHook, Config1); + ct_suite_init(Suite,HookFunc,PostInitHook,Config1); Fun -> PostInitHookResult = do_post_init_hook(PostInitHook, Config1), @@ -249,16 +268,16 @@ init_tc2(Mod,Suite,Func,SuiteInfo,MergeResult,Config) -> end end. -ct_suite_init(Suite, FuncSpec, PostInitHook, Config) when is_list(Config) -> - case ct_hooks:init_tc(Suite, FuncSpec, Config) of +ct_suite_init(Suite,HookFunc,PostInitHook,Config) when is_list(Config) -> + case ct_hooks:init_tc(Suite,HookFunc,Config) of NewConfig when is_list(NewConfig) -> - PostInitHookResult = do_post_init_hook(PostInitHook, NewConfig), + PostInitHookResult = do_post_init_hook(PostInitHook,NewConfig), {ok, [PostInitHookResult ++ NewConfig]}; Else -> Else end. -do_post_init_hook(PostInitHook, Config) -> +do_post_init_hook(PostInitHook,Config) -> lists:flatmap(fun({Tag,Fun}) -> case lists:keysearch(Tag,1,Config) of {value,_} -> @@ -635,16 +654,43 @@ try_set_default(Name,Key,Info,Where) -> end_tc(Mod, Fun, Args) -> %% Have to keep end_tc/3 for backwards compatibility issues end_tc(Mod, Fun, Args, '$end_tc_dummy'). -end_tc(?MODULE,error_in_suite,_, _) -> % bad start! +end_tc(?MODULE,error_in_suite,{Result,[Args]},Return) -> + %% this clause gets called if CT has encountered a suite that + %% can't be executed + FinalNotify = + case ct_hooks:end_tc(?MODULE, error_in_suite, Args, Result, Return) of + '$ct_no_change' -> + Result; + HookResult -> + HookResult + end, + Event = #event{name=tc_done, + node=node(), + data={?MODULE,error_in_suite,tag(FinalNotify)}}, + ct_event:sync_notify(Event), ok; end_tc(Mod,Func,{TCPid,Result,[Args]}, Return) when is_pid(TCPid) -> end_tc(Mod,Func,TCPid,Result,Args,Return); end_tc(Mod,Func,{Result,[Args]}, Return) -> end_tc(Mod,Func,self(),Result,Args,Return). -end_tc(Mod,Func,TCPid,Result,Args,Return) -> +end_tc(Mod,IPTC={init_per_testcase,_Func},_TCPid,Result,Args,Return) -> + %% in case Mod == ct_framework, lookup the suite name + Suite = get_suite_name(Mod, Args), + case ct_hooks:end_tc(Suite,IPTC,Args,Result,Return) of + '$ct_no_change' -> + ok; + HookResult -> + HookResult + end; + +end_tc(Mod,Func0,TCPid,Result,Args,Return) -> %% in case Mod == ct_framework, lookup the suite name Suite = get_suite_name(Mod, Args), + {EPTC,Func} = case Func0 of + {end_per_testcase,F} -> {true,F}; + _ -> {false,Func0} + end, test_server:timetrap_cancel(), @@ -671,11 +717,15 @@ end_tc(Mod,Func,TCPid,Result,Args,Return) -> end, ct_util:delete_suite_data(last_saved_config), - FuncSpec = group_or_func(Func,Args), - + {FuncSpec,HookFunc} = + if not EPTC -> + FS = group_or_func(Func,Args), + {FS,FS}; + true -> + {Func,Func0} + end, {Result1,FinalNotify} = - case ct_hooks:end_tc( - Suite, FuncSpec, Args, Result, Return) of + case ct_hooks:end_tc(Suite,HookFunc,Args,Result,Return) of '$ct_no_change' -> {ok,Result}; HookResult -> @@ -816,13 +866,13 @@ tag(_Other) -> %%% <code>Func</code> in suite <code>Mod</code> crashing. %%% <code>Error</code> specifies the reason for failing. error_notification(Mod,Func,_Args,{Error,Loc}) -> - ErrSpec = case Error of + ErrorSpec = case Error of {What={_E,_R},Trace} when is_list(Trace) -> What; What -> What end, - ErrStr = case ErrSpec of + ErrorStr = case ErrorSpec of {badmatch,Descr} -> Descr1 = lists:flatten(io_lib:format("~P",[Descr,10])), if length(Descr1) > 50 -> @@ -844,7 +894,8 @@ error_notification(Mod,Func,_Args,{Error,Loc}) -> Other -> io_lib:format("~P", [Other,5]) end, - ErrorHtml = "<font color=\"brown\">" ++ ErrStr ++ "</font>", + ErrorHtml = + "<font color=\"brown\">" ++ ct_logs:escape_chars(ErrorStr) ++ "</font>", case {Mod,Error} of %% some notifications come from the main test_server process %% and for these cases the existing comment may not be modified @@ -872,41 +923,43 @@ error_notification(Mod,Func,_Args,{Error,Loc}) -> end end, - PrintErr = fun(ErrFormat, ErrArgs) -> - Div = "~n- - - - - - - - - - - - - - - - " - "- - - - - - - - - -~n", - io:format(user, lists:concat([Div,ErrFormat,Div,"~n"]), - ErrArgs), + PrintError = fun(ErrorFormat, ErrorArgs) -> + Div = "~n- - - - - - - - - - - - - - - - - - - " + "- - - - - - - - - - - - - - - - - - - - -~n", + ErrorStr2 = io_lib:format(ErrorFormat, ErrorArgs), + io:format(user, lists:concat([Div,ErrorStr2,Div,"~n"]), + []), Link = "\n\n<a href=\"#end\">" "Full error description and stacktrace" "</a>", + ErrorHtml2 = ct_logs:escape_chars(ErrorStr2), ct_logs:tc_log(ct_error_notify, ?MAX_IMPORTANCE, "CT Error Notification", - ErrFormat++Link, ErrArgs) + ErrorHtml2++Link, [], []) end, case Loc of [{?MODULE,error_in_suite}] -> - PrintErr("Error in suite detected: ~ts", [ErrStr]); + PrintError("Error in suite detected: ~ts", [ErrorStr]); R when R == unknown; R == undefined -> - PrintErr("Error detected: ~ts", [ErrStr]); + PrintError("Error detected: ~ts", [ErrorStr]); %% if a function specified by all/0 does not exist, we %% pick up undef here - [{LastMod,LastFunc}|_] when ErrStr == "undef" -> - PrintErr("~w:~w could not be executed~nReason: ~ts", - [LastMod,LastFunc,ErrStr]); + [{LastMod,LastFunc}|_] when ErrorStr == "undef" -> + PrintError("~w:~w could not be executed~nReason: ~ts", + [LastMod,LastFunc,ErrorStr]); [{LastMod,LastFunc}|_] -> - PrintErr("~w:~w failed~nReason: ~ts", [LastMod,LastFunc,ErrStr]); + PrintError("~w:~w failed~nReason: ~ts", [LastMod,LastFunc,ErrorStr]); [{LastMod,LastFunc,LastLine}|_] -> %% print error to console, we are only %% interested in the last executed expression - PrintErr("~w:~w failed on line ~w~nReason: ~ts", - [LastMod,LastFunc,LastLine,ErrStr]), + PrintError("~w:~w failed on line ~w~nReason: ~ts", + [LastMod,LastFunc,LastLine,ErrorStr]), case ct_util:read_suite_data({seq,Mod,Func}) of undefined -> @@ -1062,9 +1115,32 @@ get_all_cases1(_, []) -> get_all(Mod, ConfTests) -> case catch apply(Mod, all, []) of - {'EXIT',_} -> - Reason = - list_to_atom(atom_to_list(Mod)++":all/0 is missing"), + {'EXIT',{undef,[{Mod,all,[],_} | _]}} -> + Reason = + case code:which(Mod) of + non_existing -> + list_to_atom(atom_to_list(Mod)++ + " can not be compiled or loaded"); + _ -> + list_to_atom(atom_to_list(Mod)++":all/0 is missing") + end, + %% this makes test_server call error_in_suite as first + %% (and only) test case so we can report Reason properly + [{?MODULE,error_in_suite,[[{error,Reason}]]}]; + {'EXIT',ExitReason} -> + case ct_util:get_testdata({error_in_suite,Mod}) of + undefined -> + ErrStr = io_lib:format("~n*** ERROR *** " + "~w:all/0 failed: ~p~n", + [Mod,ExitReason]), + io:format(user, ErrStr, []), + %% save the error info so it doesn't get printed twice + ct_util:set_testdata_async({{error_in_suite,Mod}, + ExitReason}); + _ExitReason -> + ct_util:delete_testdata({error_in_suite,Mod}) + end, + Reason = list_to_atom(atom_to_list(Mod)++":all/0 failed"), %% this makes test_server call error_in_suite as first %% (and only) test case so we can report Reason properly [{?MODULE,error_in_suite,[[{error,Reason}]]}]; @@ -1287,6 +1363,8 @@ report(What,Data) -> end, ct_logs:unregister_groupleader(ReportingPid), case {Func,Result} of + {error_in_suite,_} when Suite == ?MODULE -> + ok; {init_per_suite,_} -> ok; {end_per_suite,_} -> @@ -1440,3 +1518,8 @@ get_html_wrapper(TestName, PrintLabel, Cwd, TableCols) -> get_html_wrapper(TestName, PrintLabel, Cwd, TableCols, Encoding) -> ct_logs:get_ts_html_wrapper(TestName, PrintLabel, Cwd, TableCols, Encoding). + +%%%----------------------------------------------------------------- +%%% @spec get_log_dir() -> {ok,LogDir} +get_log_dir() -> + ct_logs:get_log_dir(true). diff --git a/lib/common_test/src/ct_ftp.erl b/lib/common_test/src/ct_ftp.erl index 71fd8754ff..616b1a8934 100644 --- a/lib/common_test/src/ct_ftp.erl +++ b/lib/common_test/src/ct_ftp.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2003-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. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% diff --git a/lib/common_test/src/ct_gen_conn.erl b/lib/common_test/src/ct_gen_conn.erl index 56082086f6..e46fd77383 100644 --- a/lib/common_test/src/ct_gen_conn.erl +++ b/lib/common_test/src/ct_gen_conn.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2003-2014. 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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -24,10 +25,9 @@ -module(ct_gen_conn). --compile(export_all). - --export([start/4, stop/1, get_conn_pid/1]). +-export([start/4, stop/1, get_conn_pid/1, check_opts/1]). -export([call/2, call/3, return/2, do_within_time/2]). +-export([log/3, start_log/1, cont_log/2, end_log/0]). %%---------------------------------------------------------------------- %% Exported types diff --git a/lib/common_test/src/ct_groups.erl b/lib/common_test/src/ct_groups.erl index 14a8aab881..899d2bfb5a 100644 --- a/lib/common_test/src/ct_groups.erl +++ b/lib/common_test/src/ct_groups.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2004-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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -80,7 +81,7 @@ find(Mod, all, all, [{Name,Props,Tests} | Gs], Known, Defs, _) find(Mod, all, TCs, [{Name,Props,Tests} | Gs], Known, Defs, _) when is_atom(Name), is_list(Props), is_list(Tests) -> cyclic_test(Mod, Name, Known), - Tests1 = rm_unwanted_tcs(Tests, TCs, []), + Tests1 = modify_tc_list(Tests, TCs, []), trim(make_conf(Mod, Name, Props, find(Mod, all, TCs, Tests1, [Name | Known], Defs, true))) ++ @@ -90,7 +91,7 @@ find(Mod, all, TCs, [{Name,Props,Tests} | Gs], Known, Defs, _) find(Mod, [Name|GrNames]=SPath, TCs, [{Name,Props,Tests} | Gs], Known, Defs, FindAll) when is_atom(Name), is_list(Props), is_list(Tests) -> cyclic_test(Mod, Name, Known), - Tests1 = rm_unwanted_tcs(Tests, TCs, GrNames), + Tests1 = modify_tc_list(Tests, TCs, GrNames), trim(make_conf(Mod, Name, Props, find(Mod, GrNames, TCs, Tests1, [Name|Known], Defs, FindAll))) ++ @@ -132,7 +133,7 @@ find(_Mod, [_|_], _TCs, [], _Known, _Defs, _) -> find(Mod, GrNames, TCs, [{Name,Props,Tests} | Gs], Known, Defs, FindAll) when is_atom(Name), is_list(Props), is_list(Tests) -> cyclic_test(Mod, Name, Known), - Tests1 = rm_unwanted_tcs(Tests, TCs, GrNames), + Tests1 = modify_tc_list(Tests, TCs, GrNames), trim(make_conf(Mod, Name, Props, find(Mod, GrNames, TCs, Tests1, [Name|Known], Defs, FindAll))) ++ @@ -283,70 +284,57 @@ trim_test(Test) -> %% GrNames is [] if the terminating group has been found. From %% that point, all specified test should be included (as well as %% sub groups for deeper search). -rm_unwanted_tcs(Tests, all, []) -> - Tests; - -rm_unwanted_tcs(Tests, TCs, []) -> - sort_tests(lists:flatmap(fun(Test) when is_tuple(Test), - (size(Test) > 2) -> - [Test]; - (Test={group,_}) -> - [Test]; - (Test={_M,TC}) -> - case lists:member(TC, TCs) of - true -> [Test]; - false -> [] - end; - (Test) when is_atom(Test) -> - case lists:keysearch(Test, 2, TCs) of - {value,_} -> - [Test]; - _ -> - case lists:member(Test, TCs) of - true -> [Test]; - false -> [] - end - end; - (Test) -> [Test] - end, Tests), TCs); - -rm_unwanted_tcs(Tests, _TCs, _) -> - [Test || Test <- Tests, not is_atom(Test)]. - -%% make sure the order of tests is according to the order in TCs -sort_tests(Tests, TCs) when is_list(TCs)-> - lists:sort(fun(T1, T2) -> - case {is_tc(T1),is_tc(T2)} of - {true,true} -> - (position(T1, TCs) =< - position(T2, TCs)); - {false,true} -> - (position(T2, TCs) == (length(TCs)+1)); - _ -> true - - end - end, Tests); -sort_tests(Tests, _) -> - Tests. - -is_tc(T) when is_atom(T) -> true; -is_tc({group,_}) -> false; -is_tc({_M,T}) when is_atom(T) -> true; -is_tc(_) -> false. - -position(T, TCs) -> - position(T, TCs, 1). - -position(T, [T|_TCs], Pos) -> - Pos; -position(T, [{_,T}|_TCs], Pos) -> - Pos; -position({M,T}, [T|_TCs], Pos) when M /= group -> - Pos; -position(T, [_|TCs], Pos) -> - position(T, TCs, Pos+1); -position(_, [], Pos) -> - Pos. +modify_tc_list(GrSpecTs, all, []) -> + GrSpecTs; + +modify_tc_list(GrSpecTs, TSCs, []) -> + modify_tc_list1(GrSpecTs, TSCs); + +modify_tc_list(GrSpecTs, _TSCs, _) -> + [Test || Test <- GrSpecTs, not is_atom(Test)]. + +modify_tc_list1(GrSpecTs, TSCs) -> + %% remove all cases in group tc list that should not be executed + GrSpecTs1 = + lists:flatmap(fun(Test) when is_tuple(Test), + (size(Test) > 2) -> + [Test]; + (Test={group,_}) -> + [Test]; + (Test={_M,TC}) -> + case lists:member(TC, TSCs) of + true -> [Test]; + false -> [] + end; + (Test) when is_atom(Test) -> + case lists:keysearch(Test, 2, TSCs) of + {value,_} -> + [Test]; + _ -> + case lists:member(Test, TSCs) of + true -> [Test]; + false -> [] + end + end; + (Test) -> [Test] + end, GrSpecTs), + {TSCs2,GrSpecTs3} = + lists:foldr( + fun(TC, {TSCs1,GrSpecTs2}) -> + case lists:member(TC,GrSpecTs1) of + true -> + {[TC|TSCs1],lists:delete(TC,GrSpecTs2)}; + false -> + case lists:keysearch(TC, 2, GrSpecTs) of + {value,Test} -> + {[Test|TSCs1], + lists:keydelete(TC, 2, GrSpecTs2)}; + false -> + {TSCs1,GrSpecTs2} + end + end + end, {[],GrSpecTs1}, TSCs), + TSCs2 ++ GrSpecTs3. %%%----------------------------------------------------------------- diff --git a/lib/common_test/src/ct_hooks.erl b/lib/common_test/src/ct_hooks.erl index df4c98d9d1..90f36cbdc2 100644 --- a/lib/common_test/src/ct_hooks.erl +++ b/lib/common_test/src/ct_hooks.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2004-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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -66,6 +67,8 @@ terminate(Hooks) -> %% tests. -spec init_tc(Mod :: atom(), FuncSpec :: atom() | + {ConfigFunc :: init_per_testcase | end_per_testcase, + TestCase :: atom()} | {ConfigFunc :: init_per_group | end_per_group, GroupName :: atom(), Properties :: list()}, @@ -92,13 +95,19 @@ init_tc(Mod, {init_per_group, GroupName, Properties}, Config) -> call(fun call_generic/3, Config, [pre_init_per_group, GroupName]); init_tc(_Mod, {end_per_group, GroupName, _}, Config) -> call(fun call_generic/3, Config, [pre_end_per_group, GroupName]); -init_tc(_Mod, TC, Config) -> +init_tc(_Mod, {init_per_testcase,TC}, Config) -> + call(fun call_generic/3, Config, [pre_init_per_testcase, TC]); +init_tc(_Mod, {end_per_testcase,TC}, Config) -> + call(fun call_generic/3, Config, [pre_end_per_testcase, TC]); +init_tc(_Mod, TC = error_in_suite, Config) -> call(fun call_generic/3, Config, [pre_init_per_testcase, TC]). %% @doc Called as each test case is completed. This includes all configuration %% tests. -spec end_tc(Mod :: atom(), - FuncSpec :: atom() | + FuncSpec :: atom() | + {ConfigFunc :: init_per_testcase | end_per_testcase, + TestCase :: atom()} | {ConfigFunc :: init_per_group | end_per_group, GroupName :: atom(), Properties :: list()}, @@ -125,10 +134,17 @@ end_tc(Mod, {end_per_group, GroupName, Properties}, Config, Result, _Return) -> [post_end_per_group, GroupName, Config], '$ct_no_change'), maybe_stop_locker(Mod, GroupName, Properties), Res; -end_tc(_Mod, TC, Config, Result, _Return) -> +end_tc(_Mod, {init_per_testcase,TC}, Config, Result, _Return) -> + call(fun call_generic/3, Result, [post_init_per_testcase, TC, Config], + '$ct_no_change'); +end_tc(_Mod, {end_per_testcase,TC}, Config, Result, _Return) -> + call(fun call_generic/3, Result, [post_end_per_testcase, TC, Config], + '$ct_no_change'); +end_tc(_Mod, TC = error_in_suite, Config, Result, _Return) -> call(fun call_generic/3, Result, [post_end_per_testcase, TC, Config], '$ct_no_change'). + %% Case = TestCase | {TestCase,GroupName} on_tc_skip(How, {Suite, Case, Reason}) -> call(fun call_cleanup/3, {How, Reason}, [on_tc_skip, Suite, Case]). @@ -243,6 +259,8 @@ remove(_, Else) -> %% Translate scopes, i.e. init_per_group,group1 -> end_per_group,group1 etc scope([pre_init_per_testcase, TC|_]) -> + [post_init_per_testcase, TC]; +scope([pre_end_per_testcase, TC|_]) -> [post_end_per_testcase, TC]; scope([pre_init_per_group, GroupName|_]) -> [post_end_per_group, GroupName]; @@ -316,7 +334,8 @@ get_hooks() -> %% If we are doing a cleanup call i.e. {post,pre}_end_per_*, all priorities %% are reversed. Probably want to make this sorting algorithm pluginable %% as some point... -resort(Calls,Hooks,[F|_R]) when F == post_end_per_testcase; +resort(Calls,Hooks,[F|_R]) when F == pre_end_per_testcase; + F == post_end_per_testcase; F == pre_end_per_group; F == post_end_per_group; F == pre_end_per_suite; @@ -366,10 +385,10 @@ pos(Id,[_|Rest],Num) -> catch_apply(M,F,A, Default) -> try - apply(M,F,A) + erlang:apply(M,F,A) catch _:Reason -> case erlang:get_stacktrace() of - %% Return the default if it was the CTH module which did not have the function. + %% Return the default if it was the CTH module which did not have the function. [{M,F,A,_}|_] when Reason == undef -> Default; Trace -> diff --git a/lib/common_test/src/ct_hooks_lock.erl b/lib/common_test/src/ct_hooks_lock.erl index e33fa278dc..1a058aa8ca 100644 --- a/lib/common_test/src/ct_hooks_lock.erl +++ b/lib/common_test/src/ct_hooks_lock.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2004-2011. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl index fa55a9754e..a9ad571bfc 100644 --- a/lib/common_test/src/ct_logs.erl +++ b/lib/common_test/src/ct_logs.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2003-2014. 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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -36,15 +37,17 @@ -export([add_external_logs/1, add_link/3]). -export([make_last_run_index/0]). -export([make_all_suites_index/1,make_all_runs_index/1]). --export([get_ts_html_wrapper/5]). +-export([get_ts_html_wrapper/5, escape_chars/1]). -export([xhtml/2, locate_priv_file/1, make_relative/1]). -export([insert_javascript/1]). -export([uri/1]). %% Logging stuff directly from testcase --export([tc_log/3, tc_log/4, tc_log/5, tc_log_async/3, tc_log_async/5, +-export([tc_log/3, tc_log/4, tc_log/5, tc_log/6, + tc_log_async/3, tc_log_async/5, tc_print/3, tc_print/4, - tc_pal/3, tc_pal/4, ct_log/3, basic_html/0]). + tc_pal/3, tc_pal/4, ct_log/3, + basic_html/0]). %% Simulate logger process for use without ct environment running -export([simulate/0]). @@ -73,6 +76,8 @@ -define(abs(Name), filename:absname(Name)). +-define(now, os:timestamp()). + -record(log_cache, {version, all_runs = [], tests = []}). @@ -264,7 +269,7 @@ cast(Msg) -> %%% <p>This function is called by ct_framework:init_tc/3</p> init_tc(RefreshLog) -> call({init_tc,self(),group_leader(),RefreshLog}), - io:format(xhtml("", "<br />")), + io:format(["$tc_html",xhtml("", "<br />")]), ok. %%%----------------------------------------------------------------- @@ -311,9 +316,10 @@ unregister_groupleader(Pid) -> %%% data to log (as in <code>io:format(Format,Args)</code>).</p> log(Heading,Format,Args) -> cast({log,sync,self(),group_leader(),ct_internal,?MAX_IMPORTANCE, - [{int_header(),[log_timestamp(now()),Heading]}, + [{hd,int_header(),[log_timestamp(?now),Heading]}, {Format,Args}, - {int_footer(),[]}]}), + {ft,int_footer(),[]}], + true}), ok. %%%----------------------------------------------------------------- @@ -333,7 +339,7 @@ log(Heading,Format,Args) -> %%% @see end_log/0 start_log(Heading) -> cast({log,sync,self(),group_leader(),ct_internal,?MAX_IMPORTANCE, - [{int_header(),[log_timestamp(now()),Heading]}]}), + [{hd,int_header(),[log_timestamp(?now),Heading]}],false}), ok. %%%----------------------------------------------------------------- @@ -348,7 +354,7 @@ cont_log([],[]) -> cont_log(Format,Args) -> maybe_log_timestamp(), cast({log,sync,self(),group_leader(),ct_internal,?MAX_IMPORTANCE, - [{Format,Args}]}), + [{Format,Args}],true}), ok. %%%----------------------------------------------------------------- @@ -360,7 +366,7 @@ cont_log(Format,Args) -> %%% @see cont_log/2 end_log() -> cast({log,sync,self(),group_leader(),ct_internal,?MAX_IMPORTANCE, - [{int_footer(), []}]}), + [{ft,int_footer(), []}],false}), ok. @@ -397,32 +403,46 @@ add_link(Heading,File,Type) -> %%% @spec tc_log(Category,Format,Args) -> ok %%% @equiv tc_log(Category,?STD_IMPORTANCE,Format,Args) tc_log(Category,Format,Args) -> - tc_log(Category,?STD_IMPORTANCE,Format,Args). + tc_log(Category,?STD_IMPORTANCE,"User",Format,Args,[]). %%%----------------------------------------------------------------- %%% @spec tc_log(Category,Importance,Format,Args) -> ok %%% @equiv tc_log(Category,Importance,"User",Format,Args) tc_log(Category,Importance,Format,Args) -> - tc_log(Category,Importance,"User",Format,Args). + tc_log(Category,Importance,"User",Format,Args,[]). + +%%%----------------------------------------------------------------- +%%% @spec tc_log(Category,Importance,Format,Args) -> ok +%%% @equiv tc_log(Category,Importance,"User",Format,Args) +tc_log(Category,Importance,Format,Args,Opts) -> + tc_log(Category,Importance,"User",Format,Args,Opts). %%%----------------------------------------------------------------- -%%% @spec tc_log(Category,Importance,Printer,Format,Args) -> ok +%%% @spec tc_log(Category,Importance,Printer,Format,Args,Opts) -> ok %%% Category = atom() %%% Importance = integer() %%% Printer = string() %%% Format = string() %%% Args = list() +%%% Opts = list() %%% %%% @doc Printout from a testcase. %%% %%% <p>This function is called by <code>ct</code> when logging %%% stuff directly from a testcase (i.e. not from within the CT %%% framework).</p> -tc_log(Category,Importance,Printer,Format,Args) -> - cast({log,sync,self(),group_leader(),Category,Importance, - [{div_header(Category,Printer),[]}, - {Format,Args}, - {div_footer(),[]}]}), +tc_log(Category,Importance,Printer,Format,Args,Opts) -> + Data = + case lists:member(no_css, Opts) of + true -> + [{Format,Args}]; + false -> + [{hd,div_header(Category,Printer),[]}, + {Format,Args}, + {ft,div_footer(),[]}] + end, + cast({log,sync,self(),group_leader(),Category,Importance,Data, + lists:member(esc_chars, Opts)}), ok. %%%----------------------------------------------------------------- @@ -448,9 +468,10 @@ tc_log_async(Category,Format,Args) -> %%% asks ct_logs for an html wrapper.</p> tc_log_async(Category,Importance,Printer,Format,Args) -> cast({log,async,self(),group_leader(),Category,Importance, - [{div_header(Category,Printer),[]}, + [{hd,div_header(Category,Printer),[]}, {Format,Args}, - {div_footer(),[]}]}), + {ft,div_footer(),[]}], + true}), ok. %%%----------------------------------------------------------------- %%% @spec tc_print(Category,Format,Args) @@ -491,11 +512,11 @@ tc_print(Category,Importance,Format,Args) -> get_heading(default) -> io_lib:format("\n-----------------------------" "-----------------------\n~s\n", - [log_timestamp(now())]); + [log_timestamp(?now)]); get_heading(Category) -> io_lib:format("\n-----------------------------" "-----------------------\n~s ~w\n", - [log_timestamp(now()),Category]). + [log_timestamp(?now),Category]). %%%----------------------------------------------------------------- @@ -519,53 +540,55 @@ tc_pal(Category,Format,Args) -> tc_pal(Category,Importance,Format,Args) -> tc_print(Category,Importance,Format,Args), cast({log,sync,self(),group_leader(),Category,Importance, - [{div_header(Category),[]}, + [{hd,div_header(Category),[]}, {Format,Args}, - {div_footer(),[]}]}), + {ft,div_footer(),[]}], + true}), ok. %%%----------------------------------------------------------------- -%%% @spec ct_pal(Category,Format,Args) -> ok +%%% @spec ct_log(Category,Format,Args) -> ok %%% Category = atom() %%% Format = string() %%% Args = list() %%% -%%% @doc Print and log to the ct framework log +%%% @doc Print to the ct framework log %%% %%% <p>This function is called by internal ct functions to %%% force logging to the ct framework log</p> ct_log(Category,Format,Args) -> - cast({ct_log,[{div_header(Category),[]}, + cast({ct_log,[{hd,div_header(Category),[]}, {Format,Args}, - {div_footer(),[]}]}), + {ft,div_footer(),[]}], + true}), ok. %%%================================================================= %%% Internal functions int_header() -> - "<div class=\"ct_internal\"><b>*** CT ~s *** ~ts</b>". + "</pre>\n<div class=\"ct_internal\"><pre><b>*** CT ~s *** ~ts</b>". int_footer() -> - "</div>". + "</pre></div>\n<pre>". div_header(Class) -> div_header(Class,"User"). div_header(Class,Printer) -> - "\n<div class=\"" ++ atom_to_list(Class) ++ "\"><b>*** " ++ Printer ++ - " " ++ log_timestamp(now()) ++ " ***</b>". + "\n</pre>\n<div class=\"" ++ atom_to_list(Class) ++ "\"><pre><b>*** " + ++ Printer ++ " " ++ log_timestamp(?now) ++ " ***</b>". div_footer() -> - "</div>". + "</pre></div>\n<pre>". maybe_log_timestamp() -> - {MS,S,US} = now(), + {MS,S,US} = ?now, case get(log_timestamp) of {MS,S,_} -> ok; _ -> cast({log,sync,self(),group_leader(),ct_internal,?MAX_IMPORTANCE, - [{"<i>~s</i>",[log_timestamp({MS,S,US})]}]}) + [{hd,"<i>~s</i>",[log_timestamp({MS,S,US})]}],false}) end. log_timestamp({MS,S,US}) -> @@ -586,7 +609,8 @@ log_timestamp({MS,S,US}) -> ct_log_fd, tc_groupleaders, stylesheet, - async_print_jobs}). + async_print_jobs, + tc_esc_chars}). logger(Parent, Mode, Verbosity) -> register(?MODULE,self()), @@ -686,7 +710,7 @@ logger(Parent, Mode, Verbosity) -> make_last_run_index(Time), CtLogFd = open_ctlog(?misc_io_log), io:format(CtLogFd,int_header()++int_footer(), - [log_timestamp(now()),"Common Test Logger started"]), + [log_timestamp(?now),"Common Test Logger started"]), Parent ! {started,self(),{Time,filename:absname("")}}, set_evmgr_gl(CtLogFd), @@ -705,14 +729,18 @@ logger(Parent, Mode, Verbosity) -> end end || {Cat,VLvl} <- Verbosity], io:nl(CtLogFd), - + TcEscChars = case application:get_env(common_test, esc_chars) of + {ok,ECBool} -> ECBool; + _ -> true + end, logger_loop(#logger_state{parent=Parent, log_dir=AbsDir, start_time=Time, orig_GL=group_leader(), ct_log_fd=CtLogFd, tc_groupleaders=[], - async_print_jobs=[]}). + async_print_jobs=[], + tc_esc_chars=TcEscChars}). copy_priv_files([SrcF | SrcFs], [DestF | DestFs]) -> case file:copy(SrcF, DestF) of @@ -726,7 +754,7 @@ copy_priv_files([], []) -> logger_loop(State) -> receive - {log,SyncOrAsync,Pid,GL,Category,Importance,List} -> + {log,SyncOrAsync,Pid,GL,Category,Importance,Content,EscChars} -> VLvl = case Category of ct_internal -> ?MAX_VERBOSITY; @@ -738,20 +766,21 @@ logger_loop(State) -> end, if Importance >= (100-VLvl) -> CtLogFd = State#logger_state.ct_log_fd, + DoEscChars = State#logger_state.tc_esc_chars and EscChars, case get_groupleader(Pid, GL, State) of {tc_log,TCGL,TCGLs} -> case erlang:is_process_alive(TCGL) of true -> State1 = print_to_log(SyncOrAsync, Pid, - Category, - TCGL, List, State), + Category, TCGL, Content, + DoEscChars, State), logger_loop(State1#logger_state{ tc_groupleaders = TCGLs}); false -> %% Group leader is dead, so write to the %% CtLog or unexpected_io log instead - unexpected_io(Pid,Category,Importance, - List,CtLogFd), + unexpected_io(Pid, Category, Importance, + Content, CtLogFd, DoEscChars), logger_loop(State) end; @@ -759,7 +788,8 @@ logger_loop(State) -> %% If category is ct_internal then write %% to ct_log, else write to unexpected_io %% log - unexpected_io(Pid,Category,Importance,List,CtLogFd), + unexpected_io(Pid, Category, Importance, Content, + CtLogFd, DoEscChars), logger_loop(State#logger_state{ tc_groupleaders = TCGLs}) end; @@ -770,7 +800,7 @@ logger_loop(State) -> %% make sure no IO for this test case from the %% CT logger gets rejected test_server:permit_io(GL, self()), - print_style(GL, State#logger_state.stylesheet), + print_style(GL,GL,State#logger_state.stylesheet), set_evmgr_gl(GL), TCGLs = add_tc_gl(TCPid,GL,State), if not RefreshLog -> @@ -815,10 +845,17 @@ logger_loop(State) -> logger_loop(State); {clear_stylesheet,_} -> logger_loop(State#logger_state{stylesheet = undefined}); - {ct_log, List} -> + {ct_log,Content,EscChars} -> + Str = lists:map(fun({_HdOrFt,Str,Args}) -> + [io_lib:format(Str,Args),io_lib:nl()]; + ({Str,Args}) when EscChars -> + Io = io_lib:format(Str,Args), + [escape_chars(Io),io_lib:nl()]; + ({Str,Args}) -> + [io_lib:format(Str,Args),io_lib:nl()] + end, Content), Fd = State#logger_state.ct_log_fd, - [begin io:format(Fd,Str,Args),io:nl(Fd) end || - {Str,Args} <- List], + io:format(Fd, "~ts", [Str]), logger_loop(State); {'DOWN',Ref,_,_Pid,_} -> %% there might be print jobs executing in parallel with ct_logs @@ -835,50 +872,84 @@ logger_loop(State) -> stop -> io:format(State#logger_state.ct_log_fd, int_header()++int_footer(), - [log_timestamp(now()),"Common Test Logger finished"]), + [log_timestamp(?now),"Common Test Logger finished"]), close_ctlog(State#logger_state.ct_log_fd), ok end. -create_io_fun(FromPid, CtLogFd) -> +create_io_fun(FromPid, CtLogFd, EscChars) -> %% we have to build one io-list of all strings %% before printing, or other io printouts (made in %% parallel) may get printed between this header %% and footer - fun({Str,Args}, IoList) -> - case catch io_lib:format(Str,Args) of - {'EXIT',_Reason} -> + fun(FormatData, IoList) -> + {Escapable,Str,Args} = + case FormatData of + {_HdOrFt,S,A} -> {false,S,A}; + {S,A} -> {true,S,A} + end, + try io_lib:format(Str, Args) of + IoStr when Escapable, EscChars, IoList == [] -> + escape_chars(IoStr); + IoStr when Escapable, EscChars -> + [IoList,"\n",escape_chars(IoStr)]; + IoStr when IoList == [] -> + IoStr; + IoStr -> + [IoList,"\n",IoStr] + catch + _:_Reason -> io:format(CtLogFd, "Logging fails! Str: ~p, Args: ~p~n", [Str,Args]), %% stop the testcase, we need to see the fault exit(FromPid, {log_printout_error,Str,Args}), - []; - IoStr when IoList == [] -> - [IoStr]; - IoStr -> - [IoList,"\n",IoStr] + [] end end. -print_to_log(sync, FromPid, Category, TCGL, List, State) -> +escape_chars([Bin | Io]) when is_binary(Bin) -> + [Bin | escape_chars(Io)]; +escape_chars([List | Io]) when is_list(List) -> + [escape_chars(List) | escape_chars(Io)]; +escape_chars([$< | Io]) -> + ["<" | escape_chars(Io)]; +escape_chars([$> | Io]) -> + [">" | escape_chars(Io)]; +escape_chars([$& | Io]) -> + ["&" | escape_chars(Io)]; +escape_chars([Char | Io]) when is_integer(Char) -> + [Char | escape_chars(Io)]; +escape_chars([]) -> + []; +escape_chars(Bin) -> + Bin. + +print_to_log(sync, FromPid, Category, TCGL, Content, EscChars, State) -> %% in some situations (exceptions), the printout is made from the %% test server IO process and there's no valid group leader to send to CtLogFd = State#logger_state.ct_log_fd, if FromPid /= TCGL -> - IoFun = create_io_fun(FromPid, CtLogFd), - io:format(TCGL,"~ts", [lists:foldl(IoFun, [], List)]); + IoFun = create_io_fun(FromPid, CtLogFd, EscChars), + IoList = lists:foldl(IoFun, [], Content), + try io:format(TCGL,["$tc_html","~ts"], [IoList]) of + ok -> ok + catch + _:_ -> + io:format(TCGL,"~ts", [IoList]) + end; true -> - unexpected_io(FromPid,Category,?MAX_IMPORTANCE,List,CtLogFd) + unexpected_io(FromPid, Category, ?MAX_IMPORTANCE, Content, + CtLogFd, EscChars) end, State; -print_to_log(async, FromPid, Category, TCGL, List, State) -> +print_to_log(async, FromPid, Category, TCGL, Content, EscChars, State) -> %% in some situations (exceptions), the printout is made from the %% test server IO process and there's no valid group leader to send to CtLogFd = State#logger_state.ct_log_fd, Printer = if FromPid /= TCGL -> - IoFun = create_io_fun(FromPid, CtLogFd), + IoFun = create_io_fun(FromPid, CtLogFd, EscChars), fun() -> test_server:permit_io(TCGL, self()), @@ -891,25 +962,28 @@ print_to_log(async, FromPid, Category, TCGL, List, State) -> case erlang:is_process_alive(TCGL) of true -> - try io:format(TCGL, "~ts", - [lists:foldl(IoFun,[],List)]) of + try io:format(TCGL, ["$tc_html","~ts"], + [lists:foldl(IoFun,[],Content)]) of _ -> ok catch _:terminated -> unexpected_io(FromPid, Category, ?MAX_IMPORTANCE, - List, CtLogFd) + Content, CtLogFd, EscChars); + _:_ -> + io:format(TCGL, "~ts", + [lists:foldl(IoFun,[],Content)]) end; false -> unexpected_io(FromPid, Category, ?MAX_IMPORTANCE, - List, CtLogFd) + Content, CtLogFd, EscChars) end end; true -> fun() -> unexpected_io(FromPid, Category, ?MAX_IMPORTANCE, - List, CtLogFd) + Content, CtLogFd, EscChars) end end, case State#logger_state.async_print_jobs of @@ -1039,26 +1113,27 @@ open_ctlog(MiscIoName) -> "View I/O logged after the test run</a></li>\n</ul>\n", [MiscIoName,MiscIoName]), - print_style(Fd,undefined), + print_style(Fd,group_leader(),undefined), io:format(Fd, xhtml("<br><h2>Progress Log</h2>\n<pre>\n", "<br />\n<h4>PROGRESS LOG</h4>\n<pre>\n"), []), Fd. -print_style(Fd,undefined) -> +print_style(Fd,GL,undefined) -> case basic_html() of true -> - io:format(Fd, - "<style>\n" - "div.ct_internal { background:lightgrey; color:black; }\n" - "div.default { background:lightgreen; color:black; }\n" - "</style>\n", - []); + Style = "<style>\n + div.ct_internal { background:lightgrey; color:black; }\n + div.default { background:lightgreen; color:black; }\n + </style>\n", + if Fd == GL -> io:format(["$tc_html",Style], []); + true -> io:format(Fd, Style, []) + end; _ -> ok end; -print_style(Fd,StyleSheet) -> +print_style(Fd,GL,StyleSheet) -> case file:read_file(StyleSheet) of {ok,Bin} -> Str = b2s(Bin,encoding(StyleSheet)), @@ -1071,29 +1146,30 @@ print_style(Fd,StyleSheet) -> N1 -> N1 end, if (Pos0 == 0) and (Pos1 /= 0) -> - print_style_error(Fd,StyleSheet,missing_style_start_tag); + print_style_error(Fd,GL,StyleSheet,missing_style_start_tag); (Pos0 /= 0) and (Pos1 == 0) -> - print_style_error(Fd,StyleSheet,missing_style_end_tag); + print_style_error(Fd,GL,StyleSheet,missing_style_end_tag); Pos0 /= 0 -> Style = string:sub_string(Str,Pos0,Pos1+7), - io:format(Fd,"~ts\n",[Style]); + if Fd == GL -> io:format(Fd,["$tc_html","~ts\n"],[Style]); + true -> io:format(Fd,"~ts\n",[Style]) + end; Pos0 == 0 -> - io:format(Fd,"<style>~ts</style>\n",[Str]) + if Fd == GL -> io:format(Fd,["$tc_html","<style>\n~ts</style>\n"],[Str]); + true -> io:format(Fd,"<style>\n~ts</style>\n",[Str]) + end end; {error,Reason} -> - print_style_error(Fd,StyleSheet,Reason) + print_style_error(Fd,GL,StyleSheet,Reason) end. -%% Simple link version, doesn't work with all browsers unfortunately. :-( -%% print_style(Fd, StyleSheet) -> -%% io:format(Fd, -%% "<link href=~p rel=\"stylesheet\" type=\"text/css\">", -%% [StyleSheet]). - -print_style_error(Fd,StyleSheet,Reason) -> - io:format(Fd,"\n<!-- Failed to load stylesheet ~ts: ~p -->\n", - [StyleSheet,Reason]), - print_style(Fd,undefined). +print_style_error(Fd,GL,StyleSheet,Reason) -> + IO = io_lib:format("\n<!-- Failed to load stylesheet ~ts: ~p -->\n", + [StyleSheet,Reason]), + if Fd == GL -> io:format(Fd,["$tc_html",IO],[]); + true -> io:format(Fd,IO,[]) + end, + print_style(Fd,GL,undefined). close_ctlog(Fd) -> io:format(Fd, "\n</pre>\n", []), @@ -1361,11 +1437,11 @@ total_row(Success, Fail, UserSkip, AutoSkip, NotBuilt, All) -> [xhtml("<tr valign=top>\n", ["</tbody>\n<tfoot>\n<tr class=\"",odd_or_even(),"\">\n"]), "<td><b>Total</b></td>\n", Label, TimestampCell, - "<td align=right><b>",integer_to_list(Success),"<b></td>\n", - "<td align=right><b>",integer_to_list(Fail),"<b></td>\n", + "<td align=right><b>",integer_to_list(Success),"</b></td>\n", + "<td align=right><b>",integer_to_list(Fail),"</b></td>\n", "<td align=right>",integer_to_list(AllSkip), " (",UserSkipStr,"/",AutoSkipStr,")</td>\n", - "<td align=right><b>",integer_to_list(NotBuilt),"<b></td>\n", + "<td align=right><b>",integer_to_list(NotBuilt),"</b></td>\n", AllInfo, "</tr>\n", xhtml("","</tfoot>\n")]. @@ -1557,10 +1633,12 @@ header1(Title, SubTitle, TableCols) -> "<!-- autogenerated by '"++atom_to_list(?MODULE)++"' -->\n", "<head>\n", "<title>" ++ Title ++ " " ++ SubTitle ++ "</title>\n", - "<meta http-equiv=\"cache-control\" content=\"no-cache\">\n", - "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">\n", + "<meta http-equiv=\"cache-control\" content=\"no-cache\"></meta>\n", + "<meta http-equiv=\"content-type\" content=\"text/html; " + "charset=utf-8\"></meta>\n", xhtml("", - ["<link rel=\"stylesheet\" href=\"",uri(CSSFile),"\" type=\"text/css\">\n"]), + ["<link rel=\"stylesheet\" href=\"",uri(CSSFile), + "\" type=\"text/css\"></link>\n"]), xhtml("", ["<script type=\"text/javascript\" src=\"",JQueryFile, "\"></script>\n"]), @@ -1607,7 +1685,7 @@ footer() -> "Copyright © ", year(), " <a href=\"http://www.erlang.org\">Open Telecom Platform</a>", xhtml("<br>\n", "<br />\n"), - "Updated: <!date>", current_time(), "<!/date>", + "Updated: <!--date-->", current_time(), "<!--/date-->", xhtml("<br>\n", "<br />\n"), xhtml("</font></p>\n", "</div>\n"), "</center>\n" @@ -1982,9 +2060,9 @@ interactive_link() -> "<!-- autogenerated by '"++atom_to_list(?MODULE)++"' -->\n", "<head>\n", "<title>Last interactive run</title>\n", - "<meta http-equiv=\"cache-control\" content=\"no-cache\">\n", + "<meta http-equiv=\"cache-control\" content=\"no-cache\"></meta>\n", "<meta http-equiv=\"content-type\" content=\"text/html; " - "charset=utf-8\">\n", + "charset=utf-8\"></meta>\n", "</head>\n", "<body>\n", "Log from last interactive run: <a href=\"",uri(CtLog),"\">", @@ -2052,6 +2130,13 @@ runentry(Dir, Totals={Node,Label,Logs, ?testname_width-3)), lists:flatten(io_lib:format("~ts...",[Trunc])) end, + TotMissingStr = + if NotBuilt > 0 -> + ["<font color=\"red\">", + integer_to_list(NotBuilt),"</font>"]; + true -> + integer_to_list(NotBuilt) + end, Total = TotSucc+TotFail+AllSkip, A = xhtml(["<td align=center><font size=\"-1\">",Node, "</font></td>\n", @@ -2071,7 +2156,7 @@ runentry(Dir, Totals={Node,Label,Logs, "<td align=right>",TotFailStr,"</td>\n", "<td align=right>",integer_to_list(AllSkip), " (",UserSkipStr,"/",AutoSkipStr,")</td>\n", - "<td align=right>",integer_to_list(NotBuilt),"</td>\n"], + "<td align=right>",TotMissingStr,"</td>\n"], TotalsStr = A++B++C, XHTML = [xhtml("<tr>\n", ["<tr class=\"",odd_or_even(),"\">\n"]), @@ -2836,8 +2921,12 @@ simulate() -> simulate_logger_loop() -> receive - {log,_,_,_,_,_,List} -> - S = [[io_lib:format(Str,Args),io_lib:nl()] || {Str,Args} <- List], + {log,_,_,_,_,_,Content,_} -> + S = lists:map(fun({_,Str,Args}) -> + [io_lib:format(Str,Args),io_lib:nl()]; + ({Str,Args}) -> + [io_lib:format(Str,Args),io_lib:nl()] + end, Content), io:format("~ts",[S]), simulate_logger_loop(); stop -> @@ -3043,15 +3132,15 @@ get_ts_html_wrapper(TestName, Logdir, PrintLabel, Cwd, TableCols, Encoding) -> "Copyright © ", year(), " <a href=\"http://www.erlang.org\">", "Open Telecom Platform</a><br>\n", - "Updated: <!date>", current_time(), "<!/date>", + "Updated: <!--date-->", current_time(), "<!--/date-->", "<br>\n</font></p>\n"], {basic_html, ["<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n", "<html>\n", "<head><title>", TestName1, "</title>\n", - "<meta http-equiv=\"cache-control\" content=\"no-cache\">\n", + "<meta http-equiv=\"cache-control\" content=\"no-cache\"></meta>\n", "<meta http-equiv=\"content-type\" content=\"text/html; charset=", - html_encoding(Encoding),"\">\n", + html_encoding(Encoding),"\"></meta>\n", "</head>\n", "<body", Bgr, " bgcolor=\"white\" text=\"black\" ", "link=\"blue\" vlink=\"purple\" alink=\"red\">\n", @@ -3068,7 +3157,7 @@ get_ts_html_wrapper(TestName, Logdir, PrintLabel, Cwd, TableCols, Encoding) -> "Copyright © ", year(), " <a href=\"http://www.erlang.org\">", "Open Telecom Platform</a><br />\n", - "Updated: <!date>", current_time(), "<!/date>", + "Updated: <!--date-->", current_time(), "<!--/date-->", "<br />\n</div>\n"], CSSFile = xhtml(fun() -> "" end, @@ -3095,9 +3184,11 @@ get_ts_html_wrapper(TestName, Logdir, PrintLabel, Cwd, TableCols, Encoding) -> "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n", "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n", "<head>\n<title>", TestName1, "</title>\n", - "<meta http-equiv=\"cache-control\" content=\"no-cache\">\n", - "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">\n", - "<link rel=\"stylesheet\" href=\"", uri(CSSFile), "\" type=\"text/css\">\n", + "<meta http-equiv=\"cache-control\" content=\"no-cache\"></meta>\n", + "<meta http-equiv=\"content-type\" content=\"text/html; ", + "charset=utf-8\"></meta>\n", + "<link rel=\"stylesheet\" href=\"", uri(CSSFile), + "\" type=\"text/css\"></link>\n", "<script type=\"text/javascript\" src=\"", JQueryFile, "\"></script>\n", "<script type=\"text/javascript\" src=\"", TableSorterFile, "\"></script>\n"] ++ TableSorterScript ++ ["</head>\n","<body>\n", LabelStr, "\n"], @@ -3223,11 +3314,11 @@ html_encoding(latin1) -> html_encoding(utf8) -> "utf-8". -unexpected_io(Pid,ct_internal,_Importance,List,CtLogFd) -> - IoFun = create_io_fun(Pid,CtLogFd), - io:format(CtLogFd, "~ts", [lists:foldl(IoFun, [], List)]); -unexpected_io(Pid,_Category,_Importance,List,CtLogFd) -> - IoFun = create_io_fun(Pid,CtLogFd), - Data = io_lib:format("~ts", [lists:foldl(IoFun, [], List)]), +unexpected_io(Pid, ct_internal, _Importance, Content, CtLogFd, EscChars) -> + IoFun = create_io_fun(Pid, CtLogFd, EscChars), + io:format(CtLogFd, "~ts", [lists:foldl(IoFun, [], Content)]); +unexpected_io(Pid, _Category, _Importance, Content, CtLogFd, EscChars) -> + IoFun = create_io_fun(Pid, CtLogFd, EscChars), + Data = io_lib:format("~ts", [lists:foldl(IoFun, [], Content)]), test_server_io:print_unexpected(Data), ok. diff --git a/lib/common_test/src/ct_make.erl b/lib/common_test/src/ct_make.erl index d4bd81e78d..f4b81a0ef6 100644 --- a/lib/common_test/src/ct_make.erl +++ b/lib/common_test/src/ct_make.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2009-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. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% diff --git a/lib/common_test/src/ct_master.erl b/lib/common_test/src/ct_master.erl index 2cdb259899..d24edad2eb 100644 --- a/lib/common_test/src/ct_master.erl +++ b/lib/common_test/src/ct_master.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2006-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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -26,7 +27,7 @@ -export([run_on_node/2,run_on_node/3]). -export([run_test/1,run_test/2]). -export([get_event_mgr_ref/0]). --export([basic_html/1]). +-export([basic_html/1,esc_chars/1]). -export([abort/0,abort/1,progress/0]). @@ -316,6 +317,16 @@ basic_html(Bool) -> ok. %%%----------------------------------------------------------------- +%%% @spec esc_chars(Bool) -> ok +%%% Bool = true | false +%%% +%%% @doc If set to false, the ct_master logs will be written without +%%% special characters being escaped in the HTML logs. +esc_chars(Bool) -> + application:set_env(common_test_master, esc_chars, Bool), + ok. + +%%%----------------------------------------------------------------- %%% MASTER, runs on central controlling node. %%%----------------------------------------------------------------- start_master(NodeOptsList) -> diff --git a/lib/common_test/src/ct_master_event.erl b/lib/common_test/src/ct_master_event.erl index fd97ab16f7..0d7d220fd0 100644 --- a/lib/common_test/src/ct_master_event.erl +++ b/lib/common_test/src/ct_master_event.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2006-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. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -168,7 +169,7 @@ handle_info(_Info,State) -> {ok,State}. %%-------------------------------------------------------------------- -%% Function: terminate(Reason, State) -> void() +%% Function: terminate(Reason, State) -> ok %% Description:Whenever an event handler is deleted from an event manager, %% this function is called. It should be the opposite of Module:init/1 and %% do any necessary cleaning up. diff --git a/lib/common_test/src/ct_master_logs.erl b/lib/common_test/src/ct_master_logs.erl index 5393097f57..54190a8254 100644 --- a/lib/common_test/src/ct_master_logs.erl +++ b/lib/common_test/src/ct_master_logs.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2006-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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -37,6 +38,8 @@ -define(details_file_name,"details.info"). -define(table_color,"lightblue"). +-define(now, os:timestamp()). + %%%-------------------------------------------------------------------- %%% API %%%-------------------------------------------------------------------- @@ -54,7 +57,7 @@ start(LogDir,Nodes) -> end. log(Heading,Format,Args) -> - cast({log,self(),[{int_header(),[log_timestamp(now()),Heading]}, + cast({log,self(),[{int_header(),[log_timestamp(?now),Heading]}, {Format,Args}, {int_footer(),[]}]}), ok. @@ -132,7 +135,7 @@ init(Parent,LogDir,Nodes) -> atom_to_list(N) ++ " " end,Nodes)), - io:format(CtLogFd,int_header(),[log_timestamp(now()),"Test Nodes\n"]), + io:format(CtLogFd,int_header(),[log_timestamp(?now),"Test Nodes\n"]), io:format(CtLogFd,"~ts\n",[NodeStr]), io:put_chars(CtLogFd,[int_footer(),"\n"]), @@ -189,7 +192,7 @@ loop(State) -> make_all_runs_index(State#state.logdir), io:format(State#state.log_fd, int_header()++int_footer(), - [log_timestamp(now()),"Finished!"]), + [log_timestamp(?now),"Finished!"]), close_ct_master_log(State#state.log_fd), close_nodedir_index(State#state.nodedir_ix_fd), ok @@ -235,9 +238,9 @@ config_table1([]) -> ["</tbody>\n</table>\n"]. int_header() -> - "<div class=\"ct_internal\"><b>*** CT MASTER ~s *** ~ts</b>". + "</pre>\n<div class=\"ct_internal\"><pre><b>*** CT MASTER ~s *** ~ts</b>". int_footer() -> - "</div>". + "</pre></div>\n<pre>". %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% NodeDir Index functions %%% @@ -384,11 +387,12 @@ header(Title, TableCols) -> "<!-- autogenerated by '"++atom_to_list(?MODULE)++"' -->\n", "<head>\n", "<title>" ++ Title ++ "</title>\n", - "<meta http-equiv=\"cache-control\" content=\"no-cache\">\n", - "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">\n", + "<meta http-equiv=\"cache-control\" content=\"no-cache\"></meta>\n", + "<meta http-equiv=\"content-type\" content=\"text/html; ", + "charset=utf-8\"></meta>\n", xhtml("", ["<link rel=\"stylesheet\" href=\"",ct_logs:uri(CSSFile), - "\" type=\"text/css\">"]), + "\" type=\"text/css\"></link>\n"]), xhtml("", ["<script type=\"text/javascript\" src=\"",JQueryFile, "\"></script>\n"]), @@ -416,7 +420,7 @@ footer() -> "Copyright © ", year(), " <a href=\"http://www.erlang.org\">Open Telecom Platform</a>", xhtml("<br>\n", "<br />\n"), - "Updated: <!date>", current_time(), "<!/date>", + "Updated: <!--date-->", current_time(), "<--!/date-->", xhtml("<br>\n", "<br />\n"), xhtml("</font></p>\n", "</div>\n"), "</center>\n" diff --git a/lib/common_test/src/ct_master_status.erl b/lib/common_test/src/ct_master_status.erl index f9f511ecca..6a3572b261 100644 --- a/lib/common_test/src/ct_master_status.erl +++ b/lib/common_test/src/ct_master_status.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2006-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. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -100,8 +101,8 @@ handle_info(_Info, State) -> {ok, State}. %%-------------------------------------------------------------------- -%% Function: terminate(Reason, State) -> void() -%% Description:Whenever an event handler is deleted from an event manager, +%% Function: terminate(Reason, State) -> ok +%% Description: Whenever an event handler is deleted from an event manager, %% this function is called. It should be the opposite of Module:init/1 and %% do any necessary cleaning up. %%-------------------------------------------------------------------- diff --git a/lib/common_test/src/ct_netconfc.erl b/lib/common_test/src/ct_netconfc.erl index 80ffb51ab9..8812514ad9 100644 --- a/lib/common_test/src/ct_netconfc.erl +++ b/lib/common_test/src/ct_netconfc.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2012-2014. 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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -191,6 +192,7 @@ get_config/4, edit_config/3, edit_config/4, + edit_config/5, delete_config/2, delete_config/3, copy_config/3, @@ -232,7 +234,6 @@ %% Internal defines %%---------------------------------------------------------------------- -define(APPLICATION,?MODULE). --define(VALID_SSH_OPTS,[user, password, user_dir]). -define(DEFAULT_STREAM,"NETCONF"). -define(error(ConnName,Report), @@ -262,6 +263,7 @@ session_id, msg_id = 1, hello_status, + no_end_tag_buff = <<>>, buff = <<>>, pending = [], % [#pending] event_receiver}).% pid @@ -691,15 +693,39 @@ get_config(Client, Source, Filter, Timeout) -> %%---------------------------------------------------------------------- %% @spec edit_config(Client, Target, Config) -> Result -%% @equiv edit_config(Client, Target, Config, infinity) +%% @equiv edit_config(Client, Target, Config, [], infinity) edit_config(Client, Target, Config) -> edit_config(Client, Target, Config, ?DEFAULT_TIMEOUT). %%---------------------------------------------------------------------- --spec edit_config(Client, Target, Config, Timeout) -> Result when +-spec edit_config(Client, Target, Config, OptParamsOrTimeout) -> Result when Client :: client(), Target :: netconf_db(), Config :: simple_xml(), + OptParamsOrTimeout :: [simple_xml()] | timeout(), + Result :: ok | {error,error_reason()}. +%% @doc +%% +%% If `OptParamsOrTimeout' is a timeout value, then this is +%% equivalent to {@link edit_config/5. edit_config(Client, Target, +%% Config, [], Timeout)}. +%% +%% If `OptParamsOrTimeout' is a list of simple XML, then this is +%% equivalent to {@link edit_config/5. edit_config(Client, Target, +%% Config, OptParams, infinity)}. +%% +%% @end +edit_config(Client, Target, Config, Timeout) when ?is_timeout(Timeout) -> + edit_config(Client, Target, Config, [], Timeout); +edit_config(Client, Target, Config, OptParams) when is_list(OptParams) -> + edit_config(Client, Target, Config, OptParams, ?DEFAULT_TIMEOUT). + +%%---------------------------------------------------------------------- +-spec edit_config(Client, Target, Config, OptParams, Timeout) -> Result when + Client :: client(), + Target :: netconf_db(), + Config :: simple_xml(), + OptParams :: [simple_xml()], Timeout :: timeout(), Result :: ok | {error,error_reason()}. %% @doc Edit configuration data. @@ -708,10 +734,20 @@ edit_config(Client, Target, Config) -> %% include `:candidate' or `:startup' in its list of %% capabilities. %% +%% `OptParams' can be used for specifying optional parameters +%% (`default-operation', `test-option' or `error-option') that will be +%% added to the `edit-config' request. The value must be a list +%% containing valid simple XML, for example +%% +%% ``` +%% [{'default-operation', ["none"]}, +%% {'error-option', ["rollback-on-error"]}] +%%''' +%% %% @end %%---------------------------------------------------------------------- -edit_config(Client, Target, Config, Timeout) -> - call(Client, {send_rpc_op, edit_config, [Target,Config], Timeout}). +edit_config(Client, Target, Config, OptParams, Timeout) -> + call(Client, {send_rpc_op, edit_config, [Target,Config,OptParams], Timeout}). %%---------------------------------------------------------------------- @@ -1100,6 +1136,7 @@ handle_msg({get_event_streams=Op,Streams,Timeout}, From, State) -> SimpleXml = encode_rpc_operation(get,[Filter]), do_send_rpc(Op, SimpleXml, Timeout, From, State). +%% @private handle_msg({ssh_cm, CM, {data, Ch, _Type, Data}}, State) -> ssh_connection:adjust_window(CM,Ch,size(Data)), handle_data(Data, State); @@ -1133,7 +1170,7 @@ handle_msg({Ref,timeout},#state{pending=Pending} = State) -> end, %% Halfhearted try to get in correct state, this matches %% the implementation before this patch - {R,State#state{pending=Pending1, buff= <<>>}}. + {R,State#state{pending=Pending1, no_end_tag_buff= <<>>, buff= <<>>}}. %% @private %% Called by ct_util_server to close registered connections before terminate. @@ -1168,7 +1205,7 @@ call(Client, Msg, Timeout, WaitStop) -> {error,no_such_client}; {error,{process_down,Pid,normal}} when WaitStop -> %% This will happen when server closes connection - %% before clien received rpc-reply on + %% before client received rpc-reply on %% close-session. ok; {error,{process_down,Pid,normal}} -> @@ -1219,13 +1256,11 @@ check_options([{port,Port}|T], Host, _, #options{} = Options) -> check_options([{timeout, Timeout}|T], Host, Port, Options) when is_integer(Timeout); Timeout==infinity -> check_options(T, Host, Port, Options#options{timeout = Timeout}); -check_options([{X,_}=Opt|T], Host, Port, #options{ssh=SshOpts}=Options) -> - case lists:member(X,?VALID_SSH_OPTS) of - true -> - check_options(T, Host, Port, Options#options{ssh=[Opt|SshOpts]}); - false -> - {error, {invalid_option, Opt}} - end. +check_options([{timeout, _} = Opt|_T], _Host, _Port, _Options) -> + {error, {invalid_option, Opt}}; +check_options([Opt|T], Host, Port, #options{ssh=SshOpts}=Options) -> + %% Option verified by ssh + check_options(T, Host, Port, Options#options{ssh=[Opt|SshOpts]}). %%%----------------------------------------------------------------- set_request_timer(infinity) -> @@ -1235,6 +1270,14 @@ set_request_timer(T) -> {ok,TRef} = timer:send_after(T,{Ref,timeout}), {Ref,TRef}. +%%%----------------------------------------------------------------- +cancel_request_timer(undefined,undefined) -> + ok; +cancel_request_timer(Ref,TRef) -> + _ = timer:cancel(TRef), + receive {Ref,timeout} -> ok + after 0 -> ok + end. %%%----------------------------------------------------------------- client_hello(Options) when is_list(Options) -> @@ -1254,8 +1297,8 @@ encode_rpc_operation(get,[Filter]) -> {get,filter(Filter)}; encode_rpc_operation(get_config,[Source,Filter]) -> {'get-config',[{source,[Source]}] ++ filter(Filter)}; -encode_rpc_operation(edit_config,[Target,Config]) -> - {'edit-config',[{target,[Target]},{config,[Config]}]}; +encode_rpc_operation(edit_config,[Target,Config,OptParams]) -> + {'edit-config',[{target,[Target]}] ++ OptParams ++ [{config,[Config]}]}; encode_rpc_operation(delete_config,[Target]) -> {'delete-config',[{target,[Target]}]}; encode_rpc_operation(copy_config,[Target,Source]) -> @@ -1327,17 +1370,37 @@ to_xml_doc(Simple) -> %%%----------------------------------------------------------------- %%% Parse and handle received XML data -handle_data(NewData,#state{connection=Connection,buff=Buff0} = State0) -> +%%% Two buffers are used: +%%% * 'no_end_tag_buff' contains data that is checked and does not +%%% contain any (part of an) end tag. +%%% * 'buff' contains all other saved data - it may or may not +%%% include (a part of) an end tag. +%%% The reason for this is to avoid running binary:split/3 multiple +%%% times on the same data when it does not contain an end tag. This +%%% can be a considerable optimation in the case when a lot of data is +%%% received (e.g. when fetching all data from a node) and the data is +%%% sent in multiple ssh packages. +handle_data(NewData,#state{connection=Connection} = State0) -> log(Connection,recv,NewData), - Data = append_wo_initial_nl(Buff0,NewData), - case binary:split(Data,[?END_TAG],[]) of + NoEndTag0 = State0#state.no_end_tag_buff, + Buff0 = State0#state.buff, + Data = <<Buff0/binary, NewData/binary>>, + case binary:split(Data,?END_TAG,[]) of [_NoEndTagFound] -> - {noreply, State0#state{buff=Data}}; - [FirstMsg,Buff1] -> + NoEndTagSize = case byte_size(Data) of + Sz when Sz<5 -> 0; + Sz -> Sz-5 + end, + <<NoEndTag1:NoEndTagSize/binary,Buff/binary>> = Data, + NoEndTag = <<NoEndTag0/binary,NoEndTag1/binary>>, + {noreply, State0#state{no_end_tag_buff=NoEndTag, buff=Buff}}; + [FirstMsg0,Buff1] -> + FirstMsg = remove_initial_nl(<<NoEndTag0/binary,FirstMsg0/binary>>), SaxArgs = [{event_fun,fun sax_event/3}, {event_state,[]}], case xmerl_sax_parser:stream(FirstMsg, SaxArgs) of {ok, Simple, _Thrash} -> - case decode(Simple, State0#state{buff=Buff1}) of + case decode(Simple, State0#state{no_end_tag_buff= <<>>, + buff=Buff1}) of {noreply, #state{buff=Buff} = State} when Buff =/= <<>> -> %% Recurse if we have more data in buffer handle_data(<<>>, State); @@ -1349,17 +1412,18 @@ handle_data(NewData,#state{connection=Connection,buff=Buff0} = State0) -> [{parse_error,Reason}, {buffer, Buff0}, {new_data,NewData}]), - handle_error(Reason, State0#state{buff= <<>>}) + handle_error(Reason, State0#state{no_end_tag_buff= <<>>, + buff= <<>>}) end end. + %% xml does not accept a leading nl and some netconf server add a nl after %% each ?END_TAG, ignore them -append_wo_initial_nl(<<>>,NewData) -> NewData; -append_wo_initial_nl(<<"\n", Data/binary>>, NewData) -> - append_wo_initial_nl(Data, NewData); -append_wo_initial_nl(Data, NewData) -> - <<Data/binary, NewData/binary>>. +remove_initial_nl(<<"\n", Data/binary>>) -> + remove_initial_nl(Data); +remove_initial_nl(Data) -> + Data. handle_error(Reason, State) -> Pending1 = case State#state.pending of @@ -1367,9 +1431,9 @@ handle_error(Reason, State) -> Pending -> %% Assuming the first request gets the %% first answer - P=#pending{tref=TRef,caller=Caller} = + P=#pending{tref=TRef,ref=Ref,caller=Caller} = lists:last(Pending), - _ = timer:cancel(TRef), + cancel_request_timer(Ref,TRef), Reason1 = {failed_to_parse_received_data,Reason}, ct_gen_conn:return(Caller,{error,Reason1}), lists:delete(P,Pending) @@ -1455,8 +1519,8 @@ decode({Tag,Attrs,_}=E, #state{connection=Connection,pending=Pending}=State) -> {error,Reason} -> {noreply,State#state{hello_status = {error,Reason}}} end; - #pending{tref=TRef,caller=Caller} -> - _ = timer:cancel(TRef), + #pending{tref=TRef,ref=Ref,caller=Caller} -> + cancel_request_timer(Ref,TRef), case decode_hello(E) of {ok,SessionId,Capabilities} -> ct_gen_conn:return(Caller,ok), @@ -1482,9 +1546,8 @@ decode({Tag,Attrs,_}=E, #state{connection=Connection,pending=Pending}=State) -> %% there is just one pending that matches (i.e. has %% undefined msg_id and op) case [P || P = #pending{msg_id=undefined,op=undefined} <- Pending] of - [#pending{tref=TRef, - caller=Caller}] -> - _ = timer:cancel(TRef), + [#pending{tref=TRef,ref=Ref,caller=Caller}] -> + cancel_request_timer(Ref,TRef), ct_gen_conn:return(Caller,E), {noreply,State#state{pending=[]}}; _ -> @@ -1505,8 +1568,8 @@ get_msg_id(Attrs) -> decode_rpc_reply(MsgId,{_,Attrs,Content0}=E,#state{pending=Pending} = State) -> case lists:keytake(MsgId,#pending.msg_id,Pending) of - {value, #pending{tref=TRef,op=Op,caller=Caller}, Pending1} -> - _ = timer:cancel(TRef), + {value, #pending{tref=TRef,ref=Ref,op=Op,caller=Caller}, Pending1} -> + cancel_request_timer(Ref,TRef), Content = forward_xmlns_attr(Attrs,Content0), {CallerReply,{ServerReply,State2}} = do_decode_rpc_reply(Op,Content,State#state{pending=Pending1}), @@ -1518,10 +1581,11 @@ decode_rpc_reply(MsgId,{_,Attrs,Content0}=E,#state{pending=Pending} = State) -> %% pending that matches (i.e. has undefined msg_id and op) case [P || P = #pending{msg_id=undefined,op=undefined} <- Pending] of [#pending{tref=TRef, + ref=Ref, msg_id=undefined, op=undefined, caller=Caller}] -> - _ = timer:cancel(TRef), + cancel_request_timer(Ref,TRef), ct_gen_conn:return(Caller,E), {noreply,State#state{pending=[]}}; _ -> @@ -1712,6 +1776,7 @@ log(#connection{host=Host,port=Port,name=Name},Action,Data) -> %% Log callback - called from the error handler process +%% @private format_data(How,Data) -> %% Assuming that the data is encoded as UTF-8. If it is not, then %% the printout might be wrong, but the format function will not @@ -1724,9 +1789,14 @@ format_data(How,Data) -> do_format_data(raw,Data) -> io_lib:format("~n~ts~n",[hide_password(Data)]); do_format_data(pretty,Data) -> - io_lib:format("~n~ts~n",[indent(Data)]); + maybe_io_lib_format(indent(Data)); do_format_data(html,Data) -> - io_lib:format("~n~ts~n",[html_format(Data)]). + maybe_io_lib_format(html_format(Data)). + +maybe_io_lib_format(<<>>) -> + []; +maybe_io_lib_format(String) -> + io_lib:format("~n~ts~n",[String]). %%%----------------------------------------------------------------- %%% Hide password elements from XML data @@ -1765,13 +1835,21 @@ indent1("<?"++Rest1,Indent1) -> Line++indent1(Rest2,Indent2); indent1("</"++Rest1,Indent1) -> %% Stop tag - {Line,Rest2,Indent2} = indent_line1(Rest1,Indent1,[$/,$<]), - "\n"++Line++indent1(Rest2,Indent2); + case indent_line1(Rest1,Indent1,[$/,$<]) of + {[],[],_} -> + []; + {Line,Rest2,Indent2} -> + "\n"++Line++indent1(Rest2,Indent2) + end; indent1("<"++Rest1,Indent1) -> %% Start- or empty tag put(tag,get_tag(Rest1)), - {Line,Rest2,Indent2} = indent_line(Rest1,Indent1,[$<]), - "\n"++Line++indent1(Rest2,Indent2); + case indent_line(Rest1,Indent1,[$<]) of + {[],[],_} -> + []; + {Line,Rest2,Indent2} -> + "\n"++Line++indent1(Rest2,Indent2) + end; indent1([H|T],Indent) -> [H|indent1(T,Indent)]; indent1([],_Indent) -> diff --git a/lib/common_test/src/ct_netconfc.hrl b/lib/common_test/src/ct_netconfc.hrl index 295a61a98b..e4746fe8b7 100644 --- a/lib/common_test/src/ct_netconfc.hrl +++ b/lib/common_test/src/ct_netconfc.hrl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2012. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% diff --git a/lib/common_test/src/ct_property_test.erl b/lib/common_test/src/ct_property_test.erl index 52acda5388..5ee7435695 100644 --- a/lib/common_test/src/ct_property_test.erl +++ b/lib/common_test/src/ct_property_test.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2003-2014. 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. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% diff --git a/lib/common_test/src/ct_release_test.erl b/lib/common_test/src/ct_release_test.erl new file mode 100644 index 0000000000..5d7e945cc3 --- /dev/null +++ b/lib/common_test/src/ct_release_test.erl @@ -0,0 +1,950 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2014-2015. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%%----------------------------------------------------------------- +%% @doc EXPERIMENTAL support for testing of upgrade. +%% +%% This is a library module containing support for test of release +%% related activities in one or more applications. Currenty it +%% supports upgrade only. +%% +%% == Configuration == +%% +%% In order to find version numbers of applications to upgrade from, +%% `{@module}' needs to access and start old OTP +%% releases. A `common_test' configuration file can be used for +%% specifying the location of such releases, for example: +%% +%% ``` +%% %% old-rels.cfg +%% {otp_releases,[{r16b,"/path/to/R16B03-1/bin/erl"}, +%% {'17',"/path/to/17.3/bin/erl"}]}.''' +%% +%% The configuration file should preferably point out the latest patch +%% level on each major release. +%% +%% If no such configuration file is given, {@link init/1} will return +%% `{skip,Reason}' and any attempt at running {@link upgrade/4} +%% will fail. +%% +%% == Callback functions == +%% +%% The following functions should be exported from a {@module} +%% callback module. +%% +%% All callback functions are called on the node where the upgrade is +%% executed. +%% +%% <dl> +%% <dt>Module:upgrade_init(CtData,State) -> NewState</dt> +%% <dd>Types: +%% +%% <b><code>CtData = {@link ct_data()}</code></b><br/> +%% <b><code>State = NewState = cb_state()</code></b> +%% +%% Initialyze system before upgrade test starts. +%% +%% This function is called before the upgrade is started. All +%% applications given in {@link upgrade/4} are already started by +%% the boot script, so this callback is intended for additional +%% initialization, if necessary. +%% +%% <code>CtData</code> is an opaque data structure which shall be used +%% in any call to <code>ct_release_test</code> inside the callback. +%% +%% Example: +%% +%% ``` +%% upgrade_init(CtData,State) -> +%% {ok,{FromVsn,ToVsn}} = ct_release_test:get_app_vsns(CtData,myapp), +%% open_connection(State).''' +%% </dd> +%% +%% <dt>Module:upgrade_upgraded(CtData,State) -> NewState</dt> +%% <dd>Types: +%% +%% <b><code>CtData = {@link ct_data()}</code></b><br/> +%% <b><code>State = NewState = cb_state()</code></b> +%% +%% Check that upgrade was successful. +%% +%% This function is called after the release_handler has +%% successfully unpacked and installed the new release, and it has +%% been made permanent. It allows application specific checks to +%% ensure that the upgrade was successful. +%% +%% <code>CtData</code> is an opaque data structure which shall be used +%% in any call to <code>ct_release_test</code> inside the callback. +%% +%% Example: +%% +%% ``` +%% upgrade_upgraded(CtData,State) -> +%% check_connection_still_open(State).''' +%% </dd> +%% +%% <dt>Module:upgrade_downgraded(CtData,State) -> NewState</dt> +%% <dd>Types: +%% +%% <b><code>CtData = {@link ct_data()}</code></b><br/> +%% <b><code>State = NewState = cb_state()</code></b> +%% +%% Check that downgrade was successful. +%% +%% This function is called after the release_handler has +%% successfully re-installed the original release, and it has been +%% made permanent. It allows application specific checks to ensure +%% that the downgrade was successful. +%% +%% <code>CtData</code> is an opaque data structure which shall be used +%% in any call to <code>ct_release_test</code> inside the callback. +%% +%% Example: +%% +%% ``` +%% upgrade_downgraded(CtData,State) -> +%% check_connection_closed(State).''' +%% </dd> +%% </dl> +%% @end +%%----------------------------------------------------------------- +-module(ct_release_test). + +-export([init/1, upgrade/4, cleanup/1, get_app_vsns/2, get_appup/2]). + +-include_lib("kernel/include/file.hrl"). + +%%----------------------------------------------------------------- +-define(testnode, 'ct_release_test-upgrade'). +-define(exclude_apps, [hipe, typer, dialyzer]). % never include these apps + +%%----------------------------------------------------------------- +-record(ct_data, {from,to}). + +%%----------------------------------------------------------------- +-type config() :: [{atom(),term()}]. +-type cb_state() :: term(). +-opaque ct_data() :: #ct_data{}. +-export_type([ct_data/0]). + +-callback upgrade_init(ct_data(),cb_state()) -> cb_state(). +-callback upgrade_upgraded(ct_data(),cb_state()) -> cb_state(). +-callback upgrade_downgraded(ct_data(),cb_state()) -> cb_state(). + +%%----------------------------------------------------------------- +-spec init(Config) -> Result when + Config :: config(), + Result :: config() | SkipOrFail, + SkipOrFail :: {skip,Reason} | {fail,Reason}. +%% @doc Initialize `{@module}'. +%% +%% This function can be called from any of the +%% `init_per_*' functions in the test suite. It updates +%% the given `Config' with data that will be +%% used by future calls to other functions in this module. The +%% returned configuration must therefore also be returned from +%% the calling `init_per_*'. +%% +%% If the initialization fails, e.g. if a required release can +%% not be found, the function returns `{skip,Reason}'. In +%% this case the other test support functions in this mudule +%% can not be used. +%% +%% Example: +%% +%% ``` +%% init_per_suite(Config) -> +%% ct_release_test:init(Config).''' +%% +init(Config) -> + try init_upgrade_test() of + {Major,Minor} -> + [{release_test,[{major,Major},{minor,Minor}]} | Config] + catch throw:Thrown -> + Thrown + end. + +%%----------------------------------------------------------------- +-spec upgrade(App,Level,Callback,Config) -> any() when + App :: atom(), + Level :: minor | major, + Callback :: {module(),InitState}, + InitState :: cb_state(), + Config :: config(); + (Apps,Level,Callback,Config) -> any() when + Apps :: [App], + App :: atom(), + Level :: minor | major, + Callback :: {module(),InitState}, + InitState :: cb_state(), + Config :: config(). +%% @doc Test upgrade of the given application(s). +%% +%% This function can be called from a test case. It requires that +%% `Config' has been initialized by calling {@link +%% init/1} prior to this, for example from `init_per_suite/1'. +%% +%% Upgrade tests are performed as follows: +%% +%% <ol> +%% <li>Figure out which OTP release to test upgrade +%% from. Start a node running that release and find the +%% application versions on that node. Terminate the +%% node.</li> +%% <li>Figure out all dependencies for the applications under +%% test.</li> +%% <li>Create a release containing the core +%% applications `kernel', `stdlib' and `sasl' +%% in addition to the application(s) under test and all +%% dependencies of these. The versions of the applications +%% under test will be the ones found on the OTP release to +%% upgrade from. The versions of all other applications will +%% be those found on the current node, i.e. the common_test +%% node. This is the "From"-release.</li> +%% <li>Create another release containing the same +%% applications as in the previous step, but with all +%% application versions taken from the current node. This is +%% the "To"-release.</li> +%% <li>Install the "From"-release and start a new node +%% running this release.</li> +%% <li>Perform the upgrade test and allow customized +%% control by using callbacks: +%% <ol> +%% <li>Callback: `upgrade_init/2'</li> +%% <li>Unpack the new release</li> +%% <li>Install the new release</li> +%% <li>Callback: `upgrade_upgraded/2'</li> +%% <li>Install the original release</li> +%% <li>Callback: `upgrade_downgraded/2'</li> +%% </ol> +%% </li> +%% </ol> +%% +%% `App' or `Apps' +%% specifies the applications under test, i.e. the applications +%% which shall be upgraded. All other applications that are +%% included have the same releases in the "From"- and +%% "To"-releases and will therefore not be upgraded. +%% +%% `Level' specifies which OTP release to +%% pick the "From" versions from. +%% <dl> +%% <dt>major</dt> +%% <dd>From verions are picked from the previous major +%% release. For example, if the test is run on an OTP-17 +%% node, `{@module}' will pick the application +%% "From" versions from an OTP installation running OTP +%% R16B.</dd> +%% +%% <dt>minor</dt> +%% <dd>From verions are picked from the current major +%% release. For example, if the test is run on an OTP-17 +%% node, `{@module}' will pick the application +%% "From" versions from an OTP installation running an +%% earlier patch level of OTP-17.</dd> +%% </dl> +%% +%% The application "To" versions are allways picked from the +%% current node, i.e. the common_test node. +%% +%% `Callback' specifies the module (normally the +%% test suite) which implements the {@section Callback functions}, and +%% the initial value of the `State' variable used in these +%% functions. +%% +%% `Config' is the input argument received +%% in the test case function. +%% +%% Example: +%% +%% ``` +%% minor_upgrade(Config) -> +%% ct_release_test:upgrade(ssl,minor,{?MODULE,[]},Config). +%% ''' +%% +upgrade(App,Level,Callback,Config) when is_atom(App) -> + upgrade([App],Level,Callback,Config); +upgrade(Apps,Level,Callback,Config) -> + Dir = proplists:get_value(priv_dir,Config), + CreateDir = filename:join([Dir,Level,create]), + InstallDir = filename:join([Dir,Level,install]), + ok = filelib:ensure_dir(filename:join(CreateDir,"*")), + ok = filelib:ensure_dir(filename:join(InstallDir,"*")), + try upgrade(Apps,Level,Callback,CreateDir,InstallDir,Config) of + ok -> + %%rm_rf(CreateDir), + Tars = filelib:wildcard(filename:join(CreateDir,"*.tar.gz")), + _ = [file:delete(Tar) || Tar <- Tars], + rm_rf(InstallDir), + ok + catch throw:{fail,Reason} -> + ct:fail(Reason); + throw:{skip,Reason} -> + rm_rf(CreateDir), + rm_rf(InstallDir), + {skip,Reason} + after + %% Brutally kill all nodes that erroneously survived the test. + %% Note, we will not reach this if the test fails with a + %% timetrap timeout in the test suite! Thus we can have + %% hanging nodes... + Nodes = lists:filter(fun(Node) -> + case atom_to_list(Node) of + "ct_release_test-" ++_ -> true; + _ -> false + end + end, + nodes()), + [rpc:call(Node,erlang,halt,[]) || Node <- Nodes] + end. + +%%----------------------------------------------------------------- +-spec cleanup(Config) -> Result when + Config :: config(), + Result :: config(). +%% @doc Clean up after tests. +%% +%% This function shall be called from the `end_per_*' function +%% complementing the `init_per_*' function where {@link init/1} +%% is called. +%% +%% It cleans up after the test, for example kills hanging +%% nodes. +%% +%% Example: +%% +%% ``` +%% end_per_suite(Config) -> +%% ct_release_test:cleanup(Config).''' +%% +cleanup(Config) -> + AllNodes = [node_name(?testnode)|nodes()], + Nodes = lists:filter(fun(Node) -> + case atom_to_list(Node) of + "ct_release_test-" ++_ -> true; + _ -> false + end + end, + AllNodes), + [rpc:call(Node,erlang,halt,[]) || Node <- Nodes], + Config. + +%%----------------------------------------------------------------- +-spec get_app_vsns(CtData,App) -> {ok,{From,To}} | {error,Reason} when + CtData :: ct_data(), + App :: atom(), + From :: string(), + To :: string(), + Reason :: {app_not_found,App}. +%% @doc Get versions involved in this upgrade for the given application. +%% +%% This function can be called from inside any of the callback +%% functions. It returns the old (From) and new (To) versions involved +%% in the upgrade/downgrade test for the given application. +%% +%% <code>CtData</code> must be the first argument received in the +%% calling callback function - an opaque data structure set by +%% <code>ct_release_tests</code>. +get_app_vsns(#ct_data{from=FromApps,to=ToApps},App) -> + case {lists:keyfind(App,1,FromApps),lists:keyfind(App,1,ToApps)} of + {{App,FromVsn,_},{App,ToVsn,_}} -> + {ok,{FromVsn,ToVsn}}; + _ -> + {error,{app_not_found,App}} + end. + +%%----------------------------------------------------------------- +-spec get_appup(CtData,App) -> {ok,Appup} | {error,Reason} when + CtData :: ct_data(), + App :: atom(), + Appup :: {From,To,Up,Down}, + From :: string(), + To :: string(), + Up :: [Instr], + Down :: [Instr], + Instr :: term(), + Reason :: {app_not_found,App} | {vsn_not_found,{App,From}}. +%% @doc Get appup instructions for the given application. +%% +%% This function can be called from inside any of the callback +%% functions. It reads the appup file for the given application and +%% returns the instructions for upgrade and downgrade for the versions +%% in the test. +%% +%% <code>CtData</code> must be the first argument received in the +%% calling callback function - an opaque data structure set by +%% <code>ct_release_tests</code>. +%% +%% See reference manual for appup files for types definitions for the +%% instructions. +get_appup(#ct_data{from=FromApps,to=ToApps},App) -> + case lists:keyfind(App,1,ToApps) of + {App,ToVsn,ToDir} -> + Appup = filename:join([ToDir, "ebin", atom_to_list(App)++".appup"]), + {ok, [{ToVsn, Ups, Downs}]} = file:consult(Appup), + {App,FromVsn,_} = lists:keyfind(App,1,FromApps), + case {systools_relup:appup_search_for_version(FromVsn,Ups), + systools_relup:appup_search_for_version(FromVsn,Downs)} of + {{ok,Up},{ok,Down}} -> + {ok,{FromVsn,ToVsn,Up,Down}}; + _ -> + {error,{vsn_not_found,{App,FromVsn}}} + end; + false -> + {error,{app_not_found,App}} + end. + +%%----------------------------------------------------------------- +init_upgrade_test() -> + %% Check that a real release is running, not e.g. cerl + ok = application:ensure_started(sasl), + case release_handler:which_releases() of + [{_,_,[],_}] -> + %% Fake release, no applications + throw({skip, "Need a real release running to create other releases"}); + _ -> + Major = init_upgrade_test(major), + Minor = init_upgrade_test(minor), + {Major,Minor} + end. + +init_upgrade_test(Level) -> + {FromVsn,ToVsn} = get_rels(Level), + OldRel = + case test_server:is_release_available(FromVsn) of + true -> + {release,FromVsn}; + false -> + case ct:get_config({otp_releases,list_to_atom(FromVsn)}) of + undefined -> + false; + Prog0 -> + case os:find_executable(Prog0) of + false -> + false; + Prog -> + {prog,Prog} + end + end + end, + case OldRel of + false -> + ct:log("Release ~p is not available." + " Upgrade on '~p' level can not be tested.", + [FromVsn,Level]), + undefined; + _ -> + init_upgrade_test(FromVsn,ToVsn,OldRel) + end. + +get_rels(major) -> + %% Given that the current major release is X, then this is an + %% upgrade from major release X-1 to the current release. + Current = erlang:system_info(otp_release), + PreviousMajor = previous_major(Current), + {PreviousMajor,Current}; +get_rels(minor) -> + %% Given that this is a (possibly) patched version of major + %% release X, then this is an upgrade from major release X to the + %% current release. + CurrentMajor = erlang:system_info(otp_release), + Current = CurrentMajor++"_patched", + {CurrentMajor,Current}. + +init_upgrade_test(FromVsn,ToVsn,OldRel) -> + Name = list_to_atom("ct_release_test-otp-"++FromVsn), + ct:log("Starting node to fetch application versions to upgrade from"), + {ok,Node} = test_server:start_node(Name,peer,[{erl,[OldRel]}]), + {Apps,Path} = fetch_all_apps(Node), + test_server:stop_node(Node), + {FromVsn,ToVsn,Apps,Path}. + +fetch_all_apps(Node) -> + Paths = rpc:call(Node,code,get_path,[]), + %% Find all possible applications in the path + AppFiles = + lists:flatmap( + fun(P) -> + filelib:wildcard(filename:join(P,"*.app")) + end, + Paths), + %% Figure out which version of each application is running on this + %% node. Using application:load and application:get_key instead of + %% reading the .app files since there might be multiple versions + %% of a .app file and we only want the one that is actually + %% running. + AppVsns = + lists:flatmap( + fun(F) -> + A = list_to_atom(filename:basename(filename:rootname(F))), + _ = rpc:call(Node,application,load,[A]), + case rpc:call(Node,application,get_key,[A,vsn]) of + {ok,V} -> [{A,V}]; + _ -> [] + end + end, + AppFiles), + ErtsVsn = rpc:call(Node, erlang, system_info, [version]), + {[{erts,ErtsVsn}|AppVsns], Paths}. + + +%%----------------------------------------------------------------- +upgrade(Apps,Level,Callback,CreateDir,InstallDir,Config) -> + ct:log("Test upgrade of the following applications: ~p",[Apps]), + ct:log(".rel files and start scripts are created in:~n~ts",[CreateDir]), + ct:log("The release is installed in:~n~ts",[InstallDir]), + case proplists:get_value(release_test,Config) of + undefined -> + throw({fail,"ct_release_test:init/1 not run"}); + RTConfig -> + case proplists:get_value(Level,RTConfig) of + undefined -> + throw({skip,"Old release not available"}); + Data -> + {FromVsn,FromRel,FromAppsVsns} = + target_system(Apps, CreateDir, InstallDir, Data), + {ToVsn,ToRel,ToAppsVsns} = + upgrade_system(Apps, FromRel, CreateDir, + InstallDir, Data), + ct:log("Upgrade from: OTP-~ts, ~p",[FromVsn, FromAppsVsns]), + ct:log("Upgrade to: OTP-~ts, ~p",[ToVsn, ToAppsVsns]), + do_upgrade(Callback, FromVsn, FromAppsVsns, ToRel, + ToAppsVsns, InstallDir) + end + end. + +%%% This is similar to sasl/examples/src/target_system.erl, but with +%%% the following adjustments: +%%% - add a log directory +%%% - use an own 'start' script +%%% - chmod 'start' and 'start_erl' +target_system(Apps,CreateDir,InstallDir,{FromVsn,_,AllAppsVsns,Path}) -> + RelName0 = "otp-"++FromVsn, + + AppsVsns = [{A,V} || {A,V} <- AllAppsVsns, lists:member(A,Apps)], + {RelName,ErtsVsn} = create_relfile(AppsVsns,CreateDir,RelName0,FromVsn), + + %% Create .script and .boot + ok = systools(make_script,[RelName,[{path,Path}]]), + + %% Create base tar file - i.e. erts and all apps + ok = systools(make_tar,[RelName,[{erts,code:root_dir()}, + {path,Path}]]), + + %% Unpack the tar to complete the installation + erl_tar:extract(RelName ++ ".tar.gz", [{cwd, InstallDir}, compressed]), + + %% Add bin and log dirs + BinDir = filename:join([InstallDir, "bin"]), + file:make_dir(BinDir), + file:make_dir(filename:join(InstallDir,"log")), + + %% Delete start scripts - they will be added later + ErtsBinDir = filename:join([InstallDir, "erts-" ++ ErtsVsn, "bin"]), + file:delete(filename:join([ErtsBinDir, "erl"])), + file:delete(filename:join([ErtsBinDir, "start"])), + file:delete(filename:join([ErtsBinDir, "start_erl"])), + + %% Copy .boot to bin/start.boot + copy_file(RelName++".boot",filename:join([BinDir, "start.boot"])), + + %% Copy scripts from erts-xxx/bin to bin + copy_file(filename:join([ErtsBinDir, "epmd"]), + filename:join([BinDir, "epmd"]), [preserve]), + copy_file(filename:join([ErtsBinDir, "run_erl"]), + filename:join([BinDir, "run_erl"]), [preserve]), + copy_file(filename:join([ErtsBinDir, "to_erl"]), + filename:join([BinDir, "to_erl"]), [preserve]), + + %% create start_erl.data, sys.config and start.src + StartErlData = filename:join([InstallDir, "releases", "start_erl.data"]), + write_file(StartErlData, io_lib:fwrite("~s ~s~n", [ErtsVsn, FromVsn])), + SysConfig = filename:join([InstallDir, "releases", FromVsn, "sys.config"]), + write_file(SysConfig, "[]."), + StartSrc = filename:join(ErtsBinDir,"start.src"), + write_file(StartSrc,start_script()), + ok = file:change_mode(StartSrc,8#0755), + + %% Make start_erl executable + %% (this has been fixed in OTP 17 - it is now installed with + %% $INSTALL_SCRIPT instead of $INSTALL_DATA and should therefore + %% be executable from the start) + ok = file:change_mode(filename:join(ErtsBinDir,"start_erl.src"),8#0755), + + %% Substitute variables in erl.src, start.src and start_erl.src + %% (.src found in erts-xxx/bin - result stored in bin) + subst_src_scripts(["erl", "start", "start_erl"], ErtsBinDir, BinDir, + [{"FINAL_ROOTDIR", InstallDir}, {"EMU", "beam"}], + [preserve]), + + %% Create RELEASES + RelFile = filename:join([InstallDir, "releases", + filename:basename(RelName) ++ ".rel"]), + release_handler:create_RELEASES(InstallDir, RelFile), + + {FromVsn, RelName,AppsVsns}. + +systools(Func,Args) -> + case apply(systools,Func,Args) of + ok -> + ok; + error -> + throw({fail,{systools,Func,Args}}) + end. + +%%% This is a copy of $ROOT/erts-xxx/bin/start.src, modified to add +%%% sname and heart +start_script() -> + ["#!/bin/sh\n" + "ROOTDIR=%FINAL_ROOTDIR%\n" + "\n" + "if [ -z \"$RELDIR\" ]\n" + "then\n" + " RELDIR=$ROOTDIR/releases\n" + "fi\n" + "\n" + "START_ERL_DATA=${1:-$RELDIR/start_erl.data}\n" + "\n" + "$ROOTDIR/bin/run_erl -daemon /tmp/ $ROOTDIR/log \"exec $ROOTDIR/bin/start_erl $ROOTDIR $RELDIR $START_ERL_DATA -sname ",atom_to_list(?testnode)," -heart\"\n"]. + +%%% Create a release containing the current (the test node) OTP +%%% release, including relup to allow upgrade from an earlier OTP +%%% release. +upgrade_system(Apps, FromRel, CreateDir, InstallDir, {_,ToVsn,_,_}) -> + ct:log("Generating release to upgrade to."), + + RelName0 = "otp-"++ToVsn, + + AppsVsns = get_vsns(Apps), + {RelName,_} = create_relfile(AppsVsns,CreateDir,RelName0,ToVsn), + FromPath = filename:join([InstallDir,lib,"*",ebin]), + + ok = systools(make_script,[RelName]), + ok = systools(make_relup,[RelName,[FromRel],[FromRel], + [{path,[FromPath]}, + {outdir,CreateDir}]]), + SysConfig = filename:join([CreateDir, "sys.config"]), + write_file(SysConfig, "[]."), + + ok = systools(make_tar,[RelName,[{erts,code:root_dir()}]]), + + {ToVsn, RelName,AppsVsns}. + +%%% Start a new node running the release from target_system/6 +%%% above. Then upgrade to the system from upgrade_system/6. +do_upgrade({Cb,InitState},FromVsn,FromAppsVsns,ToRel,ToAppsVsns,InstallDir) -> + ct:log("Upgrade test attempting to start node.~n" + "If test fails, logs can be found in:~n~ts", + [filename:join(InstallDir,log)]), + Start = filename:join([InstallDir,bin,start]), + {ok,Node} = start_node(Start,FromVsn,FromAppsVsns), + + %% Add path to this module, to allow calls to get_appup/2 + Dir = filename:dirname(code:which(?MODULE)), + _ = rpc:call(Node,code,add_pathz,[Dir]), + + ct:log("Node started: ~p",[Node]), + CtData = #ct_data{from = [{A,V,code:lib_dir(A)} || {A,V} <- FromAppsVsns], + to=[{A,V,code:lib_dir(A)} || {A,V} <- ToAppsVsns]}, + State1 = do_callback(Node,Cb,upgrade_init,[CtData,InitState]), + + [{"OTP upgrade test",FromVsn,_,permanent}] = + rpc:call(Node,release_handler,which_releases,[]), + ToRelName = filename:basename(ToRel), + copy_file(ToRel++".tar.gz", + filename:join([InstallDir,releases,ToRelName++".tar.gz"])), + ct:log("Unpacking new release"), + {ok,ToVsn} = rpc:call(Node,release_handler,unpack_release,[ToRelName]), + [{"OTP upgrade test",ToVsn,_,unpacked}, + {"OTP upgrade test",FromVsn,_,permanent}] = + rpc:call(Node,release_handler,which_releases,[]), + ct:log("Installing new release"), + case rpc:call(Node,release_handler,install_release,[ToVsn]) of + {ok,FromVsn,_} -> + ok; + {continue_after_restart,FromVsn,_} -> + ct:log("Waiting for node restart") + end, + %% even if install_release returned {ok,...} there might be an + %% emulator restart (instruction restart_emulator), so we must + %% always make sure the node is running. + wait_node_up(current,ToVsn,ToAppsVsns), + + [{"OTP upgrade test",ToVsn,_,current}, + {"OTP upgrade test",FromVsn,_,permanent}] = + rpc:call(Node,release_handler,which_releases,[]), + ct:log("Permanenting new release"), + ok = rpc:call(Node,release_handler,make_permanent,[ToVsn]), + [{"OTP upgrade test",ToVsn,_,permanent}, + {"OTP upgrade test",FromVsn,_,old}] = + rpc:call(Node,release_handler,which_releases,[]), + + State2 = do_callback(Node,Cb,upgrade_upgraded,[CtData,State1]), + + ct:log("Re-installing old release"), + case rpc:call(Node,release_handler,install_release,[FromVsn]) of + {ok,FromVsn,_} -> + ok; + {continue_after_restart,FromVsn,_} -> + ct:log("Waiting for node restart") + end, + %% even if install_release returned {ok,...} there might be an + %% emulator restart (instruction restart_emulator), so we must + %% always make sure the node is running. + wait_node_up(current,FromVsn,FromAppsVsns), + + [{"OTP upgrade test",ToVsn,_,permanent}, + {"OTP upgrade test",FromVsn,_,current}] = + rpc:call(Node,release_handler,which_releases,[]), + ct:log("Permanenting old release"), + ok = rpc:call(Node,release_handler,make_permanent,[FromVsn]), + [{"OTP upgrade test",ToVsn,_,old}, + {"OTP upgrade test",FromVsn,_,permanent}] = + rpc:call(Node,release_handler,which_releases,[]), + + _State3 = do_callback(Node,Cb,upgrade_downgraded,[CtData,State2]), + + ct:log("Terminating node ~p",[Node]), + erlang:monitor_node(Node,true), + _ = rpc:call(Node,init,stop,[]), + receive {nodedown,Node} -> ok end, + ct:log("Node terminated"), + + ok. + +do_callback(Node,Mod,Func,Args) -> + Dir = filename:dirname(code:which(Mod)), + _ = rpc:call(Node,code,add_path,[Dir]), + ct:log("Calling ~p:~p/1",[Mod,Func]), + R = rpc:call(Node,Mod,Func,Args), + ct:log("~p:~p/~w returned: ~p",[Mod,Func,length(Args),R]), + case R of + {badrpc,Error} -> + throw({fail,{test_upgrade_callback,Mod,Func,Args,Error}}); + NewState -> + NewState + end. + +%%% Library functions +previous_major("17") -> + "r16b"; +previous_major(Rel) -> + integer_to_list(list_to_integer(Rel)-1). + +create_relfile(AppsVsns,CreateDir,RelName0,RelVsn) -> + UpgradeAppsVsns = [{A,V,restart_type(A)} || {A,V} <- AppsVsns], + + CoreAppVsns0 = get_vsns([kernel,stdlib,sasl]), + CoreAppVsns = + [{A,V,restart_type(A)} || {A,V} <- CoreAppVsns0, + false == lists:keymember(A,1,AppsVsns)], + + Apps = [App || {App,_} <- AppsVsns], + StartDepsVsns = get_start_deps(Apps,CoreAppVsns), + StartApps = [StartApp || {StartApp,_,_} <- StartDepsVsns] ++ Apps, + + {RuntimeDepsVsns,_} = get_runtime_deps(StartApps,StartApps,[],[]), + + AllAppsVsns0 = StartDepsVsns ++ UpgradeAppsVsns ++ RuntimeDepsVsns, + + %% Should test tools really be included? Some library functions + %% here could be used by callback, but not everything since + %% processes of these applications will not be running. + TestToolAppsVsns0 = get_vsns([test_server,common_test]), + TestToolAppsVsns = + [{A,V,none} || {A,V} <- TestToolAppsVsns0, + false == lists:keymember(A,1,AllAppsVsns0)], + + AllAppsVsns1 = AllAppsVsns0 ++ TestToolAppsVsns, + AllAppsVsns = [AV || AV={A,_,_} <- AllAppsVsns1, + false == lists:member(A,?exclude_apps)], + + ErtsVsn = erlang:system_info(version), + + %% Create the .rel file + RelContent = {release,{"OTP upgrade test",RelVsn},{erts,ErtsVsn},AllAppsVsns}, + RelName = filename:join(CreateDir,RelName0), + RelFile = RelName++".rel", + {ok,Fd} = file:open(RelFile,[write,{encoding,utf8}]), + io:format(Fd,"~tp.~n",[RelContent]), + ok = file:close(Fd), + {RelName,ErtsVsn}. + +get_vsns(Apps) -> + [begin + _ = application:load(A), + {ok,V} = application:get_key(A,vsn), + {A,V} + end || A <- Apps]. + +get_start_deps([App|Apps],Acc) -> + _ = application:load(App), + {ok,StartDeps} = application:get_key(App,applications), + StartDepsVsns = + [begin + _ = application:load(StartApp), + {ok,StartVsn} = application:get_key(StartApp,vsn), + {StartApp,StartVsn,restart_type(StartApp)} + end || StartApp <- StartDeps, + false == lists:keymember(StartApp,1,Acc)], + DepsStartDeps = get_start_deps(StartDeps,Acc ++ StartDepsVsns), + get_start_deps(Apps,DepsStartDeps); +get_start_deps([],Acc) -> + Acc. + +get_runtime_deps([App|Apps],StartApps,Acc,Visited) -> + case lists:member(App,Visited) of + true -> + get_runtime_deps(Apps,StartApps,Acc,Visited); + false -> + %% runtime_dependencies should be possible to read with + %% application:get_key/2, but still isn't so we need to + %% read the .app file... + AppFile = code:where_is_file(atom_to_list(App) ++ ".app"), + {ok,[{application,App,Attrs}]} = file:consult(AppFile), + RuntimeDeps = + lists:flatmap( + fun(Str) -> + [RuntimeAppStr,_] = string:tokens(Str,"-"), + RuntimeApp = list_to_atom(RuntimeAppStr), + case {lists:keymember(RuntimeApp,1,Acc), + lists:member(RuntimeApp,StartApps)} of + {false,false} when RuntimeApp=/=erts -> + [RuntimeApp]; + _ -> + [] + end + end, + proplists:get_value(runtime_dependencies,Attrs,[])), + RuntimeDepsVsns = + [begin + _ = application:load(RuntimeApp), + {ok,RuntimeVsn} = application:get_key(RuntimeApp,vsn), + {RuntimeApp,RuntimeVsn,none} + end || RuntimeApp <- RuntimeDeps], + {DepsRuntimeDeps,NewVisited} = + get_runtime_deps(RuntimeDeps,StartApps,Acc++RuntimeDepsVsns,[App|Visited]), + get_runtime_deps(Apps,StartApps,DepsRuntimeDeps,NewVisited) + end; +get_runtime_deps([],_,Acc,Visited) -> + {Acc,Visited}. + +restart_type(App) when App==kernel; App==stdlib; App==sasl -> + permanent; +restart_type(_) -> + temporary. + +copy_file(Src, Dest) -> + copy_file(Src, Dest, []). + +copy_file(Src, Dest, Opts) -> + {ok,_} = file:copy(Src, Dest), + case lists:member(preserve, Opts) of + true -> + {ok, FileInfo} = file:read_file_info(Src), + file:write_file_info(Dest, FileInfo); + false -> + ok + end. + +write_file(FName, Conts) -> + Enc = file:native_name_encoding(), + {ok, Fd} = file:open(FName, [write]), + file:write(Fd, unicode:characters_to_binary(Conts,Enc,Enc)), + file:close(Fd). + +%% Substitute all occurrences of %Var% for Val in the given scripts +subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) -> + lists:foreach(fun(Script) -> + subst_src_script(Script, SrcDir, DestDir, + Vars, Opts) + end, Scripts). + +subst_src_script(Script, SrcDir, DestDir, Vars, Opts) -> + subst_file(filename:join([SrcDir, Script ++ ".src"]), + filename:join([DestDir, Script]), + Vars, Opts). + +subst_file(Src, Dest, Vars, Opts) -> + {ok, Bin} = file:read_file(Src), + Conts = binary_to_list(Bin), + NConts = subst(Conts, Vars), + write_file(Dest, NConts), + case lists:member(preserve, Opts) of + true -> + {ok, FileInfo} = file:read_file_info(Src), + file:write_file_info(Dest, FileInfo); + false -> + ok + end. + +subst(Str, [{Var,Val}|Vars]) -> + subst(re:replace(Str,"%"++Var++"%",Val,[{return,list}]),Vars); +subst(Str, []) -> + Str. + +%%% Start a node by executing the given start command. This node will +%%% be used for upgrade. +start_node(Start,ExpVsn,ExpAppsVsns) -> + Port = open_port({spawn_executable, Start}, []), + unlink(Port), + erlang:port_close(Port), + wait_node_up(permanent,ExpVsn,ExpAppsVsns). + +wait_node_up(ExpStatus,ExpVsn,ExpAppsVsns) -> + Node = node_name(?testnode), + wait_node_up(Node,ExpStatus,ExpVsn,lists:keysort(1,ExpAppsVsns),60). + +wait_node_up(Node,ExpStatus,ExpVsn,ExpAppsVsns,0) -> + test_server:fail({node_not_started,app_check_failed,ExpVsn,ExpAppsVsns, + rpc:call(Node,release_handler,which_releases,[ExpStatus]), + rpc:call(Node,application,which_applications,[])}); +wait_node_up(Node,ExpStatus,ExpVsn,ExpAppsVsns,N) -> + case {rpc:call(Node,release_handler,which_releases,[ExpStatus]), + rpc:call(Node, application, which_applications, [])} of + {[{_,ExpVsn,_,_}],Apps} when is_list(Apps) -> + case [{A,V} || {A,_,V} <- lists:keysort(1,Apps), + lists:keymember(A,1,ExpAppsVsns)] of + ExpAppsVsns -> + {ok,Node}; + _ -> + timer:sleep(2000), + wait_node_up(Node,ExpStatus,ExpVsn,ExpAppsVsns,N-1) + end; + _ -> + timer:sleep(2000), + wait_node_up(Node,ExpStatus,ExpVsn,ExpAppsVsns,N-1) + end. + +node_name(Sname) -> + {ok,Host} = inet:gethostname(), + list_to_atom(atom_to_list(Sname) ++ "@" ++ Host). + +rm_rf(Dir) -> + case file:read_file_info(Dir) of + {ok, #file_info{type = directory}} -> + {ok, Content} = file:list_dir_all(Dir), + [rm_rf(filename:join(Dir,C)) || C <- Content], + ok=file:del_dir(Dir), + ok; + {ok, #file_info{}} -> + ok=file:delete(Dir); + _ -> + ok + end. diff --git a/lib/common_test/src/ct_repeat.erl b/lib/common_test/src/ct_repeat.erl index f4d9949776..1cb32f5bcd 100644 --- a/lib/common_test/src/ct_repeat.erl +++ b/lib/common_test/src/ct_repeat.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2007-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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% diff --git a/lib/common_test/src/ct_rpc.erl b/lib/common_test/src/ct_rpc.erl index 03d95d1408..73520b3dd5 100644 --- a/lib/common_test/src/ct_rpc.erl +++ b/lib/common_test/src/ct_rpc.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2004-2009. 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. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index be547b443b..a0f9f47b41 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2014. All Rights Reserved. +%% Copyright Ericsson AB 2004-2016. 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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -64,6 +65,7 @@ logdir, logopts = [], basic_html, + esc_chars = true, verbosity = [], config = [], event_handlers = [], @@ -77,12 +79,13 @@ multiply_timetraps = 1, scale_timetraps = false, create_priv_dir, - testspecs = [], + testspec_files = [], + current_testspec, tests, starter}). %%%----------------------------------------------------------------- -%%% @spec script_start() -> void() +%%% @spec script_start() -> term() %%% %%% @doc Start tests via the ct_run program or script. %%% @@ -344,6 +347,15 @@ script_start1(Parent, Args) -> application:set_env(common_test, basic_html, true), true end, + %% esc_chars - used by ct_logs + EscChars = case proplists:get_value(no_esc_chars, Args) of + undefined -> + application:set_env(common_test, esc_chars, true), + undefined; + _ -> + application:set_env(common_test, esc_chars, false), + false + end, %% disable_log_cache - used by ct_logs case proplists:get_value(disable_log_cache, Args) of undefined -> @@ -357,6 +369,7 @@ script_start1(Parent, Args) -> cover = Cover, cover_stop = CoverStop, logdir = LogDir, logopts = LogOpts, basic_html = BasicHtml, + esc_chars = EscChars, verbosity = Verbosity, event_handlers = EvHandlers, ct_hooks = CTHooks, @@ -492,8 +505,11 @@ execute_one_spec(TS, Opts, Args) -> case check_and_install_configfiles(AllConfig, TheLogDir, Opts) of ok -> % read tests from spec {Run,Skip} = ct_testspec:prepare_tests(TS, node()), - do_run(Run, Skip, Opts#opts{config=AllConfig, - logdir=TheLogDir}, Args); + Result = do_run(Run, Skip, Opts#opts{config=AllConfig, + logdir=TheLogDir, + current_testspec=TS}, Args), + ct_util:delete_testdata(testspec), + Result; Error -> Error end. @@ -582,14 +598,26 @@ combine_test_opts(TS, Specs, Opts) -> BHBool end, + EscChars = + case choose_val(Opts#opts.esc_chars, + TSOpts#opts.esc_chars) of + undefined -> + true; + ECBool -> + application:set_env(common_test, esc_chars, + ECBool), + ECBool + end, + Opts#opts{label = Label, profile = Profile, - testspecs = Specs, + testspec_files = Specs, cover = Cover, cover_stop = CoverStop, logdir = which(logdir, LogDir), logopts = AllLogOpts, basic_html = BasicHtml, + esc_chars = EscChars, verbosity = AllVerbosity, silent_connections = AllSilentConns, config = TSOpts#opts.config, @@ -683,8 +711,10 @@ script_start3(Opts, Args) -> if Opts#opts.vts ; Opts#opts.shell -> script_start4(Opts#opts{tests = []}, Args); true -> - script_usage(), - {error,missing_start_options} + %% no start options, use default "-dir ./" + {ok,Dir} = file:get_cwd(), + io:format("ct_run -dir ~ts~n~n", [Dir]), + script_start4(Opts#opts{tests = tests([Dir])}, Args) end end. @@ -709,7 +739,7 @@ script_start4(#opts{label = Label, profile = Profile, logopts = LogOpts, verbosity = Verbosity, enable_builtin_hooks = EnableBuiltinHooks, - logdir = LogDir, testspecs = Specs}, _Args) -> + logdir = LogDir, testspec_files = Specs}, _Args) -> %% label - used by ct_logs application:set_env(common_test, test_label, Label), @@ -763,82 +793,87 @@ script_start4(Opts = #opts{tests = Tests}, Args) -> %%% @spec script_usage() -> ok %%% @doc Print usage information for <code>ct_run</code>. script_usage() -> - io:format("\n\nUsage:\n\n"), + io:format("\nUsage:\n\n"), io:format("Run tests from command line:\n\n" - "\tct_run [-dir TestDir1 TestDir2 .. TestDirN] |" - "\n\t[[-dir TestDir] -suite Suite1 Suite2 .. SuiteN" - "\n\t [[-group Groups1 Groups2 .. GroupsN] [-case Case1 Case2 .. CaseN]]]" - "\n\t[-step [config | keep_inactive]]" - "\n\t[-config ConfigFile1 ConfigFile2 .. ConfigFileN]" - "\n\t[-userconfig CallbackModule ConfigFile1 .. ConfigFileN]" - "\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]" - "\n\t[-logdir LogDir]" - "\n\t[-logopts LogOpt1 LogOpt2 .. LogOptN]" - "\n\t[-verbosity GenVLvl | [CategoryVLvl1 .. CategoryVLvlN]]" - "\n\t[-silent_connections [ConnType1 ConnType2 .. ConnTypeN]]" - "\n\t[-stylesheet CSSFile]" - "\n\t[-cover CoverCfgFile]" - "\n\t[-cover_stop Bool]" - "\n\t[-event_handler EvHandler1 EvHandler2 .. EvHandlerN]" - "\n\t[-ct_hooks CTHook1 CTHook2 .. CTHookN]" - "\n\t[-include InclDir1 InclDir2 .. InclDirN]" - "\n\t[-no_auto_compile]" - "\n\t[-abort_if_missing_suites]" - "\n\t[-multiply_timetraps N]" - "\n\t[-scale_timetraps]" - "\n\t[-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]" - "\n\t[-basic_html]" - "\n\t[-repeat N] |" - "\n\t[-duration HHMMSS [-force_stop [skip_rest]]] |" - "\n\t[-until [YYMoMoDD]HHMMSS [-force_stop [skip_rest]]]\n\n"), + "\tct_run -dir TestDir1 TestDir2 .. TestDirN |" + "\n\t [-dir TestDir] -suite Suite1 Suite2 .. SuiteN" + "\n\t [-group Group1 Group2 .. GroupN] [-case Case1 Case2 .. CaseN]" + "\n\t [-step [config | keep_inactive]]" + "\n\t [-config ConfigFile1 ConfigFile2 .. ConfigFileN]" + "\n\t [-userconfig CallbackModule ConfigFile1 .. ConfigFileN]" + "\n\t [-decrypt_key Key] | [-decrypt_file KeyFile]" + "\n\t [-logdir LogDir]" + "\n\t [-logopts LogOpt1 LogOpt2 .. LogOptN]" + "\n\t [-verbosity GenVLvl | [CategoryVLvl1 .. CategoryVLvlN]]" + "\n\t [-silent_connections [ConnType1 ConnType2 .. ConnTypeN]]" + "\n\t [-stylesheet CSSFile]" + "\n\t [-cover CoverCfgFile]" + "\n\t [-cover_stop Bool]" + "\n\t [-event_handler EvHandler1 EvHandler2 .. EvHandlerN]" + "\n\t [-ct_hooks CTHook1 CTHook2 .. CTHookN]" + "\n\t [-include InclDir1 InclDir2 .. InclDirN]" + "\n\t [-no_auto_compile]" + "\n\t [-abort_if_missing_suites]" + "\n\t [-multiply_timetraps N]" + "\n\t [-scale_timetraps]" + "\n\t [-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]" + "\n\t [-basic_html]" + "\n\t [-no_esc_chars]" + "\n\t [-repeat N] |" + "\n\t [-duration HHMMSS [-force_stop [skip_rest]]] |" + "\n\t [-until [YYMoMoDD]HHMMSS [-force_stop [skip_rest]]]" + "\n\t [-exit_status ignore_config]" + "\n\t [-help]\n\n"), io:format("Run tests using test specification:\n\n" "\tct_run -spec TestSpec1 TestSpec2 .. TestSpecN" - "\n\t[-config ConfigFile1 ConfigFile2 .. ConfigFileN]" - "\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]" - "\n\t[-logdir LogDir]" - "\n\t[-logopts LogOpt1 LogOpt2 .. LogOptN]" - "\n\t[-verbosity GenVLvl | [CategoryVLvl1 .. CategoryVLvlN]]" - "\n\t[-allow_user_terms]" - "\n\t[-join_specs]" - "\n\t[-silent_connections [ConnType1 ConnType2 .. ConnTypeN]]" - "\n\t[-stylesheet CSSFile]" - "\n\t[-cover CoverCfgFile]" - "\n\t[-cover_stop Bool]" - "\n\t[-event_handler EvHandler1 EvHandler2 .. EvHandlerN]" - "\n\t[-ct_hooks CTHook1 CTHook2 .. CTHookN]" - "\n\t[-include InclDir1 InclDir2 .. InclDirN]" - "\n\t[-no_auto_compile]" - "\n\t[-abort_if_missing_suites]" - "\n\t[-multiply_timetraps N]" - "\n\t[-scale_timetraps]" - "\n\t[-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]" - "\n\t[-basic_html]" - "\n\t[-repeat N] |" - "\n\t[-duration HHMMSS [-force_stop [skip_rest]]] |" - "\n\t[-until [YYMoMoDD]HHMMSS [-force_stop [skip_rest]]]\n\n"), + "\n\t [-config ConfigFile1 ConfigFile2 .. ConfigFileN]" + "\n\t [-decrypt_key Key] | [-decrypt_file KeyFile]" + "\n\t [-logdir LogDir]" + "\n\t [-logopts LogOpt1 LogOpt2 .. LogOptN]" + "\n\t [-verbosity GenVLvl | [CategoryVLvl1 .. CategoryVLvlN]]" + "\n\t [-allow_user_terms]" + "\n\t [-join_specs]" + "\n\t [-silent_connections [ConnType1 ConnType2 .. ConnTypeN]]" + "\n\t [-stylesheet CSSFile]" + "\n\t [-cover CoverCfgFile]" + "\n\t [-cover_stop Bool]" + "\n\t [-event_handler EvHandler1 EvHandler2 .. EvHandlerN]" + "\n\t [-ct_hooks CTHook1 CTHook2 .. CTHookN]" + "\n\t [-include InclDir1 InclDir2 .. InclDirN]" + "\n\t [-no_auto_compile]" + "\n\t [-abort_if_missing_suites]" + "\n\t [-multiply_timetraps N]" + "\n\t [-scale_timetraps]" + "\n\t [-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]" + "\n\t [-basic_html]" + "\n\t [-no_esc_chars]" + "\n\t [-repeat N] |" + "\n\t [-duration HHMMSS [-force_stop [skip_rest]]] |" + "\n\t [-until [YYMoMoDD]HHMMSS [-force_stop [skip_rest]]]\n\n"), io:format("Refresh the HTML index files:\n\n" "\tct_run -refresh_logs [LogDir]" - "[-logdir LogDir] " - "[-basic_html]\n\n"), + " [-logdir LogDir] " + " [-basic_html]\n\n"), io:format("Run CT in interactive mode:\n\n" "\tct_run -shell" - "\n\t[-config ConfigFile1 ConfigFile2 .. ConfigFileN]" - "\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]\n\n"), + "\n\t [-config ConfigFile1 ConfigFile2 .. ConfigFileN]" + "\n\t [-decrypt_key Key] | [-decrypt_file KeyFile]\n\n"), io:format("Run tests in web based GUI:\n\n" "\tct_run -vts [-browser Browser]" - "\n\t[-config ConfigFile1 ConfigFile2 .. ConfigFileN]" - "\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]" - "\n\t[-dir TestDir1 TestDir2 .. TestDirN] |" - "\n\t[-suite Suite [-case Case]]" - "\n\t[-logopts LogOpt1 LogOpt2 .. LogOptN]" - "\n\t[-verbosity GenVLvl | [CategoryVLvl1 .. CategoryVLvlN]]" - "\n\t[-include InclDir1 InclDir2 .. InclDirN]" - "\n\t[-no_auto_compile]" - "\n\t[-abort_if_missing_suites]" - "\n\t[-multiply_timetraps N]" - "\n\t[-scale_timetraps]" - "\n\t[-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]" - "\n\t[-basic_html]\n\n"). + "\n\t [-config ConfigFile1 ConfigFile2 .. ConfigFileN]" + "\n\t [-decrypt_key Key] | [-decrypt_file KeyFile]" + "\n\t [-dir TestDir1 TestDir2 .. TestDirN] |" + "\n\t [-suite Suite [-case Case]]" + "\n\t [-logopts LogOpt1 LogOpt2 .. LogOptN]" + "\n\t [-verbosity GenVLvl | [CategoryVLvl1 .. CategoryVLvlN]]" + "\n\t [-include InclDir1 InclDir2 .. InclDirN]" + "\n\t [-no_auto_compile]" + "\n\t [-abort_if_missing_suites]" + "\n\t [-multiply_timetraps N]" + "\n\t [-scale_timetraps]" + "\n\t [-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]" + "\n\t [-basic_html]" + "\n\t [-no_esc_chars]\n\n"). %%%----------------------------------------------------------------- %%% @hidden @@ -900,7 +935,7 @@ run_test(StartOpt) when is_tuple(StartOpt) -> run_test([StartOpt]); run_test(StartOpts) when is_list(StartOpts) -> - CTPid = spawn(fun() -> run_test1(StartOpts) end), + CTPid = spawn(run_test1_fun(StartOpts)), Ref = monitor(process, CTPid), receive {'DOWN',Ref,process,CTPid,{user_error,Error}} -> @@ -909,6 +944,11 @@ run_test(StartOpts) when is_list(StartOpts) -> Other end. +-spec run_test1_fun(_) -> fun(() -> no_return()). + +run_test1_fun(StartOpts) -> + fun() -> run_test1(StartOpts) end. + run_test1(StartOpts) when is_list(StartOpts) -> case proplists:get_value(refresh_logs, StartOpts) of undefined -> @@ -1075,7 +1115,17 @@ run_test2(StartOpts) -> application:set_env(common_test, basic_html, BasicHtmlBool), BasicHtmlBool end, - + %% esc_chars - used by ct_logs + EscChars = + case proplists:get_value(esc_chars, StartOpts) of + undefined -> + application:set_env(common_test, esc_chars, true), + undefined; + EscCharsBool -> + application:set_env(common_test, esc_chars, EscCharsBool), + EscCharsBool + end, + %% disable_log_cache - used by ct_logs case proplists:get_value(disable_log_cache, StartOpts) of undefined -> application:set_env(common_test, disable_log_cache, false); @@ -1090,6 +1140,7 @@ run_test2(StartOpts) -> cover = Cover, cover_stop = CoverStop, step = Step, logdir = LogDir, logopts = LogOpts, basic_html = BasicHtml, + esc_chars = EscChars, config = CfgFiles, verbosity = Verbosity, event_handlers = EvHandlers, @@ -1110,7 +1161,7 @@ run_test2(StartOpts) -> undefined -> case lists:keysearch(prepared_tests, 1, StartOpts) of {value,{_,{Run,Skip},Specs}} -> % use prepared tests - run_prepared(Run, Skip, Opts#opts{testspecs = Specs}, + run_prepared(Run, Skip, Opts#opts{testspec_files = Specs}, StartOpts); false -> run_dir(Opts, StartOpts) @@ -1118,11 +1169,11 @@ run_test2(StartOpts) -> Specs -> Relaxed = get_start_opt(allow_user_terms, value, false, StartOpts), %% using testspec(s) as input for test - run_spec_file(Relaxed, Opts#opts{testspecs = Specs}, StartOpts) + run_spec_file(Relaxed, Opts#opts{testspec_files = Specs}, StartOpts) end. run_spec_file(Relaxed, - Opts = #opts{testspecs = Specs}, + Opts = #opts{testspec_files = Specs}, StartOpts) -> Specs1 = case Specs of [X|_] when is_integer(X) -> [Specs]; @@ -1161,7 +1212,10 @@ run_all_specs([{Specs,TS} | TSs], Opts, StartOpts, TotResult) -> log_ts_names(Specs), Combined = #opts{config = TSConfig} = combine_test_opts(TS, Specs, Opts), AllConfig = merge_vals([Opts#opts.config, TSConfig]), - try run_one_spec(TS, Combined#opts{config = AllConfig}, StartOpts) of + try run_one_spec(TS, + Combined#opts{config = AllConfig, + current_testspec=TS}, + StartOpts) of Result -> run_all_specs(TSs, Opts, StartOpts, [Result | TotResult]) catch @@ -1340,7 +1394,9 @@ run_dir(Opts = #opts{logdir = LogDir, end; {undefined,undefined,[]} -> - exit({error,no_test_specified}); + {ok,Dir} = file:get_cwd(), + %% No start options, use default {dir,CWD} + reformat_result(catch do_run(tests(Dir), [], Opts1, StartOpts)); {Dir,Suite,GsAndCs} -> exit({error,{incorrect_start_options,{Dir,Suite,GsAndCs}}}) @@ -1355,7 +1411,7 @@ run_dir(Opts = #opts{logdir = LogDir, %%% @equiv ct:run_testspec/1 %%%----------------------------------------------------------------- run_testspec(TestSpec) -> - CTPid = spawn(fun() -> run_testspec1(TestSpec) end), + CTPid = spawn(run_testspec1_fun(TestSpec)), Ref = monitor(process, CTPid), receive {'DOWN',Ref,process,CTPid,{user_error,Error}} -> @@ -1364,6 +1420,11 @@ run_testspec(TestSpec) -> Other end. +-spec run_testspec1_fun(_) -> fun(() -> no_return()). + +run_testspec1_fun(TestSpec) -> + fun() -> run_testspec1(TestSpec) end. + run_testspec1(TestSpec) -> {ok,Cwd} = file:get_cwd(), io:format("~nCommon Test starting (cwd is ~ts)~n~n", [Cwd]), @@ -1406,7 +1467,7 @@ run_testspec2(TestSpec) -> case check_and_install_configfiles( Opts#opts.config, LogDir1, Opts) of ok -> - Opts1 = Opts#opts{testspecs = [], + Opts1 = Opts#opts{testspec_files = [], logdir = LogDir1, include = AllInclude}, {Run,Skip} = ct_testspec:prepare_tests(TS, node()), @@ -1421,6 +1482,7 @@ get_data_for_node(#testspec{label = Labels, logdir = LogDirs, logopts = LogOptsList, basic_html = BHs, + esc_chars = EscChs, stylesheet = SSs, verbosity = VLvls, silent_connections = SilentConnsList, @@ -1448,6 +1510,7 @@ get_data_for_node(#testspec{label = Labels, LOs -> LOs end, BasicHtml = proplists:get_value(Node, BHs), + EscChars = proplists:get_value(Node, EscChs), Stylesheet = proplists:get_value(Node, SSs), Verbosity = case proplists:get_value(Node, VLvls) of undefined -> []; @@ -1474,6 +1537,7 @@ get_data_for_node(#testspec{label = Labels, logdir = LogDir, logopts = LogOpts, basic_html = BasicHtml, + esc_chars = EscChars, stylesheet = Stylesheet, verbosity = Verbosity, silent_connections = SilentConns, @@ -1627,11 +1691,15 @@ groups_and_cases(Gs, Cs) -> tests(TestDir, Suites, []) when is_list(TestDir), is_integer(hd(TestDir)) -> [{?testdir(TestDir,Suites),ensure_atom(Suites),all}]; tests(TestDir, Suite, Cases) when is_list(TestDir), is_integer(hd(TestDir)) -> + [{?testdir(TestDir,Suite),ensure_atom(Suite),Cases}]; +tests([TestDir], Suite, Cases) when is_list(TestDir), is_integer(hd(TestDir)) -> [{?testdir(TestDir,Suite),ensure_atom(Suite),Cases}]. tests([{Dir,Suite}],Cases) -> [{?testdir(Dir,Suite),ensure_atom(Suite),Cases}]; tests(TestDir, Suite) when is_list(TestDir), is_integer(hd(TestDir)) -> - tests(TestDir, ensure_atom(Suite), all). + tests(TestDir, ensure_atom(Suite), all); +tests([TestDir], Suite) when is_list(TestDir), is_integer(hd(TestDir)) -> + tests(TestDir, ensure_atom(Suite), all). tests(DirSuites) when is_list(DirSuites), is_tuple(hd(DirSuites)) -> [{?testdir(Dir,Suite),ensure_atom(Suite),all} || {Dir,Suite} <- DirSuites]; tests(TestDir) when is_list(TestDir), is_integer(hd(TestDir)) -> @@ -1713,6 +1781,9 @@ compile_and_run(Tests, Skip, Opts, Args) -> ct_util:set_testdata({stylesheet,Opts#opts.stylesheet}), %% save logopts ct_util:set_testdata({logopts,Opts#opts.logopts}), + %% save info about current testspec (testspec record or undefined) + ct_util:set_testdata({testspec,Opts#opts.current_testspec}), + %% enable silent connections case Opts#opts.silent_connections of [] -> @@ -1727,7 +1798,7 @@ compile_and_run(Tests, Skip, Opts, Args) -> ct_logs:log("Silent connections", "~p", [Conns]) end end, - log_ts_names(Opts#opts.testspecs), + log_ts_names(Opts#opts.testspec_files), TestSuites = suite_tuples(Tests), {_TestSuites1,SuiteMakeErrors,AllMakeErrors} = @@ -1750,7 +1821,18 @@ compile_and_run(Tests, Skip, Opts, Args) -> {Tests1,Skip1} -> ReleaseSh = proplists:get_value(release_shell, Args), ct_util:set_testdata({release_shell,ReleaseSh}), - possibly_spawn(ReleaseSh == true, Tests1, Skip1, Opts) + TestResult = + possibly_spawn(ReleaseSh == true, Tests1, Skip1, Opts), + case TestResult of + {Ok,Errors,Skipped} -> + NoOfMakeErrors = + lists:foldl(fun({_,BadMods}, X) -> + X + length(BadMods) + end, 0, SuiteMakeErrors), + {Ok,Errors+NoOfMakeErrors,Skipped}; + ErrorResult -> + ErrorResult + end catch _:BadFormat -> {error,BadFormat} @@ -1976,22 +2058,7 @@ final_tests(Tests, Skip, Bad) -> final_tests1([{TestDir,Suites,_}|Tests], Final, Skip, Bad) when is_list(Suites), is_atom(hd(Suites)) -> -% Separate = -% fun(S,{DoSuite,Dont}) -> -% case lists:keymember({TestDir,S},1,Bad) of -% false -> -% {[S|DoSuite],Dont}; -% true -> -% SkipIt = {TestDir,S,"Make failed"}, -% {DoSuite,Dont++[SkipIt]} -% end -% end, - -% {DoSuites,Skip1} = -% lists:foldl(Separate,{[],Skip},Suites), -% Do = {TestDir,lists:reverse(DoSuites),all}, - - Skip1 = [{TD,S,"Make failed"} || {{TD,S},_} <- Bad, S1 <- Suites, + Skip1 = [{TD,S,make_failed} || {{TD,S},_} <- Bad, S1 <- Suites, S == S1, TD == TestDir], Final1 = [{TestDir,S,all} || S <- Suites], final_tests1(Tests, lists:reverse(Final1)++Final, Skip++Skip1, Bad); @@ -2004,7 +2071,7 @@ final_tests1([{TestDir,all,all}|Tests], Final, Skip, Bad) -> false -> [] end, - Missing = [{TestDir,S,"Make failed"} || S <- MissingSuites], + Missing = [{TestDir,S,make_failed} || S <- MissingSuites], Final1 = [{TestDir,all,all}|Final], final_tests1(Tests, Final1, Skip++Missing, Bad); @@ -2016,7 +2083,7 @@ final_tests1([{TestDir,Suite,GrsOrCs}|Tests], Final, Skip, Bad) when is_list(GrsOrCs) -> case lists:keymember({TestDir,Suite}, 1, Bad) of true -> - Skip1 = Skip ++ [{TestDir,Suite,all,"Make failed"}], + Skip1 = Skip ++ [{TestDir,Suite,all,make_failed}], final_tests1(Tests, [{TestDir,Suite,all}|Final], Skip1, Bad); false -> GrsOrCs1 = @@ -2028,6 +2095,13 @@ final_tests1([{TestDir,Suite,GrsOrCs}|Tests], Final, Skip, Bad) when ({skipped,Group,TCs}) -> [ct_groups:make_conf(TestDir, Suite, Group, [skipped], TCs)]; + ({skipped,TC}) -> + case lists:member(TC, GrsOrCs) of + true -> + []; + false -> + [TC] + end; ({GrSpec = {GroupName,_},TCs}) -> Props = [{override,GrSpec}], [ct_groups:make_conf(TestDir, Suite, @@ -2067,7 +2141,9 @@ final_skip([], Final) -> continue([], _) -> true; -continue(_MakeErrors, AbortIfMissingSuites) -> +continue(_MakeErrors, true) -> + false; +continue(_MakeErrors, _AbortIfMissingSuites) -> io:nl(), OldGl = group_leader(), case set_group_leader_same_as_shell() of @@ -2095,11 +2171,10 @@ continue(_MakeErrors, AbortIfMissingSuites) -> true end; false -> % no shell process to use - not AbortIfMissingSuites + true end. set_group_leader_same_as_shell() -> - %%! Locate the shell process... UGLY!!! GS2or3 = fun(P) -> case process_info(P,initial_call) of {initial_call,{group,server,X}} when X == 2 ; X == 3 -> @@ -2147,10 +2222,18 @@ do_run_test(Tests, Skip, Opts0) -> %% test_server needs to know the include path too InclPath = case application:get_env(common_test, include) of {ok,Incls} -> Incls; - _ -> [] + _ -> [] end, application:set_env(test_server, include, InclPath), + %% copy the escape characters setting to test_server + EscChars = + case application:get_env(common_test, esc_chars) of + {ok,ECBool} -> ECBool; + _ -> true + end, + application:set_env(test_server, esc_chars, EscChars), + test_server_ctrl:start_link(local), %% let test_server expand the test tuples and count no of cases @@ -3036,6 +3119,10 @@ opts2args(EnvStartOpts) -> [{basic_html,[]}]; ({basic_html,false}) -> []; + ({esc_chars,false}) -> + [{no_esc_chars,[]}]; + ({esc_chars,true}) -> + []; ({event_handler,EH}) when is_atom(EH) -> [{event_handler,[atom_to_list(EH)]}]; ({event_handler,EHs}) when is_list(EHs) -> diff --git a/lib/common_test/src/ct_slave.erl b/lib/common_test/src/ct_slave.erl index 872c39de04..3ad3937548 100644 --- a/lib/common_test/src/ct_slave.erl +++ b/lib/common_test/src/ct_slave.erl @@ -1,18 +1,19 @@ %%-------------------------------------------------------------------- %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2013. All Rights Reserved. +%% Copyright Ericsson AB 2010-2016. 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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% @@ -37,7 +38,7 @@ -record(options, {username, password, boot_timeout, init_timeout, startup_timeout, startup_functions, monitor_master, - kill_if_fail, erl_flags, env}). + kill_if_fail, erl_flags, env, ssh_port, ssh_opts}). %%%----------------------------------------------------------------- %%% @spec start(Node) -> Result @@ -133,7 +134,7 @@ start(Host, Node) -> %%% executed after startup of the node. Note that all used modules should be %%% present in the code path on the <code>Host</code>.</p> %%% -%%% <p>The timeouts are applied as follows: +%%% <p>The timeouts are applied as follows:</p> %%% <list> %%% <item> %%% <code>BootTimeout</code> - time to start the Erlang node, in seconds. @@ -153,7 +154,7 @@ start(Host, Node) -> %%% If this timeout occurs, the result %%% <code>{error, startup_timeout, NodeName}</code> is returned. %%% </item> -%%% </list></p> +%%% </list> %%% %%% <p>Option <code>monitor_master</code> specifies, if the slave node should be %%% stopped in case of master node stop. Defaults to false.</p> @@ -169,7 +170,7 @@ start(Host, Node) -> %%% <p>Option <code>env</code> specifies a list of environment variables %%% that will extended the environment.</p> %%% -%%% <p>Special return values are: +%%% <p>Special return values are:</p> %%% <list> %%% <item><code>{error, already_started, NodeName}</code> - if the node with %%% the given name is already started on a given host;</item> @@ -178,7 +179,7 @@ start(Host, Node) -> %%% <item><code>{error, not_alive, NodeName}</code> - if node on which the %%% <code>ct_slave:start/3</code> is called, is not alive. Note that %%% <code>NodeName</code> is the name of current node in this case.</item> -%%% </list></p> +%%% </list> %%% start(Host, Node, Opts) -> ENode = enodename(Host, Node), @@ -254,11 +255,13 @@ fetch_options(Options) -> KillIfFail = get_option_value(kill_if_fail, Options, true), ErlFlags = get_option_value(erl_flags, Options, []), EnvVars = get_option_value(env, Options, []), + SSHPort = get_option_value(ssh_port, Options, []), + SSHOpts = get_option_value(ssh_opts, Options, []), #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}. + erl_flags=ErlFlags, env=EnvVars, ssh_port=SSHPort, ssh_opts=SSHOpts}. % send a message when slave node is started % @hidden @@ -312,7 +315,7 @@ enodename(Host, Node) -> do_start(Host, Node, Options) -> ENode = enodename(Host, Node), Functions = - lists:concat([[{ct_slave, slave_started, [ENode, self()]}], + lists:append([[{ct_slave, slave_started, [ENode, self()]}], Options#options.startup_functions, [{ct_slave, slave_ready, [ENode, self()]}]]), Functions2 = if @@ -399,27 +402,18 @@ spawn_local_node(Node, Options) -> Cmd = get_cmd(Node, ErlFlags), open_port({spawn, Cmd}, [stream,{env,Env}]). -% start crypto and ssh if not yet started -check_for_ssh_running() -> - case application:get_application(crypto) of - undefined-> - application:start(crypto), - case application:get_application(ssh) of - undefined-> - application:start(ssh); - {ok, ssh}-> - ok - end; - {ok, crypto}-> - ok - end. - % spawn node remotely spawn_remote_node(Host, Node, Options) -> #options{username=Username, password=Password, erl_flags=ErlFlags, - env=Env} = Options, + env=Env, + ssh_port=MaybeSSHPort, + ssh_opts=SSHOpts} = Options, + SSHPort = case MaybeSSHPort of + [] -> 22; % Use default SSH port + A -> A + end, SSHOptions = case {Username, Password} of {[], []}-> []; @@ -427,14 +421,13 @@ spawn_remote_node(Host, Node, Options) -> [{user, Username}]; {_, _}-> [{user, Username}, {password, Password}] - end ++ [{silently_accept_hosts, true}], - check_for_ssh_running(), - {ok, SSHConnRef} = ssh:connect(atom_to_list(Host), 22, SSHOptions), + end ++ [{silently_accept_hosts, true}] ++ SSHOpts, + application:ensure_all_started(ssh), + {ok, SSHConnRef} = ssh:connect(atom_to_list(Host), SSHPort, SSHOptions), {ok, SSHChannelId} = ssh_connection:session_channel(SSHConnRef, infinity), ssh_setenv(SSHConnRef, SSHChannelId, Env), ssh_connection:exec(SSHConnRef, SSHChannelId, get_cmd(Node, ErlFlags), infinity). - ssh_setenv(SSHConnRef, SSHChannelId, [{Var, Value} | Vars]) when is_list(Var), is_list(Value) -> success = ssh_connection:setenv(SSHConnRef, SSHChannelId, diff --git a/lib/common_test/src/ct_snmp.erl b/lib/common_test/src/ct_snmp.erl index 71038bd4f4..bb0167eb22 100644 --- a/lib/common_test/src/ct_snmp.erl +++ b/lib/common_test/src/ct_snmp.erl @@ -1,25 +1,26 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2012. All Rights Reserved. +%% Copyright Ericsson AB 2004-2015. 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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% %%% @doc Common Test user interface module for the OTP snmp application %%% -%%% The purpose of this module is to make snmp configuration easier for +%%% <p>The purpose of this module is to make snmp configuration easier for %%% the test case writer. Many test cases can use default values for common %%% operations and then no snmp configuration files need to be supplied. When %%% it is necessary to change particular configuration parameters, a subset @@ -30,7 +31,7 @@ %%% To simplify the test suite, Common Test keeps track %%% of some of the snmp manager information. This way the test suite doesn't %%% have to handle as many input parameters as it would if it had to interface the -%%% OTP snmp manager directly. +%%% OTP snmp manager directly.</p> %%% %%% <p> The following snmp manager and agent parameters are configurable: </p> %%% @@ -325,9 +326,9 @@ set_info(Config) -> %%% @doc Register the manager entity (=user) responsible for specific agent(s). %%% Corresponds to making an entry in users.conf. %%% -%%% This function will try to register the given users, without +%%% <p>This function will try to register the given users, without %%% checking if any of them already exist. In order to change an -%%% already registered user, the user must first be unregistered. +%%% already registered user, the user must first be unregistered.</p> register_users(MgrAgentConfName, Users) -> case setup_users(Users) of ok -> @@ -350,10 +351,10 @@ register_users(MgrAgentConfName, Users) -> %%% @doc Explicitly instruct the manager to handle this agent. %%% Corresponds to making an entry in agents.conf %%% -%%% This function will try to register the given managed agents, +%%% <p>This function will try to register the given managed agents, %%% without checking if any of them already exist. In order to change %%% an already registered managed agent, the agent must first be -%%% unregistered. +%%% unregistered.</p> register_agents(MgrAgentConfName, ManagedAgents) -> case setup_managed_agents(MgrAgentConfName,ManagedAgents) of ok -> @@ -377,9 +378,9 @@ register_agents(MgrAgentConfName, ManagedAgents) -> %%% @doc Explicitly instruct the manager to handle this USM user. %%% Corresponds to making an entry in usm.conf %%% -%%% This function will try to register the given users, without +%%% <p>This function will try to register the given users, without %%% checking if any of them already exist. In order to change an -%%% already registered user, the user must first be unregistered. +%%% already registered user, the user must first be unregistered.</p> register_usm_users(MgrAgentConfName, UsmUsers) -> EngineID = ct:get_config({MgrAgentConfName, engine_id}, ?ENGINE_ID), case setup_usm_users(UsmUsers, EngineID) of diff --git a/lib/common_test/src/ct_ssh.erl b/lib/common_test/src/ct_ssh.erl index 974791bd70..d19004fa2c 100644 --- a/lib/common_test/src/ct_ssh.erl +++ b/lib/common_test/src/ct_ssh.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2009-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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% diff --git a/lib/common_test/src/ct_telnet.erl b/lib/common_test/src/ct_telnet.erl index 844f53731e..f5f4f648f4 100644 --- a/lib/common_test/src/ct_telnet.erl +++ b/lib/common_test/src/ct_telnet.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2014. All Rights Reserved. +%% Copyright Ericsson AB 2003-2015. 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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -29,7 +30,7 @@ %% Command timeout = 10 sec (time to wait for a command to return) %% Max no of reconnection attempts = 3 %% Reconnection interval = 5 sek (time to wait in between reconnection attempts) -%% Keep alive = true (will send NOP to the server every 10 sec if connection is idle) +%% Keep alive = true (will send NOP to the server every 8 sec if connection is idle) %% Polling limit = 0 (max number of times to poll to get a remaining string terminated) %% Polling interval = 1 sec (sleep time between polls)</pre> %% <p>These parameters can be altered by the user with the following @@ -41,7 +42,8 @@ %% {reconnection_interval,Millisec}, %% {keep_alive,Bool}, %% {poll_limit,N}, -%% {poll_interval,Millisec}]}.</pre> +%% {poll_interval,Millisec}, +%% {tcp_nodelay,Bool}]}.</pre> %% <p><code>Millisec = integer(), N = integer()</code></p> %% <p>Enter the <code>telnet_settings</code> term in a configuration %% file included in the test and ct_telnet will retrieve the information @@ -181,7 +183,8 @@ conn_to=?DEFAULT_TIMEOUT, com_to=?DEFAULT_TIMEOUT, reconns=?RECONNS, - reconn_int=?RECONN_TIMEOUT}). + reconn_int=?RECONN_TIMEOUT, + tcp_nodelay=false}). %%%----------------------------------------------------------------- %%% @spec open(Name) -> {ok,Handle} | {error,Reason} @@ -326,16 +329,16 @@ cmd(Connection,Cmd) -> %%% Reason = term() %%% @doc Send a command via telnet and wait for prompt. %%% -%%% This function will by default add a newline to the end of the +%%% <p>This function will by default add a newline to the end of the %%% given command. If this is not desired, the option %%% `{newline,false}' can be used. This is necessary, for example, %%% when sending telnet command sequences (prefixed with the -%%% Interprete As Command, IAC, character). +%%% Interprete As Command, IAC, character).</p> %%% -%%% The option `timeout' specifies how long the client shall wait for +%%% <p>The option `timeout' specifies how long the client shall wait for %%% prompt. If the time expires, the function returns %%% `{error,timeout}'. See the module description for information -%%% about the default value for the command timeout. +%%% about the default value for the command timeout.</p> cmd(Connection,Cmd,Opts) when is_list(Opts) -> case check_cmd_opts(Opts) of ok -> @@ -377,7 +380,7 @@ cmdf(Connection,CmdFormat,Args) -> %%% @doc Send a telnet command and wait for prompt %%% (uses a format string and list of arguments to build the command). %%% -%%% See {@link cmd/3} further description. +%%% <p>See {@link cmd/3} further description.</p> cmdf(Connection,CmdFormat,Args,Opts) when is_list(Args) -> Cmd = lists:flatten(io_lib:format(CmdFormat,Args)), cmd(Connection,Cmd,Opts). @@ -601,8 +604,18 @@ init(Name,{Ip,Port,Type},{TargetMod,KeepAlive,Extra}) -> Settings -> set_telnet_defaults(Settings,#state{}) end, - case catch TargetMod:connect(Name,Ip,Port,S0#state.conn_to, - KeepAlive,Extra) of + %% Handle old user versions of TargetMod + code:ensure_loaded(TargetMod), + try + case erlang:function_exported(TargetMod,connect,7) of + true -> + TargetMod:connect(Name,Ip,Port,S0#state.conn_to, + KeepAlive,S0#state.tcp_nodelay,Extra); + false -> + TargetMod:connect(Name,Ip,Port,S0#state.conn_to, + KeepAlive,Extra) + end + of {ok,TelnPid} -> put({ct_telnet_pid2name,TelnPid},Name), S1 = S0#state{host=Ip, @@ -624,15 +637,18 @@ init(Name,{Ip,Port,Type},{TargetMod,KeepAlive,Extra}) -> "Connection timeout: ~p\n" "Keep alive: ~w\n" "Poll limit: ~w\n" - "Poll interval: ~w", + "Poll interval: ~w\n" + "TCP nodelay: ~w", [Ip,Port,S1#state.com_to,S1#state.reconns, S1#state.reconn_int,S1#state.conn_to,KeepAlive, - S1#state.poll_limit,S1#state.poll_interval]), + S1#state.poll_limit,S1#state.poll_interval, + S1#state.tcp_nodelay]), {ok,TelnPid,S1}; - {'EXIT',Reason} -> - {error,Reason}; Error -> Error + catch + _:Reason -> + {error,Reason} end. type(telnet) -> ip; @@ -652,6 +668,8 @@ set_telnet_defaults([{poll_limit,PL}|Ss],S) -> set_telnet_defaults(Ss,S#state{poll_limit=PL}); set_telnet_defaults([{poll_interval,PI}|Ss],S) -> set_telnet_defaults(Ss,S#state{poll_interval=PI}); +set_telnet_defaults([{tcp_nodelay,NoDelay}|Ss],S) -> + set_telnet_defaults(Ss,S#state{tcp_nodelay=NoDelay}); set_telnet_defaults([Unknown|Ss],S) -> force_log(S,error, "Bad element in telnet_settings: ~p",[Unknown]), @@ -793,8 +811,17 @@ reconnect(Ip,Port,N,State=#state{name=Name, keep_alive=KeepAlive, extra=Extra, conn_to=ConnTo, - reconn_int=ReconnInt}) -> - case TargetMod:connect(Name,Ip,Port,ConnTo,KeepAlive,Extra) of + reconn_int=ReconnInt, + tcp_nodelay=NoDelay}) -> + %% Handle old user versions of TargetMod + ConnResult = + case erlang:function_exported(TargetMod,connect,7) of + true -> + TargetMod:connect(Name,Ip,Port,ConnTo,KeepAlive,NoDelay,Extra); + false -> + TargetMod:connect(Name,Ip,Port,ConnTo,KeepAlive,Extra) + end, + case ConnResult of {ok,NewPid} -> put({ct_telnet_pid2name,NewPid},Name), {ok, NewPid, State#state{teln_pid=NewPid}}; @@ -1021,7 +1048,7 @@ silent_teln_expect(Name,Pid,Data,Pattern,Prx,Opts) -> put(silent,Old), Result. -%% teln_expect/5 +%% teln_expect/6 %% %% This function implements the expect functionality over telnet. In %% general there are three possible ways to go: diff --git a/lib/common_test/src/ct_telnet_client.erl b/lib/common_test/src/ct_telnet_client.erl index 0c448e5b35..bdab456cfa 100644 --- a/lib/common_test/src/ct_telnet_client.erl +++ b/lib/common_test/src/ct_telnet_client.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2003-2014. 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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -34,12 +35,12 @@ %%-define(debug, true). --export([open/2, open/3, open/4, open/5, close/1]). +-export([open/2, open/3, open/4, open/5, open/6, close/1]). -export([send_data/2, send_data/3, get_data/1]). -define(TELNET_PORT, 23). -define(OPEN_TIMEOUT,10000). --define(IDLE_TIMEOUT,10000). +-define(IDLE_TIMEOUT,8000). %% telnet control characters -define(SE, 240). @@ -69,19 +70,22 @@ -record(state,{conn_name, get_data, keep_alive=true, log_pos=1}). open(Server, ConnName) -> - open(Server, ?TELNET_PORT, ?OPEN_TIMEOUT, true, ConnName). + open(Server, ?TELNET_PORT, ?OPEN_TIMEOUT, true, false, ConnName). open(Server, Port, ConnName) -> - open(Server, Port, ?OPEN_TIMEOUT, true, ConnName). + open(Server, Port, ?OPEN_TIMEOUT, true, false, ConnName). open(Server, Port, Timeout, ConnName) -> - open(Server, Port, Timeout, true, ConnName). + open(Server, Port, Timeout, true, false, ConnName). open(Server, Port, Timeout, KeepAlive, ConnName) -> + open(Server, Port, Timeout, KeepAlive, false, ConnName). + +open(Server, Port, Timeout, KeepAlive, NoDelay, ConnName) -> Self = self(), Pid = spawn(fun() -> init(Self, Server, Port, Timeout, - KeepAlive, ConnName) + KeepAlive, NoDelay, ConnName) end), receive {open,Pid} -> @@ -113,8 +117,8 @@ get_data(Pid) -> %%%----------------------------------------------------------------- %%% Internal functions -init(Parent, Server, Port, Timeout, KeepAlive, ConnName) -> - case gen_tcp:connect(Server, Port, [list,{packet,0}], Timeout) of +init(Parent, Server, Port, Timeout, KeepAlive, NoDelay, ConnName) -> + case gen_tcp:connect(Server, Port, [list,{packet,0},{nodelay,NoDelay}], Timeout) of {ok,Sock} -> dbg("~p connected to: ~p (port: ~w, keep_alive: ~w)\n", [ConnName,Server,Port,KeepAlive]), @@ -393,7 +397,7 @@ cmd_dbg(Prefix,Cmd) -> end. timestamp() -> - {MS,S,US} = now(), + {MS,S,US} = os:timestamp(), {{Year,Month,Day}, {Hour,Min,Sec}} = calendar:now_to_local_time({MS,S,US}), MilliSec = trunc(US/1000), diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl index 10a9bdac67..61d8f49dcc 100644 --- a/lib/common_test/src/ct_testspec.erl +++ b/lib/common_test/src/ct_testspec.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2006-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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -27,6 +28,8 @@ collect_tests_from_list/2, collect_tests_from_list/3, collect_tests_from_file/2, collect_tests_from_file/3]). +-export([testspec_rec2list/1, testspec_rec2list/2]). + -include("ct_util.hrl"). -define(testspec_fields, record_info(fields, testspec)). @@ -67,13 +70,17 @@ prepare_tests(TestSpec) when is_record(TestSpec,testspec) -> Tests = TestSpec#testspec.tests, %% Sort Tests into "flat" Run and Skip lists (not sorted per node). {Run,Skip} = get_run_and_skip(Tests,[],[]), + %% Create initial list of {Node,{Run,Skip}} tuples NodeList = lists:map(fun(N) -> {N,{[],[]}} end, list_nodes(TestSpec)), + %% Get all Run tests sorted per node basis. NodeList1 = run_per_node(Run,NodeList, - TestSpec#testspec.merge_tests), + TestSpec#testspec.merge_tests), + %% Get all Skip entries sorted per node basis. NodeList2 = skip_per_node(Skip,NodeList1), + %% Change representation. Result= lists:map(fun({Node,{Run1,Skip1}}) -> @@ -100,7 +107,7 @@ run_per_node([{{Node,Dir},Test}|Ts],Result,MergeTests) -> true -> merge_tests(Dir,Test,Run) end, - run_per_node(Ts,insert_in_order({Node,{Run1,Skip}},Result), + run_per_node(Ts,insert_in_order({Node,{Run1,Skip}},Result,replace), MergeTests); run_per_node([],Result,_) -> Result. @@ -137,7 +144,7 @@ merge_suites(Dir,Test,[]) -> skip_per_node([{{Node,Dir},Test}|Ts],Result) -> {value,{Node,{Run,Skip}}} = lists:keysearch(Node,1,Result), Skip1 = [{Dir,Test}|Skip], - skip_per_node(Ts,insert_in_order({Node,{Run,Skip1}},Result)); + skip_per_node(Ts,insert_in_order({Node,{Run,Skip1}},Result,replace)); skip_per_node([],Result) -> Result. @@ -153,7 +160,7 @@ skip_per_node([],Result) -> %% %% Skip entry: {Suites,Comment} or {Suite,Cases,Comment} %% -get_run_and_skip([{{Node,Dir},Suites}|Tests],Run,Skip) -> +get_run_and_skip([{{Node,Dir},Suites}|Tests],Run,Skip) -> TestDir = ct_util:get_testdir(Dir,catch element(1,hd(Suites))), case lists:keysearch(all,1,Suites) of {value,_} -> % all Suites in Dir @@ -180,18 +187,33 @@ prepare_suites(Node,Dir,[{Suite,Cases}|Suites],Run,Skip) -> [[{{Node,Dir},{Suite,all}}]|Run], [Skipped|Skip]); false -> - {RL,SL} = prepare_cases(Node,Dir,Suite,Cases), - prepare_suites(Node,Dir,Suites,[RL|Run],[SL|Skip]) + {Run1,Skip1} = prepare_cases(Node,Dir,Suite,Cases,Run,Skip), + prepare_suites(Node,Dir,Suites,Run1,Skip1) end; prepare_suites(_Node,_Dir,[],Run,Skip) -> {lists:flatten(lists:reverse(Run)), lists:flatten(lists:reverse(Skip))}. -prepare_cases(Node,Dir,Suite,Cases) -> +prepare_cases(Node,Dir,Suite,Cases,Run,Skip) -> case get_skipped_cases(Node,Dir,Suite,Cases) of - SkipAll=[{{Node,Dir},{Suite,_Cmt}}] -> % all cases to be skipped - %% note: this adds an 'all' test even if only skip is specified - {[{{Node,Dir},{Suite,all}}],SkipAll}; + [SkipAll={{Node,Dir},{Suite,_Cmt}}] -> % all cases to be skipped + case lists:any(fun({{N,D},{S,all}}) when N == Node, + D == Dir, + S == Suite -> + true; + ({{N,D},{S,Cs}}) when N == Node, + D == Dir, + S == Suite -> + lists:member(all,Cs); + (_) -> false + end, lists:flatten(Run)) of + true -> + {Run,[SkipAll|Skip]}; + false -> + %% note: this adds an 'all' test even if + %% only skip is specified + {[{{Node,Dir},{Suite,all}}|Run],[SkipAll|Skip]} + end; Skipped -> %% note: this adds a test even if only skip is specified PrepC = lists:foldr(fun({{G,Cs},{skip,_Cmt}}, Acc) when @@ -207,11 +229,11 @@ prepare_cases(Node,Dir,Suite,Cases) -> true -> Acc; false -> - [C|Acc] + [{skipped,C}|Acc] end; (C,Acc) -> [C|Acc] end, [], Cases), - {{{Node,Dir},{Suite,PrepC}},Skipped} + {[{{Node,Dir},{Suite,PrepC}}|Run],[Skipped|Skip]} end. get_skipped_suites(Node,Dir,Suites) -> @@ -428,6 +450,7 @@ collect_tests({Replace,Terms},TestSpec=#testspec{alias=As,nodes=Ns},Relaxed) -> merge_tests = MergeTestsDef}), TestSpec2 = get_all_nodes(Terms2,TestSpec1), {Terms3, TestSpec3} = filter_init_terms(Terms2, [], TestSpec2), + add_tests(Terms3,TestSpec3). %% replace names (atoms) in the testspec matching those in 'define' terms by @@ -973,7 +996,8 @@ add_tests([Term={Tag,all_nodes,Data}|Ts],Spec) -> should_be_added(Tag,Node,Data,Spec)], add_tests(Tests++Ts,Spec); invalid -> % ignore term - add_tests(Ts,Spec) + Unknown = Spec#testspec.unknown, + add_tests(Ts,Spec#testspec{unknown=Unknown++[Term]}) end; %% create one test entry per node in Nodes and reinsert add_tests([{Tag,[],Data}|Ts],Spec) -> @@ -1001,7 +1025,8 @@ add_tests([Term={Tag,NodeOrOther,Data}|Ts],Spec) -> handle_data(Tag,Node,Data,Spec), add_tests(Ts,mod_field(Spec,Tag,NodeIxData)); invalid -> % ignore term - add_tests(Ts,Spec) + Unknown = Spec#testspec.unknown, + add_tests(Ts,Spec#testspec{unknown=Unknown++[Term]}) end; false -> add_tests([{Tag,all_nodes,{NodeOrOther,Data}}|Ts],Spec) @@ -1012,13 +1037,15 @@ add_tests([Term={Tag,Data}|Ts],Spec) -> valid -> add_tests([{Tag,all_nodes,Data}|Ts],Spec); invalid -> - add_tests(Ts,Spec) + Unknown = Spec#testspec.unknown, + add_tests(Ts,Spec#testspec{unknown=Unknown++[Term]}) end; %% some other data than a tuple add_tests([Other|Ts],Spec) -> case get(relaxed) of - true -> - add_tests(Ts,Spec); + true -> + Unknown = Spec#testspec.unknown, + add_tests(Ts,Spec#testspec{unknown=Unknown++[Other]}); false -> throw({error,{undefined_term_in_spec,Other}}) end; @@ -1119,8 +1146,9 @@ should_be_added(Tag,Node,_Data,Spec) -> if %% list terms *without* possible duplicates here Tag == logdir; Tag == logopts; - Tag == basic_html; Tag == label; - Tag == auto_compile; Tag == abort_if_missing_suites; + Tag == basic_html; Tag == esc_chars; + Tag == label; Tag == auto_compile; + Tag == abort_if_missing_suites; Tag == stylesheet; Tag == verbosity; Tag == silent_connections -> lists:keymember(ref2node(Node,Spec#testspec.nodes),1, @@ -1149,6 +1177,24 @@ per_node([N|Ns],Tag,Data,Refs) -> per_node([],_,_,_) -> []. +%% Change the testspec record "back" to a list of tuples +testspec_rec2list(Rec) -> + {Terms,_} = lists:mapfoldl(fun(unknown, Pos) -> + {element(Pos, Rec),Pos+1}; + (F, Pos) -> + {{F,element(Pos, Rec)},Pos+1} + end,2,?testspec_fields), + lists:flatten(Terms). + +%% Extract one or more values from a testspec record and +%% return the result as a list of tuples +testspec_rec2list(Field, Rec) when is_atom(Field) -> + [Term] = testspec_rec2list([Field], Rec), + Term; +testspec_rec2list(Fields, Rec) -> + Terms = testspec_rec2list(Rec), + [{Field,proplists:get_value(Field, Terms)} || Field <- Fields]. + %% read the value for FieldName in record Rec#testspec read_field(Rec, FieldName) -> catch lists:foldl(fun(F, Pos) when F == FieldName -> @@ -1232,7 +1278,7 @@ insert_groups1(Suite,Groups,Suites0) -> Suites0; {value,{Suite,GrAndCases0}} -> GrAndCases = insert_groups2(Groups,GrAndCases0), - insert_in_order({Suite,GrAndCases},Suites0); + insert_in_order({Suite,GrAndCases},Suites0,replace); false -> insert_in_order({Suite,Groups},Suites0) end. @@ -1257,7 +1303,7 @@ insert_cases(Node,Dir,Suite,Cases,Tests,false) when is_list(Cases) -> insert_cases(Node,Dir,Suite,Cases,Tests,true) when is_list(Cases) -> {Tests1,Done} = lists:foldr(fun(All={{N,D},[{all,_}]},{Merged,_}) when N == Node, - D == Dir -> + D == Dir -> {[All|Merged],true}; ({{N,D},Suites0},{Merged,_}) when N == Node, D == Dir -> @@ -1287,7 +1333,7 @@ insert_cases1(Suite,Cases,Suites0) -> Suites0; {value,{Suite,Cases0}} -> Cases1 = insert_in_order(Cases,Cases0), - insert_in_order({Suite,Cases1},Suites0); + insert_in_order({Suite,Cases1},Suites0,replace); false -> insert_in_order({Suite,Cases},Suites0) end. @@ -1344,9 +1390,9 @@ skip_groups1(Suite,Groups,Cmt,Suites0) -> case lists:keysearch(Suite,1,Suites0) of {value,{Suite,GrAndCases0}} -> GrAndCases1 = GrAndCases0 ++ SkipGroups, - insert_in_order({Suite,GrAndCases1},Suites0); + insert_in_order({Suite,GrAndCases1},Suites0,replace); false -> - insert_in_order({Suite,SkipGroups},Suites0) + insert_in_order({Suite,SkipGroups},Suites0,replace) end. skip_cases(Node,Dir,Suite,Cases,Cmt,Tests,false) when is_list(Cases) -> @@ -1355,7 +1401,7 @@ skip_cases(Node,Dir,Suite,Cases,Cmt,Tests,false) when is_list(Cases) -> skip_cases(Node,Dir,Suite,Cases,Cmt,Tests,true) when is_list(Cases) -> {Tests1,Done} = lists:foldr(fun({{N,D},Suites0},{Merged,_}) when N == Node, - D == Dir -> + D == Dir -> Suites1 = skip_cases1(Suite,Cases,Cmt,Suites0), {[{{N,D},Suites1}|Merged],true}; (T,{Merged,Match}) -> @@ -1376,32 +1422,55 @@ skip_cases1(Suite,Cases,Cmt,Suites0) -> case lists:keysearch(Suite,1,Suites0) of {value,{Suite,Cases0}} -> Cases1 = Cases0 ++ SkipCases, - insert_in_order({Suite,Cases1},Suites0); + insert_in_order({Suite,Cases1},Suites0,replace); false -> - insert_in_order({Suite,SkipCases},Suites0) + case Suites0 of + [{all,_}=All|Skips]-> + [All|Skips++[{Suite,SkipCases}]]; + _ -> + insert_in_order({Suite,SkipCases},Suites0,replace) + end end. append(Elem, List) -> List ++ [Elem]. -insert_in_order([E|Es],List) -> - List1 = insert_elem(E,List,[]), - insert_in_order(Es,List1); -insert_in_order([],List) -> +insert_in_order(Elems,Dest) -> + insert_in_order1(Elems,Dest,false). + +insert_in_order(Elems,Dest,replace) -> + insert_in_order1(Elems,Dest,true). + +insert_in_order1([_E|Es],all,Replace) -> + insert_in_order1(Es,all,Replace); + +insert_in_order1([E|Es],List,Replace) -> + List1 = insert_elem(E,List,[],Replace), + insert_in_order1(Es,List1,Replace); +insert_in_order1([],List,_Replace) -> List; -insert_in_order(E,List) -> - insert_elem(E,List,[]). +insert_in_order1(E,List,Replace) -> + insert_elem(E,List,[],Replace). + -%% replace an existing entry (same key) or add last in list -insert_elem({Key,_}=E,[{Key,_}|Rest],SoFar) -> +insert_elem({Key,_}=E,[{Key,_}|Rest],SoFar,true) -> lists:reverse([E|SoFar]) ++ Rest; -insert_elem({E,_},[E|Rest],SoFar) -> +insert_elem({E,_},[E|Rest],SoFar,true) -> lists:reverse([E|SoFar]) ++ Rest; -insert_elem(E,[E|Rest],SoFar) -> +insert_elem(E,[E|Rest],SoFar,true) -> + lists:reverse([E|SoFar]) ++ Rest; + +insert_elem({all,_}=E,_,SoFar,_Replace) -> + lists:reverse([E|SoFar]); +insert_elem(_E,[all|_],SoFar,_Replace) -> + lists:reverse(SoFar); +insert_elem(_E,[{all,_}],SoFar,_Replace) -> + lists:reverse(SoFar); +insert_elem({Key,_}=E,[{Key,[]}|Rest],SoFar,_Replace) -> lists:reverse([E|SoFar]) ++ Rest; -insert_elem(E,[E1|Rest],SoFar) -> - insert_elem(E,Rest,[E1|SoFar]); -insert_elem(E,[],SoFar) -> +insert_elem(E,[E1|Rest],SoFar,Replace) -> + insert_elem(E,Rest,[E1|SoFar],Replace); +insert_elem(E,[],SoFar,_Replace) -> lists:reverse([E|SoFar]). ref2node(all_nodes,_Refs) -> @@ -1476,6 +1545,8 @@ valid_terms() -> {logopts,3}, {basic_html,2}, {basic_html,3}, + {esc_chars,2}, + {esc_chars,3}, {verbosity,2}, {verbosity,3}, {silent_connections,2}, diff --git a/lib/common_test/src/ct_util.erl b/lib/common_test/src/ct_util.erl index 56027586d1..3561a0a2d3 100644 --- a/lib/common_test/src/ct_util.erl +++ b/lib/common_test/src/ct_util.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2003-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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -458,6 +459,7 @@ loop(Mode,TestData,StartDir) -> error:badarg -> [] end, ct_hooks:terminate(Callbacks), + close_connections(ets:tab2list(?conn_table)), ets:delete(?conn_table), ets:delete(?board_table), @@ -484,6 +486,8 @@ loop(Mode,TestData,StartDir) -> {'EXIT',Pid,Reason} -> case ets:lookup(?conn_table,Pid) of [#conn{address=A,callback=CB}] -> + ErrorStr = io_lib:format("~tp", [Reason]), + ErrorHtml = ct_logs:escape_chars(ErrorStr), %% A connection crashed - remove the connection but don't die ct_logs:tc_log_async(ct_error_notify, ?MAX_IMPORTANCE, @@ -491,8 +495,8 @@ loop(Mode,TestData,StartDir) -> "Connection process died: " "Pid: ~w, Address: ~p, " "Callback: ~w\n" - "Reason: ~p\n\n", - [Pid,A,CB,Reason]), + "Reason: ~ts\n\n", + [Pid,A,CB,ErrorHtml]), catch CB:close(Pid), %% in case CB:close failed to do this: unregister_connection(Pid), diff --git a/lib/common_test/src/ct_util.hrl b/lib/common_test/src/ct_util.hrl index 845bb55486..bdfe2041a5 100644 --- a/lib/common_test/src/ct_util.hrl +++ b/lib/common_test/src/ct_util.hrl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2003-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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -36,6 +37,7 @@ logdir=["."], logopts=[], basic_html=[], + esc_chars=[], verbosity=[], silent_connections=[], cover=[], @@ -55,6 +57,7 @@ create_priv_dir=[], alias=[], tests=[], + unknown=[], merge_tests=true}). -record(cover, {app=none, diff --git a/lib/common_test/src/ct_webtool.erl b/lib/common_test/src/ct_webtool.erl index b67a7c2a92..014487eb10 100644 --- a/lib/common_test/src/ct_webtool.erl +++ b/lib/common_test/src/ct_webtool.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2001-2010. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% diff --git a/lib/common_test/src/ct_webtool_sup.erl b/lib/common_test/src/ct_webtool_sup.erl index 1d612a2d18..485161c784 100644 --- a/lib/common_test/src/ct_webtool_sup.erl +++ b/lib/common_test/src/ct_webtool_sup.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2001-2009. 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. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% diff --git a/lib/common_test/src/cth_conn_log.erl b/lib/common_test/src/cth_conn_log.erl index 1e60f2751e..954b4239af 100644 --- a/lib/common_test/src/cth_conn_log.erl +++ b/lib/common_test/src/cth_conn_log.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2012-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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -131,7 +132,7 @@ pre_init_per_testcase(TestCase,Config,CthState) -> [S,ct_logs:uri(L),filename:basename(L)]) || {S,L} <- Ls] ++ "</table>", - io:format(Str,[]), + ct:log(Str,[],[no_css]), {ConnMod,{LogType,Ls}}; _ -> {ConnMod,{LogType,[]}} diff --git a/lib/common_test/src/cth_log_redirect.erl b/lib/common_test/src/cth_log_redirect.erl index 61700a2032..780fbea79a 100644 --- a/lib/common_test/src/cth_log_redirect.erl +++ b/lib/common_test/src/cth_log_redirect.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2011-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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -29,7 +30,8 @@ pre_init_per_suite/3, pre_end_per_suite/3, post_end_per_suite/4, pre_init_per_group/3, post_init_per_group/4, pre_end_per_group/3, post_end_per_group/4, - pre_init_per_testcase/3, post_end_per_testcase/4]). + pre_init_per_testcase/3, post_init_per_testcase/4, + pre_end_per_testcase/3, post_end_per_testcase/4]). %% Event handler Callbacks -export([init/1, @@ -88,6 +90,12 @@ pre_init_per_testcase(TC, Config, State) -> set_curr_func(TC, Config), {Config, State}. +post_init_per_testcase(_TC, _Config, Return, State) -> + {Return, State}. + +pre_end_per_testcase(_TC, Config, State) -> + {Config, State}. + post_end_per_testcase(_TC, _Config, Result, State) -> %% Make sure that the event queue is flushed %% before ending this test case. @@ -122,7 +130,14 @@ handle_event(Event, #eh_state{log_func = LogFunc} = State) -> tag_event(Event)), if is_list(SReport) -> SaslHeader = format_header(State), - ct_logs:LogFunc(sasl, ?STD_IMPORTANCE, SaslHeader, SReport, []); + case LogFunc of + tc_log -> + ct_logs:tc_log(sasl, ?STD_IMPORTANCE, + SaslHeader, SReport, [], []); + tc_log_async -> + ct_logs:tc_log_async(sasl, ?STD_IMPORTANCE, + SaslHeader, SReport, []) + end; true -> %% Report is an atom if no logging is to be done ignore end @@ -131,7 +146,14 @@ handle_event(Event, #eh_state{log_func = LogFunc} = State) -> tag_event(Event),io_lib), if is_list(EReport) -> ErrHeader = format_header(State), - ct_logs:LogFunc(error_logger, ?STD_IMPORTANCE, ErrHeader, EReport, []); + case LogFunc of + tc_log -> + ct_logs:tc_log(error_logger, ?STD_IMPORTANCE, + ErrHeader, EReport, [], []); + tc_log_async -> + ct_logs:tc_log_async(error_logger, ?STD_IMPORTANCE, + ErrHeader, EReport, []) + end; true -> %% Report is an atom if no logging is to be done ignore end, diff --git a/lib/common_test/src/cth_surefire.erl b/lib/common_test/src/cth_surefire.erl index bb12171ea7..d6e855c02c 100644 --- a/lib/common_test/src/cth_surefire.erl +++ b/lib/common_test/src/cth_surefire.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2012-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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %%-------------------------------------------------------------------- @@ -59,6 +60,8 @@ -define(default_report,"junit_report.xml"). -define(suite_log,"suite.log.html"). +-define(now, os:timestamp()). + %% Number of dirs from log root to testcase log file. %% ct_run.<node>.<timestamp>/<test_name>/run.<timestamp>/<tc_log>.html -define(log_depth,3). @@ -77,11 +80,12 @@ init(Path, Opts) -> axis = proplists:get_value(axis,Opts,[]), properties = proplists:get_value(properties,Opts,[]), url_base = proplists:get_value(url_base,Opts), - timer = now() }. + timer = ?now }. -pre_init_per_suite(Suite,SkipOrFail,State) when is_tuple(SkipOrFail) -> +pre_init_per_suite(Suite,SkipOrFail,#state{ test_cases = [] } = State) + when is_tuple(SkipOrFail) -> {SkipOrFail, init_tc(State#state{curr_suite = Suite, - curr_suite_ts = now()}, + curr_suite_ts = ?now}, SkipOrFail) }; pre_init_per_suite(Suite,Config,#state{ test_cases = [] } = State) -> TcLog = proplists:get_value(tc_logfile,Config), @@ -96,7 +100,7 @@ pre_init_per_suite(Suite,Config,#state{ test_cases = [] } = State) -> end, {Config, init_tc(State#state{ filepath = Path, curr_suite = Suite, - curr_suite_ts = now(), + curr_suite_ts = ?now, curr_log_dir = CurrLogDir}, Config) }; pre_init_per_suite(Suite,Config,State) -> @@ -169,9 +173,9 @@ do_tc_skip(Res, State) -> State#state{ test_cases = [NewTC | tl(TCs)]}. init_tc(State, Config) when is_list(Config) == false -> - State#state{ timer = now(), tc_log = "" }; + State#state{ timer = ?now, tc_log = "" }; init_tc(State, Config) -> - State#state{ timer = now(), + State#state{ timer = ?now, tc_log = proplists:get_value(tc_logfile, Config, [])}. end_tc(Func, Config, Res, State) when is_atom(Func) -> @@ -194,7 +198,7 @@ end_tc(Name, _Config, _Res, State = #state{ curr_suite = Suite, ClassName = atom_to_list(Suite), PGroup = string:join([ atom_to_list(Group)|| Group <- lists:reverse(Groups)],"."), - TimeTakes = io_lib:format("~f",[timer:now_diff(now(),TS) / 1000000]), + TimeTakes = io_lib:format("~f",[timer:now_diff(?now,TS) / 1000000]), State#state{ test_cases = [#testcase{ log = Log, url = Url, timestamp = now_to_string(TS), @@ -209,7 +213,7 @@ close_suite(#state{ test_cases = [] } = State) -> State; close_suite(#state{ test_cases = TCs, url_base = UrlBase } = State) -> {Total,Fail,Skip} = count_tcs(TCs,0,0,0), - TimeTaken = timer:now_diff(now(),State#state.curr_suite_ts) / 1000000, + TimeTaken = timer:now_diff(?now,State#state.curr_suite_ts) / 1000000, SuiteLog = filename:join(State#state.curr_log_dir,?suite_log), SuiteUrl = make_url(UrlBase,SuiteLog), Suite = #testsuite{ name = atom_to_list(State#state.curr_suite), diff --git a/lib/common_test/src/unix_telnet.erl b/lib/common_test/src/unix_telnet.erl index 09b6fd1510..2fc585735d 100644 --- a/lib/common_test/src/unix_telnet.erl +++ b/lib/common_test/src/unix_telnet.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2004-2014. 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/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -26,7 +27,8 @@ %%% {port,PortNum}, % optional %%% {username,UserName}, %%% {password,Password}, -%%% {keep_alive,Bool}]}. % optional</pre> +%%% {keep_alive,Bool}, % optional +%%% {tcp_nodely,Bool}]} % optional</pre> %%% %%% <p>To communicate via telnet to the host specified by %%% <code>HostNameOrIpAddress</code>, use the interface functions in @@ -54,7 +56,7 @@ -compile(export_all). %% Callbacks for ct_telnet.erl --export([connect/6,get_prompt_regexp/0]). +-export([connect/7,get_prompt_regexp/0]). -import(ct_telnet,[start_gen_log/1,log/4,end_gen_log/0]). -define(username,"login: "). @@ -81,6 +83,7 @@ get_prompt_regexp() -> %%% Port = integer() %%% Timeout = integer() %%% KeepAlive = bool() +%%% TCPNoDelay = bool() %%% Extra = ct:target_name() | {Username,Password} %%% Username = string() %%% Password = string() @@ -90,25 +93,25 @@ get_prompt_regexp() -> %%% @doc Callback for ct_telnet.erl. %%% %%% <p>Setup telnet connection to a unix host.</p> -connect(ConnName,Ip,Port,Timeout,KeepAlive,Extra) -> +connect(ConnName,Ip,Port,Timeout,KeepAlive,TCPNoDelay,Extra) -> case Extra of {Username,Password} -> - connect1(ConnName,Ip,Port,Timeout,KeepAlive, + connect1(ConnName,Ip,Port,Timeout,KeepAlive,TCPNoDelay, Username,Password); KeyOrName -> case get_username_and_password(KeyOrName) of {ok,{Username,Password}} -> - connect1(ConnName,Ip,Port,Timeout,KeepAlive, + connect1(ConnName,Ip,Port,Timeout,KeepAlive,TCPNoDelay, Username,Password); Error -> Error end end. -connect1(Name,Ip,Port,Timeout,KeepAlive,Username,Password) -> +connect1(Name,Ip,Port,Timeout,KeepAlive,TCPNoDelay,Username,Password) -> start_gen_log("unix_telnet connect"), Result = - case ct_telnet_client:open(Ip,Port,Timeout,KeepAlive,Name) of + case ct_telnet_client:open(Ip,Port,Timeout,KeepAlive,TCPNoDelay,Name) of {ok,Pid} -> case ct_telnet:silent_teln_expect(Name,Pid,[], [prompt],?prx,[]) of diff --git a/lib/common_test/src/vts.erl b/lib/common_test/src/vts.erl index ab13e7d0ee..df434d62c8 100644 --- a/lib/common_test/src/vts.erl +++ b/lib/common_test/src/vts.erl @@ -3,16 +3,17 @@ %% %% Copyright Ericsson AB 2003-2012. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% 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. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% |