diff options
Diffstat (limited to 'lib/common_test/src/ct.erl')
-rw-r--r-- | lib/common_test/src/ct.erl | 759 |
1 files changed, 576 insertions, 183 deletions
diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl index f3c2029734..ad9bf4e2d6 100644 --- a/lib/common_test/src/ct.erl +++ b/lib/common_test/src/ct.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2011. All Rights Reserved. +%% Copyright Ericsson AB 2003-2012. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -25,24 +25,24 @@ %%% %%% <p><strong>Test Suite Support Macros</strong></p> %%% -%%% <p>The <code>config</code> macro is defined in <code>ct.hrl</code>. This +%%% <p>The <c>config</c> macro is defined in <c>ct.hrl</c>. This %%% macro should be used to retrieve information from the -%%% <code>Config</code> variable sent to all test cases. It is used with two +%%% <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 <code>Config</code> +%%% 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><code>data_dir</code> - Data file directory.</li> -%%% <li><code>priv_dir</code> - Scratch file directory.</li> -%%% <li>Whatever added by <code>init_per_suite/1</code> or -%%% <code>init_per_testcase/2</code> in the test suite.</li> +%%% <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 -%%% <code>ct:require/2</code> is called, -%%% e.g. <code>ct:require(mynodename,{node,[telnet]})</code> +%%% <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. %%% @@ -51,6 +51,8 @@ -module(ct). +-include("ct.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, @@ -60,12 +62,15 @@ -export([require/1, require/2, get_config/1, get_config/2, get_config/3, reload_config/1, - log/1, log/2, log/3, - print/1, print/2, print/3, - pal/1, pal/2, pal/3, - fail/1, comment/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, sleep/1]). + 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]). @@ -94,10 +99,10 @@ %%% <p>Run this function once before first test.</p> %%% %%% <p>Example:<br/> -%%% <code>install([{config,["config_node.ctc","config_user.ctc"]}])</code>.</p> +%%% <c>install([{config,["config_node.ctc","config_user.ctc"]}])</c>.</p> %%% %%% <p>Note that this function is automatically run by the -%%% <code>ct_run</code> program.</p> +%%% <c>ct_run</c> program.</p> install(Opts) -> ct_run:install(Opts). @@ -108,12 +113,12 @@ install(Opts) -> %%% Cases = atom() | [atom()] %%% Result = [TestResult] | {error,Reason} %%% -%%% @doc Run the given testcase(s). +%%% @doc Run the given test case(s). %%% -%%% <p>Requires that <code>ct:install/1</code> has been run first.</p> +%%% <p>Requires that <c>ct:install/1</c> has been run first.</p> %%% %%% <p>Suites (*_SUITE.erl) files must be stored in -%%% <code>TestDir</code> or <code>TestDir/test</code>. All suites +%%% <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). @@ -121,7 +126,7 @@ run(TestDir,Suite,Cases) -> %%%----------------------------------------------------------------- %%% @spec run(TestDir,Suite) -> Result %%% -%%% @doc Run all testcases in the given suite. +%%% @doc Run all test cases in the given suite. %%% @see run/3. run(TestDir,Suite) -> ct_run:run(TestDir,Suite). @@ -130,7 +135,7 @@ run(TestDir,Suite) -> %%% @spec run(TestDirs) -> Result %%% TestDirs = TestDir | [TestDir] %%% -%%% @doc Run all testcases in all suites in the given directories. +%%% @doc Run all test cases in all suites in the given directories. %%% @see run/3. run(TestDirs) -> ct_run:run(TestDirs). @@ -145,15 +150,19 @@ run(TestDirs) -> %%% {silent_connections,Conns} | {stylesheet,CSSFile} | %%% {cover,CoverSpecFile} | {step,StepOpts} | %%% {event_handler,EventHandlers} | {include,InclDirs} | -%%% {auto_compile,Bool} | {multiply_timetraps,M} | {scale_timetraps,Bool} | +%%% {auto_compile,Bool} | {create_priv_dir,CreatePrivDir} | +%%% {multiply_timetraps,M} | {scale_timetraps,Bool} | %%% {repeat,N} | {duration,DurTime} | {until,StopTime} | %%% {force_stop,Bool} | {decrypt,DecryptKeyOrFile} | -%%% {refresh_logs,LogDir} | {logopts,LogOpts} | {basic_html,Bool} | -%%% {ct_hooks, CTHs} +%%% {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 = [atom()] | atom() +%%% Groups = GroupNameOrPath | [GroupNameOrPath] +%%% GroupNameOrPath = [atom()] | atom() | all %%% TestSpecs = [string()] | string() %%% Label = string() | atom() %%% CfgFiles = [string()] | string() @@ -170,6 +179,7 @@ run(TestDirs) -> %%% 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) @@ -179,26 +189,45 @@ run(TestDirs) -> %%% 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 = [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 <c>Opts</c>. %%% 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. +%%% <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()] -%%% @doc Run test specified by <code>TestSpec</code>. The terms are +%%% 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). @@ -218,8 +247,8 @@ step(TestDir,Suite,Case) -> %%% Opt = config | keep_inactive %%% %%% @doc Step through a test case with the debugger. If the -%%% <code>config</code> option has been given, breakpoints will -%%% be set also on the configuration functions in <code>Suite</code>. +%%% <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). @@ -231,22 +260,23 @@ step(TestDir,Suite,Case,Opts) -> %%% %%% <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 <code>ct_run -shell -%%% [-config File...]</code>.</p> +%%% 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 <code>ct:require/2</code>.</p> +%%% must first be required with <c>ct:require/2</c>.</p> %%% %%% <p>Example:<br/> -%%% <code>> ct:require(unix_telnet, unix).</code><br/> -%%% <code>ok</code><br/> -%%% <code>> ct_telnet:open(unix_telnet).</code><br/> -%%% <code>{ok,<0.105.0>}</code><br/> -%%% <code>> ct_telnet:cmd(unix_telnet, "ls .").</code><br/> -%%% <code>{ok,["ls","file1 ...",...]}</code></p> +%%% <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). + ct_util:start(interactive), + ok. %%%----------------------------------------------------------------- %%% @spec stop_interactive() -> ok @@ -254,7 +284,8 @@ start_interactive() -> %%% @doc Exit the interactive mode. %%% @see start_interactive/0 stop_interactive() -> - ct_util:stop(normal). + ct_util:stop(normal), + ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% MISC INTERFACE @@ -262,27 +293,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 <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> +%%% <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 <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}]}.</pre> +%%% <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 @@ -294,30 +332,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 -%%% 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> +%%% <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 <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 <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> -%%% {node,[{telnet,IpAddr}, -%%% {ftp,IpAddr}]}.</pre> +%%% <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 @@ -340,7 +384,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() @@ -352,43 +396,41 @@ get_config(Required,Default) -> %%% %%% <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 <code>require/2</code> or a +%%% (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}, -%%% {username,Username}, -%%% {password,Password}]}.</pre> -%%% <p><code>get_config(unix,Default) -> +%%% {user,[{username,Username}, +%%% {password,Password}]}]}.</pre> +%%% <p><c>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}]}]</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 <code>require/2</code> or a require statement), the name +%%% 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><code>require(myhost,unix) -> ok</code><br/> -%%% <code>get_config(myhost,Default) -> -%%% [{telnet,IpAddr}, -%%% {username,Username}, -%%% {password,Password}]</code></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 <code>all</code> option. The +%%% 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 <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> +%%% 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 @@ -399,7 +441,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() @@ -418,128 +460,279 @@ reload_config(Required)-> %%%----------------------------------------------------------------- %%% @spec log(Format) -> ok -%%% @equiv log(default,Format,[]) +%%% @equiv log(default,50,Format,[]) log(Format) -> - log(default,Format,[]). + log(default,?STD_IMPORTANCE,Format,[]). %%%----------------------------------------------------------------- %%% @spec log(X1,X2) -> ok -%%% X1 = Category | Format +%%% X1 = Category | Importance | Format %%% X2 = Format | Args -%%% @equiv log(Category,Format,Args) +%%% @equiv log(Category,Importance,Format,Args) log(X1,X2) -> - {Category,Format,Args} = - if is_atom(X1) -> {X1,X2,[]}; - is_list(X1) -> {default,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,Format,Args). + log(Category,Importance,Format,Args). %%%----------------------------------------------------------------- -%%% @spec log(Category,Format,Args) -> ok +%%% @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 testcase to the log. +%%% @doc Printout from a test case to the log file. %%% -%%% <p>This function is meant for printing stuff directly from a -%%% testcase (i.e. not from within the CT framework) in the test -%%% log.</p> +%%% <p>This function is meant for printing a string directly from a +%%% test case to the test case log file.</p> %%% -%%% <p>Default <code>Category</code> is <code>default</code> and -%%% default <code>Args</code> is <code>[]</code>.</p> -log(Category,Format,Args) -> - ct_logs:tc_log(Category,Format,Args). +%%% <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,Format,[]) +%%% @equiv print(default,50,Format,[]) print(Format) -> - print(default,Format,[]). + print(default,?STD_IMPORTANCE,Format,[]). %%%----------------------------------------------------------------- -%%% @equiv print(Category,Format,Args) +%%% @spec print(X1,X2) -> ok +%%% X1 = Category | Importance | Format +%%% X2 = Format | Args +%%% @equiv print(Category,Importance,Format,Args) print(X1,X2) -> - {Category,Format,Args} = - if is_atom(X1) -> {X1,X2,[]}; - is_list(X1) -> {default,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,Format,Args). + print(Category,Importance,Format,Args). %%%----------------------------------------------------------------- -%%% @spec print(Category,Format,Args) -> ok +%%% @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 testcase to the console. +%%% @doc Printout from a test case to the console. %%% -%%% <p>This function is meant for printing stuff from a testcase on -%%% the console.</p> +%%% <p>This function is meant for printing a string from a test case +%%% to the console.</p> %%% -%%% <p>Default <code>Category</code> is <code>default</code> and -%%% default <code>Args</code> is <code>[]</code>.</p> -print(Category,Format,Args) -> - ct_logs:tc_print(Category,Format,Args). +%%% <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,Format,[]) +%%% @equiv pal(default,50,Format,[]) pal(Format) -> - pal(default,Format,[]). + pal(default,?STD_IMPORTANCE,Format,[]). %%%----------------------------------------------------------------- %%% @spec pal(X1,X2) -> ok -%%% X1 = Category | Format +%%% X1 = Category | Importance | Format %%% X2 = Format | Args -%%% @equiv pal(Category,Format,Args) +%%% @equiv pal(Category,Importance,Format,Args) pal(X1,X2) -> - {Category,Format,Args} = - if is_atom(X1) -> {X1,X2,[]}; - is_list(X1) -> {default,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,Format,Args). + pal(Category,Importance,Format,Args). %%%----------------------------------------------------------------- -%%% @spec pal(Category,Format,Args) -> ok +%%% @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 testcase. +%%% @doc Print and log from a test case. %%% -%%% <p>This function is meant for printing stuff from a testcase both -%%% in the log and on the console.</p> +%%% <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 <code>Category</code> is <code>default</code> and -%%% default <code>Args</code> is <code>[]</code>.</p> -pal(Category,Format,Args) -> - ct_logs:tc_pal(Category,Format,Args). +%%% <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) -> void() %%% Reason = term() %%% %%% @doc Terminate a test case with the given error -%%% <code>Reason</code>. +%%% <c>Reason</c>. fail(Reason) -> - exit({test_case_failed,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) -> void() +%%% 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) -> void() %%% Comment = term() %%% -%%% @doc Print the given <code>Comment</code> in the comment field of +%%% @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. -%%% <code>comment/1</code> is also overwritten by the return value -%%% <code>{comment,Comment}</code> or by the function -%%% <code>fail/1</code> (which prints <code>Reason</code> as a -%%% comment).</p> +%%% 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("~s",[Comment])) of @@ -553,11 +746,43 @@ comment(Comment) -> Formatted = io_lib:format("~p",[Comment]), send_html_comment(lists:flatten(Formatted)). +%%%----------------------------------------------------------------- +%%% @spec comment(Format, Args) -> void() +%%% 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,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} @@ -577,11 +802,11 @@ get_target_name(Handle) -> %%% @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 -%%% <code>select</code> command in SQL. The returned -%%% <code>Table</code> is a list of tuples, where each tuple is a row +%%% <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><code>Heading</code> is a tuple of strings representing the +%%% <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). @@ -606,7 +831,7 @@ listenv(Telnet) -> %%% Testcases = list() %%% Reason = term() %%% -%%% @doc Returns all testcases in the specified suite. +%%% @doc Returns all test cases in the specified suite. testcases(TestDir, Suite) -> case make_and_load(TestDir, Suite) of E = {error,_} -> @@ -653,8 +878,8 @@ make_and_load(Dir, Suite) -> %%% SuiteUserData = [term()] %%% Reason = term() %%% -%%% @doc Returns any data specified with the tag <code>userdata</code> -%%% in the list of tuples returned from <code>Suite:suite/0</code>. +%%% @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,_} -> @@ -664,7 +889,8 @@ userdata(TestDir, Suite) -> get_userdata(Info, "suite/0") end. -get_userdata({'EXIT',{undef,_}}, Spec) -> +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}}; @@ -680,16 +906,27 @@ get_userdata(_BadTerm, Spec) -> {error,list_to_atom(Spec ++ " must return a list")}. %%%----------------------------------------------------------------- -%%% @spec userdata(TestDir, Suite, Case) -> TCUserData | {error,Reason} +%%% @spec userdata(TestDir, Suite, GroupOrCase) -> TCUserData | {error,Reason} %%% TestDir = string() %%% Suite = atom() -%%% Case = atom() +%%% GroupOrCase = {group,GroupName} | atom() +%%% GroupName = atom() %%% TCUserData = [term()] %%% Reason = term() %%% -%%% @doc Returns any data specified with the tag <code>userdata</code> -%%% in the list of tuples returned from <code>Suite:Case/0</code>. -userdata(TestDir, Suite, Case) -> +%%% @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; @@ -702,8 +939,9 @@ userdata(TestDir, Suite, Case) -> %%%----------------------------------------------------------------- %%% @spec get_status() -> TestStatus | {error,Reason} | no_tests_running %%% TestStatus = [StatusElem] -%%% StatusElem = {current,{Suite,TestCase}} | {successful,Successful} | +%%% StatusElem = {current,TestCaseInfo} | {successful,Successful} | %%% {failed,Failed} | {skipped,Skipped} | {total,Total} +%%% TestCaseInfo = {Suite,TestCase} | [{Suite,TestCase}] %%% Suite = atom() %%% TestCase = atom() %%% Successful = integer() @@ -715,7 +953,8 @@ userdata(TestDir, Suite, Case) -> %%% Reason = term() %%% %%% @doc Returns status of ongoing test. The returned list contains info about -%%% which test case is currently executing, as well as counters for +%%% 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 @@ -734,10 +973,16 @@ get_status() -> 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. @@ -751,7 +996,7 @@ get_testdata(Key) -> %%% 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><code>Reason</code>, the reason for aborting the test case, is printed +%%% <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). @@ -764,13 +1009,13 @@ abort_current_testcase(Reason) -> %%% Reason = term() %%% %%% @doc <p>This function encrypts the source config file with DES3 and -%%% saves the result in file <code>EncryptFileName</code>. The key, +%%% saves the result in file <c>EncryptFileName</c>. The key, %%% a string, must be available in a text file named -%%% <code>.ct_config.crypt</code> in the current directory, or the +%%% <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 <code>crypto</code> application for details on DES3 +%%% <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). @@ -784,13 +1029,13 @@ encrypt_config_file(SrcFileName, EncryptFileName) -> %%% Reason = term() %%% %%% @doc <p>This function encrypts the source config file with DES3 and -%%% saves the result in the target file <code>EncryptFileName</code>. +%%% saves the result in the target file <c>EncryptFileName</c>. %%% The encryption key to use is either the value in -%%% <code>{key,Key}</code> or the value stored in the file specified -%%% by <code>{file,File}</code>.</p> +%%% <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 <code>crypto</code> application for details on DES3 +%%% <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). @@ -802,11 +1047,11 @@ encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile) -> %%% TargetFileName = string() %%% Reason = term() %%% -%%% @doc <p>This function decrypts <code>EncryptFileName</code>, previously -%%% generated with <code>encrypt_config_file/2/3</code>. The original +%%% @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 -%%% <code>.ct_config.crypt</code> in the current directory, or the +%%% <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). @@ -819,8 +1064,8 @@ decrypt_config_file(EncryptFileName, TargetFileName) -> %%% KeyOrFile = {key,string()} | {file,string()} %%% Reason = term() %%% -%%% @doc <p>This function decrypts <code>EncryptFileName</code>, previously -%%% generated with <code>encrypt_config_file/2/3</code>. The original +%%% @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) -> @@ -837,7 +1082,7 @@ decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile) -> %%% 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 -%%% <code>remove_config/2</code> function.</p> +%%% <c>remove_config/2</c> function.</p> add_config(Callback, Config)-> ct_config:add_config(Callback, Config). @@ -855,18 +1100,38 @@ remove_config(Callback, Config) -> %%%----------------------------------------------------------------- %%% @spec timetrap(Time) -> ok -%%% Time = {hours,Hours} | {minutes,Mins} | {seconds,Secs} | Millisecs | infinity +%%% Time = {hours,Hours} | {minutes,Mins} | {seconds,Secs} | Millisecs | infinity | Func %%% Hours = integer() %%% Mins = integer() %%% Secs = integer() %%% Millisecs = integer() | float() -%%% -%%% @doc <p>Use this function to set a new timetrap for the running test case.</p> +%%% 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() @@ -887,3 +1152,131 @@ 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). |