%%
This module implements the command line interface for running %%% tests and some basic functions for common test case issues %%% such as configuration and logging.
%%% %%%Test Suite Support Macros
%%% %%%The config macro is defined in ct.hrl. This
%%% macro should be used to retrieve information from the
%%% Config variable sent to all test cases. It is used with two
%%% arguments, where the first is the name of the configuration
%%% variable you wish to retrieve, and the second is the Config
%%% variable supplied to the test case.
Possible configuration variables include:
%%%data_dir - Data file directory.priv_dir - Scratch file directory.init_per_suite/1 or
%%% init_per_testcase/2 in the test suite.ct:require/2 is called,
%%% e.g. ct:require(mynodename,{node,[telnet]})
%%%
%%% @type target_name() = var_name(). The name of a target.
%%%
%%% @type handle() = ct_gen_conn:handle() | term(). The identity of a
%%% specific connection.
-module(ct).
%% Command line user interface for running tests
-export([install/1, run/1, run/2, run/3,
run_test/1, run_testspec/1, step/3,
start_interactive/0, stop_interactive/0]).
%% Test suite API
-export([require/1, require/2,
get_config/1, get_config/2,
log/1, log/2, log/3,
print/1, print/2, print/3,
pal/1, pal/2, pal/3,
fail/1, comment/1,
testcases/2, userdata/2, userdata/3]).
-export([get_status/0]).
-export([get_target_name/1]).
-export([parse_table/1, listenv/1]).
%%%-----------------------------------------------------------------
%%% @spec install(Opts) -> ok | {error,Reason}
%%% Opts = [Opt]
%%% Opt = {config,ConfigFiles} | {event_handler,Modules}
%%% ConfigFiles = [ConfigFile]
%%% ConfigFile = string()
%%% Modules = [atom()]
%%% @doc Install config files and event handlers.
%%%
%%% Run this function once before first test.
%%% %%%Example:
%%% install([{config,["config_node.ctc","config_user.ctc"]}]).
Note that this function is automatically run by the
%%% run_test script.
Requires that ct:install/1 has been run first.
Suites (*_SUITE.erl) files must be stored in
%%% TestDir or TestDir/test. All suites
%%% will be compiled when test is run.
Opts.
%%% The options are the same as those used with the run_test script.
%%% Note that here a TestDir can be used to point out the path to
%%% a Suite. Note also that the option testcase
%%% corresponds to the -case option in the run_test
%%% script. Configuration files specified in Opts will be
%%% installed automatically at startup.
run_test(Opts) ->
ct_run:run_test(Opts).
%%%-----------------------------------------------------------------
%%% @spec run_testspec(TestSpec) -> Result
%%% TestSpec = [term()]
%%% @doc Run test specified by TestSpec. The terms are
%%% the same as those used in test specification files.
run_testspec(TestSpec) ->
ct_run:run_testspec(TestSpec).
%%%-----------------------------------------------------------------
%%% @spec step(TestDir,Suite,Case) -> Result
%%% Case = atom()
%%%
%%% @doc Step through a test case with the debugger.
%%% @see run/3
step(TestDir,Suite,Case) ->
ct_run:step(TestDir,Suite,Case).
%%%-----------------------------------------------------------------
%%% @spec start_interactive() -> ok
%%%
%%% @doc Start CT in interactive mode.
%%%
%%% From this mode all test case support functions can be executed
%%% directly from the erlang shell. The interactive mode can also be
%%% started from the unix command line with run_test -shell
%%% [-config File...].
If any functions using "required config data" (e.g. telnet or
%%% ftp functions) are to be called from the erlang shell, config data
%%% must first be required with ct:require/2.
Example:
%%% > ct:require(a,{unix,[telnet]}).ok
%%% > ct:cmd(a,"ls").
%%% {ok,["ls","file1 ...",...]}
Example: require the variable myvar:
%%% ok = ct:require(myvar)
In this case the config file must at least contain:
%%%
%%% {myvar,Value}.
%%%
%%% Example: require the variable myvar with
%%% subvariable sub1:
%%% ok = ct:require({myvar,sub1})
In this case the config file must at least contain:
%%%
%%% {myvar,[{sub1,Value}]}.
%%%
%%% @see require/2
%%% @see get_config/1
%%% @see get_config/2
require(Required) ->
ct_util:require(Required).
%%%-----------------------------------------------------------------
%%% @spec require(Name,Required) -> ok | {error,Reason}
%%% Name = atom()
%%% Required = Key | {Key,SubKeys}
%%% Key = atom()
%%% SubKeys = SubKey | [SubKey]
%%% SubKey = atom()
%%%
%%% @doc Check if the required configuration is available, and give it
%%% a name.
%%%
%%% If the requested data is available, the main entry will be
%%% marked as allocated. An allocated element can only be used if the
%%% correct name is given. This means that to read the value of the
%%% element with get_config/1,2, you need to provide the
%%% Name instead of the Key.
Example: Require one node with a telnet connection and an
%%% ftp connection. Name the node a:
ok =
%%% ct:require(a,{node,[telnet,ftp]}).
All references
%%% to this node must then use the node name. E.g. you can fetch a
%%% file over ftp like this:
%%% ok = ct:ftp_get(a,RemoteFile,LocalFile).
For this to work, the config file must at least contain:
%%%
%%% {node,[{telnet,IpAddr},
%%% {ftp,IpAddr}]}.
%%%
%%% @see require/1
%%% @see get_config/1
%%% @see get_config/2
require(Name,Required) ->
ct_util:require(Name,Required).
%%%-----------------------------------------------------------------
%%% @spec get_config(Required) -> Value
%%% @equiv get_config(Required,undefined)
get_config(Required) ->
ct_util:get_config(Required).
%%%-----------------------------------------------------------------
%%% @spec get_config(Required,Default) -> Value
%%% Required = KeyOrName | {KeyOrName,SubKey}
%%% KeyOrName = atom()
%%% SubKey = atom()
%%% Default = term()
%%% Value = term() | Default
%%%
%%% @doc Get the value of config data.
%%%
%%% This function returns the value of the requested config %%% element.
%%% %%%Example, given the following config file:
%%%
%%% {unix,[{telnet,IpAddr},
%%% {username,Username},
%%% {password,Password}]}.
%%% get_config(unix,Default) ->
%%% [{telnet,IpAddr},
%%% {username,Username},
%%% {password,Password}]
%%% get_config({unix,telnet},Default) -> IpAddr
%%% get_config({unix,ftp},Default) -> Default
%%% get_config(unknownkey,Default) -> Default
If you want to access a config variable which has been given a
%%% name by require/2, the name must be used instead of
%%% the key when reading the value:
require(myhost,unix) -> ok
%%% get_config(myhost,Default) ->
%%% [{telnet,IpAddr},
%%% {username,Username},
%%% {password,Password}]
This function is meant for printing stuff directly from a %%% testcase (i.e. not from within the CT framework) in the test %%% log.
%%% %%%Default Category is default and
%%% default Args is [].
This function is meant for printing stuff from a testcase on %%% the console.
%%% %%%Default Category is default and
%%% default Args is [].
This function is meant for printing stuff from a testcase both %%% in the log and on the console.
%%% %%%Default Category is default and
%%% default Args is [].
Reason.
fail(Reason) ->
exit({test_case_failed,Reason}).
%%%-----------------------------------------------------------------
%%% @spec comment(Comment) -> void()
%%% Comment = term()
%%%
%%% @doc Print the given Comment in the comment field of
%%% the table on the test suite result page.
%%%
%%% If called several times, only the last comment is printed.
%%% comment/1 is also overwritten by the return value
%%% {comment,Comment} or by the function
%%% fail/1 (which prints Reason as a
%%% comment).
The printout to parse would typically be the result of a
%%% select command in SQL. The returned
%%% Table is a list of tuples, where each tuple is a row
%%% in the table.
Heading is a tuple of strings representing the
%%% headings of each column in the table.
userdata
%%% in the list of tuples returned from Suite:suite/0.
userdata(TestDir, Suite) ->
case make_and_load(TestDir, Suite) of
E = {error,_} ->
E;
_ ->
Info = (catch Suite:suite()),
get_userdata(Info, "suite/0")
end.
get_userdata({'EXIT',{undef,_}}, Spec) ->
{error,list_to_atom(Spec ++ " is not defined")};
get_userdata({'EXIT',Reason}, Spec) ->
{error,{list_to_atom("error in " ++ Spec),Reason}};
get_userdata(List, _) when is_list(List) ->
Fun = fun({userdata,Data}, Acc) -> [Data | Acc];
(_, Acc) -> Acc
end,
case lists:foldl(Fun, [], List) of
Terms ->
lists:flatten(lists:reverse(Terms))
end;
get_userdata(_BadTerm, Spec) ->
{error,list_to_atom(Spec ++ " must return a list")}.
%%%-----------------------------------------------------------------
%%% @spec userdata(TestDir, Suite, Case) -> TCUserData | {error,Reason}
%%% TestDir = string()
%%% Suite = atom()
%%% Case = atom()
%%% TCUserData = [term()]
%%% Reason = term()
%%%
%%% @doc Returns any data specified with the tag userdata
%%% in the list of tuples returned from Suite:Case/0.
userdata(TestDir, Suite, Case) ->
case make_and_load(TestDir, Suite) of
E = {error,_} ->
E;
_ ->
Info = (catch apply(Suite, Case, [])),
get_userdata(Info, atom_to_list(Case)++"/0")
end.
%%%-----------------------------------------------------------------
%%% @spec get_status() -> TestStatus | {error,Reason}
%%% TestDir = term()
%%% Reason = term()
%%%
%%% @doc Returns status of ongoing tests.
get_status() ->
case get_testdata(curr_tc) of
{ok,TestCase} ->
case get_testdata(stats) of
{ok,{Ok,Failed,Skipped}} ->
[{current,TestCase},
{successful,Ok},
{failed,Failed},
{skipped,Skipped},
{total,Ok+Failed+Skipped}];
Err1 -> Err1
end;
Err2 -> Err2
end.
get_testdata(Key) ->
case catch ct_util:get_testdata(Key) of
Error = {error,_Reason} ->
Error;
{'EXIT',_Reason} ->
no_tests_running;
Data ->
{ok,Data}
end.