aboutsummaryrefslogtreecommitdiffstats
path: root/lib/common_test
diff options
context:
space:
mode:
Diffstat (limited to 'lib/common_test')
-rw-r--r--lib/common_test/doc/src/common_test_app.xml11
-rw-r--r--lib/common_test/doc/src/ct_hooks_chapter.xml43
-rw-r--r--lib/common_test/doc/src/ct_run.xml2
-rw-r--r--lib/common_test/doc/src/notes.xml144
-rw-r--r--lib/common_test/doc/src/run_test_chapter.xml202
-rw-r--r--lib/common_test/doc/src/write_test_chapter.xml52
-rw-r--r--lib/common_test/src/ct.erl3
-rw-r--r--lib/common_test/src/ct_groups.erl2
-rw-r--r--lib/common_test/src/ct_netconfc.erl8
-rw-r--r--lib/common_test/src/ct_slave.erl63
-rw-r--r--lib/common_test/src/cth_conn_log.erl2
-rw-r--r--lib/common_test/src/cth_surefire.erl183
-rw-r--r--lib/common_test/test/Makefile3
-rw-r--r--lib/common_test/test/ct_groups_search_SUITE_data/groups_search_dummy_1_SUITE.erl2
-rw-r--r--lib/common_test/test/ct_groups_search_SUITE_data/groups_search_dummy_2_SUITE.erl2
-rw-r--r--lib/common_test/test/ct_master_SUITE.erl57
-rw-r--r--lib/common_test/test/ct_master_SUITE_data/master/master_SUITE.erl9
-rw-r--r--lib/common_test/test/ct_surefire_SUITE.erl351
-rw-r--r--lib/common_test/test/ct_surefire_SUITE_data/surefire_SUITE.erl92
-rw-r--r--lib/common_test/test/ct_test_support.erl41
-rw-r--r--lib/common_test/vsn.mk2
21 files changed, 1107 insertions, 167 deletions
diff --git a/lib/common_test/doc/src/common_test_app.xml b/lib/common_test/doc/src/common_test_app.xml
index b6d4a633cb..151159ad69 100644
--- a/lib/common_test/doc/src/common_test_app.xml
+++ b/lib/common_test/doc/src/common_test_app.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2003</year><year>2012</year>
+ <year>2003</year><year>2013</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -170,7 +170,9 @@
<v> UserData = term()</v>
<v> Conns = [atom()]</v>
<v> CSSFile = string()</v>
- <v> CTHs = [CTHModule | {CTHModule, CTHInitArgs} | {CTHModule, CTHInitArgs, CTHPriority}]</v>
+ <v> CTHs = [CTHModule |</v>
+ <v>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{CTHModule, CTHInitArgs} |</v>
+ <v>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{CTHModule, CTHInitArgs, CTHPriority}]</v>
<v> CTHModule = atom()</v>
<v> CTHInitArgs = term()</v>
</type>
@@ -297,8 +299,9 @@
<v> UserData = term()</v>
<v> Conns = [atom()]</v>
<v> CSSFile = string()</v>
- <v> CTHs = [CTHModule | {CTHModule, CTHInitArgs} |
- {CTHModule, CTHInitArgs, CTHPriority}]</v>
+ <v> CTHs = [CTHModule |</v>
+ <v> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{CTHModule, CTHInitArgs} |</v>
+ <v> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{CTHModule, CTHInitArgs, CTHPriority}]</v>
<v> CTHModule = atom()</v>
<v> CTHInitArgs = term()</v>
</type>
diff --git a/lib/common_test/doc/src/ct_hooks_chapter.xml b/lib/common_test/doc/src/ct_hooks_chapter.xml
index 86237f5fc1..fe871eb516 100644
--- a/lib/common_test/doc/src/ct_hooks_chapter.xml
+++ b/lib/common_test/doc/src/ct_hooks_chapter.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2011</year><year>2012</year>
+ <year>2011</year><year>2013</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -439,14 +439,14 @@ terminate(State) ->
<table>
<row>
- <cell><em>CTH Name</em></cell>
- <cell><em>Is Built-in</em></cell>
- <cell><em>Description</em></cell>
+ <cell align="left"><em>CTH Name</em></cell>
+ <cell align="left"><em>Is Built-in</em></cell>
+ <cell align="left"><em>Description</em></cell>
</row>
<row>
- <cell>cth_log_redirect</cell>
- <cell>yes</cell>
- <cell>Captures all error_logger and SASL logging events and prints them
+ <cell align="left">cth_log_redirect</cell>
+ <cell align="left">yes</cell>
+ <cell align="left">Captures all error_logger and SASL logging events and prints them
to the current test case log. If an event can not be associated with a
testcase it will be printed in the common test framework log. This will
happen for testcases which are run in parallel and events which occur
@@ -455,14 +455,29 @@ terminate(State) ->
using the normal SASL mechanisms. </cell>
</row>
<row>
- <cell>cth_surefire</cell>
- <cell>no</cell>
- <cell>Captures all test results and outputs them as surefire XML into
- a file. The file which is created is by default called junit_report.xml.
- The name can be by setting the path option for this hook. e.g.
+ <cell align="left">cth_surefire</cell>
+ <cell align="left">no</cell>
+ <cell align="left"><p>Captures all test results and outputs them as surefire
+ XML into a file. The file which is created is by default
+ called junit_report.xml. The file name can be changed by
+ setting the <c>path</c> option for this hook, e.g.</p>
+
<code>-ct_hooks cth_surefire [{path,"/tmp/report.xml"}]</code>
- Surefire XML can forinstance be used by Jenkins to display test
- results.</cell>
+
+ <p>If the <c>url_base</c> option is set, an additional
+ attribute named <c>url</c> will be added to each
+ <c>testsuite</c> and <c>testcase</c> XML element. The value will
+ be constructed from the <c>url_base</c> and a relative path
+ to the test suite or test case log respectively, e.g.</p>
+
+ <code>-ct_hooks cth_surefire [{url_base, "http://myserver.com/"}]</code>
+ <p>will give a url attribute value similar to</p>
+
+ <code>"http://myserver.com/[email protected]_11.19.39/
+x86_64-unknown-linux-gnu.my_test.logs/run.2012-12-12_11.19.39/suite.log.html"</code>
+
+ <p>Surefire XML can for instance be used by Jenkins to display test
+ results.</p></cell>
</row>
</table>
diff --git a/lib/common_test/doc/src/ct_run.xml b/lib/common_test/doc/src/ct_run.xml
index da18640df7..0750f560b3 100644
--- a/lib/common_test/doc/src/ct_run.xml
+++ b/lib/common_test/doc/src/ct_run.xml
@@ -90,7 +90,7 @@
<pre>
ct_run [-dir TestDir1 TestDir2 .. TestDirN] |
[[-dir TestDir] -suite Suite1 Suite2 .. SuiteN
- [[-group Group1 Group2 .. GroupN] [-case Case1 Case2 .. CaseN]]]
+ [[-group Groups1 Groups2 .. GroupsN] [-case Case1 Case2 .. CaseN]]]
[-step [config | keep_inactive]]
[-config ConfigFile1 ConfigFile2 .. ConfigFileN]
[-userconfig CallbackModule1 ConfigString1 and CallbackModule2
diff --git a/lib/common_test/doc/src/notes.xml b/lib/common_test/doc/src/notes.xml
index abe8cb2041..8c3b13951d 100644
--- a/lib/common_test/doc/src/notes.xml
+++ b/lib/common_test/doc/src/notes.xml
@@ -32,6 +32,150 @@
<file>notes.xml</file>
</header>
+<section><title>Common_Test 1.6.3.1</title>
+
+ <section><title>Known Bugs and Problems</title>
+ <list>
+ <item>
+ <p>
+ The following corrections/changes are done in the
+ cth_surefire hook:</p>
+ <p>
+ <list> <item> Earlier there would always be a
+ 'properties' element under the 'testsuites' element. This
+ would exist even if there were no 'property' element
+ inside it. This has been changed so if there are no
+ 'property' elements to display, then there will not be a
+ 'properties' element either. </item> <item> The XML file
+ will now (unless other is specified) be stored in the top
+ log directory. Earlier, the default directory would be
+ the current working directory for the erlang node, which
+ would mostly, but not always, be the top log directory.
+ </item> <item> The 'hostname' attribute in the
+ 'testsuite' element would earlier never have the correct
+ value. This has been corrected. </item> <item> The
+ 'errors' attribute in the 'testsuite' element would
+ earlier display the number of failed testcases. This has
+ been changed and will now always have the value 0, while
+ the 'failures' attribute will show the number of failed
+ testcases. </item> <item> A new attribute 'skipped' is
+ added to the 'testsuite' element. This will display the
+ number of skipped testcases. These would earlier be
+ included in the number of failed test cases. </item>
+ <item> The total number of tests displayed by the 'tests'
+ attribute in the 'testsuite' element would earlier
+ include init/end_per_suite and init/end_per_group. This
+ is no longer the case. The 'tests' attribute will now
+ only count "real" test cases. </item> <item> Earlier,
+ auto skipped test cases would have no value in the 'log'
+ attribute. This is now corrected. </item> <item> A new
+ attributes 'log' is added to the 'testsuite' element.
+ </item> <item> A new option named 'url_base' is added for
+ this hook. If this option is used, a new attribute named
+ 'url' will be added to the 'testcase' and 'testsuite'
+ elements. </item> </list></p>
+ <p>
+ Own Id: OTP-10589</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Common_Test 1.6.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The ct:run_test/1 option 'config' only worked with a
+ single config file, not a list of files. This has been
+ fixed.</p>
+ <p>
+ Own Id: OTP-10495</p>
+ </item>
+ <item>
+ <p>
+ ct_netconfc:close_session sometimes returned
+ {error,closed} because the ssh connection was closed
+ (from the server side) before the rpc-reply was received
+ by the client. This is normal and can not be helped. It
+ has been corrected so the return will be 'ok' in this
+ case. Other error situations will still give
+ {error,Reason}.</p>
+ <p>
+ Own Id: OTP-10510 Aux Id: kunagi-320 [231] </p>
+ </item>
+ <item>
+ <p>
+ ct_netconfc:close_session sometimes returned
+ {error,closed} or (if the connection was named)
+ {error,{process_down,Pid,normal}} because the ssh
+ connection was closed (from the server side) before the
+ rpc-reply was received by the client. This is normal and
+ can not be helped. It has been corrected so the return
+ will be 'ok' in this situation.</p>
+ <p>
+ Own Id: OTP-10570</p>
+ </item>
+ <item>
+ <p>
+ Fix bug where ct:require of same name with same config
+ would return name_in_use.</p>
+ <p>
+ Own Id: OTP-10572</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ A new test case group search functionality has been
+ implemented that makes Common Test search automatically
+ through the group definitions tree (the return value of
+ groups/0) and create tests for all paths of nested groups
+ that match the specification. It also allows for
+ specifying unique paths to sub groups in order to avoid
+ execution of unwanted tests. This new feature can be used
+ whenever starting a test run by means of the ct_run
+ program, the ct:run_test/1 API function, or a Test
+ Specification. Details can be found in the Test Case
+ Group Execution section in the Running Tests chapter.</p>
+ <p>
+ Own Id: OTP-10466 Aux Id: kunagi-276 [187] </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Known Bugs and Problems</title>
+ <list>
+ <item>
+ <p>
+ Restore Config data if lost when test case fails.</p>
+ <p>
+ Own Id: OTP-10070 Aux Id: kunagi-175 [86] </p>
+ </item>
+ <item>
+ <p>
+ IO server error in test_server.</p>
+ <p>
+ Own Id: OTP-10125 Aux Id: OTP-10101, kunagi-177 [88] </p>
+ </item>
+ <item>
+ <p>
+ Faulty connection handling in common_test.</p>
+ <p>
+ Own Id: OTP-10126 Aux Id: kunagi-178 [89] </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Common_Test 1.6.2.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/common_test/doc/src/run_test_chapter.xml b/lib/common_test/doc/src/run_test_chapter.xml
index b5b914d506..d5f5d89e05 100644
--- a/lib/common_test/doc/src/run_test_chapter.xml
+++ b/lib/common_test/doc/src/run_test_chapter.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2003</year><year>2012</year>
+ <year>2003</year><year>2013</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -119,7 +119,7 @@
<item><c><![CDATA[ct_run -userconfig <callbackmodulename> <configfilenames> -suite <suiteswithfullpath>]]></c>
</item>
<item><c><![CDATA[ct_run -config <configfilenames> -suite <suitewithfullpath>
- -group <groupnames> -case <casenames>]]></c></item>
+ -group <groups> -case <casenames>]]></c></item>
</list>
<p>Examples:</p>
<p><c>$ ct_run -config $CFGS/sys1.cfg $CFGS/sys2.cfg -dir $SYS1_TEST $SYS2_TEST</c></p>
@@ -137,6 +137,8 @@
<p><c>$ ct_run -suite ./testdir/x_SUITE ./testdir/y_SUITE</c></p>
+ <p>For more details on <seealso marker="run_test_chapter#group_execution">test case group execution</seealso>, please see below.</p>
+
<p>Other flags that may be used with <c>ct_run</c>:</p>
<list>
<item><c><![CDATA[-logdir <dir>]]></c>, specifies where the HTML log files are to be written.</item>
@@ -269,6 +271,163 @@
<c><seealso marker="ct#run_test-1">ct</seealso></c> manual page.</p>
</section>
+ <marker id="group_execution"></marker>
+ <section>
+ <title>Test case group execution</title>
+
+ <p>With the <c>ct_run</c> flag, or <c>ct:run_test/1</c> option <c>group</c>,
+ one or more test case groups can be specified, optionally in combination
+ with specific test cases. The syntax for specifying groups is as follows
+ (on the command line):</p>
+
+ <pre>
+ <![CDATA[$ ct_run -group <group_names_or_paths> [-case <cases>]]]></pre>
+ <p>or (in the Erlang shell):</p>
+ <pre>
+ <![CDATA[1> ct:run_test([{group,GroupsNamesOrPaths}, {case,Cases}]).]]></pre>
+
+ <p>The <c>group_names_or_paths</c> parameter specifies either one
+ or more group names and/or one or more group paths. At start up,
+ Common Test will search for matching groups in the group definitions
+ tree (i.e. the list returned from <c>Suite:groups/0</c>, please see the
+ <seealso marker="write_test_chapter#test_case_groups">Test case groups</seealso>
+ chapter for details).
+ Given a group name, say <c>g</c>, Common Test will search for all paths
+ that lead to <c>g</c>. By path here we mean a sequence of nested groups,
+ all of which have to be followed in order to get from the top level
+ group to <c>g</c>. Actually, what Common Test needs to do in order to
+ execute the test cases in group <c>g</c>, is to call the
+ <c>init_per_group/2</c> function for each group in the path to
+ <c>g</c>, as well as all corresponding <c>end_per_group/2</c>
+ functions afterwards. The obvious reason for this is that the configuration
+ of a test case in <c>g</c> (and its <c>Config</c> input data) depends on
+ <c>init_per_testcase(TestCase, Config)</c> and its return value, which
+ in turn depends on <c>init_per_group(g, Config)</c> and its return value,
+ which in turn depends on <c>init_per_group/2</c> of the group above
+ <c>g</c>, etc, all the way up to the top level group.</p>
+
+ <p>As you may have already realized, this means that if there is more than
+ one way to locate a group (and its test cases) in a path, the result of the
+ group search operation is a number of tests, all of which will be performed.
+ Common Test actually interprets a group specification that consists of a
+ single name this way:</p>
+
+ <p>"Search and find all paths in the group definitions tree that lead
+ to the specified group and, for each path, create a test which (1) executes
+ all configuration functions in the path to the specified group, then (2)
+ executes all - or all matching - test cases in this group, as well as (3)
+ all - or all matching - test cases in all sub groups of the group".
+ </p>
+
+ <p>It is also possible for the user to specify a specific group path with
+ the <c>group_names_or_paths</c> parameter. With this type of specification it's
+ possible to avoid execution of unwanted groups (in otherwise matching paths),
+ and/or the execution of sub groups. The syntax of the group path is a list of
+ group names in the path, e.g. on the command line:
+ </p>
+ <p><c>$ ct_run -suite "./x_SUITE" -group [g1,g3,g4] -case tc1 tc5</c></p>
+ <p>or similarly in the Erlang shell (requires a list within the groups list):</p>
+ <p><c>1> ct:run_test([{suite,"./x_SUITE"}, {group,[[g1,g3,g4]]}, {testcase,[tc1,tc5]}]).</c></p>
+
+ <p>The last group in the specified path will be the terminating group in
+ the test, i.e. no sub groups following this group will be executed. In the
+ example above, <c>g4</c> is the terminating group, hence Common Test will
+ execute a test that calls all init configuration functions in the path to
+ <c>g4</c>, i.e. <c>g1..g3..g4</c>. It will then call test cases <c>tc1</c>
+ and <c>tc5</c> in <c>g4</c> and finally all end configuration functions in order
+ <c>g4..g3..g1</c>.</p>
+
+ <p>Note that the group path specification doesn't necessarily
+ have to include <em>all</em> groups in the path to the terminating group.
+ Common Test will search for all matching paths if given an incomplete group
+ path.</p>
+
+ <p>Note also that it's possible to combine group names and group paths with the
+ <c>group_names_or_paths</c> parameter. Each element is treated as
+ an individual specification in combination with the <c>cases</c> parameter.
+ See examples below.</p>
+
+ <p>Examples:</p>
+ <pre>
+ -module(x_SUITE).
+ ...
+ %% The group definitions:
+ groups() ->
+ [{top1,[],[tc11,tc12,
+ {sub11,[],[tc12,tc13]},
+ {sub12,[],[tc14,tc15,
+ {sub121,[],[tc12,tc16]}]}]},
+
+ {top2,[],[{group,sub21},{group,sub22}]},
+ {sub21,[],[tc21,{group,sub2X2}]},
+ {sub22,[],[{group,sub221},tc21,tc22,{group,sub2X2}]},
+ {sub221,[],[tc21,tc23]},
+ {sub2X2,[],[tc21,tc24]}].
+ </pre>
+ <br></br>
+ <p><c>$ ct_run -suite "x_SUITE" -group all</c></p>
+ <p><c>1> ct:run_test([{suite,"x_SUITE"}, {group,all}]).</c></p>
+ <p>Two tests will be executed, one for all cases and all sub groups under <c>top1</c>,
+ and one for all under <c>top2</c>. (We would get the same result with
+ <c>-group top1 top2</c>, or <c>{group,[top1,top2]}</c>.</p>
+ <br></br>
+ <p><c>$ ct_run -suite "x_SUITE" -group top1</c></p>
+ <p><c>1> ct:run_test([{suite,"x_SUITE"}, {group,[top1]}]).</c></p>
+ <p>This will execute one test for all cases and sub groups under <c>top1</c>.</p>
+ <br></br>
+ <p><c>$ ct_run -suite "x_SUITE" -group top1 -case tc12</c></p>
+ <p><c>1> ct:run_test([{suite,"x_SUITE"}, {group,[top1]}, {testcase,[tc12]}]).</c></p>
+ <p>This will run a test that executes <c>tc12</c> in <c>top1</c> and any sub group
+ under <c>top1</c> where it can be found (<c>sub11</c> and <c>sub121</c>).</p>
+ <br></br>
+ <p><c>$ ct_run -suite "x_SUITE" -group [top1] -case tc12</c></p>
+ <p><c>1> ct:run_test([{suite,"x_SUITE"}, {group,[[top1]]}, {testcase,[tc12]}]).</c></p>
+ <p>This will execute <c>tc12</c> <em>only</em> in group <c>top1</c>.</p>
+ <br></br>
+ <p><c>$ ct_run -suite "x_SUITE" -group top1 -case tc16</c></p>
+ <p><c>1> ct:run_test([{suite,"x_SUITE"}, {group,[top1]}, {testcase,[tc16]}]).</c></p>
+ <p>This will search <c>top1</c> and all its sub groups for <c>tc16</c> and the result
+ will be that this test case executes in group <c>sub121</c>. (The specific path:
+ <c>-group [sub121]</c> or <c>{group,[[sub121]]}</c>, would have given
+ us the same result in this example).</p>
+ <br></br>
+ <p><c>$ ct_run -suite "x_SUITE" -group sub12 [sub12]</c></p>
+ <p><c>1> ct:run_test([{suite,"x_SUITE"}, {group,[sub12,[sub12]]}]).</c></p>
+ <p>This will execute two tests, one that includes all cases and sub groups under
+ <c>sub12</c>, and one with <em>only</em> the test cases in <c>sub12</c>.</p>
+ <br></br>
+ <p><c>$ ct_run -suite "x_SUITE" -group sub2X2</c></p>
+ <p><c>1> ct:run_test([{suite,"x_SUITE"}, {group,[sub2X2]}]).</c></p>
+ <p>In this example, Common Test will find and execute two tests, one for the path from
+ <c>top2</c> to <c>sub2X2</c> via <c>sub21</c>, and one from <c>top2</c> to <c>sub2X2</c>
+ via <c>sub22</c>.</p>
+ <br></br>
+ <p><c>$ ct_run -suite "x_SUITE" -group [sub21,sub2X2]</c></p>
+ <p><c>1> ct:run_test([{suite,"x_SUITE"}, {group,[[sub21,sub2X2]]}]).</c></p>
+ <p>Here, by specifying the unique path: <c>top2 -> sub21 -> sub2X2</c>, only one test
+ is executed. The second possible path from <c>top2</c> to <c>sub2X2</c> (above)
+ will be discarded.</p>
+ <br></br>
+ <p><c>$ ct_run -suite "x_SUITE" -group [sub22] -case tc22 tc21</c></p>
+ <p><c>1> ct:run_test([{suite,"x_SUITE"}, {group,[[sub22]]}, {testcase,[tc22,tc21]}]).</c></p>
+ <p>In this example only the test cases for <c>sub22</c> will be executed, and in
+ reverse order compared to the group definition.</p>
+ <br></br>
+
+ <p>If a test case that belongs to a group (according to the group definition), is executed
+ without a group specification, i.e. simply by means of (command line):</p>
+ <p><c>$ ct_run -suite "my_SUITE" -case my_tc</c></p>
+ <p>or (Erlang shell):</p>
+ <p><c>1> ct:run_test([{suite,"my_SUITE"}, {testcase,my_tc}]).</c></p>
+ <p>then Common Test ignores the group definition and executes the test case in the scope of the
+ test suite only (no group configuration functions are called).</p>
+
+ <p>The group specification feature, exactly as it has been presented in this section, can also
+ be used in <seealso marker="run_test_chapter#test_specifications">Test
+ Specifications</seealso> (with some extra features added). Please see below.</p>
+ </section>
+
+
<section>
<title>Running the interactive shell mode</title>
@@ -400,8 +559,8 @@
terms (e.g. log directory, label, style sheet, auto compilation).</p>
<p>With test specification terms it is possible to state exactly
which tests should run and in which order. A test term specifies
- either one or more suites, one or more test case groups, or one
- or more test cases in a group or suite.</p>
+ either one or more suites, one or more test case groups (possibly nested),
+ or one or more test cases in a group (or in multiple groups) or in a suite.</p>
<p>An arbitrary number of test terms may be declared in sequence.
Common Test will by default compile the terms into one or more tests
to be performed in one resulting test run. Note that a term that
@@ -420,28 +579,32 @@
are not executed and show up in the HTML log files as
SKIPPED.</p>
<p>When a test case group is specified, the resulting test
- executes the
- <c>init_per_group</c> function, followed by all test cases and
- sub groups (including their configuration functions), and
+ executes the <c>init_per_group</c> function, followed by all test
+ cases and sub groups (including their configuration functions), and
finally the <c>end_per_group</c> function. Also if particular
test cases in a group are specified, <c>init_per_group</c>
and <c>end_per_group</c> for the group in question are
called. If a group which is defined (in <c>Suite:group/0</c>) to
- be a sub group of another group, is specified (or particular test
+ be a sub group of another group, is specified (or if particular test
cases of a sub group are), Common Test will call the configuration
functions for the top level groups as well as for the sub group
in question (making it possible to pass configuration data all
the way from <c>init_per_suite</c> down to the test cases in the
sub group).</p>
-
- <p>With the <c>GroupSpec</c> element (below) it's possible to specify
- group execution properties that will override those specified in the
+ <p>The test specification utilizes the same mechanism for specifying
+ test case groups by means of names and paths, as explained in the
+ <seealso marker="run_test_chapter#group_execution">Group Execution</seealso>
+ section above, with the addition of the <c>GroupSpec</c> element
+ described next.</p>
+ <p>The <c>GroupSpec</c> element makes it possible to specify
+ group execution properties that will override those in the
group definition (i.e. in <c>groups/0</c>). Execution properties for
sub-groups may be overridden as well. This feature makes it possible to
change properties of groups at the time of execution,
- without even having to edit the test suite. More detailed documentation,
- and examples, can be found in the
- <seealso marker="write_test_chapter#test_case_groups">
+ without even having to edit the test suite. The very same
+ feature is available for <c>group</c> elements in the <c>Suite:all/0</c>
+ list. Therefore, more detailed documentation, and examples, can be
+ found in the <seealso marker="write_test_chapter#test_case_groups">
Test case groups</seealso> chapter.</p>
<p>Below is the test specification syntax. Test specifications can
@@ -546,8 +709,8 @@
{groups, Dir, Suite, Groups}.
{groups, NodeRefs, Dir, Suite, Groups}.
- {groups, Dir, Suite, GroupSpec, {cases,Cases}}.
- {groups, NodeRefs, Dir, Suite, GroupSpec, {cases,Cases}}.
+ {groups, Dir, Suite, Groups, {cases,Cases}}.
+ {groups, NodeRefs, Dir, Suite, Groups, {cases,Cases}}.
{cases, Dir, Suite, Cases}.
{cases, NodeRefs, Dir, Suite, Cases}.
@@ -589,13 +752,16 @@
PrivDirOption = auto_per_run | auto_per_tc | manual_per_tc
EventHandlers = atom() | [atom()]
InitArgs = [term()]
- CTHModules = [CTHModule | {CTHModule, CTHInitArgs} | {CTHModule, CTHInitArgs, CTHPriority}]
+ CTHModules = [CTHModule |
+ {CTHModule, CTHInitArgs} |
+ {CTHModule, CTHInitArgs, CTHPriority}]
CTHModule = atom()
CTHInitArgs = term()
Dir = string()
Suites = atom() | [atom()] | all
Suite = atom()
- Groups = GroupSpec | [GroupSpec] | all
+ Groups = GroupPath | [GroupPath] | GroupSpec | [GroupSpec] | all
+ GroupPath = [GroupName]
GroupSpec = GroupName | {GroupName,Properties} | {GroupName,Properties,GroupSpec}
GroupName = atom()
GroupNames = GroupName | [GroupName]
diff --git a/lib/common_test/doc/src/write_test_chapter.xml b/lib/common_test/doc/src/write_test_chapter.xml
index 248d7de8b6..cc8d913994 100644
--- a/lib/common_test/doc/src/write_test_chapter.xml
+++ b/lib/common_test/doc/src/write_test_chapter.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2003</year><year>2012</year>
+ <year>2003</year><year>2013</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -982,38 +982,36 @@
<p>Example:</p>
<pre>
+ Some printouts during test case execution:
- Some printouts during test case execution:
+ io:format("1. Standard IO, importance = ~w~n", [?STD_IMPORTANCE]),
+ ct:log("2. Uncategorized, importance = ~w", [?STD_IMPORTANCE]),
+ ct:log(info, "3. Categorized info, importance = ~w", [?STD_IMPORTANCE]]),
+ ct:log(info, ?LOW_IMPORTANCE, "4. Categorized info, importance = ~w", [?LOW_IMPORTANCE]),
+ ct:log(error, "5. Categorized error, importance = ~w", [?HI_IMPORTANCE]),
+ ct:log(error, ?HI_IMPORTANCE, "6. Categorized error, importance = ~w", [?MAX_IMPORTANCE]),
- io:format("1. Standard IO, importance = ~w~n", [?STD_IMPORTANCE]),
- ct:log("2. Uncategorized, importance = ~w", [?STD_IMPORTANCE]),
- ct:log(info, "3. Categorized info, importance = ~w", [?STD_IMPORTANCE]]),
- ct:log(info, ?LOW_IMPORTANCE, "4. Categorized info, importance = ~w", [?LOW_IMPORTANCE]),
- ct:log(error, "5. Categorized error, importance = ~w", [?HI_IMPORTANCE]),
- ct:log(error, ?HI_IMPORTANCE, "6. Categorized error, importance = ~w", [?MAX_IMPORTANCE]),
+ If starting the test without specifying any verbosity levels:
- If starting the test without specifying any verbosity levels:
+ $ ct_run ...
- $ ct_run ...
+ the following gets printed:
- the following gets printed:
-
- 1. Standard IO, importance = 50
- 2. Uncategorized, importance = 50
- 3. Categorized info, importance = 50
- 5. Categorized error, importance = 75
- 6. Categorized error, importance = 99
-
- If starting the test with:
-
- $ ct_run -verbosity 1 and info 75
-
- the following gets printed:
+ 1. Standard IO, importance = 50
+ 2. Uncategorized, importance = 50
+ 3. Categorized info, importance = 50
+ 5. Categorized error, importance = 75
+ 6. Categorized error, importance = 99
+
+ If starting the test with:
+
+ $ ct_run -verbosity 1 and info 75
+
+ the following gets printed:
- 3. Categorized info, importance = 50
- 4. Categorized info, importance = 25
- 6. Categorized error, importance = 99
- </pre>
+ 3. Categorized info, importance = 50
+ 4. Categorized info, importance = 25
+ 6. Categorized error, importance = 99</pre>
<p>How categories can be mapped to CSS tags is documented in the
<seealso marker="run_test_chapter#html_stylesheet">Running Tests</seealso>
diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl
index 6ecd0a022c..8eafdff29f 100644
--- a/lib/common_test/src/ct.erl
+++ b/lib/common_test/src/ct.erl
@@ -161,7 +161,8 @@ run(TestDirs) ->
%%% 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()
diff --git a/lib/common_test/src/ct_groups.erl b/lib/common_test/src/ct_groups.erl
index 24ca3826a8..74ab5e5439 100644
--- a/lib/common_test/src/ct_groups.erl
+++ b/lib/common_test/src/ct_groups.erl
@@ -176,7 +176,7 @@ find(Mod, GrNames, all, [{M,TC} | Gs], Known,
%% Check if test case should be saved
find(Mod, GrNames, TCs, [TC | Gs], Known,
Defs, FindAll) when is_atom(TC) orelse
- ((size(TC) == 2) and (hd(TC) /= group)) ->
+ ((size(TC) == 2) and (element(1,TC) /= group)) ->
Case =
if is_atom(TC) ->
Tuple = {Mod,TC},
diff --git a/lib/common_test/src/ct_netconfc.erl b/lib/common_test/src/ct_netconfc.erl
index 294b82bff6..1ccbc86d8f 100644
--- a/lib/common_test/src/ct_netconfc.erl
+++ b/lib/common_test/src/ct_netconfc.erl
@@ -307,7 +307,7 @@
-type option() :: {ssh,host()} | {port,inet:port_number()} | {user,string()} |
{password,string()} | {user_dir,string()} |
{timeout,timeout()}.
--type host() :: inet:host_name() | inet:ip_address().
+-type host() :: inet:hostname() | inet:ip_address().
-type notification() :: {notification, xml_attributes(), notification_content()}.
-type notification_content() :: [event_time()|simple_xml()].
@@ -1073,7 +1073,8 @@ handle_msg({get_event_streams=Op,Streams,Timeout}, From, State) ->
SimpleXml = encode_rpc_operation(get,[Filter]),
do_send_rpc(Op, SimpleXml, Timeout, From, State).
-handle_msg({ssh_cm, _CM, {data, _Ch, _Type, Data}}, State) ->
+handle_msg({ssh_cm, CM, {data, Ch, _Type, Data}}, State) ->
+ ssh_connection:adjust_window(CM,Ch,size(Data)),
handle_data(Data, State);
handle_msg({ssh_cm, _CM, _SshCloseMsg}, State) ->
%% _SshCloseMsg can probably be one of
@@ -1805,7 +1806,8 @@ get_tag([]) ->
%%% SSH stuff
ssh_receive_data() ->
receive
- {ssh_cm, _CM, {data, _Ch, _Type, Data}} ->
+ {ssh_cm, CM, {data, Ch, _Type, Data}} ->
+ ssh_connection:adjust_window(CM,Ch,size(Data)),
{ok, Data};
{ssh_cm, _CM, {Closed, _Ch}} = X when Closed == closed; Closed == eof ->
{error,X};
diff --git a/lib/common_test/src/ct_slave.erl b/lib/common_test/src/ct_slave.erl
index cb05423497..1fd8c04f8b 100644
--- a/lib/common_test/src/ct_slave.erl
+++ b/lib/common_test/src/ct_slave.erl
@@ -37,7 +37,7 @@
-record(options, {username, password, boot_timeout, init_timeout,
startup_timeout, startup_functions, monitor_master,
- kill_if_fail, erl_flags}).
+ kill_if_fail, erl_flags, env}).
%%%-----------------------------------------------------------------
%%% @spec start(Node) -> Result
@@ -85,7 +85,8 @@ start(Host, Node) ->
%%% {startup_functions, StartupFunctions} |
%%% {monitor_master, Monitor} |
%%% {kill_if_fail, KillIfFail} |
-%%% {erl_flags, ErlangFlags}
+%%% {erl_flags, ErlangFlags} |
+%%% {env, [{EnvVar,Value}]}
%%% Username = string()
%%% Password = string()
%%% BootTimeout = integer()
@@ -99,6 +100,8 @@ start(Host, Node) ->
%%% Monitor = bool()
%%% KillIfFail = bool()
%%% ErlangFlags = string()
+%%% EnvVar = string()
+%%% Value = string()
%%% Result = {ok, NodeName} | {error, already_started, NodeName} |
%%% {error, started_not_connected, NodeName} |
%%% {error, boot_timeout, NodeName} |
@@ -152,6 +155,9 @@ start(Host, Node) ->
%%% <p>Option <code>erlang_flags</code> specifies, which flags will be added
%%% to the parameters of the <code>erl</code> executable.</p>
%%%
+%%% <p>Option <code>env</code> specifies a list of environment variables
+%%% that will extended the environment.</p>
+%%%
%%% <p>Special return values are:
%%% <list>
%%% <item><code>{error, already_started, NodeName}</code> - if the node with
@@ -233,10 +239,12 @@ fetch_options(Options) ->
Monitor = get_option_value(monitor_master, Options, false),
KillIfFail = get_option_value(kill_if_fail, Options, true),
ErlFlags = get_option_value(erl_flags, Options, []),
+ EnvVars = get_option_value(env, Options, []),
#options{username=UserName, password=Password,
boot_timeout=BootTimeout, init_timeout=InitTimeout,
startup_timeout=StartupTimeout, startup_functions=StartupFunctions,
- monitor_master=Monitor, kill_if_fail=KillIfFail, erl_flags=ErlFlags}.
+ monitor_master=Monitor, kill_if_fail=KillIfFail,
+ erl_flags=ErlFlags, env=EnvVars}.
% send a message when slave node is started
% @hidden
@@ -306,6 +314,7 @@ do_start(Host, Node, Options) ->
true->
spawn_remote_node(Host, Node, Options)
end,
+
BootTimeout = Options#options.boot_timeout,
InitTimeout = Options#options.init_timeout,
StartupTimeout = Options#options.startup_timeout,
@@ -372,9 +381,9 @@ get_cmd(Node, Flags) ->
% spawn node locally
spawn_local_node(Node, Options) ->
- ErlFlags = Options#options.erl_flags,
+ #options{env=Env,erl_flags=ErlFlags} = Options,
Cmd = get_cmd(Node, ErlFlags),
- open_port({spawn, Cmd}, [stream]).
+ open_port({spawn, Cmd}, [stream,{env,Env}]).
% start crypto and ssh if not yet started
check_for_ssh_running() ->
@@ -393,9 +402,10 @@ check_for_ssh_running() ->
% spawn node remotely
spawn_remote_node(Host, Node, Options) ->
- Username = Options#options.username,
- Password = Options#options.password,
- ErlFlags = Options#options.erl_flags,
+ #options{username=Username,
+ password=Password,
+ erl_flags=ErlFlags,
+ env=Env} = Options,
SSHOptions = case {Username, Password} of
{[], []}->
[];
@@ -407,8 +417,17 @@ spawn_remote_node(Host, Node, Options) ->
check_for_ssh_running(),
{ok, SSHConnRef} = ssh:connect(atom_to_list(Host), 22, SSHOptions),
{ok, SSHChannelId} = ssh_connection:session_channel(SSHConnRef, infinity),
+ ssh_setenv(SSHConnRef, SSHChannelId, Env),
ssh_connection:exec(SSHConnRef, SSHChannelId, get_cmd(Node, ErlFlags), infinity).
+
+ssh_setenv(SSHConnRef, SSHChannelId, [{Var, Value} | Vars])
+ when is_list(Var), is_list(Value) ->
+ success = ssh_connection:setenv(SSHConnRef, SSHChannelId,
+ Var, Value, infinity),
+ ssh_setenv(SSHConnRef, SSHChannelId, Vars);
+ssh_setenv(_SSHConnRef, _SSHChannelId, []) -> ok.
+
% call functions on a remote Erlang node
call_functions(_Node, []) ->
ok;
@@ -430,15 +449,29 @@ wait_for_node_alive(Node, N) ->
% call init:stop on a remote node
do_stop(ENode) ->
- case test_server:is_cover() of
- true ->
- MainCoverNode = cover:get_main_node(),
- rpc:call(MainCoverNode,cover,flush,[ENode]);
- false ->
- ok
+ {Cover,MainCoverNode} =
+ case test_server:is_cover() of
+ true ->
+ Main = cover:get_main_node(),
+ rpc:call(Main,cover,flush,[ENode]),
+ {true,Main};
+ false ->
+ {false,undefined}
end,
spawn(ENode, init, stop, []),
- wait_for_node_dead(ENode, 5).
+ case wait_for_node_dead(ENode, 5) of
+ {ok,ENode} ->
+ if Cover ->
+ %% To avoid that cover is started again if a node
+ %% with the same name is started later.
+ rpc:call(MainCoverNode,cover,stop,[ENode]);
+ true ->
+ ok
+ end,
+ {ok,ENode};
+ Error ->
+ Error
+ end.
% wait N seconds until node is disconnected
wait_for_node_dead(Node, 0) ->
diff --git a/lib/common_test/src/cth_conn_log.erl b/lib/common_test/src/cth_conn_log.erl
index 3af89db3a5..255f3ec78a 100644
--- a/lib/common_test/src/cth_conn_log.erl
+++ b/lib/common_test/src/cth_conn_log.erl
@@ -58,7 +58,7 @@
-spec init(Id, HookOpts) -> Result when
Id :: term(),
- HookOpts :: ct:hook_options(),
+ HookOpts :: ct_netconfc:hook_options(),
Result :: {ok,[{ct_netconfc:conn_mod(),
{ct_netconfc:log_type(),[ct_netconfc:key_or_name()]}}]}.
init(_Id, HookOpts) ->
diff --git a/lib/common_test/src/cth_surefire.erl b/lib/common_test/src/cth_surefire.erl
index 76b0f0b5ea..e6eaad8d48 100644
--- a/lib/common_test/src/cth_surefire.erl
+++ b/lib/common_test/src/cth_surefire.erl
@@ -1,3 +1,22 @@
+%%--------------------------------------------------------------------
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%--------------------------------------------------------------------
+
%%% @doc Common Test Framework functions handling test specifications.
%%%
%%% <p>This module creates a junit report of the test run if plugged in
@@ -27,18 +46,28 @@
-export([terminate/1]).
-record(state, { filepath, axis, properties, package, hostname,
- curr_suite, curr_suite_ts, curr_group = [], curr_tc,
- curr_log_dir, timer, tc_log,
+ curr_suite, curr_suite_ts, curr_group = [],
+ curr_log_dir, timer, tc_log, url_base,
test_cases = [],
test_suites = [] }).
--record(testcase, { log, group, classname, name, time, failure, timestamp }).
--record(testsuite, { errors, failures, hostname, name, tests,
+-record(testcase, { log, url, group, classname, name, time, result, timestamp }).
+-record(testsuite, { errors, failures, skipped, hostname, name, tests,
time, timestamp, id, package,
- properties, testcases }).
+ properties, testcases, log, url }).
+
+-define(default_report,"junit_report.xml").
+-define(suite_log,"suite.log.html").
+
+%% Number of dirs from log root to testcase log file.
+%% ct_run.<node>.<timestamp>/<test_name>/run.<timestamp>/<tc_log>.html
+-define(log_depth,3).
id(Opts) ->
- filename:absname(proplists:get_value(path, Opts, "junit_report.xml")).
+ case proplists:get_value(path, Opts) of
+ undefined -> ?default_report;
+ Path -> filename:absname(Path)
+ end.
init(Path, Opts) ->
{ok, Host} = inet:gethostname(),
@@ -47,10 +76,24 @@ init(Path, Opts) ->
package = proplists:get_value(package,Opts),
axis = proplists:get_value(axis,Opts,[]),
properties = proplists:get_value(properties,Opts,[]),
+ url_base = proplists:get_value(url_base,Opts),
timer = now() }.
pre_init_per_suite(Suite,Config,#state{ test_cases = [] } = State) ->
- {Config, init_tc(State#state{ curr_suite = Suite, curr_suite_ts = now() },
+ TcLog = proplists:get_value(tc_logfile,Config),
+ CurrLogDir = filename:dirname(TcLog),
+ Path =
+ case State#state.filepath of
+ ?default_report ->
+ RootDir = get_test_root(TcLog),
+ filename:join(RootDir,?default_report);
+ P ->
+ P
+ end,
+ {Config, init_tc(State#state{ filepath = Path,
+ curr_suite = Suite,
+ curr_suite_ts = now(),
+ curr_log_dir = CurrLogDir},
Config) };
pre_init_per_suite(Suite,Config,State) ->
%% Have to close the previous suite
@@ -59,7 +102,8 @@ pre_init_per_suite(Suite,Config,State) ->
post_init_per_suite(_Suite,Config, Result, State) ->
{Result, end_tc(init_per_suite,Config,Result,State)}.
-pre_end_per_suite(_Suite,Config,State) -> {Config, init_tc(State, Config)}.
+pre_end_per_suite(_Suite,Config,State) ->
+ {Config, init_tc(State, Config)}.
post_end_per_suite(_Suite,Config,Result,State) ->
{Result, end_tc(end_per_suite,Config,Result,State)}.
@@ -71,13 +115,15 @@ pre_init_per_group(Group,Config,State) ->
post_init_per_group(_Group,Config,Result,State) ->
{Result, end_tc(init_per_group,Config,Result,State)}.
-pre_end_per_group(_Group,Config,State) -> {Config, init_tc(State, Config)}.
+pre_end_per_group(_Group,Config,State) ->
+ {Config, init_tc(State, Config)}.
post_end_per_group(_Group,Config,Result,State) ->
NewState = end_tc(end_per_group, Config, Result, State),
{Result, NewState#state{ curr_group = tl(NewState#state.curr_group)}}.
-pre_init_per_testcase(_TC,Config,State) -> {Config, init_tc(State, Config)}.
+pre_init_per_testcase(_TC,Config,State) ->
+ {Config, init_tc(State, Config)}.
post_end_per_testcase(TC,Config,Result,State) ->
{Result, end_tc(TC,Config, Result,State)}.
@@ -88,11 +134,19 @@ on_tc_fail(_TC, Res, State) ->
TCs = State#state.test_cases,
TC = hd(TCs),
NewTC = TC#testcase{
- failure =
+ result =
{fail,lists:flatten(io_lib:format("~p",[Res]))} },
State#state{ test_cases = [NewTC | tl(TCs)]}.
-on_tc_skip(Tc,{Type,_Reason} = Res, State) when Type == tc_auto_skip ->
+on_tc_skip(Tc,{Type,_Reason} = Res, State0) when Type == tc_auto_skip ->
+ TcStr = atom_to_list(Tc),
+ State =
+ case State0#state.test_cases of
+ [#testcase{name=TcStr}|TCs] ->
+ State0#state{test_cases=TCs};
+ _ ->
+ State0
+ end,
do_tc_skip(Res, end_tc(Tc,[],Res,init_tc(State,[])));
on_tc_skip(_Tc, _Res, State = #state{test_cases = []}) ->
State;
@@ -103,7 +157,7 @@ do_tc_skip(Res, State) ->
TCs = State#state.test_cases,
TC = hd(TCs),
NewTC = TC#testcase{
- failure =
+ result =
{skipped,lists:flatten(io_lib:format("~p",[Res]))} },
State#state{ test_cases = [NewTC | tl(TCs)]}.
@@ -117,33 +171,52 @@ end_tc(Func, Config, Res, State) when is_atom(Func) ->
end_tc(atom_to_list(Func), Config, Res, State);
end_tc(Name, _Config, _Res, State = #state{ curr_suite = Suite,
curr_group = Groups,
- timer = TS, tc_log = Log } ) ->
+ curr_log_dir = CurrLogDir,
+ timer = TS,
+ tc_log = Log0,
+ url_base = UrlBase } ) ->
+ Log =
+ case Log0 of
+ "" ->
+ LowerSuiteName = string:to_lower(atom_to_list(Suite)),
+ filename:join(CurrLogDir,LowerSuiteName++"."++Name++".html");
+ _ ->
+ Log0
+ end,
+ Url = make_url(UrlBase,Log),
ClassName = atom_to_list(Suite),
PGroup = string:join([ atom_to_list(Group)||
Group <- lists:reverse(Groups)],"."),
TimeTakes = io_lib:format("~f",[timer:now_diff(now(),TS) / 1000000]),
State#state{ test_cases = [#testcase{ log = Log,
+ url = Url,
timestamp = now_to_string(TS),
classname = ClassName,
group = PGroup,
name = Name,
time = TimeTakes,
- failure = passed }| State#state.test_cases]}.
+ result = passed }|
+ State#state.test_cases],
+ tc_log = ""}. % so old tc_log is not set if next is on_tc_skip
close_suite(#state{ test_cases = [] } = State) ->
State;
-close_suite(#state{ test_cases = TCs } = State) ->
- Total = length(TCs),
- Succ = length(lists:filter(fun(#testcase{ failure = F }) ->
- F == passed
- end,TCs)),
- Fail = Total - Succ,
+close_suite(#state{ test_cases = TCs, url_base = UrlBase } = State) ->
+ {Total,Fail,Skip} = count_tcs(TCs,0,0,0),
TimeTaken = timer:now_diff(now(),State#state.curr_suite_ts) / 1000000,
+ SuiteLog = filename:join(State#state.curr_log_dir,?suite_log),
+ SuiteUrl = make_url(UrlBase,SuiteLog),
Suite = #testsuite{ name = atom_to_list(State#state.curr_suite),
package = State#state.package,
+ hostname = State#state.hostname,
time = io_lib:format("~f",[TimeTaken]),
timestamp = now_to_string(State#state.curr_suite_ts),
- errors = Fail, tests = Total,
- testcases = lists:reverse(TCs) },
+ errors = 0,
+ failures = Fail,
+ skipped = Skip,
+ tests = Total,
+ testcases = lists:reverse(TCs),
+ log = SuiteLog,
+ url = SuiteUrl},
State#state{ test_cases = [],
test_suites = [Suite | State#state.test_suites]}.
@@ -159,14 +232,15 @@ terminate(State) ->
-to_xml(#testcase{ group = Group, classname = CL, log = L, name = N, time = T, timestamp = TS, failure = F}) ->
+to_xml(#testcase{ group = Group, classname = CL, log = L, url = U, name = N, time = T, timestamp = TS, result = R}) ->
["<testcase ",
- [["group=\"",Group,"\""]||Group /= ""]," "
+ [["group=\"",Group,"\" "]||Group /= ""],
"name=\"",N,"\" "
"time=\"",T,"\" "
- "timestamp=\"",TS,"\" "
+ "timestamp=\"",TS,"\" ",
+ [["url=\"",U,"\" "]||U /= undefined],
"log=\"",L,"\">",
- case F of
+ case R of
passed ->
[];
{skipped,Reason} ->
@@ -176,22 +250,29 @@ to_xml(#testcase{ group = Group, classname = CL, log = L, name = N, time = T, ti
["<failure message=\"Test ",N," in ",CL," failed!\" type=\"crash\">",
sanitize(Reason),"</failure>"]
end,"</testcase>"];
-to_xml(#testsuite{ package = P, hostname = H, errors = E, time = Time,
- timestamp = TS, tests = T, name = N, testcases = Cases }) ->
+to_xml(#testsuite{ package = P, hostname = H, errors = E, failures = F,
+ skipped = S, time = Time, timestamp = TS, tests = T, name = N,
+ testcases = Cases, log = Log, url = Url }) ->
["<testsuite ",
[["package=\"",P,"\" "]||P /= undefined],
- [["hostname=\"",P,"\" "]||H /= undefined],
- [["name=\"",N,"\" "]||N /= undefined],
- [["time=\"",Time,"\" "]||Time /= undefined],
- [["timestamp=\"",TS,"\" "]||TS /= undefined],
+ "hostname=\"",H,"\" "
+ "name=\"",N,"\" "
+ "time=\"",Time,"\" "
+ "timestamp=\"",TS,"\" "
"errors=\"",integer_to_list(E),"\" "
- "tests=\"",integer_to_list(T),"\">",
+ "failures=\"",integer_to_list(F),"\" "
+ "skipped=\"",integer_to_list(S),"\" "
+ "tests=\"",integer_to_list(T),"\" ",
+ [["url=\"",Url,"\" "]||Url /= undefined],
+ "log=\"",Log,"\">",
[to_xml(Case) || Case <- Cases],
"</testsuite>"];
to_xml(#state{ test_suites = TestSuites, axis = Axis, properties = Props }) ->
["<testsuites>",properties_to_xml(Axis,Props),
[to_xml(TestSuite) || TestSuite <- TestSuites],"</testsuites>"].
+properties_to_xml([],[]) ->
+ [];
properties_to_xml(Axis,Props) ->
["<properties>",
[["<property name=\"",Name,"\" axis=\"yes\" value=\"",Value,"\" />"] || {Name,Value} <- Axis],
@@ -217,3 +298,37 @@ sanitize([]) ->
now_to_string(Now) ->
{{YY,MM,DD},{HH,Mi,SS}} = calendar:now_to_local_time(Now),
io_lib:format("~p-~2..0B-~2..0BT~2..0B:~2..0B:~2..0B",[YY,MM,DD,HH,Mi,SS]).
+
+make_url(undefined,_) ->
+ undefined;
+make_url(_,[]) ->
+ undefined;
+make_url(UrlBase0,Log) ->
+ UrlBase = string:strip(UrlBase0,right,$/),
+ RelativeLog = get_relative_log_url(Log),
+ string:join([UrlBase,RelativeLog],"/").
+
+get_test_root(Log) ->
+ LogParts = filename:split(Log),
+ filename:join(lists:sublist(LogParts,1,length(LogParts)-?log_depth)).
+
+get_relative_log_url(Log) ->
+ LogParts = filename:split(Log),
+ Start = length(LogParts)-?log_depth,
+ Length = ?log_depth+1,
+ string:join(lists:sublist(LogParts,Start,Length),"/").
+
+count_tcs([#testcase{name=ConfCase}|TCs],Ok,F,S)
+ when ConfCase=="init_per_suite";
+ ConfCase=="end_per_suite";
+ ConfCase=="init_per_group";
+ ConfCase=="end_per_group" ->
+ count_tcs(TCs,Ok,F,S);
+count_tcs([#testcase{result=passed}|TCs],Ok,F,S) ->
+ count_tcs(TCs,Ok+1,F,S);
+count_tcs([#testcase{result={fail,_}}|TCs],Ok,F,S) ->
+ count_tcs(TCs,Ok,F+1,S);
+count_tcs([#testcase{result={skipped,_}}|TCs],Ok,F,S) ->
+ count_tcs(TCs,Ok,F,S+1);
+count_tcs([],Ok,F,S) ->
+ {Ok+F+S,F,S}.
diff --git a/lib/common_test/test/Makefile b/lib/common_test/test/Makefile
index df816f9a61..d469d03e04 100644
--- a/lib/common_test/test/Makefile
+++ b/lib/common_test/test/Makefile
@@ -56,7 +56,8 @@ MODULES= \
ct_snmp_SUITE \
ct_group_leader_SUITE \
ct_cover_SUITE \
- ct_groups_search_SUITE
+ ct_groups_search_SUITE \
+ ct_surefire_SUITE
ERL_FILES= $(MODULES:%=%.erl)
diff --git a/lib/common_test/test/ct_groups_search_SUITE_data/groups_search_dummy_1_SUITE.erl b/lib/common_test/test/ct_groups_search_SUITE_data/groups_search_dummy_1_SUITE.erl
index 6f6922e686..1c5b572f92 100644
--- a/lib/common_test/test/ct_groups_search_SUITE_data/groups_search_dummy_1_SUITE.erl
+++ b/lib/common_test/test/ct_groups_search_SUITE_data/groups_search_dummy_1_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2009-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2009-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
diff --git a/lib/common_test/test/ct_groups_search_SUITE_data/groups_search_dummy_2_SUITE.erl b/lib/common_test/test/ct_groups_search_SUITE_data/groups_search_dummy_2_SUITE.erl
index ac3c000079..060012de29 100644
--- a/lib/common_test/test/ct_groups_search_SUITE_data/groups_search_dummy_2_SUITE.erl
+++ b/lib/common_test/test/ct_groups_search_SUITE_data/groups_search_dummy_2_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2009-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2009-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
diff --git a/lib/common_test/test/ct_master_SUITE.erl b/lib/common_test/test/ct_master_SUITE.erl
index 27243a0067..56a343a96f 100644
--- a/lib/common_test/test/ct_master_SUITE.erl
+++ b/lib/common_test/test/ct_master_SUITE.erl
@@ -117,14 +117,8 @@ ct_master_test(Config) when is_list(Config) ->
reformat(Events, ?eh),
PrivDir, []),
- find_events(NodeNames, [{tc_start,{master_SUITE,init_per_suite}},
- {tc_start,{master_SUITE,first_testcase}},
- {tc_start,{master_SUITE,second_testcase}},
- {tc_start,{master_SUITE,third_testcase}},
- {tc_start,{master_SUITE,end_per_suite}}],
- Events),
-
- ok.
+ TestEvents = events_to_check(ct_master_test),
+ ok = find_events(NodeNames, TestEvents, Events, Config).
%%%-----------------------------------------------------------------
%%% HELP FUNCTIONS
@@ -153,13 +147,15 @@ make_spec(DataDir, FileName, NodeNames, Suites, Config) ->
CM = [{config,master,filename:join(DataDir,"master/config.txt")}],
+ Env = [{"THIS_MUST_BE_SET","yes"},
+ {"SO_MUST_THIS","value"}],
NS = lists:map(
fun(NodeName) ->
{init,NodeName,[
{node_start,[{startup_functions,[]},
- {monitor_master,true}]},
- {eval,{erlang,nodes,[]}}
- ]
+ {monitor_master,true},
+ {env,Env}]},
+ {eval,{erlang,nodes,[]}}]
}
end,
NodeNames),
@@ -199,7 +195,6 @@ run_test(_Name, FileName, Config) ->
[{FileName,ok}] = ct_test_support:run({ct_master,run,[FileName]},
[{ct_master,basic_html,[true]}],
Config),
- timer:sleep(5000),
[{FileName,ok}] = ct_test_support:run({ct_master,run,[FileName]},
[{ct_master,basic_html,[false]}],
Config).
@@ -210,28 +205,26 @@ reformat(Events, EH) ->
%%%-----------------------------------------------------------------
%%% TEST EVENTS
%%%-----------------------------------------------------------------
-find_events([], _CheckEvents, _) ->
- ok;
-find_events([NodeName|NodeNames],CheckEvents,AllEvents) ->
- find_events(NodeNames, CheckEvents,
- remove_events(add_host(NodeName),CheckEvents, AllEvents, [])).
-
-remove_events(Node,[{Name,Data} | RestChecks],
- [{?eh,#event{ name = Name, node = Node, data = Data }}|RestEvs],
- Acc) ->
- remove_events(Node, RestChecks, RestEvs, Acc);
-remove_events(Node, Checks, [Event|RestEvs], Acc) ->
- remove_events(Node, Checks, RestEvs, [Event | Acc]);
-remove_events(_Node, [], [], Acc) ->
- lists:reverse(Acc);
-remove_events(Node, Events, [], Acc) ->
- test_server:format("Could not find events: ~p in ~p for node ~p",
- [Events, lists:reverse(Acc), Node]),
- exit(event_not_found).
+
+find_events(NodeNames, TestEvents, Events, Config) ->
+ [begin
+ Node = add_host(Node0),
+ io:format("Searching for events for node: ~s", [Node]),
+ ok = ct_test_support:verify_events(TestEvents, Events, Node, Config),
+ io:nl()
+ end || Node0 <- NodeNames],
+ ok.
add_host(NodeName) ->
{ok, HostName} = inet:gethostname(),
list_to_atom(atom_to_list(NodeName)++"@"++HostName).
-expected_events(_) ->
- [].
+events_to_check(_) ->
+ [{?eh,tc_start,{master_SUITE,first_testcase}},
+ {?eh,tc_done,{master_SUITE,first_testcase,ok}},
+ {?eh,tc_start,{master_SUITE,second_testcase}},
+ {?eh,tc_done,{master_SUITE,second_testcase,ok}},
+ {?eh,tc_start,{master_SUITE,third_testcase}},
+ {?eh,tc_done,{master_SUITE,third_testcase,ok}},
+ {?eh,tc_start,{master_SUITE,env_vars}},
+ {?eh,tc_done,{master_SUITE,env_vars,ok}}].
diff --git a/lib/common_test/test/ct_master_SUITE_data/master/master_SUITE.erl b/lib/common_test/test/ct_master_SUITE_data/master/master_SUITE.erl
index 032d69ad9f..8a5009ad62 100644
--- a/lib/common_test/test/ct_master_SUITE_data/master/master_SUITE.erl
+++ b/lib/common_test/test/ct_master_SUITE_data/master/master_SUITE.erl
@@ -39,7 +39,8 @@ init_per_suite(Config) ->
end_per_suite(_) ->
ok.
-all() -> [first_testcase, second_testcase, third_testcase].
+all() -> [first_testcase, second_testcase, third_testcase,
+ env_vars].
init_per_testcase(_, Config) ->
Config.
@@ -56,3 +57,9 @@ second_testcase(_)->
third_testcase(_)->
A = 4,
A = 2*2.
+
+env_vars(_) ->
+ io:format("~p\n", [os:getenv()]),
+ "yes" = os:getenv("THIS_MUST_BE_SET"),
+ "value" = os:getenv("SO_MUST_THIS"),
+ ok.
diff --git a/lib/common_test/test/ct_surefire_SUITE.erl b/lib/common_test/test/ct_surefire_SUITE.erl
new file mode 100644
index 0000000000..69e98cef48
--- /dev/null
+++ b/lib/common_test/test/ct_surefire_SUITE.erl
@@ -0,0 +1,351 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%%-------------------------------------------------------------------
+%%% File: ct_surefire_SUITE
+%%%
+%%% Description:
+%%% Test cth_surefire hook
+%%%
+%%%-------------------------------------------------------------------
+-module(ct_surefire_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("common_test/include/ct_event.hrl").
+
+-include_lib("xmerl/include/xmerl.hrl").
+
+-define(eh, ct_test_support_eh).
+
+-define(url_base,"http://my.host.com/").
+
+%%--------------------------------------------------------------------
+%% TEST SERVER CALLBACK FUNCTIONS
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+%% Description: Since Common Test starts another Test Server
+%% instance, the tests need to be performed on a separate node (or
+%% there will be clashes with logging processes etc).
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ Config1 = ct_test_support:init_per_suite(Config),
+ Config1.
+
+end_per_suite(Config) ->
+ ct_test_support:end_per_suite(Config).
+
+init_per_testcase(TestCase, Config) ->
+ ct_test_support:init_per_testcase(TestCase, Config).
+
+end_per_testcase(TestCase, Config) ->
+ ct_test_support:end_per_testcase(TestCase, Config).
+
+suite() -> [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [
+ default,
+ absolute_path,
+ relative_path,
+ url,
+ logdir
+ ].
+
+%%--------------------------------------------------------------------
+%% TEST CASES
+%%--------------------------------------------------------------------
+
+%%%-----------------------------------------------------------------
+%%%
+default(Config) when is_list(Config) ->
+ run(default,[cth_surefire],Config),
+ PrivDir = ?config(priv_dir,Config),
+ XmlRe = filename:join([PrivDir,"*","junit_report.xml"]),
+ check_xml(default,XmlRe).
+
+absolute_path(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir,Config),
+ Path = filename:join(PrivDir,"abspath.xml"),
+ run(absolute_path,[{cth_surefire,[{path,Path}]}],Config),
+ check_xml(absolute_path,Path).
+
+relative_path(Config) when is_list(Config) ->
+ Path = "relpath.xml",
+ run(relative_path,[{cth_surefire,[{path,Path}]}],Config),
+ PrivDir = ?config(priv_dir,Config),
+ XmlRe = filename:join([PrivDir,"*",Path]),
+ check_xml(relative_path,XmlRe).
+
+url(Config) when is_list(Config) ->
+ Path = "url.xml",
+ run(url,[{cth_surefire,[{url_base,?url_base},
+ {path,Path}]}],Config),
+ PrivDir = ?config(priv_dir,Config),
+ XmlRe = filename:join([PrivDir,"*",Path]),
+ check_xml(url,XmlRe).
+
+logdir(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir,Config),
+ LogDir = filename:join(PrivDir,"specific_logdir"),
+ file:make_dir(LogDir),
+ Path = "logdir.xml",
+ run(logdir,[{cth_surefire,[{path,Path}]}],Config,[{logdir,LogDir}]),
+ PrivDir = ?config(priv_dir,Config),
+ XmlRe = filename:join([LogDir,"*",Path]),
+ check_xml(logdir,XmlRe).
+
+%%%-----------------------------------------------------------------
+%%% HELP FUNCTIONS
+%%%-----------------------------------------------------------------
+run(Case,CTHs,Config) ->
+ run(Case,CTHs,Config,[]).
+run(Case,CTHs,Config,ExtraOpts) ->
+ DataDir = ?config(data_dir, Config),
+ Suite = filename:join(DataDir, "surefire_SUITE"),
+ {Opts,ERPid} = setup([{suite,Suite},{ct_hooks,CTHs},{label,Case}|ExtraOpts],
+ Config),
+ ok = execute(Case, Opts, ERPid, Config).
+
+setup(Test, Config) ->
+ Opts0 = ct_test_support:get_opts(Config),
+ Opts1 =
+ case lists:keymember(logdir,1,Test) of
+ true -> lists:keydelete(logdir,1,Opts0);
+ false -> Opts0
+ end,
+ Level = ?config(trace_level, Config),
+ EvHArgs = [{cbm,ct_test_support},{trace_level,Level}],
+ Opts = Opts1 ++ [{event_handler,{?eh,EvHArgs}}|Test],
+ ERPid = ct_test_support:start_event_receiver(Config),
+ {Opts,ERPid}.
+
+execute(Name, Opts, ERPid, Config) ->
+ ok = ct_test_support:run(Opts, Config),
+ Events = ct_test_support:get_events(ERPid, Config),
+
+ ct_test_support:log_events(Name,
+ reformat(Events, ?eh),
+ ?config(priv_dir, Config),
+ Opts),
+
+ TestEvents = events_to_check(Name),
+ ct_test_support:verify_events(TestEvents, Events, Config).
+
+reformat(Events, EH) ->
+ ct_test_support:reformat(Events, EH).
+
+%%%-----------------------------------------------------------------
+%%% TEST EVENTS
+%%%-----------------------------------------------------------------
+events_to_check(Test) ->
+ %% 2 tests (ct:run_test + script_start) is default
+ events_to_check(Test, 2).
+
+events_to_check(_, 0) ->
+ [];
+events_to_check(Test, N) ->
+ test_events(Test) ++ events_to_check(Test, N-1).
+
+test_events(_) ->
+ [{?eh,start_logging,'_'},
+ {?eh,start_info,{1,1,9}},
+ {?eh,tc_start,{surefire_SUITE,init_per_suite}},
+ {?eh,tc_done,{surefire_SUITE,init_per_suite,ok}},
+ {?eh,tc_start,{surefire_SUITE,tc_ok}},
+ {?eh,tc_done,{surefire_SUITE,tc_ok,ok}},
+ {?eh,test_stats,{1,0,{0,0}}},
+ {?eh,tc_start,{surefire_SUITE,tc_fail}},
+ {?eh,tc_done,{surefire_SUITE,tc_fail,
+ {failed,{error,{test_case_failed,"this test should fail"}}}}},
+ {?eh,test_stats,{1,1,{0,0}}},
+ {?eh,tc_start,{surefire_SUITE,tc_skip}},
+ {?eh,tc_done,{surefire_SUITE,tc_skip,{skipped,"this test is skipped"}}},
+ {?eh,test_stats,{1,1,{1,0}}},
+ {?eh,tc_start,{surefire_SUITE,tc_autoskip_require}},
+ {?eh,tc_done,{surefire_SUITE,tc_autoskip_require,
+ {skipped,{require_failed,'_'}}}},
+ {?eh,test_stats,{1,1,{1,1}}},
+ [{?eh,tc_start,{surefire_SUITE,{init_per_group,g,[]}}},
+ {?eh,tc_done,{surefire_SUITE,{init_per_group,g,[]},ok}},
+ {?eh,tc_start,{surefire_SUITE,tc_ok}},
+ {?eh,tc_done,{surefire_SUITE,tc_ok,ok}},
+ {?eh,test_stats,{2,1,{1,1}}},
+ {?eh,tc_start,{surefire_SUITE,tc_fail}},
+ {?eh,tc_done,{surefire_SUITE,tc_fail,
+ {failed,{error,{test_case_failed,"this test should fail"}}}}},
+ {?eh,test_stats,{2,2,{1,1}}},
+ {?eh,tc_start,{surefire_SUITE,tc_skip}},
+ {?eh,tc_done,{surefire_SUITE,tc_skip,{skipped,"this test is skipped"}}},
+ {?eh,test_stats,{2,2,{2,1}}},
+ {?eh,tc_start,{surefire_SUITE,tc_autoskip_require}},
+ {?eh,tc_done,{surefire_SUITE,tc_autoskip_require,
+ {skipped,{require_failed,'_'}}}},
+ {?eh,test_stats,{2,2,{2,2}}},
+ {?eh,tc_start,{surefire_SUITE,{end_per_group,g,[]}}},
+ {?eh,tc_done,{surefire_SUITE,{end_per_group,g,[]},ok}}],
+ [{?eh,tc_start,{surefire_SUITE,{init_per_group,g_fail,[]}}},
+ {?eh,tc_done,{surefire_SUITE,{init_per_group,g_fail,[]},
+ {failed,{error,all_cases_should_be_skipped}}}},
+ {?eh,tc_auto_skip,{surefire_SUITE,tc_ok,
+ {failed,
+ {surefire_SUITE,init_per_group,
+ {'EXIT',all_cases_should_be_skipped}}}}},
+ {?eh,test_stats,{2,2,{2,3}}},
+ {?eh,tc_auto_skip,{surefire_SUITE,end_per_group,
+ {failed,
+ {surefire_SUITE,init_per_group,
+ {'EXIT',all_cases_should_be_skipped}}}}}],
+ {?eh,tc_start,{surefire_SUITE,end_per_suite}},
+ {?eh,tc_done,{surefire_SUITE,end_per_suite,ok}},
+ {?eh,stop_logging,[]}].
+
+
+%%%-----------------------------------------------------------------
+%%% Check generated xml log files
+check_xml(Case,XmlRe) ->
+ case filelib:wildcard(XmlRe) of
+ [] ->
+ ct:fail("No xml files found with regexp ~p~n", [XmlRe]);
+ [_] = Xmls when Case==absolute_path ->
+ do_check_xml(Case,Xmls);
+ [_,_] = Xmls ->
+ do_check_xml(Case,Xmls)
+ end.
+
+%% Allowed structure:
+%% <testsuites>
+%% <testsuite>
+%% <properties>
+%% <property/>
+%% ...
+%% </properties>
+%% <testcase>
+%% [<failure/> | <error/> | <skipped/> ]
+%% </testcase>
+%% ...
+%% </testsuite>
+%% ...
+%% </testsuites>
+do_check_xml(Case,[Xml|Xmls]) ->
+ ct:log("Checking <a href=~p>~s</a>~n",[Xml,Xml]),
+ {E,_} = xmerl_scan:file(Xml),
+ Expected = events_to_result(lists:flatten(test_events(Case))),
+ ParseResult = testsuites(Case,E),
+ ct:log("Expecting: ~p~n",[[Expected]]),
+ ct:log("Actual : ~p~n",[ParseResult]),
+ [Expected] = ParseResult,
+ do_check_xml(Case,Xmls);
+do_check_xml(_,[]) ->
+ ok.
+
+%% Scanning the XML to get the same type of result as events_to_result/1
+testsuites(Case,#xmlElement{name=testsuites,content=TS}) ->
+ %% OTP-10589 - move properties element to <testsuite>
+ false = lists:keytake(properties,#xmlElement.name,TS),
+ testsuite(Case,TS).
+
+testsuite(Case,[#xmlElement{name=testsuite,content=TC,attributes=A}|TS]) ->
+ {ET,EF,ES} = events_to_numbers(lists:flatten(test_events(Case))),
+ {T,E,F,S} = get_numbers_from_attrs(A,false,false,false,false),
+ ct:log("Expecting total:~p, error:~p, failure:~p, skipped:~p~n",[ET,0,EF,ES]),
+ ct:log("Actual total:~p, error:~p, failure:~p, skipped:~p~n",[T,E,F,S]),
+ {ET,0,EF,ES} = {T,E,F,S},
+
+ %% properties should only be there if given a options to hook
+ false = lists:keytake(properties,#xmlElement.name,TC),
+ %% system-out and system-err is not used by common_test
+ false = lists:keytake('system-out',#xmlElement.name,TC),
+ false = lists:keytake('system-err',#xmlElement.name,TC),
+ R=testcase(Case,TC),
+ [R|testsuite(Case,TS)];
+testsuite(_Case,[]) ->
+ [].
+
+testcase(url=Case,[#xmlElement{name=testcase,attributes=A,content=C}|TC]) ->
+ R = failed_or_skipped(C),
+ case R of
+ [s] ->
+ case lists:keyfind(url,#xmlAttribute.name,A) of
+ false -> ok;
+ #xmlAttribute{value=UrlAttr} ->
+ lists:keyfind(url,#xmlAttribute.name,A),
+ true = lists:prefix(?url_base,UrlAttr)
+ end;
+ _ ->
+ #xmlAttribute{value=UrlAttr} =
+ lists:keyfind(url,#xmlAttribute.name,A),
+ true = lists:prefix(?url_base,UrlAttr)
+ end,
+ [R|testcase(Case,TC)];
+testcase(Case,[#xmlElement{name=testcase,attributes=A,content=C}|TC]) ->
+ false = lists:keyfind(url,#xmlAttribute.name,A),
+ R = failed_or_skipped(C),
+ [R|testcase(Case,TC)];
+testcase(_Case,[]) ->
+ [].
+
+failed_or_skipped([#xmlElement{name=failure}|E]) ->
+ [f|failed_or_skipped(E)];
+failed_or_skipped([#xmlElement{name=error}|E]) ->
+ [e|failed_or_skipped(E)];
+failed_or_skipped([#xmlElement{name=skipped}|E]) ->
+ [s|failed_or_skipped(E)];
+failed_or_skipped([]) ->
+ [].
+
+%% Using the expected events to produce the expected result of the XML scanning.
+%% The result is a list of test suites:
+%% Testsuites = [Testsuite]
+%% Testsuite = [Testcase]
+%% Testcase = [] | [f] | [s], indicating ok, failed and skipped respectively
+events_to_result([{?eh,tc_done,{_Suite,_Case,R}}|E]) ->
+ [result(R)|events_to_result(E)];
+events_to_result([{?eh,tc_auto_skip,_}|E]) ->
+ [[s]|events_to_result(E)];
+events_to_result([_|E]) ->
+ events_to_result(E);
+events_to_result([]) ->
+ [].
+
+result(ok) ->[];
+result({skipped,_}) -> [s];
+result({failed,_}) -> [f].
+
+%% Using the expected events' last test_stats element to produce the
+%% expected number of totla, errors, failed and skipped testcases.
+events_to_numbers(E) ->
+ RevE = lists:reverse(E),
+ {?eh,test_stats,{Ok,F,{US,AS}}} = lists:keyfind(test_stats,2,RevE),
+ {Ok+F+US+AS,F,US+AS}.
+
+get_numbers_from_attrs([#xmlAttribute{name=tests,value=X}|A],false,E,F,S) ->
+ get_numbers_from_attrs(A,list_to_integer(X),E,F,S);
+get_numbers_from_attrs([#xmlAttribute{name=errors,value=X}|A],T,false,F,S) ->
+ get_numbers_from_attrs(A,T,list_to_integer(X),F,S);
+get_numbers_from_attrs([#xmlAttribute{name=failures,value=X}|A],T,E,false,S) ->
+ get_numbers_from_attrs(A,T,E,list_to_integer(X),S);
+get_numbers_from_attrs([#xmlAttribute{name=skipped,value=X}|A],T,E,F,false) ->
+ get_numbers_from_attrs(A,T,E,F,list_to_integer(X));
+get_numbers_from_attrs([_|A],T,E,F,S) ->
+ get_numbers_from_attrs(A,T,E,F,S);
+get_numbers_from_attrs([],T,E,F,S) ->
+ {T,E,F,S}.
diff --git a/lib/common_test/test/ct_surefire_SUITE_data/surefire_SUITE.erl b/lib/common_test/test/ct_surefire_SUITE_data/surefire_SUITE.erl
new file mode 100644
index 0000000000..677aee46c5
--- /dev/null
+++ b/lib/common_test/test/ct_surefire_SUITE_data/surefire_SUITE.erl
@@ -0,0 +1,92 @@
+%%--------------------------------------------------------------------
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%----------------------------------------------------------------------
+%% File: surefire_SUITE.erl
+%%
+%% Description:
+%% This file contains the test cases for cth_surefire.
+%%
+%% @author Support
+%% @doc Test of surefire support in common_test
+%% @end
+%%----------------------------------------------------------------------
+%%----------------------------------------------------------------------
+-module(surefire_SUITE).
+-include_lib("common_test/include/ct.hrl").
+
+-compile(export_all).
+
+%% Default timetrap timeout (set in init_per_testcase).
+-define(default_timeout, ?t:minutes(1)).
+
+all() ->
+ testcases() ++ [{group,g},{group,g_fail}].
+
+groups() ->
+ [{g,testcases()},
+ {g_fail,[tc_ok]}].
+
+testcases() ->
+ [tc_ok,
+ tc_fail,
+ tc_skip,
+ tc_autoskip_require].
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(Config) ->
+ Config.
+
+init_per_group(g_fail, _Config) ->
+ exit(all_cases_should_be_skipped);
+init_per_group(_, Config) ->
+ Config.
+
+end_per_group(_Group, Config) ->
+ Config.
+
+init_per_testcase(_Case, Config) ->
+ Dog = test_server:timetrap(?default_timeout),
+ [{watchdog, Dog}|Config].
+
+end_per_testcase(_Case, Config) ->
+ Dog=?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+%%%-----------------------------------------------------------------
+%%% Test cases
+break(_Config) ->
+ test_server:break(""),
+ ok.
+
+tc_ok(_Config) ->
+ ok.
+
+tc_fail(_Config) ->
+ ct:fail("this test should fail").
+
+tc_skip(_Config) ->
+ {skip,"this test is skipped"}.
+
+tc_autoskip_require() ->
+ [{require,whatever}].
+tc_autoskip_require(Config) ->
+ ct:fail("this test should never be executed - it should be autoskipped").
diff --git a/lib/common_test/test/ct_test_support.erl b/lib/common_test/test/ct_test_support.erl
index 901e53c71d..fc572aa82f 100644
--- a/lib/common_test/test/ct_test_support.erl
+++ b/lib/common_test/test/ct_test_support.erl
@@ -32,7 +32,7 @@
run/2, run/3, run/4, get_opts/1, wait_for_ct_stop/1]).
-export([handle_event/2, start_event_receiver/1, get_events/2,
- verify_events/3, reformat/2, log_events/4,
+ verify_events/3, verify_events/4, reformat/2, log_events/4,
join_abs_dirs/2]).
-export([ct_test_halt/1]).
@@ -117,11 +117,7 @@ end_per_suite(Config) ->
CTNode = proplists:get_value(ct_node, Config),
PrivDir = proplists:get_value(priv_dir, Config),
true = rpc:call(CTNode, code, del_path, [filename:join(PrivDir,"")]),
- case test_server:is_cover() of
- true -> cover:flush(CTNode);
- false -> ok
- end,
- slave:stop(CTNode),
+ slave_stop(CTNode),
ok.
%%%-----------------------------------------------------------------
@@ -152,11 +148,7 @@ end_per_testcase(_TestCase, Config) ->
case wait_for_ct_stop(CTNode) of
%% Common test was not stopped to we restart node.
false ->
- case test_server:is_cover() of
- true -> cover:flush(CTNode);
- false -> ok
- end,
- slave:stop(CTNode),
+ slave_stop(CTNode),
start_slave(Config,proplists:get_value(trace_level,Config)),
{fail, "Could not stop common_test"};
true ->
@@ -370,6 +362,14 @@ verify_events(TEvs, Evs, Config) ->
ok
end.
+verify_events(TEvs, Evs, Node, Config) ->
+ case catch verify_events1(TEvs, Evs, Node, Config) of
+ {'EXIT',Reason} ->
+ Reason;
+ _ ->
+ ok
+ end.
+
verify_events1([TestEv|_], [{TEH,#event{name=stop_logging,node=Node,data=_}}|_], Node, _)
when element(1,TestEv) == TEH, element(2,TestEv) =/= stop_logging ->
test_server:format("Failed to find ~p in the list of events!~n", [TestEv]),
@@ -1266,3 +1266,22 @@ rm_files([F | Fs]) ->
rm_files([]) ->
ok.
+%%%-----------------------------------------------------------------
+%%%
+slave_stop(Node) ->
+ Cover = test_server:is_cover(),
+ if Cover-> cover:flush(Node);
+ true -> ok
+ end,
+ erlang:monitor_node(Node, true),
+ slave:stop(Node),
+ receive
+ {nodedown, Node} ->
+ if Cover -> cover:stop(Node);
+ true -> ok
+ end
+ after 5000 ->
+ erlang:monitor_node(Node, false),
+ receive {nodedown, Node} -> ok after 0 -> ok end %flush
+ end,
+ ok.
diff --git a/lib/common_test/vsn.mk b/lib/common_test/vsn.mk
index 5c9fdfc47e..f9bb22867e 100644
--- a/lib/common_test/vsn.mk
+++ b/lib/common_test/vsn.mk
@@ -1 +1 @@
-COMMON_TEST_VSN = 1.6.2.1
+COMMON_TEST_VSN = 1.6.3