diff options
Diffstat (limited to 'lib/common_test/src')
-rw-r--r-- | lib/common_test/src/ct.erl | 182 | ||||
-rw-r--r-- | lib/common_test/src/ct_config.erl | 250 | ||||
-rw-r--r-- | lib/common_test/src/ct_conn_log_h.erl | 2 | ||||
-rw-r--r-- | lib/common_test/src/ct_framework.erl | 23 | ||||
-rw-r--r-- | lib/common_test/src/ct_ftp.erl | 6 | ||||
-rw-r--r-- | lib/common_test/src/ct_hooks.erl | 2 | ||||
-rw-r--r-- | lib/common_test/src/ct_netconfc.erl | 15 | ||||
-rw-r--r-- | lib/common_test/src/ct_run.erl | 55 | ||||
-rw-r--r-- | lib/common_test/src/ct_ssh.erl | 7 | ||||
-rw-r--r-- | lib/common_test/src/ct_telnet.erl | 2 | ||||
-rw-r--r-- | lib/common_test/src/ct_testspec.erl | 20 | ||||
-rw-r--r-- | lib/common_test/src/ct_util.erl | 20 | ||||
-rw-r--r-- | lib/common_test/src/ct_util.hrl | 3 | ||||
-rw-r--r-- | lib/common_test/src/cth_surefire.erl | 2 |
14 files changed, 350 insertions, 239 deletions
diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl index 8d4721ab63..aa8813c391 100644 --- a/lib/common_test/src/ct.erl +++ b/lib/common_test/src/ct.erl @@ -157,7 +157,7 @@ run(TestDirs) -> %%% {refresh_logs,LogDir} | {logopts,LogOpts} | %%% {verbosity,VLevels} | {basic_html,Bool} | %%% {ct_hooks, CTHs} | {enable_builtin_hooks,Bool} | -%%% {noinput,Bool} +%%% {release_shell,Bool} %%% TestDirs = [string()] | string() %%% Suites = [string()] | [atom()] | string() | atom() %%% Cases = [atom()] | atom() @@ -194,15 +194,24 @@ run(TestDirs) -> %%% CTHs = [CTHModule | {CTHModule, CTHInitArgs}] %%% CTHModule = atom() %%% CTHInitArgs = term() -%%% Result = [TestResult] | {error,Reason} -%%% @doc Run tests as specified by the combination of options in <code>Opts</code>. +%%% Result = {Ok,Failed,{UserSkipped,AutoSkipped}} | TestRunnerPid | {error,Reason} +%%% Ok = integer() +%%% Failed = integer() +%%% UserSkipped = integer() +%%% AutoSkipped = integer() +%%% TestRunnerPid = pid() +%%% Reason = term() +%%% @doc <p>Run tests as specified by the combination of options in <code>Opts</code>. %%% The options are the same as those used with the %%% <seealso marker="ct_run#ct_run"><code>ct_run</code></seealso> program. %%% Note that here a <code>TestDir</code> can be used to point out the path to %%% a <code>Suite</code>. Note also that the option <code>testcase</code> %%% corresponds to the <code>-case</code> option in the <code>ct_run</code> %%% program. Configuration files specified in <code>Opts</code> will be -%%% installed automatically at startup. +%%% installed automatically at startup.</p> +%%% <p><code>TestRunnerPid</code> is returned if <code>release_shell == true</code> +%%% (see the User's Guide for details).</p> +%%% <p><code>Reason</code> indicates what type of error has been encountered.</p> run_test(Opts) -> ct_run:run_test(Opts). @@ -274,27 +283,34 @@ stop_interactive() -> %%%----------------------------------------------------------------- %%% @spec require(Required) -> ok | {error,Reason} -%%% Required = Key | {Key,SubKeys} +%%% Required = Key | {Key,SubKeys} | {Key,SubKey,SubKeys} %%% Key = atom() %%% SubKeys = SubKey | [SubKey] %%% SubKey = atom() %%% -%%% @doc Check if the required configuration is available. +%%% @doc Check if the required configuration is available. It is possible +%%% to specify arbitrarily deep tuples as <c>Required</c>. Note that it is +%%% only the last element of the tuple which can be a list of <c>SubKey</c>s. %%% -%%% <p>Example: require the variable <code>myvar</code>:<br/> -%%% <code>ok = ct:require(myvar)</code></p> +%%% <p>Example 1: require the variable <code>myvar</code>:</p> +%%% <pre>ok = ct:require(myvar).</pre> %%% %%% <p>In this case the config file must at least contain:</p> -%%% <pre> -%%% {myvar,Value}.</pre> +%%% <pre>{myvar,Value}.</pre> %%% -%%% <p>Example: require the variable <code>myvar</code> with -%%% subvariable <code>sub1</code>:<br/> -%%% <code>ok = ct:require({myvar,sub1})</code></p> +%%% <p>Example 2: require the key <code>myvar</code> with +%%% subkeys <code>sub1</code> and <code>sub2</code>:</p> +%%% <pre>ok = ct:require({myvar,[sub1,sub2]}).</pre> %%% %%% <p>In this case the config file must at least contain:</p> -%%% <pre> -%%% {myvar,[{sub1,Value}]}.</pre> +%%% <pre>{myvar,[{sub1,Value},{sub2,Value}]}.</pre> +%%% +%%% <p>Example 3: require the key <code>myvar</code> with +%%% subkey <code>sub1</code> with <code>subsub1</code>:</p> +%%% <pre>ok = ct:require({myvar,sub1,sub2}).</pre> +%%% +%%% <p>In this case the config file must at least contain:</p> +%%% <pre>{myvar,[{sub1,[{sub2,Value}]}]}.</pre> %%% %%% @see require/2 %%% @see get_config/1 @@ -306,30 +322,36 @@ require(Required) -> %%%----------------------------------------------------------------- %%% @spec require(Name,Required) -> ok | {error,Reason} %%% Name = atom() -%%% Required = Key | {Key,SubKeys} +%%% Required = Key | {Key,SubKey} | {Key,SubKey,SubKey} +%%% SubKey = Key %%% Key = atom() -%%% SubKeys = SubKey | [SubKey] -%%% SubKey = atom() %%% %%% @doc Check if the required configuration is available, and give it -%%% a name. +%%% a name. The semantics for <c>Required</c> is the same as in +%%% <c>required/1</c> except that it is not possible to specify a list +%%% of <c>SubKey</c>s. %%% -%%% <p>If the requested data is available, the main entry will be +%%% <p>If the requested data is available, the sub entry will be %%% associated with <code>Name</code> so that the value of the element %%% can be read with <code>get_config/1,2</code> provided -%%% <code>Name</code> instead of the <code>Key</code>.</p> +%%% <code>Name</code> instead of the whole <code>Required</code> term.</p> %%% %%% <p>Example: Require one node with a telnet connection and an -%%% ftp connection. Name the node <code>a</code>:<br/> <code>ok = -%%% ct:require(a,{node,[telnet,ftp]}).</code><br/> All references -%%% to this node may then use the node name. E.g. you can fetch a -%%% file over ftp like this:<br/> -%%% <code>ok = ct:ftp_get(a,RemoteFile,LocalFile).</code></p> +%%% ftp connection. Name the node <code>a</code>: +%%% <pre>ok = ct:require(a,{machine,node}).</pre> +%%% All references to this node may then use the node name. +%%% E.g. you can fetch a file over ftp like this:</p> +%%% <pre>ok = ct:ftp_get(a,RemoteFile,LocalFile).</pre> %%% %%% <p>For this to work, the config file must at least contain:</p> -%%% <pre> -%%% {node,[{telnet,IpAddr}, -%%% {ftp,IpAddr}]}.</pre> +%%% <pre>{machine,[{node,[{telnet,IpAddr},{ftp,IpAddr}]}]}.</pre> +%%% +%%% <note>The behaviour of this function changed radically in common_test +%%% 1.6.2. In order too keep some backwards compatability it is still possible +%%% to do: <br/><c>ct:require(a,{node,[telnet,ftp]}).</c><br/> +%%% This will associate the name <c>a</c> with the top level <c>node</c> entry. +%%% For this to work, the config file must at least contain:<br/> +%%% <c>{node,[{telnet,IpAddr},{ftp,IpAddr}]}.</c></note> %%% %%% @see require/1 %%% @see get_config/1 @@ -352,7 +374,7 @@ get_config(Required,Default) -> %%%----------------------------------------------------------------- %%% @spec get_config(Required,Default,Opts) -> ValueOrElement -%%% Required = KeyOrName | {KeyOrName,SubKey} +%%% Required = KeyOrName | {KeyOrName,SubKey} | {KeyOrName,SubKey,SubKey} %%% KeyOrName = atom() %%% SubKey = atom() %%% Default = term() @@ -370,25 +392,25 @@ get_config(Required,Default) -> %%% <p>Example, given the following config file:</p> %%% <pre> %%% {unix,[{telnet,IpAddr}, -%%% {username,Username}, -%%% {password,Password}]}.</pre> -%%% <p><code>get_config(unix,Default) -> +%%% {user,[{username,Username}, +%%% {password,Password}]}]}.</pre> +%%% <p><code>ct:get_config(unix,Default) -> %%% [{telnet,IpAddr}, -%%% {username,Username}, -%%% {password,Password}]</code><br/> -%%% <code>get_config({unix,telnet},Default) -> IpAddr</code><br/> -%%% <code>get_config({unix,ftp},Default) -> Default</code><br/> -%%% <code>get_config(unknownkey,Default) -> Default</code></p> +%%% {user, [{username,Username}, +%%% {password,Password}]}]</code><br/> +%%% <code>ct:get_config({unix,telnet},Default) -> IpAddr</code><br/> +%%% <code>ct:get_config({unix,user,username},Default) -> Username</code><br/> +%%% <code>ct:get_config({unix,ftp},Default) -> Default</code><br/> +%%% <code>ct:get_config(unknownkey,Default) -> Default</code></p> %%% %%% <p>If a config variable key has been associated with a name (by %%% means of <code>require/2</code> or a require statement), the name %%% may be used instead of the key to read the value:</p> %%% -%%% <p><code>require(myhost,unix) -> ok</code><br/> -%%% <code>get_config(myhost,Default) -> -%%% [{telnet,IpAddr}, -%%% {username,Username}, -%%% {password,Password}]</code></p> +%%% <p><code>ct:require(myuser,{unix,user}) -> ok.</code><br/> +%%% <code>ct:get_config(myuser,Default) -> +%%% [{username,Username}, +%%% {password,Password}]</code></p> %%% %%% <p>If a config variable is defined in multiple files and you want to %%% access all possible values, use the <code>all</code> option. The @@ -398,9 +420,7 @@ get_config(Required,Default) -> %%% %%% <p>If you want config elements (key-value tuples) returned as result %%% instead of values, use the <code>element</code> option. -%%% The returned elements will then be on the form <code>{KeyOrName,Value}</code>, -%%% or (in case a subkey has been specified) -%%% <code>{{KeyOrName,SubKey},Value}</code></p> +%%% The returned elements will then be on the form <code>{Required,Value}</code></p> %%% %%% @see get_config/1 %%% @see get_config/2 @@ -411,7 +431,7 @@ get_config(Required,Default,Opts) -> %%%----------------------------------------------------------------- %%% @spec reload_config(Required) -> ValueOrElement -%%% Required = KeyOrName | {KeyOrName,SubKey} +%%% Required = KeyOrName | {KeyOrName,SubKey} | {KeyOrName,SubKey,SubKey} %%% KeyOrName = atom() %%% SubKey = atom() %%% ValueOrElement = term() @@ -949,6 +969,8 @@ get_testdata(Key) -> Error; {'EXIT',_Reason} -> no_tests_running; + undefined -> + {error,no_testdata}; [CurrTC] when Key == curr_tc -> {ok,CurrTC}; Data -> @@ -1148,7 +1170,8 @@ sync_notify(Name,Data) -> %%%----------------------------------------------------------------- %%% @spec break(Comment) -> ok | {error,Reason} %%% Comment = string() -%%% Reason = {multiple_cases_running,TestCases} +%%% Reason = {multiple_cases_running,TestCases} | +%%% 'enable break with release_shell option' %%% TestCases = [atom()] %%% %%% @doc <p>This function will cancel all timetraps and pause the @@ -1159,21 +1182,33 @@ sync_notify(Name,Data) -> %%% test case. If a parallel group is executing, <c>break/2</c> %%% should be called instead.</p> break(Comment) -> - case get_testdata(curr_tc) of - {ok,{_,TestCase}} -> - test_server:break(?MODULE, Comment); - {ok,Cases} when is_list(Cases) -> - {error,{multiple_cases_running, - [TC || {_,TC} <- Cases]}}; - Error -> - {error,Error} + case {ct_util:get_testdata(starter), + ct_util:get_testdata(release_shell)} of + {ct,ReleaseSh} when ReleaseSh /= true -> + Warning = "ct:break/1 can only be used if release_shell == true.\n", + ct_logs:log("Warning!", Warning, []), + io:format(user, "Warning! " ++ Warning, []), + {error,'enable break with release_shell option'}; + _ -> + case get_testdata(curr_tc) of + {ok,{_,TestCase}} -> + test_server:break(?MODULE, Comment); + {ok,Cases} when is_list(Cases) -> + {error,{'multiple cases running', + [TC || {_,TC} <- Cases]}}; + Error = {error,_} -> + Error; + Error -> + {error,Error} + end end. %%%----------------------------------------------------------------- %%% @spec break(TestCase, Comment) -> ok | {error,Reason} %%% TestCase = atom() %%% Comment = string() -%%% Reason = test_case_not_running +%%% Reason = 'test case not running' | +%%% 'enable break with release_shell option' %%% %%% @doc <p>This function works the same way as <c>break/1</c>, %%% only the <c>TestCase</c> argument makes it possible to @@ -1181,18 +1216,29 @@ break(Comment) -> %%% <c>continue/1</c> function should be used to resume %%% execution of <c>TestCase</c>.</p> break(TestCase, Comment) -> - case get_testdata(curr_tc) of - {ok,Cases} when is_list(Cases) -> - case lists:keymember(TestCase, 2, Cases) of - true -> + case {ct_util:get_testdata(starter), + ct_util:get_testdata(release_shell)} of + {ct,ReleaseSh} when ReleaseSh /= true -> + Warning = "ct:break/2 can only be used if release_shell == true.\n", + ct_logs:log("Warning!", Warning, []), + io:format(user, "Warning! " ++ Warning, []), + {error,'enable break with release_shell option'}; + _ -> + case get_testdata(curr_tc) of + {ok,Cases} when is_list(Cases) -> + case lists:keymember(TestCase, 2, Cases) of + true -> + test_server:break(?MODULE, TestCase, Comment); + false -> + {error,'test case not running'} + end; + {ok,{_,TestCase}} -> test_server:break(?MODULE, TestCase, Comment); - false -> - {error,test_case_not_running} - end; - {ok,{_,TestCase}} -> - test_server:break(?MODULE, TestCase, Comment); - Error -> - {error,Error} + Error = {error,_} -> + Error; + Error -> + {error,Error} + end end. %%%----------------------------------------------------------------- diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl index c585fe0995..463b7d180c 100644 --- a/lib/common_test/src/ct_config.erl +++ b/lib/common_test/src/ct_config.erl @@ -122,8 +122,8 @@ return({To,Ref},Result) -> loop(StartDir) -> receive - {{require,Name,Tag,SubTags},From} -> - Result = do_require(Name,Tag,SubTags), + {{require,Name,Key},From} -> + Result = do_require(Name,Key), return(From,Result), loop(StartDir); {{set_default_config,{Config,Scope}},From} -> @@ -168,16 +168,19 @@ reload_config(KeyOrName) -> call({reload_config, KeyOrName}). process_default_configs(Opts) -> - case lists:keysearch(config, 1, Opts) of - {value,{_,Files=[File|_]}} when is_list(File) -> - Files; - {value,{_,File=[C|_]}} when is_integer(C) -> - [File]; - {value,{_,[]}} -> - []; - false -> - [] - end. + lists:flatmap(fun({config,[_|_] = FileOrFiles}) -> + case {io_lib:printable_list(FileOrFiles), + io_lib:printable_list(hd(FileOrFiles))} of + {true,true} -> + FileOrFiles; + {true,false} -> + [FileOrFiles]; + _ -> + [] + end; + (_) -> + [] + end,Opts). process_user_configs(Opts, Acc) -> case lists:keytake(userconfig, 1, Opts) of @@ -319,75 +322,58 @@ get_config(KeyOrName,Default) -> get_config(KeyOrName,Default,[]). get_config(KeyOrName,Default,Opts) when is_atom(KeyOrName) -> - case lookup_config(KeyOrName) of - [] -> - Default; - [{_Ref,Val}|_] = Vals -> - case {lists:member(all,Opts),lists:member(element,Opts)} of - {true,true} -> - [{KeyOrName,V} || {_R,V} <- lists:sort(Vals)]; - {true,false} -> - [V || {_R,V} <- lists:sort(Vals)]; - {false,true} -> - {KeyOrName,Val}; - {false,false} -> - Val - end + case get_config({KeyOrName}, Default, Opts) of + %% If only an atom is given, then we need to unwrap the + %% key if it is returned + {{KeyOrName}, Val} -> + {KeyOrName, Val}; + [{{KeyOrName}, _Val}|_] = Res -> + [{K, Val} || {{K},Val} <- Res, K == KeyOrName]; + Else -> + Else end; -get_config({KeyOrName,SubKey},Default,Opts) -> - case lookup_config(KeyOrName) of +%% This useage of get_config is only used by internal ct functions +%% and may change at any time +get_config({DeepKey,SubKey}, Default, Opts) when is_tuple(DeepKey) -> + get_config(erlang:append_element(DeepKey, SubKey), Default, Opts); +get_config(KeyOrName,Default,Opts) when is_tuple(KeyOrName) -> + case lookup_config(element(1,KeyOrName)) of [] -> - Default; + format_value([Default],KeyOrName,Opts); Vals -> - Vals1 = case [Val || {_Ref,Val} <- lists:sort(Vals)] of - Result=[L|_] when is_list(L) -> - case L of - [{_,_}|_] -> - Result; - _ -> - [] - end; - _ -> - [] - end, - case get_subconfig([SubKey],Vals1,[],Opts) of - {ok,[{_,SubVal}|_]=SubVals} -> - case {lists:member(all,Opts),lists:member(element,Opts)} of - {true,true} -> - [{{KeyOrName,SubKey},Val} || {_,Val} <- SubVals]; - {true,false} -> - [Val || {_SubKey,Val} <- SubVals]; - {false,true} -> - {{KeyOrName,SubKey},SubVal}; - {false,false} -> - SubVal - end; - _ -> - Default - end + NewVals = + lists:map( + fun({Val}) -> + get_config(tl(tuple_to_list(KeyOrName)), + Val,Default,Opts) + end,Vals), + format_value(NewVals,KeyOrName,Opts) end. -get_subconfig(SubKeys,Values) -> - get_subconfig(SubKeys,Values,[],[]). - -get_subconfig(SubKeys,[Value|Rest],Mapped,Opts) -> - case do_get_config(SubKeys,Value,[]) of - {ok,SubMapped} -> - case lists:member(all,Opts) of - true -> - get_subconfig(SubKeys,Rest,Mapped++SubMapped,Opts); - false -> - {ok,SubMapped} - end; - _Error -> - get_subconfig(SubKeys,Rest,Mapped,Opts) +get_config([],Vals,_Default,_Opts) -> + Vals; +get_config([[]],Vals,Default,Opts) -> + get_config([],Vals,Default,Opts); +%% This case is used by {require,{unix,[port,host]}} functionality +get_config([SubKeys], Vals, Default, _Opts) when is_list(SubKeys) -> + case do_get_config(SubKeys, Vals, []) of + {ok, SubVals} -> + [SubVal || {_,SubVal} <- SubVals]; + + _ -> + Default end; -get_subconfig(SubKeys,[],[],_) -> - {error,{not_available,SubKeys}}; -get_subconfig(_SubKeys,[],Mapped,_) -> - {ok,Mapped}. +get_config([Key|Rest], Vals, Default, Opts) -> + case do_get_config([Key], Vals, []) of + {ok, [{Key,NewVals}]} -> + get_config(Rest, NewVals, Default, Opts); + _ -> + Default + end. +do_get_config([Key|_], Available, _Mapped) when not is_list(Available) -> + {error,{not_available,Key}}; do_get_config([Key|Required],Available,Mapped) -> case lists:keysearch(Key,1,Available) of {value,{Key,Value}} -> @@ -403,8 +389,7 @@ do_get_config([],_Available,Mapped) -> get_all_config() -> ets:select(?attr_table,[{#ct_conf{name='$1',key='$2',value='$3', default='$4',_='_'}, - [], - [{{'$1','$2','$3','$4'}}]}]). + [],[{{'$1','$2','$3','$4'}}]}]). lookup_config(KeyOrName) -> case lookup_name(KeyOrName) of @@ -415,13 +400,23 @@ lookup_config(KeyOrName) -> end. lookup_name(Name) -> - ets:select(?attr_table,[{#ct_conf{ref='$1',value='$2',name=Name,_='_'}, - [], - [{{'$1','$2'}}]}]). + ets:select(?attr_table,[{#ct_conf{value='$1',name=Name,_='_'}, + [],[{{'$1'}}]}]). lookup_key(Key) -> - ets:select(?attr_table,[{#ct_conf{key=Key,ref='$1',value='$2',name='_UNDEF',_='_'}, - [], - [{{'$1','$2'}}]}]). + ets:select(?attr_table,[{#ct_conf{key=Key,value='$1',name='_UNDEF',_='_'}, + [],[{{'$1'}}]}]). + +format_value([SubVal|_] = SubVals, KeyOrName, Opts) -> + case {lists:member(all,Opts),lists:member(element,Opts)} of + {true,true} -> + [{KeyOrName,Val} || Val <- SubVals]; + {true,false} -> + [Val || Val <- SubVals]; + {false,true} -> + {KeyOrName,SubVal}; + {false,false} -> + SubVal + end. lookup_handler_for_config({Key, _Subkey}) -> lookup_handler_for_config(Key); @@ -475,65 +470,78 @@ release_allocated([H|T]) -> release_allocated([]) -> ok. -allocate(Name,Key,SubKeys) -> - case ets:match_object(?attr_table,#ct_conf{key=Key,name='_UNDEF',_='_'}) of - [] -> +allocate(Name,Key) -> + Ref = make_ref(), + case get_config(Key,Ref,[all,element]) of + [{_,Ref}] -> {error,{not_available,Key}}; - Available -> - case allocate_subconfig(Name,SubKeys,Available,false) of - ok -> - ok; - Error -> - Error - end + Configs -> + associate(Name,Key,Configs), + ok end. -allocate_subconfig(Name,SubKeys,[C=#ct_conf{value=Value}|Rest],Found) -> - case do_get_config(SubKeys,Value,[]) of - {ok,_SubMapped} -> - ets:insert(?attr_table,C#ct_conf{name=Name}), - allocate_subconfig(Name,SubKeys,Rest,true); - _Error -> - allocate_subconfig(Name,SubKeys,Rest,Found) - end; -allocate_subconfig(_Name,_SubKeys,[],true) -> + +associate('_UNDEF',_Key,_Configs) -> ok; -allocate_subconfig(_Name,SubKeys,[],false) -> - {error,{not_available,SubKeys}}. +associate(Name,{Key,SubKeys},Configs) when is_atom(Key), is_list(SubKeys) -> + associate_int(Name,Configs,"true"); +associate(Name,_Key,Configs) -> + associate_int(Name,Configs,os:getenv("COMMON_TEST_ALIAS_TOP")). + +associate_int(Name,Configs,"true") -> + lists:map(fun({K,_Config}) -> + Cs = ets:match_object( + ?attr_table, + #ct_conf{key=element(1,K), + name='_UNDEF',_='_'}), + [ets:insert(?attr_table,C#ct_conf{name=Name}) + || C <- Cs] + end,Configs); +associate_int(Name,Configs,_) -> + lists:map(fun({K,Config}) -> + Key = if is_tuple(K) -> element(1,K); + is_atom(K) -> K + end, + + Cs = ets:match_object( + ?attr_table, + #ct_conf{key=Key, + name='_UNDEF',_='_'}), + [ets:insert(?attr_table,C#ct_conf{name=Name, + value=Config}) + || C <- Cs] + end,Configs). + + delete_config(Default) -> ets:match_delete(?attr_table,#ct_conf{default=Default,_='_'}), ok. -require(Key) when is_atom(Key) -> - require({Key,[]}); -require({Key,SubKeys}) when is_atom(Key) -> - allocate('_UNDEF',Key,to_list(SubKeys)); +require(Key) when is_atom(Key); is_tuple(Key) -> + allocate('_UNDEF',Key); require(Key) -> {error,{invalid,Key}}. -require(Name,Key) when is_atom(Key) -> - require(Name,{Key,[]}); -require(Name,{Key,SubKeys}) when is_atom(Name), is_atom(Key) -> - call({require,Name,Key,to_list(SubKeys)}); +require(Name,Key) when is_atom(Name),is_atom(Key) orelse is_tuple(Key) -> + call({require,Name,Key}); require(Name,Keys) -> {error,{invalid,{Name,Keys}}}. -to_list(X) when is_list(X) -> X; -to_list(X) -> [X]. - -do_require(Name,Key,SubKeys) when is_list(SubKeys) -> +do_require(Name,Key) -> case get_key_from_name(Name) of {error,_} -> - allocate(Name,Key,SubKeys); + allocate(Name,Key); {ok,Key} -> %% already allocated - check that it has all required subkeys - Vals = [Val || {_Ref,Val} <- lookup_name(Name)], - case get_subconfig(SubKeys,Vals) of - {ok,_SubMapped} -> - ok; - Error -> - Error + R = make_ref(), + case get_config(Key,R,[]) of + R -> + {error,{not_available,Key}}; + {error,_} = Error -> + Error; + _Error -> + ok end; {ok,OtherKey} -> {error,{name_in_use,Name,OtherKey}} diff --git a/lib/common_test/src/ct_conn_log_h.erl b/lib/common_test/src/ct_conn_log_h.erl index f3b6781971..bf27238121 100644 --- a/lib/common_test/src/ct_conn_log_h.erl +++ b/lib/common_test/src/ct_conn_log_h.erl @@ -84,7 +84,7 @@ handle_call(_Query, State) -> {ok, {error, bad_query}, State}. terminate(_,#state{logs=Logs}) -> - [file:close(Fd) || {_,_,Fds} <- Logs, Fd <- Fds], + [file:close(Fd) || {_,{_,Fds}} <- Logs, {_,Fd} <- Fds], ok. diff --git a/lib/common_test/src/ct_framework.erl b/lib/common_test/src/ct_framework.erl index e8d5d4708b..4d47731239 100644 --- a/lib/common_test/src/ct_framework.erl +++ b/lib/common_test/src/ct_framework.erl @@ -71,16 +71,13 @@ init_tc(Mod,Func,Config) -> {skip,{require_failed_in_suite0,Reason}}; {Suite,{suite0_failed,_}=Failure} -> {skip,Failure}; - CurrTC -> - case CurrTC of - undefined -> - ct_util:set_testdata({curr_tc,[{Suite,Func}]}); - _ when is_list(CurrTC) -> - ct_util:update_testdata(curr_tc, - fun(Running) -> - [{Suite,Func}|Running] - end) - end, + _ -> + ct_util:update_testdata(curr_tc, + fun(undefined) -> + [{Suite,Func}]; + (Running) -> + [{Suite,Func}|Running] + end, [create]), case ct_util:read_suite_data({seq,Suite,Func}) of undefined -> init_tc1(Mod,Suite,Func,Config); @@ -671,13 +668,15 @@ end_tc(Mod,Func,TCPid,Result,Args,Return) -> %% reset the curr_tc state, or delete this TC from the list of %% executing cases (if in a parallel group) ClearCurrTC = fun(Running = [_,_|_]) -> - lists:delete({Mod,Func},Running); + lists:keydelete(Func,2,Running); ({_,{suite0_failed,_}}) -> undefined; ([{_,CurrTC}]) when CurrTC == Func -> undefined; (undefined) -> - undefined + undefined; + (Unexpected) -> + exit({error,{reset_curr_tc,{Mod,Func},Unexpected}}) end, ct_util:update_testdata(curr_tc,ClearCurrTC), diff --git a/lib/common_test/src/ct_ftp.erl b/lib/common_test/src/ct_ftp.erl index 5db73066a3..723715c986 100644 --- a/lib/common_test/src/ct_ftp.erl +++ b/lib/common_test/src/ct_ftp.erl @@ -66,6 +66,7 @@ %%% {unix,[{ftp,IpAddr}, %%% {username,Username}, %%% {password,Password}]}.</pre> +%%% @see ct:require/2 put(KeyOrName,LocalFile,RemoteFile) -> Fun = fun(Ftp) -> send(Ftp,LocalFile,RemoteFile) end, open_and_do(KeyOrName,Fun). @@ -85,6 +86,7 @@ put(KeyOrName,LocalFile,RemoteFile) -> %%% %%% <p>The config file must be as for put/3.</p> %%% @see put/3 +%%% @see ct:require/2 get(KeyOrName,RemoteFile,LocalFile) -> Fun = fun(Ftp) -> recv(Ftp,RemoteFile,LocalFile) end, open_and_do(KeyOrName,Fun). @@ -105,6 +107,10 @@ get(KeyOrName,RemoteFile,LocalFile) -> %%% simply use <code>Key</code>, the configuration variable name, to %%% specify the target. Note that a connection that has no associated target %%% name can only be closed with the handle value.</p> +%%% +%%% <p>See <c>ct:require/2</c> for how to create a new <c>Name</c></p> +%%% +%%% @see ct:require/2 open(KeyOrName) -> case ct_util:get_key_from_name(KeyOrName) of {ok,node} -> diff --git a/lib/common_test/src/ct_hooks.erl b/lib/common_test/src/ct_hooks.erl index d0432b604d..1bcc63738e 100644 --- a/lib/common_test/src/ct_hooks.erl +++ b/lib/common_test/src/ct_hooks.erl @@ -48,7 +48,7 @@ %% @doc Called before any suites are started -spec init(State :: term()) -> ok | - {error, Reason :: term()}. + {fail, Reason :: term()}. init(Opts) -> call(get_new_hooks(Opts, undefined) ++ get_builtin_hooks(Opts), ok, init, []). diff --git a/lib/common_test/src/ct_netconfc.erl b/lib/common_test/src/ct_netconfc.erl index d9c4a962dc..52fe9599ce 100644 --- a/lib/common_test/src/ct_netconfc.erl +++ b/lib/common_test/src/ct_netconfc.erl @@ -408,6 +408,7 @@ open(Options) -> %% server. It is not used for any other purposes during the lifetime %% of the connection. %% +%% @see ct:require/2 %% @end %%---------------------------------------------------------------------- open(KeyOrName, ExtraOpts) -> @@ -1074,12 +1075,18 @@ handle_msg({get_event_streams=Op,Streams,Timeout}, From, State) -> handle_msg({ssh_cm, _CM, {data, _Ch, _Type, Data}}, State) -> handle_data(Data, State); -handle_msg({ssh_cm, _CM, {closed,_Ch}}, State) -> - %% This will happen if the server terminates the connection, as in - %% kill-session (or if ssh:close is called from somewhere +handle_msg({ssh_cm, _CM, _SshCloseMsg}, State) -> + %% _SshCloseMsg can probably be one of + %% {eof,Ch} + %% {exit_status,Ch,Status} + %% {exit_signal,Ch,ExitSignal,ErrorMsg,LanguageString} + %% {signal,Ch,Signal} + + %% This might e.g. happen if the server terminates the connection, + %% as in kill-session (or if ssh:close is called from somewhere %% unexpected). - %%! Log this?? - i.e. as server closing the connection + %%! Log this?? %%! Currently the log will say that the client closed the %%! connection - due to terminate/2 diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index a202ca15e2..d80d216f9e 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -70,13 +70,14 @@ enable_builtin_hooks, include = [], auto_compile, - silent_connections, + silent_connections = [], stylesheet, multiply_timetraps = 1, scale_timetraps = false, create_priv_dir, testspecs = [], - tests}). + tests, + starter}). %%%----------------------------------------------------------------- %%% @spec script_start() -> void() @@ -304,9 +305,9 @@ script_start1(Parent, Args) -> %% silent connections SilentConns = get_start_opt(silent_connections, - fun(["all"]) -> []; + fun(["all"]) -> [all]; (Conns) -> [list_to_atom(Conn) || Conn <- Conns] - end, Args), + end, [], Args), %% stylesheet Stylesheet = get_start_opt(stylesheet, fun([SS]) -> ?abs(SS) end, Args), @@ -334,7 +335,8 @@ script_start1(Parent, Args) -> stylesheet = Stylesheet, multiply_timetraps = MultTT, scale_timetraps = ScaleTT, - create_priv_dir = CreatePrivDir}, + create_priv_dir = CreatePrivDir, + starter = script}, %% check if log files should be refreshed or go on to run tests... Result = run_or_refresh(StartOpts, Args), @@ -401,6 +403,9 @@ script_start2(StartOpts = #opts{vts = undefined, AllVerbosity = merge_keyvals([StartOpts#opts.verbosity, SpecStartOpts#opts.verbosity]), + AllSilentConns = + merge_vals([StartOpts#opts.silent_connections, + SpecStartOpts#opts.silent_connections]), Cover = choose_val(StartOpts#opts.cover, SpecStartOpts#opts.cover), @@ -467,6 +472,7 @@ script_start2(StartOpts = #opts{vts = undefined, logopts = AllLogOpts, basic_html = BasicHtml, verbosity = AllVerbosity, + silent_connections = AllSilentConns, config = SpecStartOpts#opts.config, event_handlers = AllEvHs, ct_hooks = AllCTHooks, @@ -914,9 +920,9 @@ run_test2(StartOpts) -> %% silent connections SilentConns = get_start_opt(silent_connections, - fun(all) -> []; + fun(all) -> [all]; (Conns) -> Conns - end, StartOpts), + end, [], StartOpts), %% stylesheet Stylesheet = get_start_opt(stylesheet, fun(SS) -> ?abs(SS) end, @@ -999,7 +1005,8 @@ run_test2(StartOpts) -> stylesheet = Stylesheet, multiply_timetraps = MultiplyTT, scale_timetraps = ScaleTT, - create_priv_dir = CreatePrivDir}, + create_priv_dir = CreatePrivDir, + starter = ct}, %% test specification case proplists:get_value(spec, StartOpts) of @@ -1043,6 +1050,8 @@ run_spec_file(Relaxed, SpecOpts#opts.stylesheet), AllVerbosity = merge_keyvals([Opts#opts.verbosity, SpecOpts#opts.verbosity]), + AllSilentConns = merge_vals([Opts#opts.silent_connections, + SpecOpts#opts.silent_connections]), AllConfig = merge_vals([CfgFiles, SpecOpts#opts.config]), Cover = choose_val(Opts#opts.cover, SpecOpts#opts.cover), @@ -1091,6 +1100,7 @@ run_spec_file(Relaxed, stylesheet = Stylesheet, basic_html = BasicHtml, verbosity = AllVerbosity, + silent_connections = AllSilentConns, config = AllConfig, event_handlers = AllEvHs, auto_compile = AutoCompile, @@ -1354,6 +1364,7 @@ get_data_for_node(#testspec{label = Labels, basic_html = BHs, stylesheet = SSs, verbosity = VLvls, + silent_connections = SilentConnsList, cover = CoverFs, config = Cfgs, userconfig = UsrCfgs, @@ -1381,6 +1392,10 @@ get_data_for_node(#testspec{label = Labels, undefined -> []; Lvls -> Lvls end, + SilentConns = case proplists:get_value(Node, SilentConnsList) of + undefined -> []; + SCs -> SCs + end, Cover = proplists:get_value(Node, CoverFs), MT = proplists:get_value(Node, MTs), ST = proplists:get_value(Node, STs), @@ -1398,6 +1413,7 @@ get_data_for_node(#testspec{label = Labels, basic_html = BasicHtml, stylesheet = Stylesheet, verbosity = Verbosity, + silent_connections = SilentConns, cover = Cover, config = ConfigFiles, event_handlers = EvHandlers, @@ -1619,6 +1635,7 @@ do_run(Tests, Skip, Opts, Args) when is_record(Opts, opts) -> "run ct:start_interactive()\n\n",[]), {error,interactive_mode}; _Pid -> + ct_util:set_testdata({starter,Opts#opts.starter}), compile_and_run(Tests, Skip, Opts1#opts{verbosity=Verbosity}, Args) end @@ -1632,13 +1649,16 @@ compile_and_run(Tests, Skip, Opts, Args) -> %% enable silent connections case Opts#opts.silent_connections of [] -> - Conns = ct_util:override_silence_all_connections(), - ct_logs:log("Silent connections", "~p", [Conns]); - Conns when is_list(Conns) -> - ct_util:override_silence_connections(Conns), - ct_logs:log("Silent connections", "~p", [Conns]); - _ -> - ok + ok; + Conns -> + case lists:member(all, Conns) of + true -> + Conns1 = ct_util:override_silence_all_connections(), + ct_logs:log("Silent connections", "~p", [Conns1]); + false -> + ct_util:override_silence_connections(Conns), + ct_logs:log("Silent connections", "~p", [Conns]) + end end, log_ts_names(Opts#opts.testspecs), TestSuites = suite_tuples(Tests), @@ -1661,8 +1681,9 @@ compile_and_run(Tests, Skip, Opts, Args) -> {Tests1,Skip1} = final_tests(Tests,Skip,SavedErrors), - possibly_spawn(true == proplists:get_value(noinput, Args), - Tests1, Skip1, Opts); + ReleaseSh = proplists:get_value(release_shell, Args), + ct_util:set_testdata({release_shell,ReleaseSh}), + possibly_spawn(ReleaseSh == true, Tests1, Skip1, Opts); false -> io:nl(), ct_util:stop(clean), diff --git a/lib/common_test/src/ct_ssh.erl b/lib/common_test/src/ct_ssh.erl index 09cd4ef02a..d0d94e1e6e 100644 --- a/lib/common_test/src/ct_ssh.erl +++ b/lib/common_test/src/ct_ssh.erl @@ -133,10 +133,11 @@ connect(KeyOrName, ExtraOpts) when is_list(ExtraOpts) -> %%% is used to identify the connection, this name may %%% be used as connection reference for subsequent calls. %%% It's only possible to have one open connection at a time -%%% associated with <code>Name</code>. If <code>Key</code> is +%%% associated with <code>Name</code>. If <code>Key</code> is %%% used, the returned handle must be used for subsequent calls %%% (multiple connections may be opened using the config -%%% data specified by <code>Key</code>).</p> +%%% data specified by <code>Key</code>). See <c>ct:require/2</c> +%%% for how to create a new <c>Name</c></p> %%% %%% <p><code>ConnType</code> will always override the type %%% specified in the address tuple in the configuration data (and @@ -152,6 +153,8 @@ connect(KeyOrName, ExtraOpts) when is_list(ExtraOpts) -> %%% The extra options will override any existing options with the %%% same key in the config data. For details on valid SSH %%% options, see the documentation for the OTP ssh application.</p> +%%% +%%% @see ct:require/2 connect(KeyOrName, ConnType, ExtraOpts) -> case ct:get_config(KeyOrName) of undefined -> diff --git a/lib/common_test/src/ct_telnet.erl b/lib/common_test/src/ct_telnet.erl index f4a551e3ff..e37a657617 100644 --- a/lib/common_test/src/ct_telnet.erl +++ b/lib/common_test/src/ct_telnet.erl @@ -155,6 +155,8 @@ open(KeyOrName,ConnType,TargetMod) -> %%% <p><code>TargetMod</code> is a module which exports the functions %%% <code>connect(Ip,Port,KeepAlive,Extra)</code> and <code>get_prompt_regexp()</code> %%% for the given <code>TargetType</code> (e.g. <code>unix_telnet</code>).</p> +%%% +%%% @see ct:require/2 open(KeyOrName,ConnType,TargetMod,Extra) -> case ct:get_config({KeyOrName,ConnType}) of undefined -> diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl index 7759aca16b..a8b67d0329 100644 --- a/lib/common_test/src/ct_testspec.erl +++ b/lib/common_test/src/ct_testspec.erl @@ -779,8 +779,8 @@ add_tests([{event_handler,Node,HOrHs,Args}|Ts],Spec) -> add_tests([{enable_builtin_hooks,Bool}|Ts],Spec) -> add_tests(Ts, Spec#testspec{enable_builtin_hooks = Bool}); -add_tests([{noinput,Bool}|Ts],Spec) -> - add_tests(Ts, Spec#testspec{noinput = Bool}); +add_tests([{release_shell,Bool}|Ts],Spec) -> + add_tests(Ts, Spec#testspec{release_shell = Bool}); %% --- handled/errors --- add_tests([{define,_,_}|Ts],Spec) -> % handled @@ -940,6 +940,12 @@ handle_data(verbosity,Node,VLvls,_Spec) when is_list(VLvls) -> VLvls1 = lists:map(fun(VLvl = {_Cat,_Lvl}) -> VLvl; (Lvl) -> {'$unspecified',Lvl} end, VLvls), [{Node,VLvls1}]; +handle_data(silent_connections,Node,all,_Spec) -> + [{Node,[all]}]; +handle_data(silent_connections,Node,Conn,_Spec) when is_atom(Conn) -> + [{Node,[Conn]}]; +handle_data(silent_connections,Node,Conns,_Spec) -> + [{Node,Conns}]; handle_data(_Tag,Node,Data,_Spec) -> [{Node,Data}]. @@ -947,10 +953,10 @@ handle_data(_Tag,Node,Data,_Spec) -> should_be_added(Tag,Node,_Data,Spec) -> if %% list terms *without* possible duplicates here - Tag == logdir; Tag == logopts; - Tag == basic_html; Tag == label; + Tag == logdir; Tag == logopts; + Tag == basic_html; Tag == label; Tag == auto_compile; Tag == stylesheet; - Tag == verbosity -> + Tag == verbosity; Tag == silent_connections -> lists:keymember(ref2node(Node,Spec#testspec.nodes),1, read_field(Spec,Tag)) == false; %% for terms *with* possible duplicates @@ -1267,6 +1273,8 @@ valid_terms() -> {basic_html,3}, {verbosity,2}, {verbosity,3}, + {silent_connections,2}, + {silent_connections,3}, {label,2}, {label,3}, {event_handler,2}, @@ -1275,7 +1283,7 @@ valid_terms() -> {ct_hooks,2}, {ct_hooks,3}, {enable_builtin_hooks,2}, - {noinput,2}, + {release_shell,2}, {multiply_timetraps,2}, {multiply_timetraps,3}, {scale_timetraps,2}, diff --git a/lib/common_test/src/ct_util.erl b/lib/common_test/src/ct_util.erl index efc85543ac..cf891ed043 100644 --- a/lib/common_test/src/ct_util.erl +++ b/lib/common_test/src/ct_util.erl @@ -39,7 +39,7 @@ delete_suite_data/0, delete_suite_data/1, match_delete_suite_data/1, delete_testdata/0, delete_testdata/1, set_testdata/1, get_testdata/1, get_testdata/2, - set_testdata_async/1, update_testdata/2]). + set_testdata_async/1, update_testdata/2, update_testdata/3]). -export([override_silence_all_connections/0, override_silence_connections/1, get_overridden_silenced_connections/0, @@ -252,7 +252,10 @@ delete_testdata(Key) -> call({delete_testdata, Key}). update_testdata(Key, Fun) -> - call({update_testdata, Key, Fun}). + update_testdata(Key, Fun, []). + +update_testdata(Key, Fun, Opts) -> + call({update_testdata, Key, Fun, Opts}). set_testdata(TestData) -> call({set_testdata, TestData}). @@ -333,7 +336,7 @@ loop(Mode,TestData,StartDir) -> return(From,undefined) end, loop(From,TestData,StartDir); - {{update_testdata,Key,Fun},From} -> + {{update_testdata,Key,Fun,Opts},From} -> TestData1 = case lists:keysearch(Key,1,TestData) of {value,{Key,Val}} -> @@ -341,8 +344,15 @@ loop(Mode,TestData,StartDir) -> return(From,NewVal), [{Key,NewVal}|lists:keydelete(Key,1,TestData)]; _ -> - return(From,undefined), - TestData + case lists:member(create,Opts) of + true -> + InitVal = Fun(undefined), + return(From,InitVal), + [{Key,InitVal}|TestData]; + false -> + return(From,undefined), + TestData + end end, loop(From,TestData1,StartDir); {{set_cwd,Dir},From} -> diff --git a/lib/common_test/src/ct_util.hrl b/lib/common_test/src/ct_util.hrl index 7015014441..196b5e46d0 100644 --- a/lib/common_test/src/ct_util.hrl +++ b/lib/common_test/src/ct_util.hrl @@ -36,13 +36,14 @@ logopts=[], basic_html=[], verbosity=[], + silent_connections=[], cover=[], config=[], userconfig=[], event_handler=[], ct_hooks=[], enable_builtin_hooks=true, - noinput=false, + release_shell=false, include=[], auto_compile=[], stylesheet=[], diff --git a/lib/common_test/src/cth_surefire.erl b/lib/common_test/src/cth_surefire.erl index e7bd84e51b..76b0f0b5ea 100644 --- a/lib/common_test/src/cth_surefire.erl +++ b/lib/common_test/src/cth_surefire.erl @@ -92,7 +92,7 @@ on_tc_fail(_TC, Res, State) -> {fail,lists:flatten(io_lib:format("~p",[Res]))} }, State#state{ test_cases = [NewTC | tl(TCs)]}. -on_tc_skip(Tc,{Type,Reason} = Res, State) when Type == tc_auto_skip -> +on_tc_skip(Tc,{Type,_Reason} = Res, State) when Type == tc_auto_skip -> do_tc_skip(Res, end_tc(Tc,[],Res,init_tc(State,[]))); on_tc_skip(_Tc, _Res, State = #state{test_cases = []}) -> State; |