To avoid hard-coding data values related to the test and/or System
Under Test (SUT) in the test suites, the data can instead be specified through
configuration files or strings that
A configuration file can contain any number of elements of the type:
{CfgVarName,Value}.
where
CfgVarName = atom() Value = term() | [{CfgVarName,Value}]
In a test suite, one must require that a configuration
variable (
A
To read the value of a configuration variable, use function
Example:
suite() -> [{require, domain, 'CONN_SPEC_DNS_SUFFIX'}]. ... testcase(Config) -> Domain = ct:get_config(domain), ...
If a configuration variable is defined in multiple files and you
want to access all possible values, use function
Configuration files containing sensitive data can be encrypted if they must be stored in open and shared directories.
To have
Two different methods for opening a connection using the support functions
in, for example,
When a target name is used for referencing the configuration data
(that specifies the connection to be opened), the same name can be used
as connection identity in all subsequent calls related to the connection
(also for closing it). Only one open connection per target name
is possible. If you attempt to open a new connection using a name
already associated with an open connection,
When a configuration variable name is used as reference to the data specifying the connection, the handle returned as a result of opening the connection must be used in all subsequent calls (also for closing the connection). Repeated calls to the open function with the same variable name as reference results in multiple connections being opened. This can be useful, for example, if a test case needs to open multiple connections to the same server on the target node (using the same configuration data for each connection).
The user can specify configuration data on a
different format than key-value tuples in a text file, as described
so far. The data can, for example, be read from any files, fetched from
the web over HTTP, or requested from a user-specific process.
To support this,
An example of an XML configuration file follows:
"targethost" "tester" "letmein" "/test/loadmodules" ]]>
Once read, this file produces the same configuration variables as the following text file:
{ftp_host, [{ftp,"targethost"}, {username,"tester"}, {password,"letmein"}]}. {lm_directory, "/test/loadmodules"}.
The user-specific handler can be written to handle special configuration file formats. The parameter can be either file names or configuration strings (the empty list is valid).
The callback module implementing the handler is responsible for checking the correctness of configuration strings.
To validate the configuration strings, the callback module
is to have function
The input argument is passed from
The return value is to be any of the following values, indicating if the specified configuration parameter is valid:
The function
The return value is to be either of the following:
[{ftp_host, [{ftp, "targethost"}, {username, "tester"}, {password, "letmein"}]}, {lm_directory, "/test/loadmodules"}]
A configuration file for using the FTP client to access files on a remote host can look as follows:
{ftp_host, [{ftp,"targethost"}, {username,"tester"}, {password,"letmein"}]}. {lm_directory, "/test/loadmodules"}.
The XML version shown earlier can also be used, but it is to be
explicitly specified that the
The following is an example of how to assert that the configuration data is available and can be used for an FTP session:
init_per_testcase(ftptest, Config) -> {ok,_} = ct_ftp:open(ftp), Config. end_per_testcase(ftptest, _Config) -> ct_ftp:close(ftp). ftptest() -> [{require,ftp,ftp_host}, {require,lm_directory}]. ftptest(Config) -> Remote = filename:join(ct:get_config(lm_directory), "loadmodX"), Local = filename:join(?config(priv_dir,Config), "loadmodule"), ok = ct_ftp:recv(ftp, Remote, Local), ...
The following is an example of how the functions in the previous example can be rewritten if it is necessary to open multiple connections to the FTP server:
init_per_testcase(ftptest, Config) -> {ok,Handle1} = ct_ftp:open(ftp_host), {ok,Handle2} = ct_ftp:open(ftp_host), [{ftp_handles,[Handle1,Handle2]} | Config]. end_per_testcase(ftptest, Config) -> lists:foreach(fun(Handle) -> ct_ftp:close(Handle) end, ?config(ftp_handles,Config)). ftptest() -> [{require,ftp_host}, {require,lm_directory}]. ftptest(Config) -> Remote = filename:join(ct:get_config(lm_directory), "loadmodX"), Local = filename:join(?config(priv_dir,Config), "loadmodule"), [Handle | MoreHandles] = ?config(ftp_handles,Config), ok = ct_ftp:recv(Handle, Remote, Local), ...
A simple configuration handling driver, asking an external server for configuration data, can be implemented as follows:
-module(config_driver). -export([read_config/1, check_parameter/1]). read_config(ServerName)-> ServerModule = list_to_atom(ServerName), ServerModule:start(), ServerModule:get_config(). check_parameter(ServerName)-> ServerModule = list_to_atom(ServerName), case code:is_loaded(ServerModule) of {file, _}-> {ok, {config, ServerName}}; false-> case code:load_file(ServerModule) of {module, ServerModule}-> {ok, {config, ServerName}}; {error, nofile}-> {error, {wrong_config, "File not found: " ++ ServerName ++ ".beam"}} end end.
The configuration string for this driver can be
-module(config_server). -export([start/0, stop/0, init/1, get_config/0, loop/0]). -define(REGISTERED_NAME, ct_test_config_server). start()-> case whereis(?REGISTERED_NAME) of undefined-> spawn(?MODULE, init, [?REGISTERED_NAME]), wait(); _Pid-> ok end, ?REGISTERED_NAME. init(Name)-> register(Name, self()), loop(). get_config()-> call(self(), get_config). stop()-> call(self(), stop). call(Client, Request)-> case whereis(?REGISTERED_NAME) of undefined-> {error, {not_started, Request}}; Pid-> Pid ! {Client, Request}, receive Reply-> {ok, Reply} after 4000-> {error, {timeout, Request}} end end. loop()-> receive {Pid, stop}-> Pid ! ok; {Pid, get_config}-> {D,T} = erlang:localtime(), Pid ! [{localtime, [{date, D}, {time, T}]}, {node, erlang:node()}, {now, erlang:now()}, {config_server_pid, self()}, {config_server_vsn, ?vsn}], ?MODULE:loop() end. wait()-> case whereis(?REGISTERED_NAME) of undefined-> wait(); _Pid-> ok end.
Here, the handler also provides for dynamically reloading of
configuration variables. If