aboutsummaryrefslogtreecommitdiffstats
path: root/lib/common_test/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/common_test/src')
-rw-r--r--lib/common_test/src/ct.erl97
-rw-r--r--lib/common_test/src/ct_config.erl250
-rw-r--r--lib/common_test/src/ct_ftp.erl6
-rw-r--r--lib/common_test/src/ct_netconfc.erl1
-rw-r--r--lib/common_test/src/ct_ssh.erl5
-rw-r--r--lib/common_test/src/ct_telnet.erl2
6 files changed, 196 insertions, 165 deletions
diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl
index 6373634812..922f794395 100644
--- a/lib/common_test/src/ct.erl
+++ b/lib/common_test/src/ct.erl
@@ -266,27 +266,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
@@ -298,30 +305,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
@@ -344,7 +357,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()
@@ -362,25 +375,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
@@ -390,9 +403,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
@@ -403,7 +414,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()
diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl
index 9277af5bc1..5b7ed0f7fb 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_ftp.erl b/lib/common_test/src/ct_ftp.erl
index 5db73066a3..d1d511f137 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/3
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/3
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/3</c> for how to create a new <c>Name</c></p>
+%%%
+%%% @see ct:require/3
open(KeyOrName) ->
case ct_util:get_key_from_name(KeyOrName) of
{ok,node} ->
diff --git a/lib/common_test/src/ct_netconfc.erl b/lib/common_test/src/ct_netconfc.erl
index d9c4a962dc..8d2e25b946 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/3
%% @end
%%----------------------------------------------------------------------
open(KeyOrName, ExtraOpts) ->
diff --git a/lib/common_test/src/ct_ssh.erl b/lib/common_test/src/ct_ssh.erl
index aebb28bc42..21553f5551 100644
--- a/lib/common_test/src/ct_ssh.erl
+++ b/lib/common_test/src/ct_ssh.erl
@@ -136,7 +136,8 @@ connect(KeyOrName, ExtraOpts) when is_list(ExtraOpts) ->
%%% 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/3</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/3
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 ->