%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2003-2013. 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 Main user interface for the Common Test framework. %%% %%% <p> This module implements the command line interface for running %%% tests and some basic functions for common test case issues %%% such as configuration and logging. </p> %%% %%% <p><strong>Test Suite Support Macros</strong></p> %%% %%% <p>The <c>config</c> macro is defined in <c>ct.hrl</c>. This %%% macro should be used to retrieve information from the %%% <c>Config</c> 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 <c>Config</c> %%% variable supplied to the test case.</p> %%% %%% <p>Possible configuration variables include:</p> %%% <ul> %%% <li><c>data_dir</c> - Data file directory.</li> %%% <li><c>priv_dir</c> - Scratch file directory.</li> %%% <li>Whatever added by <c>init_per_suite/1</c> or %%% <c>init_per_testcase/2</c> in the test suite.</li> %%% </ul> %%% @type var_name() = atom(). A variable name which is specified when %%% <c>ct:require/2</c> is called, %%% e.g. <c>ct:require(mynodename,{node,[telnet]})</c> %%% %%% @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). -include("ct.hrl"). -include("ct_util.hrl"). %% Command line user interface for running tests -export([install/1, run/1, run/2, run/3, run_test/1, run_testspec/1, step/3, step/4, start_interactive/0, stop_interactive/0]). %% Test suite API -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, 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, fail/1, fail/2, comment/1, comment/2, make_priv_dir/0, testcases/2, userdata/2, userdata/3, timetrap/1, get_timetrap_info/0, sleep/1, notify/2, sync_notify/2, break/1, break/2, continue/0, continue/1]). %% New API for manipulating with config handlers -export([add_config/2, remove_config/2]). %% 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]). -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} | %%% {decrypt,KeyOrFile} %%% ConfigFiles = [ConfigFile] %%% ConfigFile = string() %%% Modules = [atom()] %%% KeyOrFile = {key,Key} | {file,KeyFile} %%% Key = string() %%% KeyFile = string() %%% @doc Install config files and event handlers. %%% %%% <p>Run this function once before first test.</p> %%% %%% <p>Example:<br/> %%% <c>install([{config,["config_node.ctc","config_user.ctc"]}])</c>.</p> %%% %%% <p>Note that this function is automatically run by the %%% <c>ct_run</c> program.</p> install(Opts) -> ct_run:install(Opts). %%%----------------------------------------------------------------- %%% @spec run(TestDir,Suite,Cases) -> Result %%% TestDir = string() %%% Suite = atom() %%% Cases = atom() | [atom()] %%% Result = [TestResult] | {error,Reason} %%% %%% @doc Run the given test case(s). %%% %%% <p>Requires that <c>ct:install/1</c> has been run first.</p> %%% %%% <p>Suites (*_SUITE.erl) files must be stored in %%% <c>TestDir</c> or <c>TestDir/test</c>. All suites %%% will be compiled when test is run.</p> run(TestDir,Suite,Cases) -> ct_run:run(TestDir,Suite,Cases). %%%----------------------------------------------------------------- %%% @spec run(TestDir,Suite) -> Result %%% %%% @doc Run all test cases in the given suite. %%% @see run/3. run(TestDir,Suite) -> ct_run:run(TestDir,Suite). %%%----------------------------------------------------------------- %%% @spec run(TestDirs) -> Result %%% TestDirs = TestDir | [TestDir] %%% %%% @doc Run all test cases in all suites in the given directories. %%% @see run/3. run(TestDirs) -> ct_run:run(TestDirs). %%%----------------------------------------------------------------- %%% @spec run_test(Opts) -> Result %%% Opts = [OptTuples] %%% OptTuples = {dir,TestDirs} | {suite,Suites} | {group,Groups} | %%% {testcase,Cases} | {spec,TestSpecs} | {join_specs,Bool} | %%% {label,Label} | {config,CfgFiles} | {userconfig, UserConfig} | %%% {allow_user_terms,Bool} | {logdir,LogDir} | %%% {silent_connections,Conns} | {stylesheet,CSSFile} | %%% {cover,CoverSpecFile} | {cover_stop,Bool} | {step,StepOpts} | %%% {event_handler,EventHandlers} | {include,InclDirs} | %%% {auto_compile,Bool} | {abort_if_missing_suites,Bool} | %%% {create_priv_dir,CreatePrivDir} | %%% {multiply_timetraps,M} | {scale_timetraps,Bool} | %%% {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} %%% TestDirs = [string()] | string() %%% Suites = [string()] | [atom()] | string() | atom() %%% Cases = [atom()] | atom() %%% Groups = GroupNameOrPath | [GroupNameOrPath] %%% GroupNameOrPath = [atom()] | atom() | all %%% TestSpecs = [string()] | string() %%% Label = string() | atom() %%% CfgFiles = [string()] | string() %%% UserConfig = [{CallbackMod,CfgStrings}] | {CallbackMod,CfgStrings} %%% CallbackMod = atom() %%% CfgStrings = [string()] | string() %%% LogDir = string() %%% Conns = all | [atom()] %%% CSSFile = string() %%% CoverSpecFile = string() %%% StepOpts = [StepOpt] | [] %%% StepOpt = config | keep_inactive %%% EventHandlers = EH | [EH] %%% EH = atom() | {atom(),InitArgs} | {[atom()],InitArgs} %%% InitArgs = [term()] %%% InclDirs = [string()] | string() %%% CreatePrivDir = auto_per_run | auto_per_tc | manual_per_tc %%% M = integer() %%% N = integer() %%% DurTime = string(HHMMSS) %%% StopTime = string(YYMoMoDDHHMMSS) | string(HHMMSS) %%% ForceStop = skip_rest | Bool %%% DecryptKeyOrFile = {key,DecryptKey} | {file,DecryptFile} %%% DecryptKey = string() %%% DecryptFile = string() %%% LogOpts = [LogOpt] %%% LogOpt = no_nl | no_src %%% VLevels = VLevel | [{Category,VLevel}] %%% VLevel = integer() %%% Category = atom() %%% CTHs = [CTHModule | {CTHModule, CTHInitArgs}] %%% CTHModule = atom() %%% CTHInitArgs = term() %%% 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 <c>Opts</c>. %%% The options are the same as those used with the %%% <seealso marker="ct_run#ct_run"><c>ct_run</c></seealso> program. %%% Note that here a <c>TestDir</c> can be used to point out the path to %%% a <c>Suite</c>. Note also that the option <c>testcase</c> %%% corresponds to the <c>-case</c> option in the <c>ct_run</c> %%% program. Configuration files specified in <c>Opts</c> will be %%% installed automatically at startup.</p> %%% <p><c>TestRunnerPid</c> is returned if <c>release_shell == true</c> %%% (see <c>break/1</c> for details).</p> %%% <p><c>Reason</c> indicates what type of error has been encountered.</p> run_test(Opts) -> ct_run:run_test(Opts). %%%----------------------------------------------------------------- %%% @spec run_testspec(TestSpec) -> Result %%% TestSpec = [term()] %%% Result = {Ok,Failed,{UserSkipped,AutoSkipped}} | {error,Reason} %%% Ok = integer() %%% Failed = integer() %%% UserSkipped = integer() %%% AutoSkipped = integer() %%% Reason = term() %%% @doc Run test specified by <c>TestSpec</c>. The terms are %%% the same as those used in test specification files. %%% <p><c>Reason</c> indicates what type of error has been encountered.</p> 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 step(TestDir,Suite,Case,Opts) -> Result %%% Case = atom() %%% Opts = [Opt] | [] %%% Opt = config | keep_inactive %%% %%% @doc Step through a test case with the debugger. If the %%% <c>config</c> option has been given, breakpoints will %%% be set also on the configuration functions in <c>Suite</c>. %%% @see run/3 step(TestDir,Suite,Case,Opts) -> ct_run:step(TestDir,Suite,Case,Opts). %%%----------------------------------------------------------------- %%% @spec start_interactive() -> ok %%% %%% @doc Start CT in interactive mode. %%% %%% <p>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 OS command line with <c>ct_run -shell %%% [-config File...]</c>.</p> %%% %%% <p>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 <c>ct:require/2</c>.</p> %%% %%% <p>Example:<br/> %%% <c>> ct:require(unix_telnet, unix).</c><br/> %%% <c>ok</c><br/> %%% <c>> ct_telnet:open(unix_telnet).</c><br/> %%% <c>{ok,<0.105.0>}</c><br/> %%% <c>> ct_telnet:cmd(unix_telnet, "ls .").</c><br/> %%% <c>{ok,["ls","file1 ...",...]}</c></p> start_interactive() -> ct_util:start(interactive), ok. %%%----------------------------------------------------------------- %%% @spec stop_interactive() -> ok %%% %%% @doc Exit the interactive mode. %%% @see start_interactive/0 stop_interactive() -> ct_util:stop(normal), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% MISC INTERFACE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%----------------------------------------------------------------- %%% @spec require(Required) -> ok | {error,Reason} %%% Required = Key | {Key,SubKeys} | {Key,SubKey,SubKeys} %%% Key = atom() %%% SubKeys = SubKey | [SubKey] %%% SubKey = atom() %%% %%% @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 1: require the variable <c>myvar</c>:</p> %%% <pre>ok = ct:require(myvar).</pre> %%% %%% <p>In this case the config file must at least contain:</p> %%% <pre>{myvar,Value}.</pre> %%% %%% <p>Example 2: require the key <c>myvar</c> with %%% subkeys <c>sub1</c> and <c>sub2</c>:</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},{sub2,Value}]}.</pre> %%% %%% <p>Example 3: require the key <c>myvar</c> with %%% subkey <c>sub1</c> with <c>subsub1</c>:</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 %%% @see get_config/2 %%% @see get_config/3 require(Required) -> ct_config:require(Required). %%%----------------------------------------------------------------- %%% @spec require(Name,Required) -> ok | {error,Reason} %%% Name = atom() %%% Required = Key | {Key,SubKey} | {Key,SubKey,SubKey} %%% SubKey = Key %%% Key = atom() %%% %%% @doc Check if the required configuration is available, and give it %%% 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 sub entry will be %%% associated with <c>Name</c> so that the value of the element %%% can be read with <c>get_config/1,2</c> provided %%% <c>Name</c> instead of the whole <c>Required</c> term.</p> %%% %%% <p>Example: Require one node with a telnet connection and an %%% ftp connection. Name the node <c>a</c>: %%% <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>{machine,[{node,[{telnet,IpAddr},{ftp,IpAddr}]}]}.</pre> %%% %%% <note><p>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></p></note> %%% %%% @see require/1 %%% @see get_config/1 %%% @see get_config/2 %%% @see get_config/3 require(Name,Required) -> ct_config:require(Name,Required). %%%----------------------------------------------------------------- %%% @spec get_config(Required) -> Value %%% @equiv get_config(Required,undefined,[]) get_config(Required) -> ct_config:get_config(Required,undefined,[]). %%%----------------------------------------------------------------- %%% @spec get_config(Required,Default) -> Value %%% @equiv get_config(Required,Default,[]) get_config(Required,Default) -> ct_config:get_config(Required,Default,[]). %%%----------------------------------------------------------------- %%% @spec get_config(Required,Default,Opts) -> ValueOrElement %%% Required = KeyOrName | {KeyOrName,SubKey} | {KeyOrName,SubKey,SubKey} %%% KeyOrName = atom() %%% SubKey = atom() %%% Default = term() %%% Opts = [Opt] | [] %%% Opt = element | all %%% ValueOrElement = term() | Default %%% %%% @doc Read config data values. %%% %%% <p>This function returns the matching value(s) or config element(s), %%% given a config variable key or its associated name %%% (if one has been specified with <c>require/2</c> or a %%% require statement).</p> %%% %%% <p>Example, given the following config file:</p> %%% <pre> %%% {unix,[{telnet,IpAddr}, %%% {user,[{username,Username}, %%% {password,Password}]}]}.</pre> %%% <p><c>ct:get_config(unix,Default) -> %%% [{telnet,IpAddr}, %%% {user, [{username,Username}, %%% {password,Password}]}]</c><br/> %%% <c>ct:get_config({unix,telnet},Default) -> IpAddr</c><br/> %%% <c>ct:get_config({unix,user,username},Default) -> Username</c><br/> %%% <c>ct:get_config({unix,ftp},Default) -> Default</c><br/> %%% <c>ct:get_config(unknownkey,Default) -> Default</c></p> %%% %%% <p>If a config variable key has been associated with a name (by %%% means of <c>require/2</c> or a require statement), the name %%% may be used instead of the key to read the value:</p> %%% %%% <p><c>ct:require(myuser,{unix,user}) -> ok.</c><br/> %%% <c>ct:get_config(myuser,Default) -> %%% [{username,Username}, %%% {password,Password}]</c></p> %%% %%% <p>If a config variable is defined in multiple files and you want to %%% access all possible values, use the <c>all</c> option. The %%% values will be returned in a list and the order of the elements %%% corresponds to the order that the config files were specified at %%% startup.</p> %%% %%% <p>If you want config elements (key-value tuples) returned as result %%% instead of values, use the <c>element</c> option. %%% The returned elements will then be on the form <c>{Required,Value}</c></p> %%% %%% @see get_config/1 %%% @see get_config/2 %%% @see require/1 %%% @see require/2 get_config(Required,Default,Opts) -> ct_config:get_config(Required,Default,Opts). %%%----------------------------------------------------------------- %%% @spec reload_config(Required) -> ValueOrElement %%% Required = KeyOrName | {KeyOrName,SubKey} | {KeyOrName,SubKey,SubKey} %%% KeyOrName = atom() %%% SubKey = atom() %%% ValueOrElement = term() %%% %%% @doc Reload config file which contains specified configuration key. %%% %%% <p>This function performs updating of the configuration data from which the %%% given configuration variable was read, and returns the (possibly) new %%% value of this variable.</p> %%% <p>Note that if some variables were present in the configuration but are not loaded %%% using this function, they will be removed from the configuration table together %%% with their aliases.</p> %%% 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 log(Format) -> ok %%% @equiv log(default,50,Format,[]) log(Format) -> log(default,?STD_IMPORTANCE,Format,[]). %%%----------------------------------------------------------------- %%% @spec log(X1,X2) -> ok %%% X1 = Category | Importance | Format %%% X2 = 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). %%%----------------------------------------------------------------- %%% @spec log(X1,X2,X3) -> 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} end, log(Category,Importance,Format,Args). %%%----------------------------------------------------------------- %%% @spec log(Category,Importance,Format,Args) -> ok %%% Category = atom() %%% Importance = integer() %%% Format = string() %%% Args = list() %%% %%% @doc Printout from a test case to the log file. %%% %%% <p>This function is meant for printing a string directly from a %%% test case to the test case log file.</p> %%% %%% <p>Default <c>Category</c> is <c>default</c>, %%% default <c>Importance</c> is <c>?STD_IMPORTANCE</c>, %%% 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). %%%----------------------------------------------------------------- %%% @spec print(Format) -> ok %%% @equiv print(default,50,Format,[]) print(Format) -> print(default,?STD_IMPORTANCE,Format,[]). %%%----------------------------------------------------------------- %%% @spec print(X1,X2) -> ok %%% X1 = Category | Importance | Format %%% X2 = Format | Args %%% @equiv print(Category,Importance,Format,Args) print(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, print(Category,Importance,Format,Args). %%%----------------------------------------------------------------- %%% @spec print(X1,X2,X3) -> ok %%% X1 = Category | Importance %%% X2 = Importance | Format %%% X3 = Format | Args %%% @equiv print(Category,Importance,Format,Args) print(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} end, print(Category,Importance,Format,Args). %%%----------------------------------------------------------------- %%% @spec print(Category,Importance,Format,Args) -> ok %%% Category = atom() %%% Importance = integer() %%% Format = string() %%% Args = list() %%% %%% @doc Printout from a test case to the console. %%% %%% <p>This function is meant for printing a string from a test case %%% to the console.</p> %%% %%% <p>Default <c>Category</c> is <c>default</c>, %%% default <c>Importance</c> is <c>?STD_IMPORTANCE</c>, %%% 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> print(Category,Importance,Format,Args) -> ct_logs:tc_print(Category,Importance,Format,Args). %%%----------------------------------------------------------------- %%% @spec pal(Format) -> ok %%% @equiv pal(default,50,Format,[]) pal(Format) -> pal(default,?STD_IMPORTANCE,Format,[]). %%%----------------------------------------------------------------- %%% @spec pal(X1,X2) -> ok %%% X1 = Category | Importance | Format %%% X2 = Format | Args %%% @equiv pal(Category,Importance,Format,Args) pal(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, pal(Category,Importance,Format,Args). %%%----------------------------------------------------------------- %%% @spec pal(X1,X2,X3) -> ok %%% X1 = Category | Importance %%% X2 = Importance | Format %%% X3 = Format | Args %%% @equiv pal(Category,Importance,Format,Args) pal(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} end, pal(Category,Importance,Format,Args). %%%----------------------------------------------------------------- %%% @spec pal(Category,Importance,Format,Args) -> ok %%% Category = atom() %%% Importance = integer() %%% Format = string() %%% Args = list() %%% %%% @doc Print and log from a test case. %%% %%% <p>This function is meant for printing a string from a test case, %%% both to the test case log file and to the console.</p> %%% %%% <p>Default <c>Category</c> is <c>default</c>, %%% default <c>Importance</c> is <c>?STD_IMPORTANCE</c>, %%% 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> pal(Category,Importance,Format,Args) -> ct_logs:tc_pal(Category,Importance,Format,Args). %%%----------------------------------------------------------------- %%% @spec capture_start() -> ok %%% %%% @doc Start capturing all text strings printed to stdout during %%% execution of the test case. %%% %%% @see capture_stop/0 %%% @see capture_get/1 capture_start() -> test_server:capture_start(). %%%----------------------------------------------------------------- %%% @spec capture_stop() -> ok %%% %%% @doc Stop capturing text strings (a session started with %%% <c>capture_start/0</c>). %%% %%% @see capture_start/0 %%% @see capture_get/1 capture_stop() -> test_server:capture_stop(). %%%----------------------------------------------------------------- %%% @spec capture_get() -> ListOfStrings %%% ListOfStrings = [string()] %%% %%% @equiv capture_get([default]) capture_get() -> %% remove default log printouts (e.g. ct:log/2 printouts) capture_get([default]). %%%----------------------------------------------------------------- %%% @spec capture_get(ExclCategories) -> ListOfStrings %%% ExclCategories = [atom()] %%% ListOfStrings = [string()] %%% %%% @doc Return and purge the list of text strings buffered %%% during the latest session of capturing printouts to stdout. %%% With <c>ExclCategories</c> it's possible to specify %%% log categories that should be ignored in <c>ListOfStrings</c>. %%% If <c>ExclCategories = []</c>, no filtering takes place. %%% %%% @see capture_start/0 %%% @see capture_stop/0 %%% @see log/3 capture_get([ExclCat | ExclCategories]) -> Strs = test_server:capture_get(), CatsStr = [atom_to_list(ExclCat) | [[$| | atom_to_list(EC)] || EC <- ExclCategories]], {ok,MP} = re:compile("<div class=\"(" ++ lists:flatten(CatsStr) ++ ")\">.*"), lists:flatmap(fun(Str) -> case re:run(Str, MP) of {match,_} -> []; nomatch -> [Str] end end, Strs); capture_get([]) -> test_server:capture_get(). %%%----------------------------------------------------------------- %%% @spec fail(Reason) -> ok %%% Reason = term() %%% %%% @doc Terminate a test case with the given error %%% <c>Reason</c>. fail(Reason) -> try exit({test_case_failed,Reason}) catch Class:R -> case erlang:get_stacktrace() of [{?MODULE,fail,1,_}|Stk] -> ok; Stk -> ok end, erlang:raise(Class, R, Stk) end. %%%----------------------------------------------------------------- %%% @spec fail(Format, Args) -> ok %%% Format = string() %%% Args = list() %%% %%% @doc Terminate a test case with an error message specified %%% by a format string and a list of values (used as arguments to %%% <c>io_lib:format/2</c>). fail(Format, Args) -> try io_lib:format(Format, Args) of Str -> try exit({test_case_failed,lists:flatten(Str)}) catch Class:R -> case erlang:get_stacktrace() of [{?MODULE,fail,2,_}|Stk] -> ok; Stk -> ok end, erlang:raise(Class, R, Stk) end catch _:BadArgs -> exit({BadArgs,{?MODULE,fail,[Format,Args]}}) end. %%%----------------------------------------------------------------- %%% @spec comment(Comment) -> ok %%% Comment = term() %%% %%% @doc Print the given <c>Comment</c> in the comment field in %%% the table on the test suite result page. %%% %%% <p>If called several times, only the last comment is printed. %%% The test case return value <c>{comment,Comment}</c> %%% overwrites the string set by this function.</p> comment(Comment) when is_list(Comment) -> Formatted = case (catch io_lib:format("~ts",[Comment])) of {'EXIT',_} -> % it's a list not a string io_lib:format("~p",[Comment]); String -> String end, send_html_comment(lists:flatten(Formatted)); comment(Comment) -> Formatted = io_lib:format("~p",[Comment]), send_html_comment(lists:flatten(Formatted)). %%%----------------------------------------------------------------- %%% @spec comment(Format, Args) -> ok %%% Format = string() %%% Args = list() %%% %%% @doc Print the formatted string in the comment field in %%% the table on the test suite result page. %%% %%% <p>The <c>Format</c> and <c>Args</c> arguments are %%% used in call to <c>io_lib:format/2</c> in order to create %%% the comment string. The behaviour of <c>comment/2</c> is %%% otherwise the same as the <c>comment/1</c> function (see %%% above for details).</p> comment(Format, Args) when is_list(Format), is_list(Args) -> Formatted = case (catch io_lib:format(Format, Args)) of {'EXIT',Reason} -> % bad args exit({Reason,{?MODULE,comment,[Format,Args]}}); String -> lists:flatten(String) end, send_html_comment(Formatted). send_html_comment(Comment) -> Html = "<font color=\"green\">" ++ Comment ++ "</font>", ct_util:set_testdata({{comment,group_leader()},Html}), test_server:comment(Html). %%%----------------------------------------------------------------- %%% @spec make_priv_dir() -> ok | {error,Reason} %%% Reason = term() %%% @doc If the test has been started with the create_priv_dir %%% option set to manual_per_tc, in order for the test case to use %%% the private directory, it must first create it by calling %%% this function. make_priv_dir() -> test_server:make_priv_dir(). %%%----------------------------------------------------------------- %%% @spec get_target_name(Handle) -> {ok,TargetName} | {error,Reason} %%% Handle = handle() %%% TargetName = target_name() %%% @doc Return the name of the target that the given connection %%% belongs to. get_target_name(Handle) -> ct_util:get_target_name(Handle). %%%----------------------------------------------------------------- %%% @spec parse_table(Data) -> {Heading,Table} %%% Data = [string()] %%% Heading = tuple() %%% Table = [tuple()] %%% %%% @doc Parse the printout from an SQL table and return a list of tuples. %%% %%% <p>The printout to parse would typically be the result of a %%% <c>select</c> command in SQL. The returned %%% <c>Table</c> is a list of tuples, where each tuple is a row %%% in the table.</p> %%% %%% <p><c>Heading</c> is a tuple of strings representing the %%% headings of each column in the table.</p> parse_table(Data) -> ct_util:parse_table(Data). %%%----------------------------------------------------------------- %%% @spec listenv(Telnet) -> [Env] %%% Telnet = term() %%% Env = {Key,Value} %%% Key = string() %%% Value = string() %%% %%% @doc Performs the listenv command on the given telnet connection %%% and returns the result as a list of Key-Value pairs. listenv(Telnet) -> ct_util:listenv(Telnet). %%%----------------------------------------------------------------- %%% @spec testcases(TestDir, Suite) -> Testcases | {error,Reason} %%% TestDir = string() %%% Suite = atom() %%% Testcases = list() %%% Reason = term() %%% %%% @doc Returns all test cases in the specified suite. testcases(TestDir, Suite) -> case make_and_load(TestDir, Suite) of E = {error,_} -> E; _ -> case (catch Suite:all()) of {'EXIT',Reason} -> {error,Reason}; TCs -> TCs end end. make_and_load(Dir, Suite) -> EnvInclude = case os:getenv("CT_INCLUDE_PATH") of false -> []; CtInclPath -> string:tokens(CtInclPath, [$:,$ ,$,]) end, StartInclude = case init:get_argument(include) of {ok,[Dirs]} -> Dirs; _ -> [] end, UserInclude = EnvInclude ++ StartInclude, case ct_run:run_make(Dir, Suite, UserInclude) of MErr = {error,_} -> MErr; _ -> TestDir = ct_util:get_testdir(Dir, Suite), File = filename:join(TestDir, atom_to_list(Suite)), case code:soft_purge(Suite) of true -> code:load_abs(File); false -> % will use loaded {module,Suite} end end. %%%----------------------------------------------------------------- %%% @spec userdata(TestDir, Suite) -> SuiteUserData | {error,Reason} %%% TestDir = string() %%% Suite = atom() %%% SuiteUserData = [term()] %%% Reason = term() %%% %%% @doc Returns any data specified with the tag <c>userdata</c> %%% in the list of tuples returned from <c>Suite:suite/0</c>. 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) when Undef == undef; Undef == function_clause -> {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, GroupOrCase) -> TCUserData | {error,Reason} %%% TestDir = string() %%% Suite = atom() %%% GroupOrCase = {group,GroupName} | atom() %%% GroupName = atom() %%% TCUserData = [term()] %%% Reason = term() %%% %%% @doc Returns any data specified with the tag <c>userdata</c> %%% in the list of tuples returned from <c>Suite:group(GroupName)</c> %%% or <c>Suite:Case()</c>. userdata(TestDir, Suite, {group,GroupName}) -> case make_and_load(TestDir, Suite) of E = {error,_} -> E; _ -> Info = (catch apply(Suite, group, [GroupName])), get_userdata(Info, "group("++atom_to_list(GroupName)++")") end; userdata(TestDir, Suite, Case) when is_atom(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} | no_tests_running %%% TestStatus = [StatusElem] %%% StatusElem = {current,TestCaseInfo} | {successful,Successful} | %%% {failed,Failed} | {skipped,Skipped} | {total,Total} %%% TestCaseInfo = {Suite,TestCase} | [{Suite,TestCase}] %%% Suite = atom() %%% TestCase = atom() %%% Successful = integer() %%% Failed = integer() %%% Skipped = {UserSkipped,AutoSkipped} %%% UserSkipped = integer() %%% AutoSkipped = integer() %%% Total = integer() %%% Reason = term() %%% %%% @doc Returns status of ongoing test. The returned list contains info about %%% which test case is currently executing (a list of cases when a %%% parallel test case group is executing), as well as counters for %%% successful, failed, skipped, and total test cases so far. get_status() -> case get_testdata(curr_tc) of {ok,TestCase} -> case get_testdata(stats) of {ok,{Ok,Failed,Skipped={UserSkipped,AutoSkipped}}} -> [{current,TestCase}, {successful,Ok}, {failed,Failed}, {skipped,Skipped}, {total,Ok+Failed+UserSkipped+AutoSkipped}]; Err1 -> Err1 end; Err2 -> Err2 end. get_testdata(Key) -> case catch ct_util:get_testdata(Key) of {error,ct_util_server_not_running} -> no_tests_running; Error = {error,_Reason} -> Error; {'EXIT',_Reason} -> no_tests_running; undefined -> {error,no_testdata}; [CurrTC] when Key == curr_tc -> {ok,CurrTC}; Data -> {ok,Data} end. %%%----------------------------------------------------------------- %%% @spec abort_current_testcase(Reason) -> ok | {error,ErrorReason} %%% Reason = term() %%% ErrorReason = no_testcase_running | parallel_group %%% %%% @doc <p>When calling this function, the currently executing test case will be aborted. %%% It is the user's responsibility to know for sure which test case is currently %%% executing. The function is therefore only safe to call from a function which %%% has been called (or synchronously invoked) by the test case.</p> %%% %%% <p><c>Reason</c>, the reason for aborting the test case, is printed %%% in the test case log.</p> abort_current_testcase(Reason) -> test_server_ctrl:abort_current_testcase(Reason). %%%----------------------------------------------------------------- %%% @spec get_event_mgr_ref() -> EvMgrRef %%% EvMgrRef = atom() %%% %%% @doc <p>Call this function in order to get a reference to the %%% CT event manager. The reference can be used to e.g. add %%% a user specific event handler while tests are running. %%% Example: %%% <c>gen_event:add_handler(ct:get_event_mgr_ref(), my_ev_h, [])</c></p> get_event_mgr_ref() -> ?CT_EVMGR_REF. %%%----------------------------------------------------------------- %%% @spec encrypt_config_file(SrcFileName, EncryptFileName) -> %%% ok | {error,Reason} %%% SrcFileName = string() %%% EncryptFileName = string() %%% Reason = term() %%% %%% @doc <p>This function encrypts the source config file with DES3 and %%% saves the result in file <c>EncryptFileName</c>. The key, %%% a string, must be available in a text file named %%% <c>.ct_config.crypt</c> in the current directory, or the %%% home directory of the user (it is searched for in that order).</p> %%% <p>See the Common Test User's Guide for information about using %%% encrypted config files when running tests.</p> %%% <p>See the <c>crypto</c> application for details on DES3 %%% encryption/decryption.</p> encrypt_config_file(SrcFileName, EncryptFileName) -> ct_config:encrypt_config_file(SrcFileName, EncryptFileName). %%%----------------------------------------------------------------- %%% @spec encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile) -> %%% ok | {error,Reason} %%% SrcFileName = string() %%% EncryptFileName = string() %%% KeyOrFile = {key,string()} | {file,string()} %%% Reason = term() %%% %%% @doc <p>This function encrypts the source config file with DES3 and %%% saves the result in the target file <c>EncryptFileName</c>. %%% The encryption key to use is either the value in %%% <c>{key,Key}</c> or the value stored in the file specified %%% by <c>{file,File}</c>.</p> %%% <p>See the Common Test User's Guide for information about using %%% encrypted config files when running tests.</p> %%% <p>See the <c>crypto</c> application for details on DES3 %%% encryption/decryption.</p> encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile) -> ct_config:encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile). %%%----------------------------------------------------------------- %%% @spec decrypt_config_file(EncryptFileName, TargetFileName) -> %%% ok | {error,Reason} %%% EncryptFileName = string() %%% TargetFileName = string() %%% Reason = term() %%% %%% @doc <p>This function decrypts <c>EncryptFileName</c>, previously %%% generated with <c>encrypt_config_file/2/3</c>. The original %%% file contents is saved in the target file. The encryption key, a %%% string, must be available in a text file named %%% <c>.ct_config.crypt</c> in the current directory, or the %%% home directory of the user (it is searched for in that order).</p> decrypt_config_file(EncryptFileName, TargetFileName) -> ct_config:decrypt_config_file(EncryptFileName, TargetFileName). %%%----------------------------------------------------------------- %%% @spec decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile) -> %%% ok | {error,Reason} %%% EncryptFileName = string() %%% TargetFileName = string() %%% KeyOrFile = {key,string()} | {file,string()} %%% Reason = term() %%% %%% @doc <p>This function decrypts <c>EncryptFileName</c>, previously %%% generated with <c>encrypt_config_file/2/3</c>. The original %%% file contents is saved in the target file. The key must have the %%% the same value as that used for encryption.</p> decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile) -> ct_config:decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile). %%%----------------------------------------------------------------- %%% @spec add_config(Callback, Config) -> ok | {error, Reason} %%% Callback = atom() %%% Config = string() %%% Reason = term() %%% %%% @doc <p>This function loads configuration variables using the %%% given callback module and configuration string. Callback module %%% should be either loaded or present in the code part. Loaded %%% configuration variables can later be removed using %%% <c>remove_config/2</c> function.</p> add_config(Callback, Config)-> ct_config:add_config(Callback, Config). %%%----------------------------------------------------------------- %%% @spec remove_config(Callback, Config) -> ok %%% Callback = atom() %%% Config = string() %%% Reason = term() %%% %%% @doc <p>This function removes configuration variables (together with %%% their aliases) which were loaded with specified callback module and %%% configuration string.</p> remove_config(Callback, Config) -> ct_config:remove_config(Callback, Config). %%%----------------------------------------------------------------- %%% @spec timetrap(Time) -> ok %%% Time = {hours,Hours} | {minutes,Mins} | {seconds,Secs} | Millisecs | infinity | Func %%% Hours = integer() %%% Mins = integer() %%% Secs = integer() %%% Millisecs = integer() | float() %%% Func = {M,F,A} | fun() %%% M = atom() %%% F = atom() %%% A = list() %%% %%% @doc <p>Use this function to set a new timetrap for the running test case. %%% If the argument is <c>Func</c>, the timetrap will be triggered %%% when this function returns. <c>Func</c> may also return a new %%% <c>Time</c> value, which in that case will be the value for the %%% new timetrap.</p> timetrap(Time) -> test_server:timetrap_cancel(), test_server:timetrap(Time). %%%----------------------------------------------------------------- %%% @spec get_timetrap_info() -> {Time,Scale} %%% Time = integer() | infinity %%% Scale = true | false %%% %%% @doc <p>Read info about the timetrap set for the current test case. %%% <c>Scale</c> indicates if Common Test will attempt to automatically %%% compensate timetraps for runtime delays introduced by e.g. tools like %%% cover.</p> get_timetrap_info() -> test_server:get_timetrap_info(). %%%----------------------------------------------------------------- %%% @spec sleep(Time) -> ok %%% Time = {hours,Hours} | {minutes,Mins} | {seconds,Secs} | Millisecs | infinity %%% Hours = integer() %%% Mins = integer() %%% Secs = integer() %%% Millisecs = integer() | float() %%% %%% @doc <p>This function, similar to <c>timer:sleep/1</c>, suspends the test %%% case for specified time. However, this function also multiplies %%% <c>Time</c> with the 'multiply_timetraps' value (if set) and under %%% certain circumstances also scales up the time automatically %%% if 'scale_timetraps' is set to true (default is false).</p> sleep({hours,Hs}) -> sleep(trunc(Hs * 1000 * 60 * 60)); sleep({minutes,Ms}) -> sleep(trunc(Ms * 1000 * 60)); sleep({seconds,Ss}) -> sleep(trunc(Ss * 1000)); sleep(Time) -> test_server:adjusted_sleep(Time). %%%----------------------------------------------------------------- %%% @spec notify(Name,Data) -> ok %%% Name = atom() %%% Data = term() %%% %%% @doc <p>Sends a asynchronous notification of type <c>Name</c> with %%% <c>Data</c>to the common_test event manager. This can later be %%% caught by any installed event manager. </p> %%% @see //stdlib/gen_event notify(Name,Data) -> ct_event:notify(Name, Data). %%%----------------------------------------------------------------- %%% @spec sync_notify(Name,Data) -> ok %%% Name = atom() %%% Data = term() %%% %%% @doc <p>Sends a synchronous notification of type <c>Name</c> with %%% <c>Data</c>to the common_test event manager. This can later be %%% caught by any installed event manager. </p> %%% @see //stdlib/gen_event sync_notify(Name,Data) -> ct_event:sync_notify(Name, Data). %%%----------------------------------------------------------------- %%% @spec break(Comment) -> ok | {error,Reason} %%% Comment = string() %%% Reason = {multiple_cases_running,TestCases} | %%% 'enable break with release_shell option' %%% TestCases = [atom()] %%% %%% @doc <p>This function will cancel any active timetrap and pause the %%% execution of the current test case until the user calls the %%% <c>continue/0</c> function. It gives the user the opportunity %%% to interact with the erlang node running the tests, e.g. for %%% debugging purposes or for manually executing a part of the %%% test case. If a parallel group is executing, <c>break/2</c> %%% should be called instead.</p> %%% <p>A cancelled timetrap will not be automatically %%% reactivated after the break, but must be started exlicitly with %%% <c>ct:timetrap/1</c></p> %%% <p>In order for the break/continue functionality to work, %%% Common Test must release the shell process controlling stdin. %%% This is done by setting the <c>release_shell</c> start option %%% to <c>true</c>. See the User's Guide for more information.</p> break(Comment) -> 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' | %%% '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 %%% pause a test case executing in a parallel group. The %%% <c>continue/1</c> function should be used to resume %%% execution of <c>TestCase</c>.</p> %%% <p>See <c>break/1</c> for more details.</p> break(TestCase, Comment) -> 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); Error = {error,_} -> Error; Error -> {error,Error} end end. %%%----------------------------------------------------------------- %%% @spec continue() -> ok %%% %%% @doc <p>This function must be called in order to continue after a %%% test case (not executing in a parallel group) has called %%% <c>break/1</c>.</p> continue() -> test_server:continue(). %%%----------------------------------------------------------------- %%% @spec continue(TestCase) -> ok %%% TestCase = atom() %%% %%% @doc <p>This function must be called in order to continue after a %%% test case has called <c>break/2</c>. If the paused test case, %%% <c>TestCase</c>, executes in a parallel group, this %%% function - rather than <c>continue/0</c> - must be used %%% in order to let the test case proceed.</p> continue(TestCase) -> test_server:continue(TestCase).