aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/common_test/doc/src/common_test_app.xml17
-rw-r--r--lib/common_test/doc/src/ct_hooks.xml125
-rw-r--r--lib/common_test/doc/src/notes.xml66
-rw-r--r--lib/common_test/doc/src/write_test_chapter.xml18
-rw-r--r--lib/common_test/src/ct_framework.erl314
-rw-r--r--lib/common_test/src/ct_groups.erl83
-rw-r--r--lib/common_test/src/ct_hooks.erl77
-rw-r--r--lib/common_test/src/ct_logs.erl14
-rw-r--r--lib/common_test/src/test_server.erl216
-rw-r--r--lib/common_test/src/test_server_ctrl.erl137
-rw-r--r--lib/common_test/src/test_server_node.erl2
-rw-r--r--lib/common_test/src/test_server_sup.erl2
-rw-r--r--lib/common_test/test/Makefile4
-rw-r--r--lib/common_test/test/ct_error_SUITE.erl49
-rw-r--r--lib/common_test/test/ct_hooks_SUITE.erl345
-rw-r--r--lib/common_test/test/ct_hooks_SUITE_data/cth/tests/all_and_groups_SUITE.erl47
-rw-r--r--lib/common_test/test/ct_hooks_SUITE_data/cth/tests/all_and_groups_cth.erl100
-rw-r--r--lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_match_state_cth.erl58
-rw-r--r--lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_scope_suite_group_only_cth_SUITE.erl54
-rw-r--r--lib/common_test/test/ct_hooks_SUITE_data/cth/tests/empty_cth.erl28
-rw-r--r--lib/common_test/test/ct_tc_repeat_SUITE.erl438
-rw-r--r--lib/common_test/test/ct_tc_repeat_SUITE_data/tc_repeat_SUITE.erl85
-rw-r--r--lib/common_test/test/ct_util_SUITE.erl490
-rw-r--r--lib/common_test/test/ct_util_SUITE_data/ct_util_cth.erl105
-rw-r--r--lib/common_test/test/ct_util_SUITE_data/curr_tc_SUITE.erl59
-rw-r--r--lib/compiler/src/beam_asm.erl26
-rw-r--r--lib/diameter/doc/src/notes.xml17
-rw-r--r--lib/diameter/src/base/diameter_dist.erl12
-rw-r--r--lib/diameter/src/diameter.appup.src6
-rw-r--r--lib/diameter/vsn.mk2
l---------lib/edoc/doc/edoc.dtd1
l---------lib/edoc/doc/edoc_doclet.hrl1
-rw-r--r--lib/edoc/doc/src/Makefile6
-rw-r--r--lib/edoc/include/edoc_doclet.hrl2
-rw-r--r--lib/edoc/src/edoc.erl2
-rw-r--r--lib/edoc/src/edoc_doclet.erl2
-rw-r--r--lib/erl_interface/doc/src/notes.xml17
-rw-r--r--lib/inets/doc/src/notes.xml24
-rw-r--r--lib/inets/src/http_server/httpd_example.erl21
-rw-r--r--lib/inets/src/http_server/mod_esi.erl4
-rw-r--r--lib/inets/test/httpd_SUITE.erl165
-rw-r--r--lib/inets/vsn.mk2
-rw-r--r--lib/os_mon/src/cpu_sup.erl4
-rw-r--r--lib/snmp/doc/src/notes.xml17
-rw-r--r--lib/snmp/src/agent/snmpa_agent.erl119
-rw-r--r--lib/snmp/src/agent/snmpa_local_db.erl9
-rw-r--r--lib/snmp/src/misc/snmp_misc.erl112
-rw-r--r--lib/snmp/src/misc/snmp_verbosity.erl16
-rw-r--r--lib/snmp/test/snmp_agent_test.erl42
-rw-r--r--lib/snmp/test/snmp_agent_test_lib.erl348
-rw-r--r--lib/snmp/test/snmp_manager_test.erl185
-rw-r--r--lib/snmp/test/snmp_test_lib.erl98
-rw-r--r--lib/snmp/test/snmp_test_lib.hrl25
-rw-r--r--lib/snmp/test/snmp_test_mgr.erl74
-rw-r--r--lib/snmp/test/snmp_test_mgr_misc.erl291
-rw-r--r--lib/ssh/doc/src/notes.xml18
-rw-r--r--lib/stdlib/doc/src/qlc.xml81
-rw-r--r--lib/stdlib/src/erl_pp.erl150
-rw-r--r--lib/stdlib/test/erl_pp_SUITE.erl57
-rw-r--r--lib/stdlib/test/qlc_SUITE.erl66
-rw-r--r--lib/stdlib/test/shell_SUITE.erl6
l---------lib/syntax_tools/doc/demo.erl1
-rw-r--r--lib/syntax_tools/doc/overview.edoc2
-rw-r--r--lib/syntax_tools/doc/src/Makefile7
-rw-r--r--lib/tools/doc/src/notes.xml16
65 files changed, 4218 insertions, 769 deletions
diff --git a/lib/common_test/doc/src/common_test_app.xml b/lib/common_test/doc/src/common_test_app.xml
index 7887a2c3ea..081adeaec7 100644
--- a/lib/common_test/doc/src/common_test_app.xml
+++ b/lib/common_test/doc/src/common_test_app.xml
@@ -72,14 +72,15 @@
<fsummary>Returns the list of all test case groups and test cases
in the module.</fsummary>
<type>
- <v>Tests = [TestCase | {group,GroupName} | {group,GroupName,Properties} | {group,GroupName,Properties,SubGroups}]</v>
+ <v>Tests = [TestCase | {testcase,TestCase,TCRepeatProps} | {group,GroupName} | {group,GroupName,Properties} | {group,GroupName,Properties,SubGroups}]</v>
<v>TestCase = atom()</v>
+ <v>TCRepeatProps = [{repeat,N} | {repeat_until_ok,N} | {repeat_until_fail,N}]</v>
<v>GroupName = atom()</v>
- <v>Properties = [parallel | sequence | Shuffle | {RepeatType,N}] | default</v>
+ <v>Properties = [parallel | sequence | Shuffle | {GroupRepeatType,N}] | default</v>
<v>SubGroups = [{GroupName,Properties} | {GroupName,Properties,SubGroups}]</v>
<v>Shuffle = shuffle | {shuffle,Seed}</v>
<v>Seed = {integer(),integer(),integer()}</v>
- <v>RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | repeat_until_any_ok | repeat_until_any_fail</v>
+ <v>GroupRepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | repeat_until_any_ok | repeat_until_any_fail</v>
<v>N = integer() | forever</v>
<v>Reason = term()</v>
</type>
@@ -91,7 +92,8 @@
test suite module to be executed. This list also specifies the
order the cases and groups are executed by <c>Common Test</c>.
A test case is represented by an atom,
- the name of the test case function. A test case group is
+ the name of the test case function, or a <c>testcase</c> tuple
+ indicating that the test case shall be repeated. A test case group is
represented by a <c>group</c> tuple, where <c>GroupName</c>,
an atom, is the name of the group (defined in
<seealso marker="#Module:groups-0"><c>groups/0</c></seealso>).
@@ -121,12 +123,13 @@
<v>GroupDefs = [Group]</v>
<v>Group = {GroupName,Properties,GroupsAndTestCases}</v>
<v>GroupName = atom()</v>
- <v>Properties = [parallel | sequence | Shuffle | {RepeatType,N}]</v>
- <v>GroupsAndTestCases = [Group | {group,GroupName} | TestCase]</v>
+ <v>Properties = [parallel | sequence | Shuffle | {GroupRepeatType,N}]</v>
+ <v>GroupsAndTestCases = [Group | {group,GroupName} | TestCase | {testcase,TestCase,TCRepeatProps}]</v>
<v>TestCase = atom()</v>
+ <v>TCRepeatProps = [{repeat,N} | {repeat_until_ok,N} | {repeat_until_fail,N}]</v>
<v>Shuffle = shuffle | {shuffle,Seed}</v>
<v>Seed = {integer(),integer(),integer()}</v>
- <v>RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | repeat_until_any_ok | repeat_until_any_fail</v>
+ <v>GroupRepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | repeat_until_any_ok | repeat_until_any_fail</v>
<v>N = integer() | forever</v>
</type>
diff --git a/lib/common_test/doc/src/ct_hooks.xml b/lib/common_test/doc/src/ct_hooks.xml
index ff0d0117cd..ff9969ebc3 100644
--- a/lib/common_test/doc/src/ct_hooks.xml
+++ b/lib/common_test/doc/src/ct_hooks.xml
@@ -109,6 +109,131 @@
</func>
<func>
+ <name since="OTP @OTP-14746@">Module:post_groups(SuiteName, GroupDefs) -&gt; NewGroupDefs</name>
+ <fsummary>Called after groups/0.</fsummary>
+ <type>
+ <v>SuiteName = atom()</v>
+ <v>GroupDefs = NewGroupDefs = [Group]</v>
+ <v>Group = {GroupName,Properties,GroupsAndTestCases}</v>
+ <v>GroupName = atom()</v>
+ <v>Properties = [parallel | sequence | Shuffle | {GroupRepeatType,N}]</v>
+ <v>GroupsAndTestCases = [Group | {group,GroupName} | TestCase | {testcase,TestCase,TCRepeatProps}]</v>
+ <v>TestCase = atom()</v>
+ <v>TCRepeatProps = [{repeat,N} | {repeat_until_ok,N} | {repeat_until_fail,N}]</v>
+ <v>Shuffle = shuffle | {shuffle,Seed}</v>
+ <v>Seed = {integer(),integer(),integer()}</v>
+ <v>GroupRepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | repeat_until_any_ok | repeat_until_any_fail</v>
+ <v>N = integer() | forever</v>
+ </type>
+ <desc>
+ <p>OPTIONAL</p>
+
+ <p>This function is called after
+ <seealso marker="common_test#Module:groups-0"><c>groups/0</c></seealso>.
+ It is used to modify the test group definitions, for
+ instance to add or remove groups or change group properties.</p>
+
+ <p><c>GroupDefs</c> is what
+ <seealso marker="common_test#Module:groups-0"><c>groups/0</c></seealso>
+ returned, that is, a list of group definitions.</p>
+
+ <p><c>NewGroupDefs</c> is the possibly modified version of this list.</p>
+
+ <p>This function is called only if the CTH is added before
+ <c>init_per_suite</c> is run. For details, see section
+ <seealso marker="ct_hooks_chapter#scope">CTH Scope</seealso>
+ in the User's Guide.</p>
+
+ <p>Notice that for CTHs that are installed by means of the
+ <seealso marker="common_test#Module:suite-0"><c>suite/0</c></seealso>
+ function, <c>post_groups/2</c> is called before
+ the <seealso marker="#Module:init-2"><c>init/2</c></seealso>
+ hook function. However, for CTHs that are installed by means
+ of the CT start flag,
+ the <seealso marker="#Module:init-2"><c>init/2</c></seealso>
+ function is called first.</p>
+
+ <note>
+ <p>Prior to each test execution, Common Test does a
+ simulated test run in order to count test suites, groups
+ and cases for logging purposes. This causes
+ the <c>post_groups/2</c> hook function to always be called
+ twice. For this reason, side effects are best avoided in
+ this callback.</p>
+ </note>
+ </desc>
+ </func>
+
+ <func>
+ <name since="OTP @OTP-14746@">Module:post_all(SuiteName, Return, GroupDefs) -&gt; NewReturn</name>
+ <fsummary>Called after all/0.</fsummary>
+ <type>
+ <v>SuiteName = atom()</v>
+ <v>Return = NewReturn = Tests | {skip,Reason}</v>
+ <v>Tests = [TestCase | {testcase,TestCase,TCRepeatProps} | {group,GroupName} | {group,GroupName,Properties} | {group,GroupName,Properties,SubGroups}]</v>
+ <v>TestCase = atom()</v>
+ <v>TCRepeatProps = [{repeat,N} | {repeat_until_ok,N} | {repeat_until_fail,N}]</v>
+ <v>GroupName = atom()</v>
+ <v>Properties = GroupProperties | default</v>
+ <v>SubGroups = [{GroupName,Properties} | {GroupName,Properties,SubGroups}]</v>
+ <v>Shuffle = shuffle | {shuffle,Seed}</v>
+ <v>Seed = {integer(),integer(),integer()}</v>
+ <v>GroupRepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | repeat_until_any_ok | repeat_until_any_fail</v>
+ <v>N = integer() | forever</v>
+ <v>GroupDefs = NewGroupDefs = [Group]</v>
+ <v>Group = {GroupName,GroupProperties,GroupsAndTestCases}</v>
+ <v>GroupProperties = [parallel | sequence | Shuffle | {GroupRepeatType,N}]</v>
+ <v>GroupsAndTestCases = [Group | {group,GroupName} | TestCase]</v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+ <p>OPTIONAL</p>
+
+ <p>This function is called after
+ <seealso marker="common_test#Module:all-0"><c>all/0</c></seealso>.
+ It is used to modify the set of test cases and test group to
+ be executed, for instance to add or remove test cases and
+ groups, change group properties, or even skip all tests in
+ the suite.</p>
+
+ <p><c>Return</c> is what
+ <seealso marker="common_test#Module:all-0"><c>all/0</c></seealso>
+ returned, that is, a list of test cases and groups to be
+ executed, or a tuple <c>{skip,Reason}</c>.</p>
+
+ <p><c>GroupDefs</c> is what
+ <seealso marker="common_test#Module:groups-0"><c>groups/0</c></seealso>
+ or the <c>post_groups/2</c> hook returned, that is, a list
+ of group definitions.</p>
+
+ <p><c>NewReturn</c> is the possibly modified version of <c>Return</c>.</p>
+
+ <p>This function is called only if the CTH is added before
+ <c>init_per_suite</c> is run. For details, see section
+ <seealso marker="ct_hooks_chapter#scope">CTH Scope</seealso>
+ in the User's Guide.</p>
+
+ <p>Notice that for CTHs that are installed by means of the
+ <seealso marker="common_test#Module:suite-0"><c>suite/0</c></seealso>
+ function, <c>post_all/2</c> is called before
+ the <seealso marker="#Module:init-2"><c>init/2</c></seealso>
+ hook function. However, for CTHs that are installed by means
+ of the CT start flag,
+ the <seealso marker="#Module:init-2"><c>init/2</c></seealso>
+ function is called first.</p>
+
+ <note>
+ <p>Prior to each test execution, Common Test does a
+ simulated test run in order to count test suites, groups
+ and cases for logging purposes. This causes
+ the <c>post_all/3</c> hook function to always be called
+ twice. For this reason, side effects are best avoided in
+ this callback.</p>
+ </note>
+ </desc>
+ </func>
+
+ <func>
<name since="OTP R14B02">Module:pre_init_per_suite(SuiteName, InitData, CTHState) -&gt; Result</name>
<fsummary>Called before init_per_suite.</fsummary>
<type>
diff --git a/lib/common_test/doc/src/notes.xml b/lib/common_test/doc/src/notes.xml
index 9bffc23150..a68cc3cca7 100644
--- a/lib/common_test/doc/src/notes.xml
+++ b/lib/common_test/doc/src/notes.xml
@@ -150,6 +150,72 @@
</section>
+<section><title>Common_Test 1.15.4.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The test result when a hook function fails is in general
+ the same as if the function that the hook is associated
+ with fails. For example, if <c>post_init_per_testcase</c>
+ fails the result is that the test case is skipped, as is
+ the case when <c>init_per_testcase</c> fails.This,
+ however, was earlier not true for timetrap timeouts or
+ other error situations where the process running the hook
+ function was killed. This is now corrected, so the error
+ handling should be the same no matter how the hook
+ function fails.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-15717 Aux Id: ERIERL-334 </p>
+ </item>
+ <item>
+ <p>
+ In some rare cases, when two common_test nodes used the
+ same log directory, a timing problem could occur which
+ caused common_test to crash because it's log cache file
+ was unexpectedly empty. This is now corrected.</p>
+ <p>
+ Own Id: OTP-15758 Aux Id: ERIERL-342 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Two new common_test hook functions are introduced:</p>
+ <p>
+ <c>post_groups/2</c>, which is called after
+ <c>Suite:groups/0</c><br/> <c>post_all/3</c>, which is
+ called after <c>Suite:all/0</c></p>
+ <p>
+ These functions allow modifying the return values from
+ the <c>groups/0</c> and <c>all/0</c> functions,
+ respectively.</p>
+ <p>
+ A new term, <c>{testcase,TestCase,RepeatProperties}</c>
+ is now also allowed in the return from <c>all/0</c>. This
+ can be used for repeating a single test case a specific
+ number of times, or until it fails or succeeds once.</p>
+ <p>
+ Own Id: OTP-14746 Aux Id: ERIERL-143 </p>
+ </item>
+ <item>
+ <p>
+ OTP internal test improvements.</p>
+ <p>
+ Own Id: OTP-15716</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Common_Test 1.15.4.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/common_test/doc/src/write_test_chapter.xml b/lib/common_test/doc/src/write_test_chapter.xml
index 82dc06834f..5eed748b08 100644
--- a/lib/common_test/doc/src/write_test_chapter.xml
+++ b/lib/common_test/doc/src/write_test_chapter.xml
@@ -455,8 +455,10 @@
GroupDefs = [GroupDef]
GroupDef = {GroupName,Properties,GroupsAndTestCases}
GroupName = atom()
- GroupsAndTestCases = [GroupDef | {group,GroupName} | TestCase]
- TestCase = atom()</pre>
+ GroupsAndTestCases = [GroupDef | {group,GroupName} | TestCase |
+ {testcase,TestCase,TCRepeatProps}]
+ TestCase = atom()
+ TCRepeatProps = [{repeat,N} | {repeat_until_ok,N} | {repeat_until_fail,N}]</pre>
<p><c>GroupName</c> is the name of the group and must be unique within
the test suite module. Groups can be nested, by including a group definition
@@ -464,11 +466,11 @@
<c>Properties</c> is the list of execution
properties for the group. The possible values are as follows:</p>
<pre>
- Properties = [parallel | sequence | Shuffle | {RepeatType,N}]
+ Properties = [parallel | sequence | Shuffle | {GroupRepeatType,N}]
Shuffle = shuffle | {shuffle,Seed}
Seed = {integer(),integer(),integer()}
- RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail |
- repeat_until_any_ok | repeat_until_any_fail
+ GroupRepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail |
+ repeat_until_any_ok | repeat_until_any_fail
N = integer() | forever</pre>
<p><em>Explanations:</em></p>
@@ -481,8 +483,8 @@
Dependencies Between Test Cases and Suites.</p></item>
<tag><c>shuffle</c></tag>
<item><p>The cases in the group are executed in random order.</p></item>
- <tag><c>repeat</c></tag>
- <item><p>Orders <c>Common Test</c> to repeat execution of the cases in the
+ <tag><c>repeat, repeat_until_*</c></tag>
+ <item><p>Orders <c>Common Test</c> to repeat execution of all the cases in the
group a given number of times, or until any, or all, cases fail or succeed.</p></item>
</taglist>
@@ -496,7 +498,7 @@
<c>{group,GroupName}</c> to the <c>all/0</c> list.</p>
<p><em>Example:</em></p>
<pre>
- all() -> [testcase1, {group,group1}, testcase2, {group,group2}].</pre>
+ all() -> [testcase1, {group,group1}, {testcase,testcase2,[{repeat,10}]}, {group,group2}].</pre>
<p>Execution properties with a group tuple in
<c>all/0</c>: <c>{group,GroupName,Properties}</c> can also be specified.
diff --git a/lib/common_test/src/ct_framework.erl b/lib/common_test/src/ct_framework.erl
index 506147474f..bce6420042 100644
--- a/lib/common_test/src/ct_framework.erl
+++ b/lib/common_test/src/ct_framework.erl
@@ -696,9 +696,16 @@ end_tc(Mod,IPTC={init_per_testcase,_Func},_TCPid,Result,Args,Return) ->
end
end;
-end_tc(Mod,Func0,TCPid,Result,Args,Return) ->
+end_tc(Mod,Func00,TCPid,Result,Args,Return) ->
%% in case Mod == ct_framework, lookup the suite name
Suite = get_suite_name(Mod, Args),
+ {OnlyCleanup,Func0} =
+ case Func00 of
+ {cleanup,F0} ->
+ {true,F0};
+ _ ->
+ {false,Func00}
+ end,
{Func,FuncSpec,HookFunc} =
case Func0 of
{end_per_testcase_not_run,F} ->
@@ -742,6 +749,8 @@ end_tc(Mod,Func0,TCPid,Result,Args,Return) ->
case HookFunc of
undefined ->
{ok,Result};
+ _ when OnlyCleanup ->
+ {ok,Result};
_ ->
case ct_hooks:end_tc(Suite,HookFunc,Args,Result,Return) of
'$ct_no_change' ->
@@ -752,6 +761,8 @@ end_tc(Mod,Func0,TCPid,Result,Args,Return) ->
end,
FinalResult =
case get('$test_server_framework_test') of
+ _ when OnlyCleanup ->
+ Result1;
undefined ->
%% send sync notification so that event handlers may print
%% in the log file before it gets closed
@@ -1056,21 +1067,40 @@ group_or_func(Func, _Config) ->
%%% should be returned.
get_suite(Mod, all) ->
- case catch apply(Mod, groups, []) of
- {'EXIT',_} ->
- get_all(Mod, []);
- GroupDefs when is_list(GroupDefs) ->
- case catch ct_groups:find_groups(Mod, all, all, GroupDefs) of
- {error,_} = Error ->
- %% this makes test_server call error_in_suite as first
- %% (and only) test case so we can report Error properly
- [{?MODULE,error_in_suite,[[Error]]}];
- ConfTests ->
- get_all(Mod, ConfTests)
- end;
- _ ->
+ case safe_apply_groups_0(Mod,{ok,[]}) of
+ {ok,GroupDefs} ->
+ try ct_groups:find_groups(Mod, all, all, GroupDefs) of
+ ConfTests when is_list(ConfTests) ->
+ get_all(Mod, ConfTests)
+ catch
+ throw:{error,Error} ->
+ [{?MODULE,error_in_suite,[[{error,Error}]]}];
+ _:Error:S ->
+ [{?MODULE,error_in_suite,[[{error,{Error,S}}]]}]
+ end;
+ {error,{bad_return,_Bad}} ->
E = "Bad return value from "++atom_to_list(Mod)++":groups/0",
- [{?MODULE,error_in_suite,[[{error,list_to_atom(E)}]]}]
+ [{?MODULE,error_in_suite,[[{error,list_to_atom(E)}]]}];
+ {error,{bad_hook_return,Bad}} ->
+ E = "Bad return value from post_groups/2 hook function",
+ [{?MODULE,error_in_suite,[[{error,{list_to_atom(E),Bad}}]]}];
+ {error,{failed,ExitReason}} ->
+ case ct_util:get_testdata({error_in_suite,Mod}) of
+ undefined ->
+ ErrStr = io_lib:format("~n*** ERROR *** "
+ "~w:groups/0 failed: ~p~n",
+ [Mod,ExitReason]),
+ io:format(?def_gl, ErrStr, []),
+ %% save the error info so it doesn't get printed twice
+ ct_util:set_testdata_async({{error_in_suite,Mod},
+ ExitReason});
+ _ExitReason ->
+ ct_util:delete_testdata({error_in_suite,Mod})
+ end,
+ Reason = list_to_atom(atom_to_list(Mod)++":groups/0 failed"),
+ [{?MODULE,error_in_suite,[[{error,Reason}]]}];
+ {error,What} ->
+ [{?MODULE,error_in_suite,[[{error,What}]]}]
end;
%%!============================================================
@@ -1080,54 +1110,74 @@ get_suite(Mod, all) ->
%% group
get_suite(Mod, Group={conf,Props,_Init,TCs,_End}) ->
- Name = ?val(name, Props),
- case catch apply(Mod, groups, []) of
- {'EXIT',_} ->
- [Group];
- GroupDefs when is_list(GroupDefs) ->
- case catch ct_groups:find_groups(Mod, Name, TCs, GroupDefs) of
- {error,_} = Error ->
- %% this makes test_server call error_in_suite as first
- %% (and only) test case so we can report Error properly
- [{?MODULE,error_in_suite,[[Error]]}];
- [] ->
- [];
- ConfTests ->
- case lists:member(skipped, Props) of
- true ->
- %% a *subgroup* specified *only* as skipped (and not
- %% as an explicit test) should not be returned, or
- %% init/end functions for top groups will be executed
- case catch ?val(name, element(2, hd(ConfTests))) of
- Name -> % top group
- ct_groups:delete_subs(ConfTests, ConfTests);
- _ ->
- []
- end;
- false ->
- ConfTests1 = ct_groups:delete_subs(ConfTests,
- ConfTests),
- case ?val(override, Props) of
- undefined ->
- ConfTests1;
- [] ->
- ConfTests1;
- ORSpec ->
- ORSpec1 = if is_tuple(ORSpec) -> [ORSpec];
- true -> ORSpec end,
- ct_groups:search_and_override(ConfTests1,
- ORSpec1, Mod)
- end
- end
- end;
- _ ->
+ case safe_apply_groups_0(Mod,{ok,[Group]}) of
+ {ok,GroupDefs} ->
+ Name = ?val(name, Props),
+ try ct_groups:find_groups(Mod, Name, TCs, GroupDefs) of
+ [] ->
+ [];
+ ConfTests when is_list(ConfTests) ->
+ case lists:member(skipped, Props) of
+ true ->
+ %% a *subgroup* specified *only* as skipped (and not
+ %% as an explicit test) should not be returned, or
+ %% init/end functions for top groups will be executed
+ try ?val(name, element(2, hd(ConfTests))) of
+ Name -> % top group
+ ct_groups:delete_subs(ConfTests, ConfTests);
+ _ -> []
+ catch
+ _:_ -> []
+ end;
+ false ->
+ ConfTests1 = ct_groups:delete_subs(ConfTests,
+ ConfTests),
+ case ?val(override, Props) of
+ undefined ->
+ ConfTests1;
+ [] ->
+ ConfTests1;
+ ORSpec ->
+ ORSpec1 = if is_tuple(ORSpec) -> [ORSpec];
+ true -> ORSpec end,
+ ct_groups:search_and_override(ConfTests1,
+ ORSpec1, Mod)
+ end
+ end
+ catch
+ throw:{error,Error} ->
+ [{?MODULE,error_in_suite,[[{error,Error}]]}];
+ _:Error:S ->
+ [{?MODULE,error_in_suite,[[{error,{Error,S}}]]}]
+ end;
+ {error,{bad_return,_Bad}} ->
E = "Bad return value from "++atom_to_list(Mod)++":groups/0",
- [{?MODULE,error_in_suite,[[{error,list_to_atom(E)}]]}]
+ [{?MODULE,error_in_suite,[[{error,list_to_atom(E)}]]}];
+ {error,{bad_hook_return,Bad}} ->
+ E = "Bad return value from post_groups/2 hook function",
+ [{?MODULE,error_in_suite,[[{error,{list_to_atom(E),Bad}}]]}];
+ {error,{failed,ExitReason}} ->
+ case ct_util:get_testdata({error_in_suite,Mod}) of
+ undefined ->
+ ErrStr = io_lib:format("~n*** ERROR *** "
+ "~w:groups/0 failed: ~p~n",
+ [Mod,ExitReason]),
+ io:format(?def_gl, ErrStr, []),
+ %% save the error info so it doesn't get printed twice
+ ct_util:set_testdata_async({{error_in_suite,Mod},
+ ExitReason});
+ _ExitReason ->
+ ct_util:delete_testdata({error_in_suite,Mod})
+ end,
+ Reason = list_to_atom(atom_to_list(Mod)++":groups/0 failed"),
+ [{?MODULE,error_in_suite,[[{error,Reason}]]}];
+ {error,What} ->
+ [{?MODULE,error_in_suite,[[{error,What}]]}]
end;
%% testcase
get_suite(Mod, Name) ->
- get_seq(Mod, Name).
+ get_seq(Mod, Name).
%%%-----------------------------------------------------------------
@@ -1161,21 +1211,48 @@ get_all_cases1(_, []) ->
%%%-----------------------------------------------------------------
-get_all(Mod, ConfTests) ->
- case catch apply(Mod, all, []) of
- {'EXIT',{undef,[{Mod,all,[],_} | _]}} ->
+get_all(Mod, ConfTests) ->
+ case safe_apply_all_0(Mod) of
+ {ok,AllTCs} ->
+ %% expand group references using ConfTests
+ try ct_groups:expand_groups(AllTCs, ConfTests, Mod) of
+ {error,_} = Error ->
+ [{?MODULE,error_in_suite,[[Error]]}];
+ Tests0 ->
+ Tests = ct_groups:delete_subs(Tests0, Tests0),
+ expand_tests(Mod, Tests)
+ catch
+ throw:{error,Error} ->
+ [{?MODULE,error_in_suite,[[{error,Error}]]}];
+ _:Error:S ->
+ [{?MODULE,error_in_suite,[[{error,{Error,S}}]]}]
+ end;
+ Skip = {skip,_Reason} ->
+ Skip;
+ {error,undef} ->
+ Reason =
+ case code:which(Mod) of
+ non_existing ->
+ list_to_atom(
+ atom_to_list(Mod)++
+ " cannot be compiled or loaded");
+ _ ->
+ list_to_atom(
+ atom_to_list(Mod)++":all/0 is missing")
+ end,
+ %% this makes test_server call error_in_suite as first
+ %% (and only) test case so we can report Reason properly
+ [{?MODULE,error_in_suite,[[{error,Reason}]]}];
+ {error,{bad_return,_Bad}} ->
Reason =
- case code:which(Mod) of
- non_existing ->
- list_to_atom(atom_to_list(Mod)++
- " cannot be compiled or loaded");
- _ ->
- list_to_atom(atom_to_list(Mod)++":all/0 is missing")
- end,
- %% this makes test_server call error_in_suite as first
- %% (and only) test case so we can report Reason properly
+ list_to_atom("Bad return value from "++
+ atom_to_list(Mod)++":all/0"),
[{?MODULE,error_in_suite,[[{error,Reason}]]}];
- {'EXIT',ExitReason} ->
+ {error,{bad_hook_return,Bad}} ->
+ Reason =
+ list_to_atom("Bad return value from post_all/3 hook function"),
+ [{?MODULE,error_in_suite,[[{error,{Reason,Bad}}]]}];
+ {error,{failed,ExitReason}} ->
case ct_util:get_testdata({error_in_suite,Mod}) of
undefined ->
ErrStr = io_lib:format("~n*** ERROR *** "
@@ -1192,28 +1269,8 @@ get_all(Mod, ConfTests) ->
%% this makes test_server call error_in_suite as first
%% (and only) test case so we can report Reason properly
[{?MODULE,error_in_suite,[[{error,Reason}]]}];
- AllTCs when is_list(AllTCs) ->
- case catch save_seqs(Mod,AllTCs) of
- {error,What} ->
- [{?MODULE,error_in_suite,[[{error,What}]]}];
- SeqsAndTCs ->
- %% expand group references in all() using ConfTests
- case catch ct_groups:expand_groups(SeqsAndTCs,
- ConfTests,
- Mod) of
- {error,_} = Error ->
- [{?MODULE,error_in_suite,[[Error]]}];
- Tests ->
- ct_groups:delete_subs(Tests, Tests)
- end
- end;
- Skip = {skip,_Reason} ->
- Skip;
- _ ->
- Reason =
- list_to_atom("Bad return value from "++
- atom_to_list(Mod)++":all/0"),
- [{?MODULE,error_in_suite,[[{error,Reason}]]}]
+ {error,What} ->
+ [{?MODULE,error_in_suite,[[{error,What}]]}]
end.
%%!============================================================
@@ -1571,3 +1628,74 @@ get_html_wrapper(TestName, PrintLabel, Cwd, TableCols, Encoding) ->
%%% -spec get_log_dir() -> {ok,LogDir}
get_log_dir() ->
ct_logs:get_log_dir(true).
+
+%%%-----------------------------------------------------------------
+%%% Call all and group callbacks and post_* hooks with error handling
+safe_apply_all_0(Mod) ->
+ try apply(Mod, all, []) of
+ AllTCs0 when is_list(AllTCs0) ->
+ try save_seqs(Mod,AllTCs0) of
+ SeqsAndTCs when is_list(SeqsAndTCs) ->
+ all_hook(Mod,SeqsAndTCs)
+ catch throw:{error,What} ->
+ {error,What}
+ end;
+ {skip,_}=Skip ->
+ all_hook(Mod,Skip);
+ Bad ->
+ {error,{bad_return,Bad}}
+ catch
+ _:Reason:Stacktrace ->
+ handle_callback_crash(Reason,Stacktrace,Mod,all,{error,undef})
+ end.
+
+all_hook(Mod, All) ->
+ case ct_hooks:all(Mod, All) of
+ AllTCs when is_list(AllTCs) ->
+ {ok,AllTCs};
+ {skip,_}=Skip ->
+ Skip;
+ {fail,Reason} ->
+ {error,Reason};
+ Bad ->
+ {error,{bad_hook_return,Bad}}
+ end.
+
+safe_apply_groups_0(Mod,Default) ->
+ try apply(Mod, groups, []) of
+ GroupDefs when is_list(GroupDefs) ->
+ case ct_hooks:groups(Mod, GroupDefs) of
+ GroupDefs1 when is_list(GroupDefs1) ->
+ {ok,GroupDefs1};
+ {fail,Reason} ->
+ {error,Reason};
+ Bad ->
+ {error,{bad_hook_return,Bad}}
+ end;
+ Bad ->
+ {error,{bad_return,Bad}}
+ catch
+ _:Reason:Stacktrace ->
+ handle_callback_crash(Reason,Stacktrace,Mod,groups,Default)
+ end.
+
+handle_callback_crash(undef,[{Mod,Func,[],_}|_],Mod,Func,Default) ->
+ case ct_hooks:Func(Mod, []) of
+ [] ->
+ Default;
+ List when is_list(List) ->
+ {ok,List};
+ {fail,Reason} ->
+ {error,Reason};
+ Bad ->
+ {error,{bad_hook_return,Bad}}
+ end;
+handle_callback_crash(Reason,Stacktrace,_Mod,_Func,_Default) ->
+ {error,{failed,{Reason,Stacktrace}}}.
+
+expand_tests(Mod, [{testcase,Case,[Prop]}|Tests]) ->
+ [{repeat,{Mod,Case},Prop}|expand_tests(Mod,Tests)];
+expand_tests(Mod,[Test|Tests]) ->
+ [Test|expand_tests(Mod,Tests)];
+expand_tests(_Mod,[]) ->
+ [].
diff --git a/lib/common_test/src/ct_groups.erl b/lib/common_test/src/ct_groups.erl
index d867069dce..f4b12c41c0 100644
--- a/lib/common_test/src/ct_groups.erl
+++ b/lib/common_test/src/ct_groups.erl
@@ -101,23 +101,34 @@ find(Mod, [], TCs, Tests, _Known, _Defs, false) ->
[{Mod,TC}];
({group,_}) ->
[];
+ ({testcase,TC,[Prop]}) when is_atom(TC), TC ==all ->
+ [{repeat,{Mod,TC},Prop}];
({_,_}=TC) when TCs == all ->
[TC];
- (TC) ->
- if is_atom(TC) ->
- Tuple = {Mod,TC},
- case lists:member(Tuple, TCs) of
- true ->
- [Tuple];
- false ->
- case lists:member(TC, TCs) of
- true -> [{Mod,TC}];
- false -> []
- end
- end;
- true ->
- []
- end
+ (TC) when is_atom(TC) ->
+ Tuple = {Mod,TC},
+ case lists:member(Tuple, TCs) of
+ true ->
+ [Tuple];
+ false ->
+ case lists:member(TC, TCs) of
+ true -> [Tuple];
+ false -> []
+ end
+ end;
+ ({testcase,TC,[Prop]}) when is_atom(TC) ->
+ Tuple = {Mod,TC},
+ case lists:member(Tuple, TCs) of
+ true ->
+ [{repeat,Tuple,Prop}];
+ false ->
+ case lists:member(TC, TCs) of
+ true -> [{repeat,Tuple,Prop}];
+ false -> []
+ end
+ end;
+ (_) ->
+ []
end, Tests),
if Cases == [] -> ['NOMATCH'];
true -> Cases
@@ -172,12 +183,19 @@ find(Mod, GrNames, all, [{M,TC} | Gs], Known,
Defs, FindAll) when is_atom(M), M /= group, is_atom(TC) ->
[{M,TC} | find(Mod, GrNames, all, Gs, Known, Defs, FindAll)];
+%% Save test case
+find(Mod, GrNames, all, [{testcase,TC,[Prop]} | Gs], Known,
+ Defs, FindAll) when is_atom(TC) ->
+ [{repeat,{Mod,TC},Prop} | find(Mod, GrNames, all, Gs, Known, Defs, FindAll)];
+
%% 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 (element(1,TC) /= group)) ->
+find(Mod, GrNames, TCs, [TC | Gs], Known, Defs, FindAll)
+ when is_atom(TC) orelse
+ ((size(TC) == 3) andalso (element(1,TC) == testcase)) orelse
+ ((size(TC) == 2) and (element(1,TC) /= group)) ->
Case =
- if is_atom(TC) ->
+ case TC of
+ _ when is_atom(TC) ->
Tuple = {Mod,TC},
case lists:member(Tuple, TCs) of
true ->
@@ -188,7 +206,18 @@ find(Mod, GrNames, TCs, [TC | Gs], Known,
false -> []
end
end;
- true ->
+ {testcase,TC0,[Prop]} when is_atom(TC0) ->
+ Tuple = {Mod,TC0},
+ case lists:member(Tuple, TCs) of
+ true ->
+ {repeat,Tuple,Prop};
+ false ->
+ case lists:member(TC0, TCs) of
+ true -> {repeat,{Mod,TC0},Prop};
+ false -> []
+ end
+ end;
+ _ ->
case lists:member(TC, TCs) of
true -> {Mod,TC};
false -> []
@@ -289,12 +318,22 @@ modify_tc_list(GrSpecTs, TSCs, []) ->
modify_tc_list1(GrSpecTs, TSCs);
modify_tc_list(GrSpecTs, _TSCs, _) ->
- [Test || Test <- GrSpecTs, not is_atom(Test)].
+ [Test || Test <- GrSpecTs, not is_atom(Test), element(1,Test)=/=testcase].
modify_tc_list1(GrSpecTs, TSCs) ->
%% remove all cases in group tc list that should not be executed
GrSpecTs1 =
- lists:flatmap(fun(Test) when is_tuple(Test),
+ lists:flatmap(fun(Test={testcase,TC,_}) ->
+ case lists:keysearch(TC, 2, TSCs) of
+ {value,_} ->
+ [Test];
+ _ ->
+ case lists:member(TC, TSCs) of
+ true -> [Test];
+ false -> []
+ end
+ end;
+ (Test) when is_tuple(Test),
(size(Test) > 2) ->
[Test];
(Test={group,_}) ->
diff --git a/lib/common_test/src/ct_hooks.erl b/lib/common_test/src/ct_hooks.erl
index 49587b3edd..97c349578f 100644
--- a/lib/common_test/src/ct_hooks.erl
+++ b/lib/common_test/src/ct_hooks.erl
@@ -22,6 +22,8 @@
%% API Exports
-export([init/1]).
+-export([groups/2]).
+-export([all/2]).
-export([init_tc/3]).
-export([end_tc/5]).
-export([terminate/1]).
@@ -37,7 +39,8 @@
opts = [],
prio = ctfirst }]).
--record(ct_hook_config, {id, module, prio, scope, opts = [], state = []}).
+-record(ct_hook_config, {id, module, prio, scope, opts = [],
+ state = [], groups = []}).
%% -------------------------------------------------------------------------
%% API Functions
@@ -49,6 +52,48 @@ init(Opts) ->
call(get_builtin_hooks(Opts) ++ get_new_hooks(Opts, undefined),
ok, init, []).
+%% Call the post_groups/2 hook callback
+groups(Mod, Groups) ->
+ Info = try proplists:get_value(ct_hooks, Mod:suite(), []) of
+ CTHooks when is_list(CTHooks) ->
+ [{?config_name,CTHooks}];
+ CTHook when is_atom(CTHook) ->
+ [{?config_name,[CTHook]}]
+ catch _:_ ->
+ %% since this might be the first time Mod:suite()
+ %% is called, and it might just fail or return
+ %% something bad, we allow any failure here - it
+ %% will be catched later if there is something
+ %% really wrong.
+ [{?config_name,[]}]
+ end,
+ case call(fun call_generic/3, Info ++ [{'$ct_groups',Groups}], [post_groups, Mod]) of
+ [{'$ct_groups',NewGroups}] ->
+ NewGroups;
+ Other ->
+ Other
+ end.
+
+%% Call the post_all/3 hook callback
+all(Mod, Tests) ->
+ Info = try proplists:get_value(ct_hooks, Mod:suite(), []) of
+ CTHooks when is_list(CTHooks) ->
+ [{?config_name,CTHooks}];
+ CTHook when is_atom(CTHook) ->
+ [{?config_name,[CTHook]}]
+ catch _:_ ->
+ %% just allow any failure here - it will be catched
+ %% later if there is something really wrong.
+ [{?config_name,[]}]
+ end,
+ case call(fun call_generic/3, Info ++ [{'$ct_all',Tests}], [post_all, Mod]) of
+ [{'$ct_all',NewTests}] ->
+ NewTests;
+ Other ->
+ Other
+ end.
+
+%% Called after all suites are done.
-spec terminate(Hooks :: term()) ->
ok.
terminate(Hooks) ->
@@ -80,6 +125,7 @@ init_tc(Mod, init_per_suite, Config) ->
[{?config_name,[]}]
end,
call(fun call_generic/3, Config ++ Info, [pre_init_per_suite, Mod]);
+
init_tc(Mod, end_per_suite, Config) ->
call(fun call_generic/3, Config, [pre_end_per_suite, Mod]);
init_tc(Mod, {init_per_group, GroupName, Properties}, Config) ->
@@ -153,7 +199,7 @@ call_id(#ct_hook_config{ module = Mod, opts = Opts} = Hook, Config, Scope) ->
{Config, Hook#ct_hook_config{ id = Id, scope = scope(Scope)}}.
call_init(#ct_hook_config{ module = Mod, opts = Opts, id = Id, prio = P} = Hook,
- Config,_Meta) ->
+ Config, _Meta) ->
case Mod:init(Id, Opts) of
{ok, NewState} when P =:= undefined ->
{Config, Hook#ct_hook_config{ state = NewState, prio = 0 } };
@@ -184,6 +230,18 @@ call_generic(Hook, Value, Meta) ->
call_generic_fallback(Hook, Value, Meta) ->
do_call_generic(Hook, Value, Meta, true).
+do_call_generic(#ct_hook_config{ module = Mod} = Hook,
+ [{'$ct_groups',Groups}], [post_groups | Args], Fallback) ->
+ NewGroups = catch_apply(Mod, post_groups, Args ++ [Groups],
+ Groups, Fallback),
+ {[{'$ct_groups',NewGroups}], Hook#ct_hook_config{ groups = NewGroups } };
+
+do_call_generic(#ct_hook_config{ module = Mod, groups = Groups} = Hook,
+ [{'$ct_all',Tests}], [post_all | Args], Fallback) ->
+ NewTests = catch_apply(Mod, post_all, Args ++ [Tests, Groups],
+ Tests, Fallback),
+ {[{'$ct_all',NewTests}], Hook};
+
do_call_generic(#ct_hook_config{ module = Mod, state = State} = Hook,
Value, [Function | Args], Fallback) ->
{NewValue, NewState} = catch_apply(Mod, Function, Args ++ [Value, State],
@@ -218,6 +276,12 @@ call([{Hook, call_id, NextFun} | Rest], Config, Meta, Hooks) ->
Rest ++ [{NewId, call_init}]};
ExistingHook when is_tuple(ExistingHook) ->
{Hooks, Rest};
+ _ when hd(Meta)=:=post_groups; hd(Meta)=:=post_all ->
+ %% If CTH is started because of a call from
+ %% groups/2 or all/2, CTH:init/1 must not be
+ %% called (the suite scope should be used).
+ {Hooks ++ [NewHook],
+ Rest ++ [{NewId,NextFun}]};
_ ->
{Hooks ++ [NewHook],
Rest ++ [{NewId, call_init}, {NewId,NextFun}]}
@@ -226,8 +290,8 @@ call([{Hook, call_id, NextFun} | Rest], Config, Meta, Hooks) ->
catch Error:Reason:Trace ->
ct_logs:log("Suite Hook","Failed to start a CTH: ~tp:~tp",
[Error,{Reason,Trace}]),
- call([], {fail,"Failed to start CTH"
- ", see the CT Log for details"}, Meta, Hooks)
+ call([], {fail,"Failed to start CTH, "
+ "see the CT Log for details"}, Meta, Hooks)
end;
call([{HookId, call_init} | Rest], Config, Meta, Hooks) ->
call([{HookId, fun call_init/3} | Rest], Config, Meta, Hooks);
@@ -267,6 +331,10 @@ scope([pre_init_per_suite, SuiteName|_]) ->
[post_end_per_suite, SuiteName];
scope([post_init_per_suite, SuiteName|_]) ->
[post_end_per_suite, SuiteName];
+scope([post_groups, SuiteName|_]) ->
+ [post_groups, SuiteName];
+scope([post_all, SuiteName|_]) ->
+ [post_all, SuiteName];
scope(init) ->
none.
@@ -353,6 +421,7 @@ resort(Calls,Hooks,[F|_R]) when F == pre_end_per_testcase;
F == pre_end_per_suite;
F == post_end_per_suite ->
lists:reverse(resort(Calls,Hooks));
+
resort(Calls,Hooks,_Meta) ->
resort(Calls,Hooks).
diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl
index 814b80b8bd..ca262b350f 100644
--- a/lib/common_test/src/ct_logs.erl
+++ b/lib/common_test/src/ct_logs.erl
@@ -149,7 +149,7 @@ close(Info, StartDir) ->
ok;
CacheBin ->
%% save final version of the log cache to file
- _ = file:write_file(?log_cache_name,CacheBin),
+ write_log_cache(CacheBin),
put(ct_log_cache,undefined)
end
end,
@@ -2022,7 +2022,7 @@ update_all_runs_in_cache(AllRunsData) ->
%% read from file as long as this logger process is alive
put(ct_log_cache,term_to_binary(LogCache));
_ ->
- file:write_file(?log_cache_name,term_to_binary(LogCache))
+ write_log_cache(term_to_binary(LogCache))
end;
SavedLogCache ->
update_all_runs_in_cache(AllRunsData,binary_to_term(SavedLogCache))
@@ -2036,7 +2036,7 @@ update_all_runs_in_cache(AllRunsData, LogCache) ->
%% read from file as long as this logger process is alive
put(ct_log_cache,term_to_binary(LogCache1));
_ ->
- file:write_file(?log_cache_name,term_to_binary(LogCache1))
+ write_log_cache(term_to_binary(LogCache1))
end.
sort_all_runs(Dirs) ->
@@ -2668,7 +2668,7 @@ update_tests_in_cache(TempData,LogCache=#log_cache{tests=Tests}) ->
{_Pid,_Pid} ->
put(ct_log_cache,CacheBin);
_ ->
- file:write_file(?log_cache_name,CacheBin)
+ write_log_cache(CacheBin)
end.
%%
@@ -3400,3 +3400,9 @@ unexpected_io(Pid, _Category, _Importance, Content, CtLogFd, EscChars) ->
Data = io_lib:format("~ts", [lists:foldl(IoFun, [], Content)]),
test_server_io:print_unexpected(Data),
ok.
+
+write_log_cache(LogCacheBin) when is_binary(LogCacheBin) ->
+ TmpFile = ?log_cache_name++".tmp",
+ _ = file:write_file(TmpFile,LogCacheBin),
+ _ = file:rename(TmpFile,?log_cache_name),
+ ok.
diff --git a/lib/common_test/src/test_server.erl b/lib/common_test/src/test_server.erl
index 9eda3f2152..756cd4d692 100644
--- a/lib/common_test/src/test_server.erl
+++ b/lib/common_test/src/test_server.erl
@@ -384,8 +384,8 @@ run_test_case_apply({CaseNum,Mod,Func,Args,Name,RunInit,TimetrapData}) ->
{Result,DetFail,ProcBef,ProcAft}.
-type tc_status() :: 'starting' | 'running' | 'init_per_testcase' |
- 'end_per_testcase' | {'framework',atom(),atom()} |
- 'tc'.
+ 'end_per_testcase' | {'framework',{atom(),atom(),list}} |
+ 'tc'.
-record(st,
{
ref :: reference(),
@@ -653,8 +653,8 @@ handle_tc_exit({testcase_aborted,{user_timetrap_error,_}=Msg,_}, St) ->
#st{config=Config,mf={Mod,Func},pid=Pid} = St,
spawn_fw_call(Mod, Func, Config, Pid, Msg, unknown, self()),
St;
-handle_tc_exit(Reason, #st{status={framework,FwMod,FwFunc},
- config=Config,pid=Pid}=St) ->
+handle_tc_exit(Reason, #st{status={framework,{FwMod,FwFunc,_}=FwMFA},
+ config=Config,mf={Mod,Func},pid=Pid}=St) ->
R = case Reason of
{timetrap_timeout,TVal,_} ->
{timetrap,TVal};
@@ -666,7 +666,7 @@ handle_tc_exit(Reason, #st{status={framework,FwMod,FwFunc},
Other
end,
Error = {framework_error,R},
- spawn_fw_call(FwMod, FwFunc, Config, Pid, Error, unknown, self()),
+ spawn_fw_call(Mod, Func, Config, Pid, {Error,FwMFA}, unknown, self()),
St;
handle_tc_exit(Reason, #st{status=tc,config=Config0,mf={Mod,Func},pid=Pid}=St)
when is_list(Config0) ->
@@ -870,22 +870,48 @@ spawn_fw_call(Mod,EPTC={end_per_testcase,Func},EndConf,Pid,
end,
spawn_link(FwCall);
-spawn_fw_call(FwMod,FwFunc,_,_Pid,{framework_error,FwError},_,SendTo) ->
+spawn_fw_call(Mod,Func,Conf,Pid,{{framework_error,FwError},
+ {FwMod,FwFunc,[A1,A2|_]}=FwMFA},_,SendTo) ->
FwCall =
fun() ->
ct_util:mark_process(),
- test_server_sup:framework_call(report, [framework_error,
- {{FwMod,FwFunc},
- FwError}]),
+ Time =
+ case FwError of
+ {timetrap,TVal} ->
+ TVal/1000;
+ _ ->
+ died
+ end,
+ {Ret,Loc,WarnOrError} =
+ cleanup_after_fw_error(Mod,Func,Conf,Pid,FwError,FwMFA),
Comment =
- lists:flatten(
- io_lib:format("<font color=\"red\">"
- "WARNING! ~w:~tw failed!</font>",
- [FwMod,FwFunc])),
+ case WarnOrError of
+ warn ->
+ group_leader() !
+ {printout,12,
+ "WARNING! ~w:~tw(~w,~tw,...) failed!\n"
+ " Reason: ~tp\n",
+ [FwMod,FwFunc,A1,A2,FwError]},
+ lists:flatten(
+ io_lib:format("<font color=\"red\">"
+ "WARNING! ~w:~tw(~w,~tw,...) "
+ "failed!</font>",
+ [FwMod,FwFunc,A1,A2]));
+ error ->
+ group_leader() !
+ {printout,12,
+ "Error! ~w:~tw(~w,~tw,...) failed!\n"
+ " Reason: ~tp\n",
+ [FwMod,FwFunc,A1,A2,FwError]},
+ lists:flatten(
+ io_lib:format("<font color=\"red\">"
+ "ERROR! ~w:~tw(~w,~tw,...) "
+ "failed!</font>",
+ [FwMod,FwFunc,A1,A2]))
+ end,
%% finished, report back
SendTo ! {self(),fw_notify_done,
- {died,{error,{FwMod,FwFunc,FwError}},
- {FwMod,FwFunc},[],Comment}}
+ {Time,Ret,Loc,[],Comment}}
end,
spawn_link(FwCall);
@@ -930,6 +956,163 @@ spawn_fw_call(Mod,Func,CurrConf,Pid,Error,Loc,SendTo) ->
end,
spawn_link(FwCall).
+cleanup_after_fw_error(_Mod,_Func,Conf,Pid,FwError,
+ {FwMod,FwFunc=init_tc,
+ [Mod,{init_per_testcase,Func}=IPTC|_]}) ->
+ %% Failed during pre_init_per_testcase, the test must be skipped
+ Skip = {auto_skip,{failed,{FwMod,FwFunc,FwError}}},
+ try begin do_end_tc_call(Mod,IPTC, {Pid,Skip,[Conf]}, FwError),
+ do_init_tc_call(Mod,{end_per_testcase_not_run,Func},
+ [Conf],{ok,[Conf]}),
+ do_end_tc_call(Mod,{end_per_testcase_not_run,Func},
+ {Pid,Skip,[Conf]}, FwError) end of
+ _ -> ok
+ catch
+ _:FwEndTCErr ->
+ exit({fw_notify_done,end_tc,FwEndTCErr})
+ end,
+ {Skip,{FwMod,FwFunc},error};
+cleanup_after_fw_error(_Mod,_Func,Conf,Pid,FwError,
+ {FwMod,FwFunc=end_tc,[Mod,{init_per_testcase,Func}|_]}) ->
+ %% Failed during post_init_per_testcase, the test must be skipped
+ Skip = {auto_skip,{failed,{FwMod,FwFunc,FwError}}},
+ try begin do_init_tc_call(Mod,{end_per_testcase_not_run,Func},
+ [Conf],{ok,[Conf]}),
+ do_end_tc_call(Mod,{end_per_testcase_not_run,Func},
+ {Pid,Skip,[Conf]}, FwError) end of
+ _ -> ok
+ catch
+ _:FwEndTCErr ->
+ exit({fw_notify_done,end_tc,FwEndTCErr})
+ end,
+ {Skip,{FwMod,FwFunc},error};
+cleanup_after_fw_error(_Mod,_Func,Conf,Pid,FwError,
+ {FwMod,FwFunc=init_tc,[Mod,{end_per_testcase,Func}|_]}) ->
+ %% Failed during pre_end_per_testcase. Warn about it.
+ {RetVal,Loc} =
+ case {proplists:get_value(tc_status, Conf),
+ proplists:get_value(tc_fail_loc, Conf, unknown)} of
+ {undefined,_} ->
+ {{failed,{FwMod,FwFunc,FwError}},{FwMod,FwFunc}};
+ {E = {failed,_Reason},unknown} ->
+ {E,[{Mod,Func}]};
+ {Result,FailLoc} ->
+ {Result,FailLoc}
+ end,
+ try begin do_end_tc_call(Mod,{end_per_testcase_not_run,Func},
+ {Pid,RetVal,[Conf]}, FwError) end of
+ _ -> ok
+ catch
+ _:FwEndTCErr ->
+ exit({fw_notify_done,end_tc,FwEndTCErr})
+ end,
+ {RetVal,Loc,warn};
+cleanup_after_fw_error(Mod,Func,Conf,Pid,FwError,
+ {FwMod,FwFunc=end_tc,[Mod,{end_per_testcase,Func}|_]}) ->
+ %% Failed during post_end_per_testcase. Warn about it.
+ {RetVal,Report,Loc} =
+ case {proplists:get_value(tc_status, Conf),
+ proplists:get_value(tc_fail_loc, Conf, unknown)} of
+ {undefined,_} ->
+ {{failed,{FwMod,FwFunc,FwError}},
+ {{FwMod,FwError},FwError},
+ {FwMod,FwFunc}};
+ {E = {failed,_Reason},unknown} ->
+ {E,{Mod,Func,E},[{Mod,Func}]};
+ {Result,FailLoc} ->
+ {Result,{Mod,Func,Result},FailLoc}
+ end,
+ try begin do_end_tc_call(Mod,{cleanup,{end_per_testcase_not_run,Func}},
+ {Pid,RetVal,[Conf]}, FwError) end of
+ _ -> ok
+ catch
+ _:FwEndTCErr ->
+ exit({fw_notify_done,end_tc,FwEndTCErr})
+ end,
+ test_server_sup:framework_call(report,[framework_error,Report]),
+ {RetVal,Loc,warn};
+cleanup_after_fw_error(Mod,Func,Conf,Pid,FwError,{FwMod,FwFunc=init_tc,_})
+ when Func =:= init_per_suite; Func =:=init_per_group ->
+ %% Failed during pre_init_per_suite or pre_init_per_group
+ RetVal = {failed,{FwMod,FwFunc,FwError}},
+ try do_end_tc_call(Mod,Func,{Pid,RetVal,[Conf]},FwError) of
+ _ -> ok
+ catch
+ _:FwEndTCErr ->
+ exit({fw_notify_done,end_tc,FwEndTCErr})
+ end,
+ {RetVal,{FwMod,FwFunc},error};
+cleanup_after_fw_error(Mod,Func,Conf,Pid,FwError,{FwMod,FwFunc=end_tc,_})
+ when Func =:= init_per_suite; Func =:=init_per_group ->
+ %% Failed during post_init_per_suite or post_init_per_group
+ RetVal = {failed,{FwMod,FwFunc,FwError}},
+ try do_end_tc_call(Mod,{cleanup,Func},{Pid,RetVal,[Conf]},FwError) of
+ _ -> ok
+ catch
+ _:FwEndTCErr ->
+ exit({fw_notify_done,end_tc,FwEndTCErr})
+ end,
+ ReportFunc =
+ case Func of
+ init_per_group ->
+ case proplists:get_value(tc_group_properties,Conf) of
+ undefined ->
+ {Func,unknown,[]};
+ GProps ->
+ Name = proplists:get_value(name,GProps),
+ {Func,Name,proplists:delete(name,GProps)}
+ end;
+ _ ->
+ Func
+ end,
+ test_server_sup:framework_call(report,[framework_error,
+ {Mod,ReportFunc,RetVal}]),
+ {RetVal,{FwMod,FwFunc},error};
+cleanup_after_fw_error(Mod,Func,Conf,Pid,FwError,{FwMod,FwFunc=init_tc,_})
+ when Func =:= end_per_suite; Func =:=end_per_group ->
+ %% Failed during pre_end_per_suite or pre_end_per_group
+ RetVal = {failed,{FwMod,FwFunc,FwError}},
+ try do_end_tc_call(Mod,Func,{Pid,RetVal,[Conf]},FwError) of
+ _ -> ok
+ catch
+ _:FwEndTCErr ->
+ exit({fw_notify_done,end_tc,FwEndTCErr})
+ end,
+ {RetVal,{FwMod,FwFunc},error};
+cleanup_after_fw_error(Mod,Func,Conf,Pid,FwError,{FwMod,FwFunc=end_tc,_})
+ when Func =:= end_per_suite; Func =:=end_per_group ->
+ %% Failed during post_end_per_suite or post_end_per_group
+ RetVal = {failed,{FwMod,FwFunc,FwError}},
+ try do_end_tc_call(Mod,{cleanup,Func},{Pid,RetVal,[Conf]},FwError) of
+ _ -> ok
+ catch
+ _:FwEndTCErr ->
+ exit({fw_notify_done,end_tc,FwEndTCErr})
+ end,
+ ReportFunc =
+ case Func of
+ end_per_group ->
+ case proplists:get_value(tc_group_properties,Conf) of
+ undefined ->
+ {Func,unknown,[]};
+ GProps ->
+ Name = proplists:get_value(name,GProps),
+ {Func,Name,proplists:delete(name,GProps)}
+ end;
+ _ ->
+ Func
+ end,
+ test_server_sup:framework_call(report,[framework_error,
+ {Mod,ReportFunc,RetVal}]),
+ {RetVal,{FwMod,FwFunc},error};
+cleanup_after_fw_error(_Mod,_Func,_Conf,_Pid,FwError,{FwMod,FwFunc,_}) ->
+ %% This is unexpected
+ test_server_sup:framework_call(report,
+ [framework_error,
+ {{FwMod,FwFunc},
+ FwError}]),
+ {FwError,{FwMod,FwFunc},error}.
+
%% The job proxy process forwards messages between the test case
%% process on a shielded node (and its descendants) and the job process.
%%
@@ -1105,6 +1288,9 @@ run_test_case_eval1(Mod, Func, Args, Name, RunInit, TCCallback) ->
EndConf1 =
user_callback(TCCallback, Mod, Func, 'end', EndConf),
+ %% save updated config in controller loop
+ set_tc_state(tc, EndConf1),
+
%% We can't handle fails or skips here
EndConf2 =
case do_init_tc_call(Mod,{end_per_testcase,Func},
diff --git a/lib/common_test/src/test_server_ctrl.erl b/lib/common_test/src/test_server_ctrl.erl
index 1518c6e8d6..003d08d70d 100644
--- a/lib/common_test/src/test_server_ctrl.erl
+++ b/lib/common_test/src/test_server_ctrl.erl
@@ -1443,6 +1443,8 @@ remove_conf([C={Mod,error_in_suite,_}|Cases], NoConf, Repeats) ->
true ->
remove_conf(Cases, [C|NoConf], Repeats)
end;
+remove_conf([C={repeat,_,_}|Cases], NoConf, _Repeats) ->
+ remove_conf(Cases, [C|NoConf], true);
remove_conf([C|Cases], NoConf, Repeats) ->
remove_conf(Cases, [C|NoConf], Repeats);
remove_conf([], NoConf, true) ->
@@ -2061,6 +2063,14 @@ add_init_and_end_per_suite([SkipCase|Cases], LastMod, LastRef, FwMod)
[SkipCase|add_init_and_end_per_suite(Cases, LastMod, LastRef, FwMod)];
add_init_and_end_per_suite([{conf,_,_,_}=Case|Cases], LastMod, LastRef, FwMod) ->
[Case|add_init_and_end_per_suite(Cases, LastMod, LastRef, FwMod)];
+add_init_and_end_per_suite([{repeat,{Mod,_},_}=Case|Cases], LastMod, LastRef, FwMod)
+ when Mod =/= LastMod, Mod =/= FwMod ->
+ {PreCases, NextMod, NextRef} =
+ do_add_init_and_end_per_suite(LastMod, LastRef, Mod, FwMod),
+ PreCases ++ [Case|add_init_and_end_per_suite(Cases, NextMod,
+ NextRef, FwMod)];
+add_init_and_end_per_suite([{repeat,_,_}=Case|Cases], LastMod, LastRef, FwMod) ->
+ [Case|add_init_and_end_per_suite(Cases, LastMod, LastRef, FwMod)];
add_init_and_end_per_suite([{Mod,_}=Case|Cases], LastMod, LastRef, FwMod)
when Mod =/= LastMod, Mod =/= FwMod ->
{PreCases, NextMod, NextRef} =
@@ -2138,7 +2148,7 @@ do_add_init_and_end_per_suite(LastMod, LastRef, Mod, FwMod) ->
%% let's call a "fake" end_per_suite if it exists
case erlang:function_exported(FwMod, end_per_suite, 1) of
true ->
- [{conf,LastRef,[{suite,Mod}],
+ [{conf,LastRef,[{suite,LastMod}],
{FwMod,end_per_suite}}|Init];
false ->
[{conf,LastRef,[],{LastMod,end_per_suite}}|Init]
@@ -2926,6 +2936,29 @@ run_test_cases_loop([{conf,_Ref,_Props,_X}=Conf|_Cases0],
Config, _TimetrapData, _Mode, _Status) ->
erlang:error(badarg, [Conf,Config]);
+run_test_cases_loop([{repeat,Case,{RepeatType,N}}|Cases0], Config,
+ TimeTrapData, Mode, Status) ->
+ Ref = make_ref(),
+ Parallel = check_prop(parallel, Mode) =/= false,
+ Sequence = check_prop(sequence, Mode) =/= false,
+ RepeatStop = RepeatType=:=repeat_until_fail
+ orelse RepeatType=:=repeat_until_ok,
+
+ if Parallel andalso RepeatStop ->
+ %% Cannot check results of test case during parallal
+ %% execution, so only RepeatType=:=repeat is allowed in
+ %% combination with parallel groups.
+ erlang:error({illegal_combination,{parallel,RepeatType}});
+ Sequence andalso RepeatStop ->
+ %% Sequence is stop on fail + skip rest, so only
+ %% RepeatType=:=repeat makes sense inside a sequence.
+ erlang:error({illegal_combination,{sequence,RepeatType}});
+ true ->
+ Mode1 = [{Ref,[{repeat,{RepeatType,1,N}}],?now}|Mode],
+ run_test_cases_loop([Case | Cases0], Config, TimeTrapData,
+ Mode1, Status)
+ end;
+
run_test_cases_loop([{Mod,Case}|Cases], Config, TimetrapData, Mode, Status) ->
ActualCfg =
case get(test_server_create_priv_dir) of
@@ -2938,7 +2971,7 @@ run_test_cases_loop([{Mod,Case}|Cases], Config, TimetrapData, Mode, Status) ->
run_test_cases_loop([{Mod,Case,[ActualCfg]}|Cases], Config,
TimetrapData, Mode, Status);
-run_test_cases_loop([{Mod,Func,Args}|Cases], Config, TimetrapData, Mode, Status) ->
+run_test_cases_loop([{Mod,Func,Args}=Case|Cases], Config, TimetrapData, Mode0, Status) ->
{Num,RunInit} =
case FwMod = get_fw_mod(?MODULE) of
Mod when Func == error_in_suite ->
@@ -2948,6 +2981,14 @@ run_test_cases_loop([{Mod,Func,Args}|Cases], Config, TimetrapData, Mode, Status)
run_init}
end,
+ Mode =
+ case Mode0 of
+ [{_,[{repeat,{_,_,_}}],_}|RestMode] ->
+ RestMode;
+ _ ->
+ Mode0
+ end,
+
%% check the current execution mode and save info about the case if
%% detected that printouts to common log files is handled later
@@ -2975,36 +3016,42 @@ run_test_cases_loop([{Mod,Func,Args}|Cases], Config, TimetrapData, Mode, Status)
if is_tuple(RetVal) -> element(1,RetVal);
true -> undefined
end,
- {Failed,Status1} =
+ {Result,Failed,Status1} =
case RetTag of
Skip when Skip==skip; Skip==skipped ->
- {false,update_status(skipped, Mod, Func, Status)};
+ {skipped,false,update_status(skipped, Mod, Func, Status)};
Fail when Fail=='EXIT'; Fail==failed ->
- {true,update_status(failed, Mod, Func, Status)};
+ {failed,true,update_status(failed, Mod, Func, Status)};
_ when Time==died, RetVal=/=ok ->
- {true,update_status(failed, Mod, Func, Status)};
+ {failed,true,update_status(failed, Mod, Func, Status)};
_ ->
- {false,update_status(ok, Mod, Func, Status)}
+ {ok,false,update_status(ok, Mod, Func, Status)}
end,
case check_prop(sequence, Mode) of
false ->
+ {Cases1,Mode1} =
+ check_repeat_testcase(Case,Result,Cases,Mode0),
stop_minor_log_file(),
- run_test_cases_loop(Cases, Config, TimetrapData, Mode, Status1);
+ run_test_cases_loop(Cases1, Config, TimetrapData, Mode1, Status1);
Ref ->
%% the case is in a sequence; we must check the result and
%% determine if the following cases should run or be skipped
if not Failed -> % proceed with next case
+ {Cases1,Mode1} =
+ check_repeat_testcase(Case,Result,Cases,Mode0),
stop_minor_log_file(),
- run_test_cases_loop(Cases, Config, TimetrapData, Mode, Status1);
+ run_test_cases_loop(Cases1, Config, TimetrapData, Mode1, Status1);
true -> % skip rest of cases in sequence
print(minor, "~n*** ~tw failed.~n"
" Skipping all other cases in sequence.",
[Func]),
+ {Cases1,Mode1} =
+ check_repeat_testcase(Case,Result,Cases,Mode0),
Reason = {failed,{Mod,Func}},
- Cases2 = skip_cases_upto(Ref, Cases, Reason, tc,
+ Cases2 = skip_cases_upto(Ref, Cases1, Reason, tc,
Mode, auto_skip_case),
stop_minor_log_file(),
- run_test_cases_loop(Cases2, Config, TimetrapData, Mode, Status1)
+ run_test_cases_loop(Cases2, Config, TimetrapData, Mode1, Status1)
end
end;
%% the test case is being executed in parallel with the main process (and
@@ -3013,7 +3060,8 @@ run_test_cases_loop([{Mod,Func,Args}|Cases], Config, TimetrapData, Mode, Status)
%% io from Pid will be buffered by the test_server_io process and
%% handled later, so we have to save info about the case
queue_test_case_io(undefined, Pid, Num+1, Mod, Func),
- run_test_cases_loop(Cases, Config, TimetrapData, Mode, Status)
+ {Cases1,Mode1} = check_repeat_testcase(Case,ok,Cases,Mode0),
+ run_test_cases_loop(Cases1, Config, TimetrapData, Mode1, Status)
end;
%% TestSpec processing finished
@@ -3452,9 +3500,19 @@ modify_cases_upto1(Ref, {skip,Reason,FType,Mode,SkipType},
T, Orig, Alt)
end;
-%% next is some other case, ignore or copy
-modify_cases_upto1(Ref, {skip,_,_,_,_}=Op, [_Other|T], Orig, Alt) ->
+%% next is a repeated test case
+modify_cases_upto1(Ref, {skip,Reason,_,Mode,SkipType}=Op,
+ [{repeat,{_M,_F}=MF,_Repeat}|T], Orig, Alt) ->
+ modify_cases_upto1(Ref, Op, T, Orig, [{SkipType,{MF,Reason},Mode}|Alt]);
+
+%% next is an already skipped case, ignore or copy
+modify_cases_upto1(Ref, {skip,_,_,_,_}=Op, [{SkipType,_,_}|T], Orig, Alt)
+ when SkipType=:=skip_case; SkipType=:=auto_skip_case ->
modify_cases_upto1(Ref, Op, T, Orig, Alt);
+
+%% next is some other case, mark as skipped or copy
+modify_cases_upto1(Ref, {skip,Reason,_,Mode,SkipType}=Op, [Other|T], Orig, Alt) ->
+ modify_cases_upto1(Ref, Op, T, Orig, [{SkipType,{Other,Reason},Mode}|Alt]);
modify_cases_upto1(Ref, CopyOp, [C|T], Orig, Alt) ->
modify_cases_upto1(Ref, CopyOp, T, [C|Orig], [C|Alt]).
@@ -3842,6 +3900,10 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit,
{died,{timetrap_timeout,TimetrapTimeout}} ->
progress(failed, Num, Mod, Func, GrName, Loc,
timetrap_timeout, TimetrapTimeout, Comment, Style);
+ {died,Reason={auto_skip,_Why}} ->
+ %% died in init_per_testcase or in a hook in this context
+ progress(skip, Num, Mod, Func, GrName, Loc, Reason,
+ Time, Comment, Style);
{died,{Skip,Reason}} when Skip==skip; Skip==skipped ->
%% died in init_per_testcase
progress(skip, Num, Mod, Func, GrName, Loc, Reason,
@@ -4800,6 +4862,14 @@ collect_cases({make,InitMFA,CaseList,FinMFA}, St0, Mode) ->
{error,_Reason} = Error -> Error
end;
+collect_cases({repeat,{Module, Case}, Repeat}, St, Mode) ->
+ case catch collect_case([Case], St#cc{mod=Module}, [], Mode) of
+ {ok, [{Module,Case}], _} ->
+ {ok, [{repeat,{Module, Case}, Repeat}], St};
+ Other ->
+ {error,Other}
+ end;
+
collect_cases({Module, Cases}, St, Mode) when is_list(Cases) ->
case (catch collect_case(Cases, St#cc{mod=Module}, [], Mode)) of
Result = {ok,_,_} ->
@@ -5763,3 +5833,42 @@ encoding(File) ->
E ->
E
end.
+
+check_repeat_testcase(Case,Result,Cases,
+ [{Ref,[{repeat,RepeatData0}],StartTime}|Mode0]) ->
+ case do_update_repeat_data(Result,RepeatData0) of
+ false ->
+ {Cases,Mode0};
+ RepeatData ->
+ {[Case|Cases],[{Ref,[{repeat,RepeatData}],StartTime}|Mode0]}
+ end;
+check_repeat_testcase(_,_,Cases,Mode) ->
+ {Cases,Mode}.
+
+do_update_repeat_data(_,{RT,N,N}) when is_integer(N) ->
+ report_repeat_testcase(N,N),
+ report_stop_repeat_testcase(done,{RT,N}),
+ false;
+do_update_repeat_data(ok,{repeat_until_ok=RT,M,N}) ->
+ report_repeat_testcase(M,N),
+ report_stop_repeat_testcase(RT,{RT,N}),
+ false;
+do_update_repeat_data(failed,{repeat_until_fail=RT,M,N}) ->
+ report_repeat_testcase(M,N),
+ report_stop_repeat_testcase(RT,{RT,N}),
+ false;
+do_update_repeat_data(_,{RT,M,N}) when is_integer(M) ->
+ report_repeat_testcase(M,N),
+ {RT,M+1,N};
+do_update_repeat_data(_,{_,M,N}=RepeatData) ->
+ report_repeat_testcase(M,N),
+ RepeatData.
+
+report_stop_repeat_testcase(Reason,RepVal) ->
+ print(minor, "~n*** Stopping test case repeat operation: ~w", [Reason]),
+ print(1, "Stopping test case repeat operation: ~w", [RepVal]).
+
+report_repeat_testcase(M,forever) ->
+ print(minor, "~n=== Repeated test case: ~w of infinity", [M]);
+report_repeat_testcase(M,N) ->
+ print(minor, "~n=== Repeated test case: ~w of ~w", [M,N]).
diff --git a/lib/common_test/src/test_server_node.erl b/lib/common_test/src/test_server_node.erl
index c11b9071cf..f77d5a4966 100644
--- a/lib/common_test/src/test_server_node.erl
+++ b/lib/common_test/src/test_server_node.erl
@@ -18,7 +18,7 @@
%% %CopyrightEnd%
%%
-module(test_server_node).
--compile(r16).
+-compile(r20).
%%%
%%% The same compiled code for this module must be possible to load
diff --git a/lib/common_test/src/test_server_sup.erl b/lib/common_test/src/test_server_sup.erl
index 26e7534c6c..ab8066a88d 100644
--- a/lib/common_test/src/test_server_sup.erl
+++ b/lib/common_test/src/test_server_sup.erl
@@ -770,7 +770,7 @@ framework_call(Callback,Func,Args,DefaultReturn) ->
end,
case SetTcState of
true ->
- test_server:set_tc_state({framework,Mod,Func});
+ test_server:set_tc_state({framework,{Mod,Func,Args}});
false ->
ok
end,
diff --git a/lib/common_test/test/Makefile b/lib/common_test/test/Makefile
index ecd1f727a2..e510b74d6a 100644
--- a/lib/common_test/test/Makefile
+++ b/lib/common_test/test/Makefile
@@ -74,7 +74,9 @@ MODULES= \
ct_SUITE \
ct_keep_logs_SUITE \
ct_unicode_SUITE \
- ct_auto_clean_SUITE
+ ct_auto_clean_SUITE \
+ ct_util_SUITE \
+ ct_tc_repeat_SUITE
ERL_FILES= $(MODULES:%=%.erl)
HRL_FILES= test_server_test_lib.hrl
diff --git a/lib/common_test/test/ct_error_SUITE.erl b/lib/common_test/test/ct_error_SUITE.erl
index 7468ebe9d9..d31bd26273 100644
--- a/lib/common_test/test/ct_error_SUITE.erl
+++ b/lib/common_test/test/ct_error_SUITE.erl
@@ -648,33 +648,35 @@ test_events(cfg_error) ->
{?eh,tc_start,{cfg_error_11_SUITE,end_per_suite}},
{?eh,tc_done,{cfg_error_11_SUITE,end_per_suite,ok}},
{?eh,tc_start,{cfg_error_12_SUITE,tc1}},
- {?eh,tc_done,{ct_framework,init_tc,{framework_error,{timetrap,500}}}},
- {?eh,test_stats,{13,8,{0,19}}},
+ {?eh,tc_done,{cfg_error_12_SUITE,tc1,
+ {auto_skipped,
+ {failed,{ct_framework,init_tc,{timetrap,500}}}}}},
+ {?eh,test_stats,{13,7,{0,20}}},
{?eh,tc_start,{cfg_error_12_SUITE,tc2}},
{?eh,tc_done,{cfg_error_12_SUITE,tc2,{failed,
{cfg_error_12_SUITE,end_per_testcase,
{timetrap_timeout,500}}}}},
- {?eh,test_stats,{14,8,{0,19}}},
+ {?eh,test_stats,{14,7,{0,20}}},
{?eh,tc_start,{cfg_error_12_SUITE,tc3}},
{?eh,tc_done,{cfg_error_12_SUITE,tc3,ok}},
- {?eh,test_stats,{15,8,{0,19}}},
+ {?eh,test_stats,{15,7,{0,20}}},
{?eh,tc_start,{cfg_error_12_SUITE,tc4}},
{?eh,tc_done,{cfg_error_12_SUITE,tc4,{failed,
{cfg_error_12_SUITE,end_per_testcase,
{timetrap_timeout,500}}}}},
- {?eh,test_stats,{16,8,{0,19}}},
+ {?eh,test_stats,{16,7,{0,20}}},
{?eh,tc_start,{cfg_error_13_SUITE,init_per_suite}},
{?eh,tc_done,{cfg_error_13_SUITE,init_per_suite,ok}},
{?eh,tc_start,{cfg_error_13_SUITE,tc1}},
{?eh,tc_done,{cfg_error_13_SUITE,tc1,ok}},
- {?eh,test_stats,{17,8,{0,19}}},
+ {?eh,test_stats,{17,7,{0,20}}},
{?eh,tc_start,{cfg_error_13_SUITE,end_per_suite}},
{?eh,tc_done,{cfg_error_13_SUITE,end_per_suite,ok}},
{?eh,tc_start,{cfg_error_14_SUITE,init_per_suite}},
{?eh,tc_done,{cfg_error_14_SUITE,init_per_suite,ok}},
{?eh,tc_start,{cfg_error_14_SUITE,tc1}},
{?eh,tc_done,{cfg_error_14_SUITE,tc1,ok}},
- {?eh,test_stats,{18,8,{0,19}}},
+ {?eh,test_stats,{18,7,{0,20}}},
{?eh,tc_start,{cfg_error_14_SUITE,end_per_suite}},
{?eh,tc_done,{cfg_error_14_SUITE,end_per_suite,
{comment,
@@ -728,25 +730,30 @@ test_events(lib_error) ->
{lib_error_1_SUITE,no_lines_throw,{failed,{error,{thrown,catch_me_if_u_can}}}}},
{?eh,test_stats,{0,8,{0,0}}},
{?eh,tc_start,{lib_error_1_SUITE,init_tc_error}},
- {?eh,tc_done,{ct_framework,init_tc,
- {framework_error,{{badmatch,[1,2]},'_'}}}},
- {?eh,test_stats,{0,9,{0,0}}},
+ {?eh,tc_done,{lib_error_1_SUITE,init_tc_error,
+ {auto_skipped,
+ {failed,
+ {ct_framework,init_tc,
+ {{badmatch,[1,2]},'_'}}}}}},
+ {?eh,test_stats,{0,8,{0,1}}},
{?eh,tc_start,{lib_error_1_SUITE,init_tc_exit}},
- {?eh,tc_done,{ct_framework,init_tc,{framework_error,byebye}}},
- {?eh,test_stats,{0,10,{0,0}}},
+ {?eh,tc_done,{lib_error_1_SUITE,init_tc_exit,
+ {auto_skipped,{failed,{ct_framework,init_tc,byebye}}}}},
+ {?eh,test_stats,{0,8,{0,2}}},
{?eh,tc_start,{lib_error_1_SUITE,init_tc_throw}},
- {?eh,tc_done,{ct_framework,init_tc,{framework_error,catch_me_if_u_can}}},
- {?eh,test_stats,{0,11,{0,0}}},
+ {?eh,tc_done,{lib_error_1_SUITE,init_tc_throw,
+ {auto_skipped,{failed,{ct_framework,init_tc,
+ catch_me_if_u_can}}}}},
+ {?eh,test_stats,{0,8,{0,3}}},
{?eh,tc_start,{lib_error_1_SUITE,end_tc_error}},
- {?eh,tc_done,{ct_framework,end_tc,
- {framework_error,{{badmatch,[1,2]},'_'}}}},
- {?eh,test_stats,{0,12,{0,0}}},
+ {?eh,tc_done,{lib_error_1_SUITE,end_tc_error,ok}}, % warning in comment
+ {?eh,test_stats,{1,8,{0,3}}},
{?eh,tc_start,{lib_error_1_SUITE,end_tc_exit}},
- {?eh,tc_done,{ct_framework,end_tc,{framework_error,byebye}}},
- {?eh,test_stats,{0,13,{0,0}}},
+ {?eh,tc_done,{lib_error_1_SUITE,end_tc_exit,ok}}, % warning in comment
+ {?eh,test_stats,{2,8,{0,3}}},
{?eh,tc_start,{lib_error_1_SUITE,end_tc_throw}},
- {?eh,tc_done,{ct_framework,end_tc,{framework_error,catch_me_if_u_can}}},
- {?eh,test_stats,{0,14,{0,0}}},
+ {?eh,tc_done,{lib_error_1_SUITE,end_tc_throw,ok}}, % warning in comment
+ {?eh,test_stats,{3,8,{0,3}}},
{?eh,tc_start,{lib_error_1_SUITE,end_per_suite}},
{?eh,tc_done,{lib_error_1_SUITE,end_per_suite,ok}},
{?eh,test_done,{'DEF','STOP_TIME'}},
diff --git a/lib/common_test/test/ct_hooks_SUITE.erl b/lib/common_test/test/ct_hooks_SUITE.erl
index 44b86b1dfe..340b8f3d52 100644
--- a/lib/common_test/test/ct_hooks_SUITE.erl
+++ b/lib/common_test/test/ct_hooks_SUITE.erl
@@ -73,11 +73,15 @@ all() ->
all(suite) ->
lists:reverse(
[
+ crash_groups, crash_all, bad_return_groups, bad_return_all,
+ illegal_values_groups, illegal_values_all, alter_groups, alter_all,
+ alter_all_to_skip, alter_all_from_skip,
one_cth, two_cth, faulty_cth_no_init, faulty_cth_id_no_init,
faulty_cth_exit_in_init, faulty_cth_exit_in_id,
faulty_cth_exit_in_init_scope_suite, minimal_cth,
minimal_and_maximal_cth, faulty_cth_undef,
scope_per_suite_cth, scope_per_group_cth, scope_suite_cth,
+ scope_suite_group_only_cth,
scope_per_suite_state_cth, scope_per_group_state_cth,
scope_suite_state_cth,
fail_pre_suite_cth, double_fail_pre_suite_cth,
@@ -152,6 +156,11 @@ scope_suite_cth(Config) when is_list(Config) ->
do_test(scope_suite_cth, "ct_scope_suite_cth_SUITE.erl",
[],Config).
+scope_suite_group_only_cth(Config) when is_list(Config) ->
+ do_test(scope_suite_group_only_cth,
+ "ct_scope_suite_group_only_cth_SUITE.erl",
+ [],Config,ok,2,[{group,g1}]).
+
scope_per_group_cth(Config) when is_list(Config) ->
do_test(scope_per_group_cth, "ct_scope_per_group_cth_SUITE.erl",
[],Config).
@@ -304,10 +313,74 @@ repeat_force_stop(Config) ->
[{force_stop,skip_rest},{duration,"000009"}]).
%% Test that expected callbacks, and only those, are called when a test
-%% are fails due to clash in config alias names
+%% fails due to clash in config alias names
config_clash(Config) ->
do_test(config_clash, "config_clash_SUITE.erl", [skip_cth], Config).
+%% Test post_groups and post_all hook callbacks, introduced by OTP-14746
+alter_groups(Config) ->
+ CfgFile = gen_config(?FUNCTION_NAME,
+ [{post_groups_return,[{new_group,[tc1,tc2]}]},
+ {post_all_return,[{group,new_group}]}],Config),
+ do_test(?FUNCTION_NAME, "all_and_groups_SUITE.erl", [all_and_groups_cth],
+ Config, ok, 2, [{config,CfgFile}]).
+
+alter_all(Config) ->
+ CfgFile = gen_config(?FUNCTION_NAME,[{post_all_return,[tc2]}],Config),
+ do_test(?FUNCTION_NAME, "all_and_groups_SUITE.erl", [all_and_groups_cth],
+ Config, ok, 2, [{config,CfgFile}]).
+
+alter_all_from_skip(Config) ->
+ CfgFile = gen_config(?FUNCTION_NAME,[{all_return,{skip,"skipped by all/0"}},
+ {post_all_return,[tc2]}],Config),
+ do_test(?FUNCTION_NAME, "all_and_groups_SUITE.erl", [all_and_groups_cth],
+ Config, ok, 2, [{config,CfgFile}]).
+
+alter_all_to_skip(Config) ->
+ CfgFile = gen_config(?FUNCTION_NAME,
+ [{post_all_return,{skip,"skipped by post_all/3"}}],
+ Config),
+ do_test(?FUNCTION_NAME, "all_and_groups_SUITE.erl", [all_and_groups_cth],
+ Config, ok, 2, [{config,CfgFile}]).
+
+bad_return_groups(Config) ->
+ CfgFile = gen_config(?FUNCTION_NAME,[{post_groups_return,not_a_list}],
+ Config),
+ do_test(?FUNCTION_NAME, "all_and_groups_SUITE.erl", [all_and_groups_cth],
+ Config, ok, 2, [{config,CfgFile}]).
+
+bad_return_all(Config) ->
+ CfgFile = gen_config(?FUNCTION_NAME,[{post_all_return,not_a_list}],
+ Config),
+ do_test(?FUNCTION_NAME, "all_and_groups_SUITE.erl", [all_and_groups_cth],
+ Config, ok, 2, [{config,CfgFile}]).
+
+illegal_values_groups(Config) ->
+ CfgFile = gen_config(?FUNCTION_NAME,
+ [{post_groups_return,[{new_group,[this_test_does_not_exist]},
+ this_is_not_a_group_def]}],
+ Config),
+ do_test(?FUNCTION_NAME, "all_and_groups_SUITE.erl", [all_and_groups_cth],
+ Config, ok, 2, [{config,CfgFile}]).
+
+illegal_values_all(Config) ->
+ CfgFile = gen_config(?FUNCTION_NAME,
+ [{post_all_return,[{group,this_group_does_not_exist},
+ {this_is_not_a_valid_term}]}],
+ Config),
+ do_test(?FUNCTION_NAME, "all_and_groups_SUITE.erl", [all_and_groups_cth],
+ Config, ok, 2, [{config,CfgFile}]).
+
+crash_groups(Config) ->
+ CfgFile = gen_config(?FUNCTION_NAME,[{post_groups_return,crash}],Config),
+ do_test(?FUNCTION_NAME, "all_and_groups_SUITE.erl", [all_and_groups_cth],
+ Config, ok, 2, [{config,CfgFile}]).
+
+crash_all(Config) ->
+ CfgFile = gen_config(?FUNCTION_NAME,[{post_all_return,crash}],Config),
+ do_test(?FUNCTION_NAME, "all_and_groups_SUITE.erl", [all_and_groups_cth],
+ Config, ok, 2, [{config,CfgFile}]).
+
%%%-----------------------------------------------------------------
%%% HELP FUNCTIONS
%%%-----------------------------------------------------------------
@@ -327,6 +400,7 @@ do_test(Tag, {WhatTag,Wildcard}, CTHs, Config, Res, EC, ExtraOpts) ->
filename:join([DataDir,"cth/tests",Wildcard])),
{Opts,ERPid} =
setup([{WhatTag,Files},{ct_hooks,CTHs},{label,Tag}|ExtraOpts], Config),
+
Res = ct_test_support:run(Opts, Config),
Events = ct_test_support:get_events(ERPid, Config),
@@ -352,6 +426,13 @@ reformat(Events, EH) ->
%reformat(Events, _EH) ->
% Events.
+gen_config(Name,KeyVals,Config) ->
+ PrivDir = ?config(priv_dir,Config),
+ File = filename:join(PrivDir,atom_to_list(Name)++".cfg"),
+ ok = file:write_file(File,[io_lib:format("~p.~n",[{Key,Value}])
+ || {Key,Value} <- KeyVals]),
+ File.
+
%%%-----------------------------------------------------------------
%%% TEST EVENTS
%%%-----------------------------------------------------------------
@@ -370,13 +451,16 @@ test_events(one_empty_cth) ->
{?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
{?eh,cth,{empty_cth,id,[[]]}},
{?eh,cth,{empty_cth,init,[{'_','_','_'},[]]}},
+ %% check that post_groups and post_all comes after init when hook
+ %% is installed with start flag/option.
+ {?eh,cth,{empty_cth,post_groups,[ct_cth_empty_SUITE,[]]}},
+ {?eh,cth,{empty_cth,post_all,[ct_cth_empty_SUITE,[test_case],[]]}},
{?eh,tc_start,{ct_cth_empty_SUITE,init_per_suite}},
{?eh,cth,{empty_cth,pre_init_per_suite,
[ct_cth_empty_SUITE,'$proplist',[]]}},
{?eh,cth,{empty_cth,post_init_per_suite,
[ct_cth_empty_SUITE,'$proplist','$proplist',[]]}},
{?eh,tc_done,{ct_cth_empty_SUITE,init_per_suite,ok}},
-
{?eh,tc_start,{ct_cth_empty_SUITE,test_case}},
{?eh,cth,{empty_cth,pre_init_per_testcase,[ct_cth_empty_SUITE,test_case,'$proplist',[]]}},
{?eh,cth,{empty_cth,post_init_per_testcase,[ct_cth_empty_SUITE,test_case,'$proplist','_',[]]}},
@@ -585,6 +669,10 @@ test_events(scope_suite_cth) ->
[
{?eh,start_logging,{'DEF','RUNDIR'}},
{?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ %% check that post_groups and post_all comes before init when hook
+ %% is installed in suite/0
+ {?eh,cth,{'_',post_groups,['_',[]]}},
+ {?eh,cth,{'_',post_all,['_','_',[]]}},
{?eh,tc_start,{ct_scope_suite_cth_SUITE,init_per_suite}},
{?eh,cth,{'_',id,[[]]}},
{?eh,cth,{'_',init,['_',[]]}},
@@ -606,6 +694,34 @@ test_events(scope_suite_cth) ->
{?eh,stop_logging,[]}
];
+test_events(scope_suite_group_only_cth) ->
+ Suite = ct_scope_suite_group_only_cth_SUITE,
+ CTH = empty_cth,
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,start_info,{1,1,1}},
+ %% check that post_groups and post_all comes before init when hook
+ %% is installed in suite/0
+ {?eh,cth,{CTH,post_groups,['_',['_']]}},
+ {negative,
+ {?eh,cth,{CTH,post_all,['_','_','_']}},
+ {?eh,tc_start,{Suite,init_per_suite}}},
+ {?eh,cth,{CTH,id,[[]]}},
+ {?eh,cth,{CTH,init,['_',[]]}},
+ {?eh,cth,{CTH,pre_init_per_suite,[Suite,'$proplist',mystate]}},
+ {?eh,cth,{CTH,post_init_per_suite,[Suite,'$proplist','$proplist',mystate]}},
+ {?eh,tc_done,{Suite,init_per_suite,ok}},
+
+ {?eh,tc_start,{Suite,end_per_suite}},
+ {?eh,cth,{CTH,pre_end_per_suite,[Suite,'$proplist',mystate]}},
+ {?eh,cth,{CTH,post_end_per_suite,[Suite,'$proplist','_',mystate]}},
+ {?eh,cth,{CTH,terminate,[mystate]}},
+ {?eh,tc_done,{Suite,end_per_suite,ok}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+ ];
+
test_events(scope_per_group_cth) ->
[
{?eh,start_logging,{'DEF','RUNDIR'}},
@@ -665,6 +781,8 @@ test_events(scope_suite_state_cth) ->
[
{?eh,start_logging,{'DEF','RUNDIR'}},
{?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,cth,{'_',post_groups,['_',[]]}},
+ {?eh,cth,{'_',post_all,['_','_',[]]}},
{?eh,tc_start,{ct_scope_suite_state_cth_SUITE,init_per_suite}},
{?eh,cth,{'_',id,[[test]]}},
{?eh,cth,{'_',init,['_',[test]]}},
@@ -2313,6 +2431,229 @@ test_events(config_clash) ->
%% Make sure no 'cth_error' events are received!
[{negative,{?eh,cth_error,'_'},E} || E <- Events];
+test_events(alter_groups) ->
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,cth,{empty_cth,id,[[]]}},
+ {?eh,cth,{empty_cth,init,[{'_','_','_'},[]]}},
+ {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,
+ [{new_group,[tc1,tc2]}]]}},
+ {?eh,cth,{empty_cth,post_all,[all_and_groups_SUITE,[{group,new_group}],
+ [{new_group,[tc1,tc2]}]]}},
+ {?eh,start_info,{1,1,2}},
+ {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,
+ [{new_group,[tc1,tc2]}]]}},
+ {?eh,cth,{empty_cth,post_all,[all_and_groups_SUITE,[{group,new_group}],
+ [{new_group,[tc1,tc2]}]]}},
+ {?eh,tc_start,{all_and_groups_SUITE,{init_per_group,new_group,[]}}},
+ {?eh,tc_done,{all_and_groups_SUITE,
+ {init_per_group,new_group,'$proplist'},ok}},
+ {?eh,tc_start,{all_and_groups_SUITE,tc1}},
+ {?eh,tc_done,{all_and_groups_SUITE,tc1,ok}},
+ {?eh,tc_start,{all_and_groups_SUITE,tc2}},
+ {?eh,tc_done,{all_and_groups_SUITE,tc2,ok}},
+ {?eh,tc_start,{all_and_groups_SUITE,{end_per_group,new_group,[]}}},
+ {?eh,tc_done,{all_and_groups_SUITE,
+ {end_per_group,new_group,'$proplist'},ok}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,cth,{empty_cth,terminate,[[]]}},
+ {?eh,stop_logging,[]}
+ ];
+
+test_events(alter_all) ->
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,cth,{empty_cth,id,[[]]}},
+ {?eh,cth,{empty_cth,init,[{'_','_','_'},[]]}},
+ {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,
+ [{test_group,[tc1]}]]}},
+ {?eh,cth,{empty_cth,post_all,[all_and_groups_SUITE,[tc2],
+ [{test_group,[tc1]}]]}},
+ {?eh,start_info,{1,1,1}},
+ {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,'_']}},
+ {?eh,cth,{empty_cth,post_all,[all_and_groups_SUITE,[tc2],'_']}},
+ {?eh,tc_start,{all_and_groups_SUITE,tc2}},
+ {?eh,tc_done,{all_and_groups_SUITE,tc2,ok}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,cth,{empty_cth,terminate,[[]]}},
+ {?eh,stop_logging,[]}
+ ];
+
+test_events(alter_all_from_skip) ->
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,cth,{empty_cth,id,[[]]}},
+ {?eh,cth,{empty_cth,init,[{'_','_','_'},[]]}},
+ {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,
+ [{test_group,[tc1]}]]}},
+ {?eh,cth,{empty_cth,post_all,[all_and_groups_SUITE,[tc2],
+ [{test_group,[tc1]}]]}},
+ {?eh,start_info,{1,1,1}},
+ {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,'_']}},
+ {?eh,cth,{empty_cth,post_all,[all_and_groups_SUITE,[tc2],'_']}},
+ {?eh,tc_start,{all_and_groups_SUITE,tc2}},
+ {?eh,tc_done,{all_and_groups_SUITE,tc2,ok}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,cth,{empty_cth,terminate,[[]]}},
+ {?eh,stop_logging,[]}
+ ];
+
+test_events(alter_all_to_skip) ->
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,cth,{empty_cth,id,[[]]}},
+ {?eh,cth,{empty_cth,init,[{'_','_','_'},[]]}},
+ {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,
+ [{test_group,[tc1]}]]}},
+ {?eh,cth,{empty_cth,post_all,[all_and_groups_SUITE,
+ {skip,"skipped by post_all/3"},
+ [{test_group,[tc1]}]]}},
+ {?eh,start_info,{1,1,0}},
+ {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,'_']}},
+ {?eh,cth,{empty_cth,post_all,[all_and_groups_SUITE,
+ {skip,"skipped by post_all/3"},
+ '_']}},
+ {?eh,tc_user_skip,{all_and_groups_SUITE,all,"skipped by post_all/3"}},
+ {?eh,cth,{'_',on_tc_skip,[all_and_groups_SUITE,all,
+ {tc_user_skip,"skipped by post_all/3"},
+ []]}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,cth,{empty_cth,terminate,[[]]}},
+ {?eh,stop_logging,[]}
+ ];
+
+test_events(illegal_values_groups) ->
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,cth,{empty_cth,id,[[]]}},
+ {?eh,cth,{empty_cth,init,[{'_','_','_'},[]]}},
+ {?eh,cth,{empty_cth,post_groups,
+ [all_and_groups_SUITE,
+ [{new_group,[this_test_does_not_exist]},
+ this_is_not_a_group_def]]}},
+ {?eh,start_info,{1,0,0}},
+ {?eh,cth,{empty_cth,post_groups,
+ [all_and_groups_SUITE,
+ [{new_group,[this_test_does_not_exist]},
+ this_is_not_a_group_def]]}},
+ {?eh,tc_start,{ct_framework,error_in_suite}},
+ {?eh,tc_done,{ct_framework,error_in_suite,{failed,{error,'_'}}}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,cth,{empty_cth,terminate,[[]]}},
+ {?eh,stop_logging,[]}
+ ];
+
+test_events(illegal_values_all) ->
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,cth,{empty_cth,id,[[]]}},
+ {?eh,cth,{empty_cth,init,[{'_','_','_'},[]]}},
+ {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,'_']}},
+ {?eh,cth,{empty_cth,post_all,
+ [all_and_groups_SUITE,
+ [{group,this_group_does_not_exist},
+ {this_is_not_a_valid_term}],'_']}},
+ {?eh,start_info,{1,0,0}},
+ {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,'_']}},
+ {?eh,cth,{empty_cth,post_all,
+ [all_and_groups_SUITE,
+ [{group,this_group_does_not_exist},
+ {this_is_not_a_valid_term}],'_']}},
+ {?eh,tc_start,{ct_framework,error_in_suite}},
+ {?eh,tc_done,
+ {ct_framework,error_in_suite,
+ {failed,
+ {error,'Invalid reference to group this_group_does_not_exist in all_and_groups_SUITE:all/0'}}}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,cth,{empty_cth,terminate,[[]]}},
+ {?eh,stop_logging,[]}
+ ];
+
+test_events(bad_return_groups) ->
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,cth,{empty_cth,id,[[]]}},
+ {?eh,cth,{empty_cth,init,[{'_','_','_'},[]]}},
+ {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,not_a_list]}},
+ {?eh,start_info,{1,0,0}},
+ {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,not_a_list]}},
+ {?eh,tc_start,{ct_framework,error_in_suite}},
+ {?eh,tc_done,
+ {ct_framework,error_in_suite,
+ {failed,
+ {error,
+ {'Bad return value from post_groups/2 hook function',not_a_list}}}}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,cth,{empty_cth,terminate,[[]]}},
+ {?eh,stop_logging,[]}
+ ];
+
+test_events(bad_return_all) ->
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,cth,{empty_cth,id,[[]]}},
+ {?eh,cth,{empty_cth,init,[{'_','_','_'},[]]}},
+ {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,'_']}},
+ {?eh,cth,{empty_cth,post_all,[all_and_groups_SUITE,not_a_list,'_']}},
+ {?eh,start_info,{1,0,0}},
+ {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,'_']}},
+ {?eh,cth,{empty_cth,post_all,[all_and_groups_SUITE,not_a_list,'_']}},
+ {?eh,tc_start,{ct_framework,error_in_suite}},
+ {?eh,tc_done,
+ {ct_framework,error_in_suite,
+ {failed,
+ {error,{'Bad return value from post_all/3 hook function',not_a_list}}}}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,cth,{empty_cth,terminate,[[]]}},
+ {?eh,stop_logging,[]}
+ ];
+
+test_events(crash_groups) ->
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,cth,{empty_cth,id,[[]]}},
+ {?eh,cth,{empty_cth,init,[{'_','_','_'},[]]}},
+ {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,crash]}},
+ {?eh,start_info,{1,0,0}},
+ {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,crash]}},
+ {?eh,tc_start,{ct_framework,error_in_suite}},
+ {?eh,tc_done,{ct_framework,error_in_suite,
+ {failed,
+ {error,"all_and_groups_cth:post_groups/2 CTH call failed"}}}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,cth,{empty_cth,terminate,[[]]}},
+ {?eh,stop_logging,[]}
+ ];
+
+test_events(crash_all) ->
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,cth,{empty_cth,id,[[]]}},
+ {?eh,cth,{empty_cth,init,[{'_','_','_'},[]]}},
+ {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,'_']}},
+ {?eh,cth,{empty_cth,post_all,[all_and_groups_SUITE,crash,'_']}},
+ {?eh,start_info,{1,0,0}},
+ {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,'_']}},
+ {?eh,cth,{empty_cth,post_all,[all_and_groups_SUITE,crash,'_']}},
+ {?eh,tc_start,{ct_framework,error_in_suite}},
+ {?eh,tc_done,{ct_framework,error_in_suite,
+ {failed,
+ {error,"all_and_groups_cth:post_all/3 CTH call failed"}}}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,cth,{empty_cth,terminate,[[]]}},
+ {?eh,stop_logging,[]}
+ ];
+
test_events(ok) ->
ok.
diff --git a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/all_and_groups_SUITE.erl b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/all_and_groups_SUITE.erl
new file mode 100644
index 0000000000..adc86005f9
--- /dev/null
+++ b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/all_and_groups_SUITE.erl
@@ -0,0 +1,47 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2017. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(all_and_groups_SUITE).
+
+-suite_defaults([{timetrap, {minutes, 10}}]).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include("ct.hrl").
+
+init_per_group(_Group,Config) ->
+ Config.
+
+end_per_group(_Group,Config) ->
+ ok.
+
+all() ->
+ ct:get_config(all_return,[{group,test_group}]).
+
+groups() ->
+ [{test_group,[tc1]}].
+
+%% Test cases starts here.
+tc1(Config) ->
+ ok.
+
+tc2(Config) ->
+ ok.
diff --git a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/all_and_groups_cth.erl b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/all_and_groups_cth.erl
new file mode 100644
index 0000000000..9ebc00e9de
--- /dev/null
+++ b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/all_and_groups_cth.erl
@@ -0,0 +1,100 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2017. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+
+-module(all_and_groups_cth).
+
+
+-include_lib("common_test/src/ct_util.hrl").
+-include_lib("common_test/include/ct_event.hrl").
+
+%% Send a cth_error event if a callback is called with unexpected arguments
+-define(fail(Info),
+ gen_event:notify(
+ ?CT_EVMGR_REF,
+ #event{ name = cth_error,
+ node = node(),
+ data = {illegal_hook_callback,{?MODULE,?FUNCTION_NAME,Info}}})).
+
+%% CT Hooks
+-compile(export_all).
+
+id(Opts) ->
+ empty_cth:id(Opts).
+
+post_groups(Suite,Groups) ->
+ case empty_cth:post_groups(Suite,ct:get_config(post_groups_return,Groups)) of
+ crash -> error(crash_in_post_groups);
+ R -> R
+ end.
+
+post_all(Suite,Tests,Groups) ->
+ case empty_cth:post_all(Suite,ct:get_config(post_all_return,Tests),Groups) of
+ crash -> error(crash_in_post_all);
+ R -> R
+ end.
+
+init(Id, Opts) ->
+ empty_cth:init(Id, Opts).
+
+pre_init_per_suite(Suite, Config, State) ->
+ empty_cth:pre_init_per_suite(Suite,Config,State).
+
+post_init_per_suite(Suite,Config,Return,State) ->
+ empty_cth:post_init_per_suite(Suite,Config,Return,State).
+
+pre_end_per_suite(Suite,Config,State) ->
+ empty_cth:pre_end_per_suite(Suite,Config,State).
+
+post_end_per_suite(Suite,Config,Return,State) ->
+ empty_cth:post_end_per_suite(Suite,Config,Return,State).
+
+pre_init_per_group(Suite,Group,Config,State) ->
+ empty_cth:pre_init_per_group(Suite,Group,Config,State).
+
+post_init_per_group(Suite,Group,Config,Return,State) ->
+ empty_cth:post_init_per_group(Suite,Group,Config,Return,State).
+
+pre_end_per_group(Suite,Group,Config,State) ->
+ empty_cth:pre_end_per_group(Suite,Group,Config,State).
+
+post_end_per_group(Suite,Group,Config,Return,State) ->
+ empty_cth:post_end_per_group(Suite,Group,Config,Return,State).
+
+pre_init_per_testcase(Suite,TC,Config,State) ->
+ empty_cth:pre_init_per_testcase(Suite,TC,Config,State).
+
+post_init_per_testcase(Suite,TC,Config,Return,State) ->
+ empty_cth:post_init_per_testcase(Suite,TC,Config,Return,State).
+
+pre_end_per_testcase(Suite,TC,Config,State) ->
+ empty_cth:pre_end_per_testcase(Suite,TC,Config,State).
+
+post_end_per_testcase(Suite,TC,Config,Return,State) ->
+ empty_cth:post_end_per_testcase(Suite,TC,Config,Return,State).
+
+on_tc_fail(Suite,TC,Reason,State) ->
+ empty_cth:on_tc_fail(Suite,TC,Reason,State).
+
+on_tc_skip(Suite,TC,Reason,State) ->
+ empty_cth:on_tc_skip(Suite,TC,Reason,State).
+
+terminate(State) ->
+ empty_cth:terminate(State).
diff --git a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_match_state_cth.erl b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_match_state_cth.erl
new file mode 100644
index 0000000000..38c9da903d
--- /dev/null
+++ b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_match_state_cth.erl
@@ -0,0 +1,58 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2017. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+
+-module(ct_match_state_cth).
+
+
+-include_lib("common_test/src/ct_util.hrl").
+-include_lib("common_test/include/ct_event.hrl").
+
+-compile(export_all).
+
+id(Opts) ->
+ empty_cth:id(Opts).
+
+post_groups(Suite, Groups) ->
+ empty_cth:post_groups(Suite, Groups).
+
+post_all(Suite, Tests, Groups) ->
+ empty_cth:post_all(Suite, Tests, Groups).
+
+init(Id, Opts) ->
+ empty_cth:init(Id, Opts),
+ {ok,mystate}.
+
+%% In the following, always match against the state value, to ensure
+%% that init has indeed been called before the rest of the hooks.
+pre_init_per_suite(Suite,Config,mystate) ->
+ empty_cth:pre_init_per_suite(Suite,Config,mystate).
+
+post_init_per_suite(Suite,Config,Return,mystate) ->
+ empty_cth:post_init_per_suite(Suite,Config,Return,mystate).
+
+pre_end_per_suite(Suite,Config,mystate) ->
+ empty_cth:pre_end_per_suite(Suite,Config,mystate).
+
+post_end_per_suite(Suite,Config,Return,mystate) ->
+ empty_cth:post_end_per_suite(Suite,Config,Return,mystate).
+
+terminate(mystate) ->
+ empty_cth:terminate(mystate).
diff --git a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_scope_suite_group_only_cth_SUITE.erl b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_scope_suite_group_only_cth_SUITE.erl
new file mode 100644
index 0000000000..537c97d3f0
--- /dev/null
+++ b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_scope_suite_group_only_cth_SUITE.erl
@@ -0,0 +1,54 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(ct_scope_suite_group_only_cth_SUITE).
+
+-suite_defaults([{timetrap, {minutes, 10}}]).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include("ct.hrl").
+
+%% Test server callback functions
+suite() ->
+ [{ct_hooks,[ct_match_state_cth]}].
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_testcase(_TestCase, Config) ->
+ Config.
+
+end_per_testcase(_TestCase, _Config) ->
+ ok.
+
+all() ->
+ [test_case].
+
+groups() ->
+ [{g1,[test_case]}].
+
+%% Test cases starts here.
+test_case(Config) when is_list(Config) ->
+ ok.
diff --git a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/empty_cth.erl b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/empty_cth.erl
index c648367838..60488e84c6 100644
--- a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/empty_cth.erl
+++ b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/empty_cth.erl
@@ -39,6 +39,9 @@
-export([id/1]).
-export([init/2]).
+-export([post_all/3]).
+-export([post_groups/2]).
+
-export([pre_init_per_suite/3]).
-export([post_init_per_suite/4]).
-export([pre_end_per_suite/3]).
@@ -71,6 +74,31 @@
-record(state, { id = ?MODULE :: term()}).
+%% Called after groups/0.
+%% You can change the return value in this function.
+-spec post_groups(Suite :: atom(), Groups :: list()) -> list().
+post_groups(Suite,Groups) ->
+ gen_event:notify(
+ ?CT_EVMGR_REF, #event{ name = cth, node = node(),
+ data = {?MODULE, post_groups,
+ [Suite,Groups]}}),
+ ct:log("~w:post_groups(~w) called", [?MODULE,Suite]),
+ Groups.
+
+%% Called after all/0.
+%% You can change the return value in this function.
+-spec post_all(Suite :: atom(),
+ Tests :: list(),
+ Groups :: term()) ->
+ list().
+post_all(Suite,Tests,Groups) ->
+ gen_event:notify(
+ ?CT_EVMGR_REF, #event{ name = cth, node = node(),
+ data = {?MODULE, post_all,
+ [Suite,Tests,Groups]}}),
+ ct:log("~w:post_all(~w) called", [?MODULE,Suite]),
+ Tests.
+
%% Always called before any other callback function. Use this to initiate
%% any common state. It should return an state for this CTH.
-spec init(Id :: term(), Opts :: proplists:proplist()) ->
diff --git a/lib/common_test/test/ct_tc_repeat_SUITE.erl b/lib/common_test/test/ct_tc_repeat_SUITE.erl
new file mode 100644
index 0000000000..433b5456fe
--- /dev/null
+++ b/lib/common_test/test/ct_tc_repeat_SUITE.erl
@@ -0,0 +1,438 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009-2017. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(ct_tc_repeat_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("common_test/include/ct_event.hrl").
+
+-define(eh, ct_test_support_eh).
+
+%%--------------------------------------------------------------------
+%% 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) ->
+ DataDir = ?config(data_dir, Config),
+ ct_test_support:init_per_suite([{path_dirs,[DataDir]} | Config]).
+
+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() ->
+ [{timetrap,{minutes,1}}].
+
+all() ->
+ all(suite).
+
+all(suite) ->
+ [
+ repeat,
+ repeat_parallel_until_ok,
+ repeat_parallel_until_fail,
+ repeat_sequence_until_ok,
+ repeat_sequence_until_fail,
+ pick_one_test_from_group,
+ pick_one_test_from_subgroup
+ ].
+
+
+%%--------------------------------------------------------------------
+%% TEST CASES
+%%--------------------------------------------------------------------
+
+%%%-----------------------------------------------------------------
+%%%
+%% Test post_groups and post_all hook callbacks, introduced by OTP-14746
+repeat(Config) ->
+ ok = do_test(?FUNCTION_NAME, "tc_repeat_SUITE", [], [], Config).
+
+repeat_parallel_until_ok(Config) ->
+ {error,{{illegal_combination,{parallel,repeat_until_ok}},_}} =
+ do_test(?FUNCTION_NAME, "tc_repeat_SUITE", [{group,g_parallel_until_ok}],
+ [], Config, 1, []).
+
+repeat_parallel_until_fail(Config) ->
+ {error,{{illegal_combination,{parallel,repeat_until_fail}},_}} =
+ do_test(?FUNCTION_NAME, "tc_repeat_SUITE", [{group,g_parallel_until_fail}],
+ [], Config, 1, []).
+
+repeat_sequence_until_ok(Config) ->
+ {error,{{illegal_combination,{sequence,repeat_until_ok}},_}} =
+ do_test(?FUNCTION_NAME, "tc_repeat_SUITE", [{group,g_sequence_until_ok}],
+ [], Config, 1, []).
+
+repeat_sequence_until_fail(Config) ->
+ {error,{{illegal_combination,{sequence,repeat_until_fail}},_}} =
+ do_test(?FUNCTION_NAME, "tc_repeat_SUITE", [{group,g_sequence_until_fail}],
+ [], Config, 1, []).
+
+pick_one_test_from_group(Config) ->
+ do_test(?FUNCTION_NAME, "tc_repeat_SUITE", [{group,g_mixed},{testcase,tc2}],
+ [], Config, 1, []).
+
+pick_one_test_from_subgroup(Config) ->
+ do_test(?FUNCTION_NAME, "tc_repeat_SUITE",
+ [{group,[[g_mixed,subgroup]]},{testcase,tc2}],
+ [], Config, 1, []).
+
+
+%%%-----------------------------------------------------------------
+%%% HELP FUNCTIONS
+%%%-----------------------------------------------------------------
+
+do_test(Tag, Suite, WTT, CTHs, Config) ->
+ do_test(Tag, Suite, WTT, CTHs, Config, 2, []).
+
+do_test(Tag, Suite0, WTT, CTHs, Config, EC, ExtraOpts) ->
+ DataDir = ?config(data_dir, Config),
+ Suite = filename:join([DataDir,Suite0]),
+ {Opts,ERPid} =
+ setup([{suite,Suite}|WTT]++[{ct_hooks,CTHs},{label,Tag}|ExtraOpts],
+ Config),
+ Res = ct_test_support:run(Opts, Config),
+ Events = ct_test_support:get_events(ERPid, Config),
+ %% io:format("~p~n",[Events]),
+
+ ct_test_support:log_events(Tag,
+ reformat(Events, ?eh),
+ ?config(priv_dir, Config),
+ Opts),
+
+ TestEvents = events_to_check(Tag, EC),
+ ok = ct_test_support:verify_events(TestEvents, Events, Config),
+ Res.
+
+setup(Test, Config) ->
+ Opts0 = ct_test_support:get_opts(Config),
+ Level = ?config(trace_level, Config),
+ EvHArgs = [{cbm,ct_test_support},{trace_level,Level}],
+ Opts = Opts0 ++ [{event_handler,{?eh,EvHArgs}}|Test],
+ ERPid = ct_test_support:start_event_receiver(Config),
+ {Opts,ERPid}.
+
+reformat(Events, EH) ->
+ ct_test_support:reformat(Events, EH).
+
+gen_config(Name,KeyVals,Config) ->
+ PrivDir = ?config(priv_dir,Config),
+ File = filename:join(PrivDir,atom_to_list(Name)++".cfg"),
+ ok = file:write_file(File,[io_lib:format("~p.~n",[{Key,Value}])
+ || {Key,Value} <- KeyVals]),
+ File.
+
+%%%-----------------------------------------------------------------
+%%% 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(repeat) ->
+ S = tc_repeat_SUITE,
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,start_info,{1,1,unknown}},
+
+ %% tc1, {repeat,2}
+ {?eh,tc_start,{S,tc1}},
+ {?eh,tc_done,{S,tc1,ok}},
+ {?eh,test_stats,{1,0,{0,0}}},
+ {?eh,tc_start,{S,tc1}},
+ {?eh,tc_done,{S,tc1,ok}},
+ {?eh,test_stats,{2,0,{0,0}}},
+ %% tc2, {repeat_until_ok,3}
+ {?eh,tc_start,{S,tc2}},
+ {?eh,tc_done,{S,tc2,ok}},
+ {?eh,test_stats,{3,0,{0,0}}},
+ %% tc3, {repeat_until_ok,3}
+ {?eh,tc_start,{S,tc3}},
+ {?eh,tc_done,{tc_repeat_SUITE,tc3,
+ {failed,{error,{test_case_failed,always_fail}}}}},
+ {?eh,test_stats,{3,1,{0,0}}},
+ {?eh,tc_start,{S,tc3}},
+ {?eh,tc_done,{S,tc3,{failed,{error,{test_case_failed,always_fail}}}}},
+ {?eh,test_stats,{3,2,{0,0}}},
+ {?eh,tc_start,{S,tc3}},
+ {?eh,tc_done,{S,tc3,{failed,{error,{test_case_failed,always_fail}}}}},
+ {?eh,test_stats,{3,3,{0,0}}},
+ %% tc4, {repeat_until_fail,3}
+ {?eh,tc_start,{S,tc4}},
+ {?eh,tc_done,{S,tc4,ok}},
+ {?eh,test_stats,{4,3,{0,0}}},
+ {?eh,tc_start,{S,tc4}},
+ {?eh,tc_done,{S,tc4,{failed,{error,{test_case_failed,second_time_fail}}}}},
+ {?eh,test_stats,{4,4,{0,0}}},
+ %% g, tc1, {repeat,2}
+ {?eh,tc_start,{S,{init_per_group,g,[]}}},
+ {?eh,tc_done,{S,{init_per_group,g,[]},ok}},
+ {?eh,tc_start,{S,tc1}},
+ {?eh,tc_done,{S,tc1,ok}},
+ {?eh,test_stats,{5,4,{0,0}}},
+ {?eh,tc_start,{S,tc1}},
+ {?eh,tc_done,{S,tc1,ok}},
+ {?eh,test_stats,{6,4,{0,0}}},
+ {?eh,tc_start,{S,{end_per_group,g,[]}}},
+ {?eh,tc_done,{S,{end_per_group,g,[]},ok}},
+ %% g_until_ok, tc2, {repeat_until_ok,3}
+ {?eh,tc_start,{S,{init_per_group,g_until_ok,[]}}},
+ {?eh,tc_done,{S,{init_per_group,g_until_ok,[]},ok}},
+ {?eh,tc_start,{S,tc2}},
+ {?eh,tc_done,{S,tc2,ok}},
+ {?eh,test_stats,{7,4,{0,0}}},
+ {?eh,tc_start,{S,{end_per_group,g_until_ok,[]}}},
+ {?eh,tc_done,{S,{end_per_group,g_until_ok,[]},ok}},
+ %% g_until_fail, tc4, {repeat_until_fail,3}
+ {?eh,tc_start,{S,{init_per_group,g_until_fail,[]}}},
+ {?eh,tc_done,{S,{init_per_group,g_until_fail,[]},ok}},
+ {?eh,tc_start,{S,tc4}},
+ {?eh,tc_done,{S,tc4,ok}},
+ {?eh,test_stats,{8,4,{0,0}}},
+ {?eh,tc_start,{S,tc4}},
+ {?eh,tc_done,{S,tc4,{failed,{error,{test_case_failed,second_time_fail}}}}},
+ {?eh,test_stats,{8,5,{0,0}}},
+ {?eh,tc_start,{S,{end_per_group,g_until_fail,[]}}},
+ {?eh,tc_done,{S,{end_per_group,g_until_fail,[]},ok}},
+ %% g, parallel, tc1, {repeat,2}
+ {parallel,
+ [{?eh,tc_start,{S,{init_per_group,g,[parallel]}}},
+ {?eh,tc_done,{S,{init_per_group,g,[parallel]},ok}},
+ {?eh,tc_start,{S,tc1}},
+ {?eh,tc_done,{S,tc1,ok}},
+ {?eh,test_stats,{9,5,{0,0}}},
+ {?eh,tc_start,{S,tc1}},
+ {?eh,tc_done,{S,tc1,ok}},
+ {?eh,test_stats,{10,5,{0,0}}},
+ {?eh,tc_start,{S,{end_per_group,g,[parallel]}}},
+ {?eh,tc_done,{S,{end_per_group,g,[parallel]},ok}}]},
+ %% g, sequence, tc1, {repeat,2}
+ {?eh,tc_start,{S,{init_per_group,g,[sequence]}}},
+ {?eh,tc_done,{S,{init_per_group,g,[sequence]},ok}},
+ {?eh,tc_start,{S,tc1}},
+ {?eh,tc_done,{S,tc1,ok}},
+ {?eh,test_stats,{11,5,{0,0}}},
+ {?eh,tc_start,{S,tc1}},
+ {?eh,tc_done,{S,tc1,ok}},
+ {?eh,test_stats,{12,5,{0,0}}},
+ {?eh,tc_start,{S,{end_per_group,g,[sequence]}}},
+ {?eh,tc_done,{S,{end_per_group,g,[sequence]},ok}},
+ %% g_sequence_skip_rest,
+ {?eh,tc_start,{S,{init_per_group,g_mixed,[sequence]}}},
+ {?eh,tc_done,{S,{init_per_group,g_mixed,[sequence]},ok}},
+ {?eh,tc_start,{S,tc1}},
+ {?eh,tc_done,{S,tc1,ok}},
+ {?eh,test_stats,{13,5,{0,0}}},
+ {?eh,tc_start,{S,tc1}},
+ {?eh,tc_done,{S,tc1,ok}},
+ {?eh,test_stats,{14,5,{0,0}}},
+ {?eh,tc_start,{S,tc4}},
+ {?eh,tc_done,{S,tc4,ok}},
+ {?eh,test_stats,{15,5,{0,0}}},
+ {?eh,tc_start,{S,tc4}},
+ {?eh,tc_done,{S,tc4,{failed,{error,{test_case_failed,second_time_fail}}}}},
+ {?eh,test_stats,{15,6,{0,0}}},
+ %% ----> fail in sequence, so skip rest
+ {?eh,tc_auto_skip,{S,{tc4,g_mixed}, % last of current repeat tc4
+ {failed,{tc_repeat_SUITE,tc4}}}},
+ {?eh,test_stats,{15,6,{0,1}}},
+ {?eh,tc_auto_skip,{S,{tc1,g_mixed}, % single tc1
+ {failed,{tc_repeat_SUITE,tc4}}}},
+ {?eh,test_stats,{15,6,{0,2}}},
+ {?eh,tc_auto_skip,{S,{tc1,g}, % group g, tc1, {repeat,2}
+ {failed,{tc_repeat_SUITE,tc4}}}},
+ {?eh,test_stats,{15,6,{0,3}}},
+ {?eh,tc_auto_skip,{S,{tc1,subgroup}, % subgroup, single tc1
+ {failed,{tc_repeat_SUITE,tc4}}}},
+ {?eh,test_stats,{15,6,{0,4}}},
+ {?eh,tc_auto_skip,{S,{tc2,subgroup}, % subgroup, tc2, {repeat,2}
+ {failed,{tc_repeat_SUITE,tc4}}}},
+ {?eh,test_stats,{15,6,{0,5}}},
+ {?eh,tc_auto_skip,{S,{tc2,g_mixed}, % tc2, {repeat,2}
+ {failed,{tc_repeat_SUITE,tc4}}}},
+ {?eh,test_stats,{15,6,{0,6}}},
+ {?eh,tc_auto_skip,{S,{tc2,g_mixed}, % single tc2
+ {failed,{tc_repeat_SUITE,tc4}}}},
+ {?eh,test_stats,{15,6,{0,7}}},
+ {?eh,tc_auto_skip,{S,{tc1,g_mixed}, % tc1, {repeat,2}
+ {failed,{tc_repeat_SUITE,tc4}}}},
+ {?eh,test_stats,{15,6,{0,8}}},
+ {?eh,tc_auto_skip,{S,{tc1,g_mixed}, % single tc1
+ {failed,{tc_repeat_SUITE,tc4}}}},
+ {?eh,test_stats,{15,6,{0,9}}},
+ {?eh,tc_start,{S,{end_per_group,g_mixed,'_'}}},
+ {?eh,tc_done,{S,{end_per_group,g_mixed,'_'},ok}},
+ %% done
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+ ];
+
+test_events(repeat_parallel_until_ok) ->
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,tc_start,{'_',{init_per_group,g_parallel_until_ok,[parallel]}}},
+ {?eh,tc_done,{'_',{init_per_group,g_parallel_until_ok,[parallel]},ok}},
+ {?eh,severe_error,{{illegal_combination,{parallel,repeat_until_ok}},'_'}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+ ];
+
+test_events(repeat_parallel_until_fail) ->
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,tc_start,{'_',{init_per_group,g_parallel_until_fail,[parallel]}}},
+ {?eh,tc_done,{'_',{init_per_group,g_parallel_until_fail,[parallel]},ok}},
+ {?eh,severe_error,{{illegal_combination,{parallel,repeat_until_fail}},'_'}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+ ];
+
+test_events(repeat_sequence_until_ok) ->
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,tc_start,{'_',{init_per_group,g_sequence_until_ok,[sequence]}}},
+ {?eh,tc_done,{'_',{init_per_group,g_sequence_until_ok,[sequence]},ok}},
+ {?eh,severe_error,{{illegal_combination,{sequence,repeat_until_ok}},'_'}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+ ];
+
+test_events(repeat_sequence_until_fail) ->
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,tc_start,{'_',{init_per_group,g_sequence_until_fail,[sequence]}}},
+ {?eh,tc_done,{'_',{init_per_group,g_sequence_until_fail,[sequence]},ok}},
+ {?eh,severe_error,{{illegal_combination,{sequence,repeat_until_fail}},'_'}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+ ];
+
+test_events(pick_one_test_from_group) ->
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,tc_start,{'_',{init_per_group,g_mixed,[]}}},
+ {?eh,tc_done,{'_',{init_per_group,g_mixed,[]},ok}},
+ {negative,
+ {?eh,tc_start,{'_',tc1}},
+ {?eh,tc_start,{'_',tc2}}}, % single tc2
+ {?eh,tc_done,{'_',tc2,ok}},
+ {?eh,tc_start,{'_',tc2}}, % tc2, {repeat,2}
+ {?eh,tc_done,{'_',tc2,ok}},
+ {?eh,tc_start,{'_',tc2}},
+ {?eh,tc_done,{'_',tc2,ok}},
+ {negative,
+ {?eh,tc_start,{'_',tc1}},
+ {?eh,tc_start,{'_',{end_per_group,g_mixed,[]}}}},
+ {?eh,tc_done,{'_',{end_per_group,g_mixed,[]},ok}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+ ];
+
+test_events(pick_one_test_from_subgroup) ->
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,tc_start,{'_',{init_per_group,g_mixed,[]}}},
+ {?eh,tc_done,{'_',{init_per_group,g_mixed,[]},ok}},
+ {negative,
+ {?eh,tc_start,{'_',tc2}},
+ {?eh,tc_start,{'_',{init_per_group,subgroup,[]}}}},
+ {?eh,tc_done,{'_',{init_per_group,subgroup,[]},ok}},
+ {negative,
+ {?eh,tc_start,{'_',tc1}},
+ {?eh,tc_start,{'_',tc2}}}, % tc2, {repeat,2}
+ {?eh,tc_done,{'_',tc2,ok}},
+ {?eh,tc_start,{'_',tc2}},
+ {?eh,tc_done,{'_',tc2,ok}},
+ {negative,
+ {?eh,tc_start,{'_',tc1}},
+ {?eh,tc_start,{'_',{end_per_group,subgroup,[]}}}},
+ {?eh,tc_done,{'_',{end_per_group,subgroup,[]},ok}},
+ {negative,
+ {?eh,tc_start,{'_',tc2}},
+ {?eh,tc_start,{'_',{end_per_group,g_mixed,[]}}}},
+ {?eh,tc_done,{'_',{end_per_group,g_mixed,[]},ok}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+ ];
+
+test_events(ok) ->
+ ok.
+
+%% test events help functions
+contains(List) ->
+ fun(Proplist) when is_list(Proplist) ->
+ contains(List,Proplist)
+ end.
+
+contains([{not_in_order,List}|T],Rest) ->
+ contains_parallel(List,Rest),
+ contains(T,Rest);
+contains([{Ele,Pos}|T] = L,[H|T2]) ->
+ case element(Pos,H) of
+ Ele ->
+ contains(T,T2);
+ _ ->
+ contains(L,T2)
+ end;
+contains([Ele|T],[{Ele,_}|T2])->
+ contains(T,T2);
+contains([Ele|T],[Ele|T2])->
+ contains(T,T2);
+contains(List,[_|T]) ->
+ contains(List,T);
+contains([],_) ->
+ match.
+
+contains_parallel([Key | T], Elems) ->
+ contains([Key],Elems),
+ contains_parallel(T,Elems);
+contains_parallel([],_Elems) ->
+ match.
+
+not_contains(List) ->
+ fun(Proplist) when is_list(Proplist) ->
+ [] = [Ele || {Ele,_} <- Proplist,
+ Test <- List,
+ Test =:= Ele]
+ end.
diff --git a/lib/common_test/test/ct_tc_repeat_SUITE_data/tc_repeat_SUITE.erl b/lib/common_test/test/ct_tc_repeat_SUITE_data/tc_repeat_SUITE.erl
new file mode 100644
index 0000000000..f5d960d12f
--- /dev/null
+++ b/lib/common_test/test/ct_tc_repeat_SUITE_data/tc_repeat_SUITE.erl
@@ -0,0 +1,85 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2017. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(tc_repeat_SUITE).
+
+-suite_defaults([{timetrap, {minutes, 10}}]).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include("ct.hrl").
+
+init_per_group(_Group,Config) ->
+ Config.
+
+end_per_group(_Group,Config) ->
+ ok.
+
+all() ->
+ [{testcase,tc1,[{repeat,2}]},
+ {testcase,tc2,[{repeat_until_ok,3}]},
+ {testcase,tc3,[{repeat_until_ok,3}]},
+ {testcase,tc4,[{repeat_until_fail,3}]},
+ {group,g},
+ {group,g_until_ok},
+ {group,g_until_fail},
+ {group,g,[parallel]},
+ {group,g,[sequence]},
+ {group,g_mixed,[sequence]}].
+
+groups() ->
+ [{g,[{testcase,tc1,[{repeat,2}]}]},
+ {g_until_ok,[{testcase,tc2,[{repeat_until_ok,3}]}]},
+ {g_until_fail,[{testcase,tc4,[{repeat_until_fail,3}]}]},
+ {g_parallel_until_ok,[parallel],[{testcase,tc2,[{repeat_until_ok,3}]}]},
+ {g_parallel_until_fail,[parallel],[{testcase,tc1,[{repeat_until_fail,2}]}]},
+ {g_sequence_until_ok,[sequence],[{testcase,tc2,[{repeat_until_ok,3}]}]},
+ {g_sequence_until_fail,[sequence],[{testcase,tc1,[{repeat_until_fail,2}]}]},
+ {g_mixed,[{testcase,tc1,[{repeat,2}]},
+ {testcase,tc4,[{repeat,3}]},
+ tc1,
+ {group,g},
+ {subgroup,[tc1,{testcase,tc2,[{repeat,2}]}]},
+ {testcase,tc2,[{repeat,2}]},
+ tc2,
+ {testcase,tc1,[{repeat,2}]},
+ tc1]}].
+
+%% Test cases starts here.
+tc1(_Config) ->
+ ok.
+
+tc2(_Config) ->
+ ok.
+
+tc3(_Config) ->
+ ct:fail(always_fail).
+
+tc4(Config) ->
+ case ?config(saved_config,Config) of
+ {tc4,_} ->
+ ct:fail(second_time_fail);
+ undefined ->
+ {save_config,Config}
+ end.
+
+tc5(_Config) ->
+ {skip,"just skip this"}.
diff --git a/lib/common_test/test/ct_util_SUITE.erl b/lib/common_test/test/ct_util_SUITE.erl
new file mode 100644
index 0000000000..1d773855da
--- /dev/null
+++ b/lib/common_test/test/ct_util_SUITE.erl
@@ -0,0 +1,490 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009-2017. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(ct_util_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("common_test/include/ct_event.hrl").
+
+-define(eh, ct_test_support_eh).
+
+%%--------------------------------------------------------------------
+%% 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) ->
+ DataDir = ?config(data_dir, Config),
+ CTHs = filelib:wildcard(filename:join(DataDir,"*_cth.erl")),
+ io:format("CTHs: ~p",[CTHs]),
+ [io:format("Compiling ~p: ~p",
+ [FileName,compile:file(FileName,[{outdir,DataDir},debug_info])]) ||
+ FileName <- CTHs],
+ ct_test_support:init_per_suite([{path_dirs,[DataDir]} | Config]).
+
+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() ->
+ [{timetrap,{minutes,1}}].
+
+all() ->
+ all(suite).
+
+all(suite) ->
+ [
+ pre_init_per_suite,
+ post_init_per_suite,
+ pre_end_per_suite,
+ post_end_per_suite,
+ pre_init_per_group,
+ post_init_per_group,
+ pre_end_per_group,
+ post_end_per_group,
+ pre_init_per_testcase,
+ post_init_per_testcase,
+ pre_end_per_testcase,
+ post_end_per_testcase
+ ].
+
+
+%%--------------------------------------------------------------------
+%% TEST CASES
+%%--------------------------------------------------------------------
+
+%%%-----------------------------------------------------------------
+%%%
+pre_init_per_suite(Config) ->
+ CfgFile = gen_config(?FUNCTION_NAME,
+ [{pre_init_per_suite,
+ {curr_tc_SUITE,kill}}],
+ Config),
+ ok = do_test(?FUNCTION_NAME,
+ "curr_tc_SUITE.erl",
+ [{ct_hooks,[ct_util_cth]},{config,CfgFile}],
+ Config).
+
+post_init_per_suite(Config) ->
+ CfgFile = gen_config(?FUNCTION_NAME,
+ [{post_init_per_suite,
+ {curr_tc_SUITE,kill}}],
+ Config),
+ ok = do_test(?FUNCTION_NAME,
+ "curr_tc_SUITE.erl",
+ [{ct_hooks,[ct_util_cth]},{config,CfgFile}],
+ Config).
+
+pre_end_per_suite(Config) ->
+ CfgFile = gen_config(?FUNCTION_NAME,
+ [{pre_end_per_suite,
+ {curr_tc_SUITE,kill}}],
+ Config),
+ ok = do_test(?FUNCTION_NAME,
+ "curr_tc_SUITE.erl",
+ [{ct_hooks,[ct_util_cth]},{config,CfgFile}],
+ Config).
+
+post_end_per_suite(Config) ->
+ CfgFile = gen_config(?FUNCTION_NAME,
+ [{post_end_per_suite,
+ {curr_tc_SUITE,kill}}],
+ Config),
+ ok = do_test(?FUNCTION_NAME,
+ "curr_tc_SUITE.erl",
+ [{ct_hooks,[ct_util_cth]},{config,CfgFile}],
+ Config).
+
+
+pre_init_per_group(Config) ->
+ CfgFile = gen_config(?FUNCTION_NAME,
+ [{pre_init_per_group,
+ {curr_tc_SUITE,g,kill}}],
+ Config),
+ ok = do_test(?FUNCTION_NAME,
+ "curr_tc_SUITE.erl",
+ [{ct_hooks,[ct_util_cth]},{config,CfgFile}],
+ Config).
+
+post_init_per_group(Config) ->
+ CfgFile = gen_config(?FUNCTION_NAME,
+ [{post_init_per_group,
+ {curr_tc_SUITE,g,kill}}],
+ Config),
+ ok = do_test(?FUNCTION_NAME,
+ "curr_tc_SUITE.erl",
+ [{ct_hooks,[ct_util_cth]},{config,CfgFile}],
+ Config).
+
+pre_end_per_group(Config) ->
+ CfgFile = gen_config(?FUNCTION_NAME,
+ [{pre_end_per_group,
+ {curr_tc_SUITE,g,kill}}],
+ Config),
+ ok = do_test(?FUNCTION_NAME,
+ "curr_tc_SUITE.erl",
+ [{ct_hooks,[ct_util_cth]},{config,CfgFile}],
+ Config).
+
+post_end_per_group(Config) ->
+ CfgFile = gen_config(?FUNCTION_NAME,
+ [{post_end_per_group,
+ {curr_tc_SUITE,g,kill}}],
+ Config),
+ ok = do_test(?FUNCTION_NAME,
+ "curr_tc_SUITE.erl",
+ [{ct_hooks,[ct_util_cth]},{config,CfgFile}],
+ Config).
+
+pre_init_per_testcase(Config) ->
+ CfgFile = gen_config(?FUNCTION_NAME,
+ [{pre_init_per_testcase,
+ {curr_tc_SUITE,tc1,kill}}],
+ Config),
+ ok = do_test(?FUNCTION_NAME,
+ "curr_tc_SUITE.erl",
+ [{ct_hooks,[ct_util_cth]},{config,CfgFile}],
+ Config).
+
+post_init_per_testcase(Config) ->
+ CfgFile = gen_config(?FUNCTION_NAME,
+ [{post_init_per_testcase,
+ {curr_tc_SUITE,tc1,{timeout,5000}}}],
+ Config),
+ ok = do_test(?FUNCTION_NAME,
+ "curr_tc_SUITE.erl",
+ [{ct_hooks,[ct_util_cth]},{config,CfgFile}],
+ Config).
+
+pre_end_per_testcase(Config) ->
+ CfgFile = gen_config(?FUNCTION_NAME,
+ [{pre_end_per_testcase,
+ {curr_tc_SUITE,tc1,{timeout,5000}}}],
+ Config),
+ ok = do_test(?FUNCTION_NAME,
+ "curr_tc_SUITE.erl",
+ [{ct_hooks,[ct_util_cth]},{config,CfgFile}],
+ Config).
+
+post_end_per_testcase(Config) ->
+ CfgFile = gen_config(?FUNCTION_NAME,
+ [{post_end_per_testcase,
+ {curr_tc_SUITE,tc1,kill}}],
+ Config),
+ ok = do_test(?FUNCTION_NAME,
+ "curr_tc_SUITE.erl",
+ [{ct_hooks,[ct_util_cth]},{config,CfgFile}],
+ Config).
+
+%%%-----------------------------------------------------------------
+%%% HELP FUNCTIONS
+%%%-----------------------------------------------------------------
+
+do_test(Tag, Suite, RunTestArgs, Config) ->
+ do_test(Tag, Suite, RunTestArgs, Config, 2).
+
+do_test(Tag, Suite0, RunTestArgs, Config, EC) ->
+ DataDir = ?config(data_dir, Config),
+ Suite = filename:join([DataDir,Suite0]),
+ {Opts,ERPid} = setup([{suite,Suite}]++[{label,Tag}|RunTestArgs],Config),
+ Res = ct_test_support:run(Opts, Config),
+ Events = ct_test_support:get_events(ERPid, Config),
+ %% io:format("~p~n",[Events]),
+
+ ct_test_support:log_events(Tag,
+ reformat(Events, ?eh),
+ ?config(priv_dir, Config),
+ Opts),
+
+ TestEvents = events_to_check(Tag, EC),
+ ok = ct_test_support:verify_events(TestEvents, Events, Config),
+ Res.
+
+setup(Test, Config) ->
+ Opts0 = ct_test_support:get_opts(Config),
+ Level = ?config(trace_level, Config),
+ EvHArgs = [{cbm,ct_test_support},{trace_level,Level}],
+ Opts = Opts0 ++ [{event_handler,{?eh,EvHArgs}}|Test],
+ ERPid = ct_test_support:start_event_receiver(Config),
+ {Opts,ERPid}.
+
+reformat(Events, EH) ->
+ ct_test_support:reformat(Events, EH).
+
+gen_config(Name,KeyVals,Config) ->
+ PrivDir = ?config(priv_dir,Config),
+ File = filename:join(PrivDir,atom_to_list(Name)++".cfg"),
+ ok = file:write_file(File,[io_lib:format("~p.~n",[{Key,Value}])
+ || {Key,Value} <- KeyVals]),
+ File.
+
+%%%-----------------------------------------------------------------
+%%% 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(IPS) when IPS=:=pre_init_per_suite; IPS=:=post_init_per_suite ->
+ S = curr_tc_SUITE,
+ FwFunc =
+ case IPS of
+ pre_init_per_suite -> init_tc;
+ post_init_per_suite -> end_tc
+ end,
+ E = {failed,{ct_framework,FwFunc,{test_case_failed,hahahahahah}}},
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,start_info,{1,1,4}},
+ {?eh,tc_start,{S,init_per_suite}},
+ {?eh,tc_done,{S,init_per_suite,E}},
+ {?eh,tc_auto_skip,{S,tc1,{failed,{S,init_per_suite,E}}}},
+ {?eh,tc_auto_skip,{S,tc2,{failed,{S,init_per_suite,E}}}},
+ {?eh,tc_auto_skip,{S,{tc1,g},{failed,{S,init_per_suite,E}}}},
+ {?eh,tc_auto_skip,{S,{tc2,g},{failed,{S,init_per_suite,E}}}},
+ {?eh,test_stats,{0,0,{0,4}}},
+ {?eh,tc_auto_skip,{S,end_per_suite,{failed,{S,init_per_suite,E}}}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+ ];
+
+test_events(EPS) when EPS=:=pre_end_per_suite; EPS=:=post_end_per_suite ->
+ S = curr_tc_SUITE,
+ FwFunc =
+ case EPS of
+ pre_end_per_suite -> init_tc;
+ post_end_per_suite -> end_tc
+ end,
+ E = {failed,{ct_framework,FwFunc,{test_case_failed,hahahahahah}}},
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,start_info,{1,1,4}},
+ {?eh,tc_start,{S,init_per_suite}},
+ {?eh,tc_done,{S,init_per_suite,ok}},
+ {?eh,tc_start,{S,tc1}},
+ {?eh,tc_done,{S,tc1,ok}},
+ {?eh,test_stats,{1,0,{0,0}}},
+ {?eh,tc_start,{S,tc2}},
+ {?eh,tc_done,{S,tc2,ok}},
+ {?eh,test_stats,{2,0,{0,0}}},
+ [{?eh,tc_start,{S,{init_per_group,g,[]}}},
+ {?eh,tc_done,{S,{init_per_group,g,[]},ok}},
+ {?eh,tc_start,{S,tc1}},
+ {?eh,tc_done,{S,tc1,ok}},
+ {?eh,test_stats,{3,0,{0,0}}},
+ {?eh,tc_start,{S,tc2}},
+ {?eh,tc_done,{S,tc2,ok}},
+ {?eh,test_stats,{4,0,{0,0}}},
+ {?eh,tc_start,{S,{end_per_group,g,[]}}},
+ {?eh,tc_done,{S,{end_per_group,g,[]},ok}}],
+ {?eh,tc_start,{S,end_per_suite}},
+ {?eh,tc_done,{S,end_per_suite,E}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+ ];
+
+test_events(IPG) when IPG=:=pre_init_per_group; IPG=:=post_init_per_group ->
+ S = curr_tc_SUITE,
+ FwFunc =
+ case IPG of
+ pre_init_per_group -> init_tc;
+ post_init_per_group -> end_tc
+ end,
+ E = {failed,{ct_framework,FwFunc,{test_case_failed,hahahahahah}}},
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,start_info,{1,1,4}},
+ {?eh,tc_start,{S,init_per_suite}},
+ {?eh,tc_done,{S,init_per_suite,ok}},
+ {?eh,tc_start,{S,tc1}},
+ {?eh,tc_done,{S,tc1,ok}},
+ {?eh,test_stats,{1,0,{0,0}}},
+ {?eh,tc_start,{S,tc2}},
+ {?eh,tc_done,{S,tc2,ok}},
+ {?eh,test_stats,{2,0,{0,0}}},
+ [{?eh,tc_start,{S,{init_per_group,g,[]}}},
+ {?eh,tc_done,{S,{init_per_group,g,[]},E}},
+ {?eh,tc_auto_skip,{S,{tc1,g},{failed,{S,init_per_group,E}}}},
+ {?eh,tc_auto_skip,{S,{tc2,g},{failed,{S,init_per_group,E}}}},
+ {?eh,test_stats,{2,0,{0,2}}},
+ {?eh,tc_auto_skip,{S,{end_per_group,g},{failed,{S,init_per_group,E}}}}],
+ {?eh,tc_start,{S,end_per_suite}},
+ {?eh,tc_done,{S,end_per_suite,ok}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+ ];
+
+test_events(EPG) when EPG=:=pre_end_per_group; EPG=:=post_end_per_group ->
+ S = curr_tc_SUITE,
+ FwFunc =
+ case EPG of
+ pre_end_per_group -> init_tc;
+ post_end_per_group -> end_tc
+ end,
+ E = {failed,{ct_framework,FwFunc,{test_case_failed,hahahahahah}}},
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,start_info,{1,1,4}},
+ {?eh,tc_start,{S,init_per_suite}},
+ {?eh,tc_done,{S,init_per_suite,ok}},
+ {?eh,tc_start,{S,tc1}},
+ {?eh,tc_done,{S,tc1,ok}},
+ {?eh,test_stats,{1,0,{0,0}}},
+ {?eh,tc_start,{S,tc2}},
+ {?eh,tc_done,{S,tc2,ok}},
+ {?eh,test_stats,{2,0,{0,0}}},
+ [{?eh,tc_start,{S,{init_per_group,g,[]}}},
+ {?eh,tc_done,{S,{init_per_group,g,[]},ok}},
+ {?eh,tc_start,{S,tc1}},
+ {?eh,tc_done,{S,tc1,ok}},
+ {?eh,test_stats,{3,0,{0,0}}},
+ {?eh,tc_start,{S,tc2}},
+ {?eh,tc_done,{S,tc2,ok}},
+ {?eh,test_stats,{4,0,{0,0}}},
+ {?eh,tc_start,{S,{end_per_group,g,[]}}},
+ {?eh,tc_done,{S,{end_per_group,g,[]},E}}],
+ {?eh,tc_start,{S,end_per_suite}},
+ {?eh,tc_done,{S,end_per_suite,ok}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+ ];
+
+test_events(IPTC) when IPTC=:=pre_init_per_testcase;
+ IPTC=:=post_init_per_testcase ->
+ S = curr_tc_SUITE,
+ E = case IPTC of
+ pre_init_per_testcase ->
+ {failed,{ct_framework,init_tc,{test_case_failed,hahahahahah}}};
+ post_init_per_testcase ->
+ {failed,{ct_framework,end_tc,{timetrap,3000}}}
+ end,
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,start_info,{1,1,4}},
+ {?eh,tc_start,{S,init_per_suite}},
+ {?eh,tc_done,{S,init_per_suite,ok}},
+ {?eh,tc_start,{S,tc1}},
+ {?eh,tc_done,{S,tc1,{auto_skipped,E}}},
+ {?eh,test_stats,{0,0,{0,1}}},
+ {?eh,tc_start,{S,tc2}},
+ {?eh,tc_done,{S,tc2,ok}},
+ {?eh,test_stats,{1,0,{0,1}}},
+ [{?eh,tc_start,{S,{init_per_group,g,[]}}},
+ {?eh,tc_done,{S,{init_per_group,g,[]},ok}},
+ {?eh,tc_start,{S,tc1}},
+ {?eh,tc_done,{S,tc1,{auto_skipped,E}}},
+ {?eh,test_stats,{1,0,{0,2}}},
+ {?eh,tc_start,{S,tc2}},
+ {?eh,tc_done,{S,tc2,ok}},
+ {?eh,test_stats,{2,0,{0,2}}},
+ {?eh,tc_start,{S,{end_per_group,g,[]}}},
+ {?eh,tc_done,{S,{end_per_group,g,[]},ok}}],
+ {?eh,tc_start,{S,end_per_suite}},
+ {?eh,tc_done,{S,end_per_suite,ok}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+ ];
+
+test_events(EPTC) when EPTC=:=pre_end_per_testcase; EPTC=:=post_end_per_testcase->
+ S = curr_tc_SUITE,
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,start_info,{1,1,4}},
+ {?eh,tc_start,{S,tc1}},
+ {?eh,tc_done,{S,tc1,ok}},
+ {?eh,test_stats,{1,0,{0,0}}},
+ {?eh,tc_start,{S,tc2}},
+ {?eh,tc_done,{S,tc2,ok}},
+ {?eh,test_stats,{2,0,{0,0}}},
+ [{?eh,tc_start,{S,{init_per_group,g,[]}}},
+ {?eh,tc_done,{S,{init_per_group,g,[]},ok}},
+ {?eh,tc_start,{S,tc1}},
+ {?eh,tc_done,{S,tc1,ok}},
+ {?eh,test_stats,{3,0,{0,0}}},
+ {?eh,tc_start,{S,tc2}},
+ {?eh,tc_done,{S,tc2,ok}},
+ {?eh,test_stats,{4,0,{0,0}}},
+ {?eh,tc_start,{S,{end_per_group,g,[]}}},
+ {?eh,tc_done,{S,{end_per_group,g,[]},ok}}],
+ {?eh,tc_start,{S,end_per_suite}},
+ {?eh,tc_done,{S,end_per_suite,ok}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+ ].
+
+%% test events help functions
+contains(List) ->
+ fun(Proplist) when is_list(Proplist) ->
+ contains(List,Proplist)
+ end.
+
+contains([{not_in_order,List}|T],Rest) ->
+ contains_parallel(List,Rest),
+ contains(T,Rest);
+contains([{Ele,Pos}|T] = L,[H|T2]) ->
+ case element(Pos,H) of
+ Ele ->
+ contains(T,T2);
+ _ ->
+ contains(L,T2)
+ end;
+contains([Ele|T],[{Ele,_}|T2])->
+ contains(T,T2);
+contains([Ele|T],[Ele|T2])->
+ contains(T,T2);
+contains(List,[_|T]) ->
+ contains(List,T);
+contains([],_) ->
+ match.
+
+contains_parallel([Key | T], Elems) ->
+ contains([Key],Elems),
+ contains_parallel(T,Elems);
+contains_parallel([],_Elems) ->
+ match.
+
+not_contains(List) ->
+ fun(Proplist) when is_list(Proplist) ->
+ [] = [Ele || {Ele,_} <- Proplist,
+ Test <- List,
+ Test =:= Ele]
+ end.
diff --git a/lib/common_test/test/ct_util_SUITE_data/ct_util_cth.erl b/lib/common_test/test/ct_util_SUITE_data/ct_util_cth.erl
new file mode 100644
index 0000000000..34c1568a87
--- /dev/null
+++ b/lib/common_test/test/ct_util_SUITE_data/ct_util_cth.erl
@@ -0,0 +1,105 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2017. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+
+-module(ct_util_cth).
+
+
+-include_lib("common_test/src/ct_util.hrl").
+-include_lib("common_test/include/ct_event.hrl").
+
+%% Send a cth_error event if a callback is called with unexpected arguments
+
+%% CT Hooks
+-compile(export_all).
+
+id(Opts) ->
+ erlang:system_time(second).
+
+init(Id, Opts) ->
+ {ok,ok}.
+
+pre_init_per_suite(Suite,Config,State) ->
+ maybe_sleep(?FUNCTION_NAME,Suite),
+ {Config, State}.
+
+post_init_per_suite(Suite,Config,Return,State) ->
+ maybe_sleep(?FUNCTION_NAME,Suite),
+ {Return, State}.
+
+pre_end_per_suite(Suite,Config,State) ->
+ maybe_sleep(?FUNCTION_NAME,Suite),
+ {Config, State}.
+
+post_end_per_suite(Suite,Config,Return,State) ->
+ maybe_sleep(?FUNCTION_NAME,Suite),
+ {Return, State}.
+
+pre_init_per_group(Suite, Group, Config, State) ->
+ maybe_sleep(?FUNCTION_NAME,Suite,Group),
+ {Config,State}.
+
+post_init_per_group(Suite, Group, Config,Return,State) ->
+ maybe_sleep(?FUNCTION_NAME,Suite,Group),
+ {Return,State}.
+
+pre_end_per_group(Suite, Group, Config, State) ->
+ maybe_sleep(?FUNCTION_NAME,Suite,Group),
+ {Config,State}.
+
+post_end_per_group(Suite, Group, Config,Return,State) ->
+ maybe_sleep(?FUNCTION_NAME,Suite,Group),
+ {Return,State}.
+
+pre_init_per_testcase(Suite, TC, Config, State) ->
+ maybe_sleep(?FUNCTION_NAME,Suite,TC),
+ {Config,State}.
+
+post_init_per_testcase(Suite, TC, Config,Return,State) ->
+ maybe_sleep(?FUNCTION_NAME,Suite,TC),
+ {Return,State}.
+
+pre_end_per_testcase(Suite, TC, Config, State) ->
+ maybe_sleep(?FUNCTION_NAME,Suite,TC),
+ {Config,State}.
+
+post_end_per_testcase(Suite, TC, Config,Return,State) ->
+ maybe_sleep(?FUNCTION_NAME,Suite,TC),
+ {Return,State}.
+
+%%%-----------------------------------------------------------------
+maybe_sleep(FuncName,Suite) ->
+ maybe_sleep(FuncName,Suite,undefined).
+maybe_sleep(FuncName,Suite,GroupOrTC) ->
+ case ct:get_config(FuncName) of
+ {Suite,GroupOrTC,Fail} ->
+ fail(Fail);
+ {Suite,Fail} when GroupOrTC=:=undefined ->
+ fail(Fail);
+ _ ->
+ ok
+ end.
+
+fail({timeout,T}) ->
+ timer:sleep(T);
+fail(kill) ->
+ spawn_link(fun() -> ct:fail(hahahahahah) end),
+ timer:sleep(10000).
+
diff --git a/lib/common_test/test/ct_util_SUITE_data/curr_tc_SUITE.erl b/lib/common_test/test/ct_util_SUITE_data/curr_tc_SUITE.erl
new file mode 100644
index 0000000000..b48ba4d24e
--- /dev/null
+++ b/lib/common_test/test/ct_util_SUITE_data/curr_tc_SUITE.erl
@@ -0,0 +1,59 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2017. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(curr_tc_SUITE).
+
+-suite_defaults([{timetrap, {seconds, 3}}]).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include("ct.hrl").
+
+init_per_suite(Config) ->
+ [{?MODULE,?FUNCTION_NAME}] = ct_util:get_testdata(curr_tc),
+ Config.
+
+end_per_suite(Config) ->
+ [{?MODULE,?FUNCTION_NAME}] = ct_util:get_testdata(curr_tc),
+ ok.
+
+init_per_group(_Group,Config) ->
+ [{?MODULE,?FUNCTION_NAME}] = ct_util:get_testdata(curr_tc),
+ Config.
+
+end_per_group(_Group,Config) ->
+ [{?MODULE,?FUNCTION_NAME}] = ct_util:get_testdata(curr_tc),
+ ok.
+
+all() ->
+ [tc1,tc2,{group,g}].
+
+groups() ->
+ [{g,[tc1,tc2]}].
+
+%% Test cases starts here.
+tc1(_Config) ->
+ [{?MODULE,?FUNCTION_NAME}] = ct_util:get_testdata(curr_tc),
+ ok.
+
+tc2(_Config) ->
+ [{?MODULE,?FUNCTION_NAME}] = ct_util:get_testdata(curr_tc),
+ ok.
diff --git a/lib/compiler/src/beam_asm.erl b/lib/compiler/src/beam_asm.erl
index bc1290f6fd..df09dcb06c 100644
--- a/lib/compiler/src/beam_asm.erl
+++ b/lib/compiler/src/beam_asm.erl
@@ -407,14 +407,14 @@ encode_arg({atom, Atom}, Dict0) when is_atom(Atom) ->
{Index, Dict} = beam_dict:atom(Atom, Dict0),
{encode(?tag_a, Index), Dict};
encode_arg({integer, N}, Dict) ->
- %% Conservatily assume that all integers whose absolute
+ %% Conservatively assume that all integers whose absolute
%% value is greater than 1 bsl 128 will be bignums in
%% the runtime system.
if
N >= 1 bsl 128 ->
- encode_arg({literal, N}, Dict);
+ encode_literal(N, Dict);
N =< -(1 bsl 128) ->
- encode_arg({literal, N}, Dict);
+ encode_literal(N, Dict);
true ->
{encode(?tag_i, N), Dict}
end;
@@ -434,7 +434,7 @@ encode_arg({list, List}, Dict0) ->
{L, Dict} = encode_list(List, Dict0, []),
{[encode(?tag_z, 1), encode(?tag_u, length(List))|L], Dict};
encode_arg({float, Float}, Dict) when is_float(Float) ->
- encode_arg({literal,Float}, Dict);
+ encode_literal(Float, Dict);
encode_arg({fr,Fr}, Dict) ->
{[encode(?tag_z, 2),encode(?tag_u, Fr)], Dict};
encode_arg({field_flags,Flags0}, Dict) ->
@@ -442,12 +442,24 @@ encode_arg({field_flags,Flags0}, Dict) ->
{encode(?tag_u, Flags), Dict};
encode_arg({alloc,List}, Dict) ->
encode_alloc_list(List, Dict);
-encode_arg({literal,Lit}, Dict0) ->
- {Index,Dict} = beam_dict:literal(Lit, Dict0),
- {[encode(?tag_z, 4),encode(?tag_u, Index)],Dict};
+encode_arg({literal,Lit}, Dict) ->
+ if
+ Lit =:= [] ->
+ encode_arg(nil, Dict);
+ is_atom(Lit) ->
+ encode_arg({atom,Lit}, Dict);
+ is_integer(Lit) ->
+ encode_arg({integer,Lit}, Dict);
+ true ->
+ encode_literal(Lit, Dict)
+ end;
encode_arg(Int, Dict) when is_integer(Int) ->
{encode(?tag_u, Int),Dict}.
+encode_literal(Literal, Dict0) ->
+ {Index,Dict} = beam_dict:literal(Literal, Dict0),
+ {[encode(?tag_z, 4),encode(?tag_u, Index)],Dict}.
+
%%flag_to_bit(aligned) -> 16#01; %% No longer useful.
flag_to_bit(little) -> 16#02;
flag_to_bit(big) -> 16#00;
diff --git a/lib/diameter/doc/src/notes.xml b/lib/diameter/doc/src/notes.xml
index 5777225ae7..8dcba93273 100644
--- a/lib/diameter/doc/src/notes.xml
+++ b/lib/diameter/doc/src/notes.xml
@@ -43,6 +43,23 @@ first.</p>
<!-- ===================================================================== -->
+<section><title>diameter 2.2.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix inadvertently broad monitor that resulted in
+ gen_server cast messages to hidden nodes from module
+ diameter_dist.</p>
+ <p>
+ Own Id: OTP-15768</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>diameter 2.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/diameter/src/base/diameter_dist.erl b/lib/diameter/src/base/diameter_dist.erl
index 5c29ea95a4..ed23152b8b 100644
--- a/lib/diameter/src/base/diameter_dist.erl
+++ b/lib/diameter/src/base/diameter_dist.erl
@@ -454,7 +454,8 @@ start_link() ->
init([]) ->
ets:new(?NODE_TABLE, [set, named_table]),
ets:new(?SERVICE_TABLE, [bag, named_table]),
- ok = net_kernel:monitor_nodes(true, [{node_type, all}, nodedown_reason]),
+ ok = net_kernel:monitor_nodes(true, [{node_type, visible},
+ nodedown_reason]),
ets:insert(?NODE_TABLE, [{?B(N), N} || N <- [node() | nodes()]]),
abcast({attach, node()}),
{ok, sets:new()}.
@@ -521,5 +522,14 @@ terminate(_, _) ->
%% code_change/3
+%% Old code inadvertently monitored all nodes: start a new
+%% subscription and remove the old one.
+code_change(_OldVsn, State, "2.2") ->
+ ok = net_kernel:monitor_nodes(true, [{node_type, visible},
+ nodedown_reason]),
+ ok = net_kernel:monitor_nodes(false, [{node_type, all},
+ nodedown_reason]),
+ {ok, State};
+
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
diff --git a/lib/diameter/src/diameter.appup.src b/lib/diameter/src/diameter.appup.src
index 52263633fb..bb2a4a8e92 100644
--- a/lib/diameter/src/diameter.appup.src
+++ b/lib/diameter/src/diameter.appup.src
@@ -61,7 +61,8 @@
{"2.1.4", [{restart_application, diameter}]}, %% 20.3
{"2.1.4.1", [{restart_application, diameter}]}, %% 20.3.8.19
{"2.1.5", [{restart_application, diameter}]}, %% 21.0
- {"2.1.6", [{restart_application, diameter}]} %% 21.1
+ {"2.1.6", [{restart_application, diameter}]}, %% 21.1
+ {"2.2", [{update, diameter_dist, {advanced, "2.2"}}]} %% 21.3
],
[
{"0.9", [{restart_application, diameter}]},
@@ -104,6 +105,7 @@
{"2.1.4", [{restart_application, diameter}]},
{"2.1.4.1", [{restart_application, diameter}]},
{"2.1.5", [{restart_application, diameter}]},
- {"2.1.6", [{restart_application, diameter}]}
+ {"2.1.6", [{restart_application, diameter}]},
+ {"2.2", [{restart_application, diameter}]}
]
}.
diff --git a/lib/diameter/vsn.mk b/lib/diameter/vsn.mk
index a900e8f28e..a8fbca5bc8 100644
--- a/lib/diameter/vsn.mk
+++ b/lib/diameter/vsn.mk
@@ -17,5 +17,5 @@
# %CopyrightEnd%
APPLICATION = diameter
-DIAMETER_VSN = 2.2
+DIAMETER_VSN = 2.2.1
APP_VSN = $(APPLICATION)-$(DIAMETER_VSN)$(PRE_VSN)
diff --git a/lib/edoc/doc/edoc.dtd b/lib/edoc/doc/edoc.dtd
new file mode 120000
index 0000000000..43f4b27db6
--- /dev/null
+++ b/lib/edoc/doc/edoc.dtd
@@ -0,0 +1 @@
+../priv/edoc.dtd \ No newline at end of file
diff --git a/lib/edoc/doc/edoc_doclet.hrl b/lib/edoc/doc/edoc_doclet.hrl
new file mode 120000
index 0000000000..4623b18bb4
--- /dev/null
+++ b/lib/edoc/doc/edoc_doclet.hrl
@@ -0,0 +1 @@
+../include/edoc_doclet.hrl \ No newline at end of file
diff --git a/lib/edoc/doc/src/Makefile b/lib/edoc/doc/src/Makefile
index aba94a6802..3e53e75c75 100644
--- a/lib/edoc/doc/src/Makefile
+++ b/lib/edoc/doc/src/Makefile
@@ -79,6 +79,11 @@ HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
+INCLUDES_DIR = ../../include
+INCLUDES = $(INCLUDES_DIR)/edoc_doclet.hrl
+
+DTDS_DIR = ../../priv
+DTDS = $(DTDS_DIR)/edoc.dtd
# ----------------------------------------------------
# FLAGS
@@ -135,5 +140,6 @@ release_docs_spec: docs
$(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
$(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
$(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
+ $(INSTALL_DATA) $(INCLUDES) $(DTDS) "$(RELSYSDIR)/doc/html"
release_spec:
diff --git a/lib/edoc/include/edoc_doclet.hrl b/lib/edoc/include/edoc_doclet.hrl
index 1429ee5971..a05a9cb2bc 100644
--- a/lib/edoc/include/edoc_doclet.hrl
+++ b/lib/edoc/include/edoc_doclet.hrl
@@ -43,7 +43,7 @@
%% @type no_app().
%% A value used to mark absence of an Erlang application
%% context. Use the macro `NO_APP' defined in
-%% <a href="../include/edoc_doclet.hrl">`edoc_doclet.hrl'</a>
+%% <a href="edoc_doclet.hrl">`edoc_doclet.hrl'</a>
%% to produce this value.
%% @type doclet_gen() = #doclet_gen{sources = [string()],
diff --git a/lib/edoc/src/edoc.erl b/lib/edoc/src/edoc.erl
index e9d62d3283..62483602aa 100644
--- a/lib/edoc/src/edoc.erl
+++ b/lib/edoc/src/edoc.erl
@@ -734,7 +734,7 @@ get_doc(File) ->
%%
%% @type edoc_module(). The EDoc documentation data for a module,
%% expressed as an XML document in {@link //xmerl. XMerL} format. See
-%% the file <a href="../priv/edoc.dtd">`edoc.dtd'</a> for details.
+%% the file <a href="edoc.dtd">`edoc.dtd'</a> for details.
%%
%% @doc Reads a source code file and extracts EDoc documentation data.
%% Note that without an environment parameter (see {@link get_doc/3}),
diff --git a/lib/edoc/src/edoc_doclet.erl b/lib/edoc/src/edoc_doclet.erl
index 6cb3095507..604291374a 100644
--- a/lib/edoc/src/edoc_doclet.erl
+++ b/lib/edoc/src/edoc_doclet.erl
@@ -62,7 +62,7 @@
%% @spec (Command::doclet_gen() | doclet_toc(), edoc_context()) -> ok
%% @doc Main doclet entry point. See the file <a
-%% href="../include/edoc_doclet.hrl">`edoc_doclet.hrl'</a> for the data
+%% href="edoc_doclet.hrl">`edoc_doclet.hrl'</a> for the data
%% structures used for passing parameters.
%%
%% Also see {@link edoc:layout/2} for layout-related options, and
diff --git a/lib/erl_interface/doc/src/notes.xml b/lib/erl_interface/doc/src/notes.xml
index 5ad0e2499b..fc6a1bb548 100644
--- a/lib/erl_interface/doc/src/notes.xml
+++ b/lib/erl_interface/doc/src/notes.xml
@@ -169,6 +169,22 @@
</section>
+<section><title>Erl_Interface 3.10.2.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix handling of Makefile dependencies so that parallel
+ make works properly.</p>
+ <p>
+ Own Id: OTP-15757</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Erl_Interface 3.10.2.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -1619,4 +1635,3 @@
</section>
</section>
</chapter>
-
diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml
index 91dd9cd6ed..2710ea2f2f 100644
--- a/lib/inets/doc/src/notes.xml
+++ b/lib/inets/doc/src/notes.xml
@@ -33,7 +33,29 @@
<file>notes.xml</file>
</header>
- <section><title>Inets 7.0.6</title>
+ <section><title>Inets 7.0.7</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix the internal handling of the option
+ erl_script_timeout in httpd. If explicit
+ erl_script_timeout value was supplied in seconds it was
+ not correctly converted to millisecond units for internal
+ usage.</p>
+ <p>
+ This change fixes the handling of erl_script_timeout in
+ all possible configuration scenarios.</p>
+ <p>
+ Own Id: OTP-15769 Aux Id: ERIERL-345 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Inets 7.0.6</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/inets/src/http_server/httpd_example.erl b/lib/inets/src/http_server/httpd_example.erl
index 37e4f97bc0..aaa7e428c2 100644
--- a/lib/inets/src/http_server/httpd_example.erl
+++ b/lib/inets/src/http_server/httpd_example.erl
@@ -24,7 +24,7 @@
-export([newformat/3, post_chunked/3, post_204/3]).
%% These are used by the inets test-suite
--export([delay/1, chunk_timeout/3]).
+-export([delay/1, chunk_timeout/3, get_chunks/3]).
print(String) ->
@@ -196,3 +196,22 @@ chunk_timeout(SessionID, _, _StrInt) ->
mod_esi:deliver(SessionID, top("Test chunk encoding timeout")),
timer:sleep(20000),
mod_esi:deliver(SessionID, footer()).
+
+get_chunks(Sid, _Env, In) ->
+ Tokens = string:tokens(In, [$&]),
+ PropList = lists:map(fun(E) ->
+ list_to_tuple(string:tokens(E,[$=])) end,
+ Tokens),
+ HeaderDelay =
+ list_to_integer(proplists:get_value("header_delay", PropList, "0")),
+ ChunkDelay =
+ list_to_integer(proplists:get_value("chunk_delay", PropList, "0")),
+ BadChunkDelay =
+ list_to_integer(proplists:get_value("bad_chunk_delay", PropList, "0")),
+ timer:sleep(HeaderDelay),
+ mod_esi:deliver(Sid, ["Content-Type: text/plain\r\n\r\n"]),
+ mod_esi:deliver(Sid, "Chunk 0 ms\r\n"),
+ timer:sleep(ChunkDelay),
+ mod_esi:deliver(Sid, io_lib:format("Chunk ~p ms\r\n", [ChunkDelay])),
+ timer:sleep(ChunkDelay + BadChunkDelay),
+ mod_esi:deliver(Sid, "BAD Chunk\r\n").
diff --git a/lib/inets/src/http_server/mod_esi.erl b/lib/inets/src/http_server/mod_esi.erl
index f495f12f03..8cbd9798e6 100644
--- a/lib/inets/src/http_server/mod_esi.erl
+++ b/lib/inets/src/http_server/mod_esi.erl
@@ -119,7 +119,7 @@ load("EvalScriptAlias " ++ EvalScriptAlias, []) ->
load("ErlScriptTimeout " ++ Timeout, [])->
case catch list_to_integer(string:strip(Timeout)) of
TimeoutSec when is_integer(TimeoutSec) ->
- {ok, [], {erl_script_timeout, TimeoutSec * 1000}};
+ {ok, [], {erl_script_timeout, TimeoutSec}};
_ ->
{error, ?NICE(string:strip(Timeout) ++
" is an invalid ErlScriptTimeout")}
@@ -500,7 +500,7 @@ kill_esi_delivery_process(Pid) ->
erl_script_timeout(Db) ->
- httpd_util:lookup(Db, erl_script_timeout, ?DEFAULT_ERL_TIMEOUT * 1000).
+ httpd_util:lookup(Db, erl_script_timeout, ?DEFAULT_ERL_TIMEOUT) * 1000.
script_elements(FuncAndInput, Input) ->
case input_type(FuncAndInput) of
diff --git a/lib/inets/test/httpd_SUITE.erl b/lib/inets/test/httpd_SUITE.erl
index fcb9ad7905..fc5ca14dcd 100644
--- a/lib/inets/test/httpd_SUITE.erl
+++ b/lib/inets/test/httpd_SUITE.erl
@@ -79,7 +79,10 @@ all() ->
{group, http_not_sup},
{group, https_not_sup},
mime_types_format,
- erl_script_timeout_option
+ erl_script_timeout_default,
+ erl_script_timeout_option,
+ erl_script_timeout_proplist,
+ erl_script_timeout_apache
].
groups() ->
@@ -384,6 +387,10 @@ init_per_testcase(disk_log_bad_file, Config0) ->
ct:timetrap({seconds, 20}),
dbg(disk_log_internal, Config1, init);
+init_per_testcase(erl_script_timeout_default, Config) ->
+ ct:timetrap({seconds, 60}),
+ dbg(erl_script_timeout_default, Config, init);
+
init_per_testcase(Case, Config) ->
ct:timetrap({seconds, 20}),
dbg(Case, Config, init).
@@ -1777,16 +1784,128 @@ mime_types_format(Config) when is_list(Config) ->
{"cpt","application/mac-compactpro"},
{"hqx","application/mac-binhex40"}]} = httpd_conf:load_mime_types(MimeTypes).
+erl_script_timeout_default(Config) when is_list(Config) ->
+ inets:start(),
+ {ok, Pid} = inets:start(httpd,
+ [{port, 0},
+ {server_name,"localhost"},
+ {server_root,"./"},
+ {document_root,"./"},
+ {bind_address, any},
+ {mimetypes, [{"html", "text/html"}]},
+ {modules,[mod_esi]},
+ {erl_script_alias, {"/erl", [httpd_example]}}
+ ]),
+ Info = httpd:info(Pid),
+
+ Port = proplists:get_value(port, Info),
+
+ %% Default erl_script_timeout is 15.
+ %% Verify: 13 =< erl_script_timeout =< 17
+ Url = http_get_url(Port, 500, 13000, 4000),
+
+ {ok, {_, _, Body}} = httpc:request(get, {Url, []}, [{timeout, 45000}], []),
+ ct:log("Response: ~p~n", [Body]),
+ verify_body(Body, 13000),
+ inets:stop().
erl_script_timeout_option(Config) when is_list(Config) ->
inets:start(),
- {ok, Pid} = inets:start(httpd, [{erl_script_timeout, 215},
- {server_name, "test"},
- {port,0},
- {server_root, "."},
- {document_root, "."}]),
+ {ok, Pid} = inets:start(httpd,
+ [{port, 0},
+ {server_name,"localhost"},
+ {server_root,"./"},
+ {document_root,"./"},
+ {bind_address, any},
+ {mimetypes, [{"html", "text/html"}]},
+ {modules,[mod_esi]},
+ {erl_script_timeout, 2},
+ {erl_script_alias, {"/erl", [httpd_example]}}
+ ]),
Info = httpd:info(Pid),
- 215 = proplists:get_value(erl_script_timeout, Info),
+ verify_timeout(Info, 2),
+
+ Port = proplists:get_value(port, Info),
+
+ %% Verify: 1 =< erl_script_timeout =< 3
+ Url = http_get_url(Port, 500, 1000, 2000),
+
+ {ok, {_, _, Body}} = httpc:request(Url),
+ ct:log("Response: ~p~n", [Body]),
+ verify_body(Body, 1000),
+ inets:stop().
+
+erl_script_timeout_proplist(Config) when is_list(Config) ->
+ HttpdConf = filename:join(get_tmp_dir(Config),
+ "httpd_erl_script_timeout_proplist.conf"),
+ ServerConfig =
+ "[{port, 0},\n" ++
+ " {server_name,\"localhost\"},\n" ++
+ " {server_root,\"./\"},\n" ++
+ " {document_root,\"./\"},\n" ++
+ " {bind_address, any},\n" ++
+ " {mimetypes, [{\"html\", \"text/html\"}]},\n" ++
+ " {modules,[mod_esi]},\n" ++
+ " {erl_script_timeout, 5},\n" ++
+ " {erl_script_alias, {\"/erl\", [httpd_example]}}\n" ++
+ "].",
+ ok = file:write_file(HttpdConf, ServerConfig),
+
+ inets:start(),
+ {ok, Pid} = inets:start(httpd,
+ [{proplist_file, HttpdConf}]),
+ Info = httpd:info(Pid),
+ verify_timeout(Info, 5),
+
+ Port = proplists:get_value(port, Info),
+
+ %% Verify: 3 =< erl_script_timeout =< 7
+ Url = http_get_url(Port, 500, 3000, 4000),
+
+ {ok, {_, _, Body}} = httpc:request(Url),
+ ct:log("Response: ~p~n", [Body]),
+ verify_body(Body, 3000),
+ inets:stop().
+
+erl_script_timeout_apache(Config) when is_list(Config) ->
+ HttpdConf = filename:join(get_tmp_dir(Config),
+ "httpd_erl_script_timeout.conf"),
+ MimeTypes = filename:join(get_tmp_dir(Config),
+ "erl_script_timeout_mime_types.conf"),
+
+ MimeTypesConf =
+ "html\n" ++
+ "text/html\n",
+
+ ok = file:write_file(MimeTypes, MimeTypesConf),
+
+ ServerConfig =
+ "Port 0\n" ++
+ "ServerName localhost\n" ++
+ "ServerRoot ./\n" ++
+ "DocumentRoot ./\n" ++
+ "BindAddress 0.0.0.0\n" ++
+ "MimeTypes " ++ MimeTypes ++ "\n" ++
+ "Modules mod_esi\n" ++
+ "ErlScriptTimeout 8\n" ++
+ "ErlScriptAlias /erl httpd_example\n",
+
+ ok = file:write_file(HttpdConf, ServerConfig),
+
+ inets:start(),
+ {ok, Pid} = inets:start(httpd,
+ [{file, HttpdConf}]),
+ Info = httpd:info(Pid),
+ verify_timeout(Info, 8),
+
+ Port = proplists:get_value(port, Info),
+
+ %% Verify: 6 =< erl_script_timeout =< 10
+ Url = http_get_url(Port, 500, 6000, 4000),
+
+ {ok, {_, _, Body}} = httpc:request(Url),
+ ct:log("Response: ~p~n", [Body]),
+ verify_body(Body, 6000),
inets:stop().
@@ -1798,6 +1917,38 @@ url(http, End, Config) ->
{ok,Host} = inet:gethostname(),
?URL_START ++ Host ++ ":" ++ integer_to_list(Port) ++ End.
+http_get_url(Port0, HeaderDelay, ChunkDelay, BadChunkDelay) ->
+ {ok, Host} = inet:gethostname(),
+ Port = integer_to_list(Port0),
+ HD = integer_to_list(HeaderDelay),
+ CD = integer_to_list(ChunkDelay),
+ BD = integer_to_list(BadChunkDelay),
+ "http://" ++ Host ++ ":" ++ Port ++
+ "/erl/httpd_example/get_chunks?header_delay=" ++ HD ++
+ "&chunk_delay=" ++ CD ++
+ "&bad_chunk_delay=" ++ BD.
+
+verify_body(Body, Timeout0) ->
+ Timeout = integer_to_list(Timeout0),
+ Res = string:find(Body, Timeout),
+ ct:log("Result: ~p~n", [Res]),
+ %% Fail if BAD chunk is found.
+ case Res =:= Timeout ++ " ms\r\n" of
+ true ->
+ ok;
+ false ->
+ ct:fail("Unexpected chunk received!")
+ end.
+
+verify_timeout(Info, Expected) ->
+ Timeout = proplists:get_value(erl_script_timeout, Info),
+ case Timeout =:= Expected of
+ true ->
+ ok;
+ false ->
+ ct:fail("Bad Timeout - Expected: ~p Got: ~p", [Expected, Timeout])
+ end.
+
do_max_clients(Config) ->
Version = proplists:get_value(http_version, Config),
Host = proplists:get_value(host, Config),
diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk
index b7ddf39ebd..fd248e793a 100644
--- a/lib/inets/vsn.mk
+++ b/lib/inets/vsn.mk
@@ -19,6 +19,6 @@
# %CopyrightEnd%
APPLICATION = inets
-INETS_VSN = 7.0.6
+INETS_VSN = 7.0.7
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)"
diff --git a/lib/os_mon/src/cpu_sup.erl b/lib/os_mon/src/cpu_sup.erl
index ba2d89313e..d28f229b3e 100644
--- a/lib/os_mon/src/cpu_sup.erl
+++ b/lib/os_mon/src/cpu_sup.erl
@@ -68,7 +68,7 @@
-type util_cpus() :: 'all' | integer() | [integer()].
-type util_state() :: 'user' | 'nice_user' | 'kernel' | 'wait' | 'idle'.
--type util_value() :: [{util_state(), float()}] | float().
+-type util_value() :: [{util_state(), number()}] | number().
-type util_desc() :: {util_cpus(), util_value(), util_value(), []}.
%%----------------------------------------------------------------------
@@ -122,7 +122,7 @@ util(Args) when is_list (Args) ->
util(_) ->
erlang:error(badarg).
--spec util() -> float() | {'error', any()}.
+-spec util() -> number() | {'error', any()}.
util() ->
case util([]) of
diff --git a/lib/snmp/doc/src/notes.xml b/lib/snmp/doc/src/notes.xml
index 423d90fef6..a6c3d57148 100644
--- a/lib/snmp/doc/src/notes.xml
+++ b/lib/snmp/doc/src/notes.xml
@@ -73,6 +73,23 @@
</section>
+ <section><title>SNMP 5.2.11.1</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ [snmp|agent] Add a get-mechanism callback module (and a
+ corresponding behaviour). The agent calls this module to
+ handle each get (get, get-next and get-bulk) request.</p>
+ <p>
+ Own Id: OTP-15691 Aux Id: ERIERL-324 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>SNMP 5.2.11</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/snmp/src/agent/snmpa_agent.erl b/lib/snmp/src/agent/snmpa_agent.erl
index a521b3773b..f280260f47 100644
--- a/lib/snmp/src/agent/snmpa_agent.erl
+++ b/lib/snmp/src/agent/snmpa_agent.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -1794,9 +1794,8 @@ worker_loop(Master) ->
GbMaxVBs, Extra)
end
catch
- T:E ->
- exit({worker_crash, Req, T, E,
- erlang:get_stacktrace()})
+ C:E:S ->
+ exit({worker_crash, Req, C, E, S})
end,
Master ! worker_available,
HandlePduRes; % For debugging...
@@ -1822,9 +1821,8 @@ worker_loop(Master) ->
get(net_if))
end
catch
- T:E ->
- exit({worker_crash, Req, T, E,
- erlang:get_stacktrace()})
+ C:E:S ->
+ exit({worker_crash, Req, C, E, S})
end,
Master ! worker_available,
SendTrapRes; % For debugging...
@@ -2543,22 +2541,31 @@ process_msg(
process_pdu(#pdu{type='get-request', request_id = ReqId, varbinds=Vbs},
_PduMS, Vsn, MibView, _GbMaxVBs) ->
?vtrace("get ~p",[ReqId]),
- Res = get_err(do_get(MibView, Vbs, false)),
- ?vtrace("get result: "
- "~n ~p",[Res]),
+ OrigRes = do_get(MibView, Vbs, false),
+ Res = get_err(OrigRes),
{ErrStatus, ErrIndex, ResVarbinds} =
if
Vsn =:= 'version-1' -> validate_get_v1(Res);
true -> Res
end,
- ?vtrace("get final result: "
- "~n Error status: ~p"
- "~n Error index: ~p"
- "~n Varbinds: ~p",
- [ErrStatus,ErrIndex,ResVarbinds]),
+ if
+ (ErrStatus =/= noError) ->
+ ?vlog("get final result: "
+ "~n Error status: ~p"
+ "~n Error index: ~p"
+ "~n when"
+ "~n Original Result: "
+ "~n ~p", [ErrStatus, ErrIndex, OrigRes]);
+ true ->
+ ?vtrace("get final result: "
+ "~n Error status: ~p"
+ "~n Error index: ~p"
+ "~n Varbinds: ~p",
+ [ErrStatus, ErrIndex, ResVarbinds])
+ end,
ResponseVarbinds = lists:keysort(#varbind.org_index, ResVarbinds),
?vtrace("response varbinds: "
- "~n ~p",[ResponseVarbinds]),
+ "~n ~p", [ResponseVarbinds]),
make_response_pdu(ReqId, ErrStatus, ErrIndex, Vbs, ResponseVarbinds);
process_pdu(#pdu{type = 'get-next-request', request_id = ReqId, varbinds = Vbs},
@@ -2566,22 +2573,31 @@ process_pdu(#pdu{type = 'get-next-request', request_id = ReqId, varbinds = Vbs},
?vtrace("process get-next-request -> entry with"
"~n ReqId: ~p"
"~n Vbs: ~p"
- "~n MibView: ~p",[ReqId, Vbs, MibView]),
- Res = get_err(do_get_next(MibView, Vbs)),
- ?vtrace("get-next result: "
- "~n ~p",[Res]),
+ "~n MibView: ~p", [ReqId, Vbs, MibView]),
+ OrigRes = do_get_next(MibView, Vbs),
+ Res = get_err(OrigRes),
{ErrStatus, ErrIndex, ResVarbinds} =
if
Vsn =:= 'version-1' -> validate_next_v1(Res, MibView);
true -> Res
end,
- ?vtrace("get-next final result -> validation result:"
- "~n Error status: ~p"
- "~n Error index: ~p"
- "~n Varbinds: ~p",[ErrStatus,ErrIndex,ResVarbinds]),
+ if
+ (ErrStatus =/= noError) ->
+ ?vlog("get-next final result: "
+ "~n Error status: ~p"
+ "~n Error index: ~p"
+ "~n when"
+ "~n Original Result: "
+ "~n ~p", [ErrStatus, ErrIndex, OrigRes]);
+ true ->
+ ?vtrace("get-next final result:"
+ "~n Error status: ~p"
+ "~n Error index: ~p"
+ "~n Varbinds: ~p", [ErrStatus, ErrIndex, ResVarbinds])
+ end,
ResponseVarbinds = lists:keysort(#varbind.org_index, ResVarbinds),
?vtrace("get-next final result -> response varbinds: "
- "~n ~p",[ResponseVarbinds]),
+ "~n ~p", [ResponseVarbinds]),
make_response_pdu(ReqId, ErrStatus, ErrIndex, Vbs, ResponseVarbinds);
process_pdu(#pdu{type = 'get-bulk-request',
@@ -2590,31 +2606,50 @@ process_pdu(#pdu{type = 'get-bulk-request',
error_status = NonRepeaters,
error_index = MaxRepetitions},
PduMS, _Vsn, MibView, GbMaxVBs) ->
- {ErrStatus, ErrIndex, ResponseVarbinds} =
- get_err(do_get_bulk(MibView, NonRepeaters, MaxRepetitions, PduMS, Vbs,
- GbMaxVBs)),
- ?vtrace("get-bulk final result: "
- "~n Error status: ~p"
- "~n Error index: ~p"
- "~n Respons varbinds: ~p",
- [ErrStatus,ErrIndex,ResponseVarbinds]),
+ OrigRes = do_get_bulk(MibView, NonRepeaters, MaxRepetitions, PduMS, Vbs,
+ GbMaxVBs),
+ {ErrStatus, ErrIndex, ResponseVarbinds} = get_err(OrigRes),
+ if
+ (ErrStatus =/= noError) ->
+ ?vlog("get-bulk final result: "
+ "~n Error Status: ~p"
+ "~n Error Index: ~p"
+ "~n when"
+ "~n Original Result: "
+ "~n ~p", [ErrStatus, ErrIndex, OrigRes]);
+ true ->
+ ?vtrace("get-bulk final result: "
+ "~n Error status: ~p"
+ "~n Error index: ~p"
+ "~n Response Varbinds: ~p",
+ [ErrStatus, ErrIndex, ResponseVarbinds])
+ end,
make_response_pdu(ReqId, ErrStatus, ErrIndex, Vbs, ResponseVarbinds);
process_pdu(#pdu{type = 'set-request', request_id = ReqId, varbinds = Vbs},
- _PduMS, Vsn, MibView, _GbMaxVbs)->
- Res = do_set(MibView, Vbs),
- ?vtrace("set result: "
- "~n ~p",[Res]),
+ _PduMS, Vsn, MibView, _GbMaxVbs) ->
+ OrigRes = do_set(MibView, Vbs),
{ErrStatus, ErrIndex} =
if
- Vsn =:= 'version-1' -> validate_err(v2_to_v1, Res);
- true -> Res
+ Vsn =:= 'version-1' -> validate_err(v2_to_v1, OrigRes);
+ true -> OrigRes
end,
- ?vtrace("set final result: "
- "~n Error status: ~p"
- "~n Error index: ~p",[ErrStatus,ErrIndex]),
+ if
+ (ErrStatus =/= noError) ->
+ ?vlog("set final result: "
+ "~n Error Status: ~p"
+ "~n Error Index: ~p"
+ "~n when"
+ "~n Original Result: "
+ "~n ~p", [ErrStatus, ErrIndex, OrigRes]);
+ true ->
+ ?vtrace("set final result: "
+ "~n Error Status: ~p"
+ "~n Error Index: ~p", [ErrStatus, ErrIndex])
+ end,
make_response_pdu(ReqId, ErrStatus, ErrIndex, Vbs, Vbs).
+
%%-----------------------------------------------------------------
%% Transform a value == noSuchInstance | noSuchObject or a
%% Counter64 type to a noSuchName error for the whole pdu.
diff --git a/lib/snmp/src/agent/snmpa_local_db.erl b/lib/snmp/src/agent/snmpa_local_db.erl
index eb67b9cd6f..f481641242 100644
--- a/lib/snmp/src/agent/snmpa_local_db.erl
+++ b/lib/snmp/src/agent/snmpa_local_db.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -147,12 +147,13 @@ init([Prio, DbDir, DbInitError, Opts]) ->
do_init(Prio, DbDir, DbInitError, Opts) ->
process_flag(priority, Prio),
process_flag(trap_exit, true),
- put(sname,ldb),
- put(verbosity,get_opt(verbosity, Opts, ?default_verbosity)),
+ put(sname, get_opt(sname, Opts, ldb)),
+ put(verbosity, get_opt(verbosity, Opts, ?default_verbosity)),
?vlog("starting",[]),
Dets = dets_open(DbDir, DbInitError, Opts),
Ets = ets:new(?ETS_TAB, [set, protected]),
?vdebug("started",[]),
+ put(started, snmp_misc:formated_timestamp()),
{ok, #state{dets = Dets, ets = Ets}}.
dets_open(DbDir, DbInitError, Opts) ->
@@ -625,7 +626,7 @@ handle_info(Info, State) ->
terminate(Reason, State) ->
- ?vlog("terminate: ~p",[Reason]),
+ ?vlog("terminate: ~p", [Reason]),
close(State).
diff --git a/lib/snmp/src/misc/snmp_misc.erl b/lib/snmp/src/misc/snmp_misc.erl
index 1f847b7a29..39254503ac 100644
--- a/lib/snmp/src/misc/snmp_misc.erl
+++ b/lib/snmp/src/misc/snmp_misc.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2015. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -64,8 +64,18 @@
strip_extension_from_filename/2,
str_xor/2,
time/3,
-
- verify_behaviour/2
+
+ verify_behaviour/2,
+
+ %% These are used both for debugging (verbosity printouts)
+ %% and other such "utility" operations.
+ format_timestamp/1, format_timestamp/2,
+ format_short_timestamp/1, format_short_timestamp/2,
+ format_long_timestamp/1, format_long_timestamp/2,
+ formated_timestamp/0,
+ formated_short_timestamp/0,
+ formated_long_timestamp/0
+
]).
@@ -112,6 +122,102 @@ now(sec) ->
erlang:monotonic_time(seconds).
+
+%% ---------------------------------------------------------------------------
+%% # formated_timstamp/0, formated_timstamp/1
+%% # format_short_timstamp/0, format_short_timstamp/1
+%% # format_long_timstamp/0, format_long_timstamp/1
+%%
+%% Create a formatted timestamp. Short means that it will not include
+%% the date in the formatted timestamp. Also it will only include millis.
+%% ---------------------------------------------------------------------------
+
+formated_timestamp() ->
+ formated_long_timestamp().
+
+formated_short_timestamp() ->
+ format_short_timestamp(os:timestamp()).
+
+formated_long_timestamp() ->
+ format_long_timestamp(os:timestamp()).
+
+
+%% ---------------------------------------------------------------------------
+%% # format_timstamp/1, format_timstamp/2
+%% # format_short_timstamp/1, format_short_timstamp/2
+%% # format_long_timstamp/1, format_long_timstamp/2
+%%
+%% Formats the provided timestamp. Short means that it will not include
+%% the date in the formatted timestamp.
+%% ---------------------------------------------------------------------------
+
+-spec format_timestamp(Now :: erlang:timestamp()) ->
+ string().
+
+format_timestamp(Now) ->
+ format_long_timestamp(Now).
+
+-spec format_short_timestamp(Now :: erlang:timestamp()) ->
+ string().
+
+format_short_timestamp(Now) ->
+ N2T = fun(N) -> calendar:now_to_local_time(N) end,
+ format_timestamp(short, Now, N2T).
+
+-spec format_long_timestamp(Now :: erlang:timestamp()) ->
+ string().
+
+format_long_timestamp(Now) ->
+ N2T = fun(N) -> calendar:now_to_local_time(N) end,
+ format_timestamp(long, Now, N2T).
+
+-spec format_timestamp(Now :: erlang:timestamp(),
+ N2T :: function()) ->
+ string().
+
+format_timestamp(Now, N2T) when is_tuple(Now) andalso is_function(N2T) ->
+ format_long_timestamp(Now, N2T).
+
+-spec format_short_timestamp(Now :: erlang:timestamp(),
+ N2T :: function()) ->
+ string().
+
+format_short_timestamp(Now, N2T) when is_tuple(Now) andalso is_function(N2T) ->
+ format_timestamp(short, Now, N2T).
+
+-spec format_long_timestamp(Now :: erlang:timestamp(),
+ N2T :: function()) ->
+ string().
+
+format_long_timestamp(Now, N2T) when is_tuple(Now) andalso is_function(N2T) ->
+ format_timestamp(long, Now, N2T).
+
+format_timestamp(Format, {_N1, _N2, N3} = Now, N2T) ->
+ {Date, Time} = N2T(Now),
+ do_format_timestamp(Format, Date, Time, N3).
+
+do_format_timestamp(short, _Date, Time, N3) ->
+ do_format_short_timestamp(Time, N3);
+do_format_timestamp(long, Date, Time, N3) ->
+ do_format_long_timestamp(Date, Time, N3).
+
+do_format_long_timestamp(Date, Time, N3) ->
+ {YYYY,MM,DD} = Date,
+ {Hour,Min,Sec} = Time,
+ FormatDate =
+ io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w.~.3.0w",
+ [YYYY, MM, DD, Hour, Min, Sec, N3 div 1000]),
+ lists:flatten(FormatDate).
+
+do_format_short_timestamp(Time, N3) ->
+ {Hour,Min,Sec} = Time,
+ FormatDate =
+ io_lib:format("~.2.0w:~.2.0w:~.2.0w.~.3.0w",
+ [Hour, Min, Sec, N3 div 1000]),
+ lists:flatten(FormatDate).
+
+
+
is_crypto_supported(Alg) ->
%% The 'try catch' handles the case when 'crypto' is
%% not present in the system (or not started).
diff --git a/lib/snmp/src/misc/snmp_verbosity.erl b/lib/snmp/src/misc/snmp_verbosity.erl
index edfb52a474..9b2676d048 100644
--- a/lib/snmp/src/misc/snmp_verbosity.erl
+++ b/lib/snmp/src/misc/snmp_verbosity.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -70,16 +70,7 @@ print2(_Verbosity,Format,Arguments) ->
timestamp() ->
- format_timestamp(os:timestamp()).
-
-format_timestamp({_N1, _N2, N3} = Now) ->
- {Date, Time} = calendar:now_to_datetime(Now),
- {YYYY,MM,DD} = Date,
- {Hour,Min,Sec} = Time,
- FormatDate =
- io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w ~w",
- [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]),
- lists:flatten(FormatDate).
+ snmp_misc:formated_timestamp().
process_args([], Acc) ->
lists:reverse(Acc);
@@ -155,7 +146,8 @@ image_of_sname(mgr) -> "MGR";
image_of_sname(mgr_misc) -> "MGR_MISC";
image_of_sname(undefined) -> "";
-image_of_sname(V) -> lists:flatten(io_lib:format("~p",[V])).
+image_of_sname(N) when is_list(N) -> N; % Used in testing
+image_of_sname(N) -> lists:flatten(io_lib:format("~p", [N])).
validate(info) -> info;
diff --git a/lib/snmp/test/snmp_agent_test.erl b/lib/snmp/test/snmp_agent_test.erl
index f9c18af6ea..71e3fa3b9a 100644
--- a/lib/snmp/test/snmp_agent_test.erl
+++ b/lib/snmp/test/snmp_agent_test.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2003-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -1116,15 +1116,15 @@ init_ms(Config, Opts) when is_list(Config) ->
Opts1 = [MasterAgentVerbosity, MibsVerbosity, SymStoreVerbosity | Opts],
[{vsn, v1} | start_v1_agent(Config, Opts1)].
-init_size_check_mse(Config) when is_list(Config) ->
- MibStorage = {mib_storage, [{module, snmpa_mib_storage_ets}]},
- init_size_check_ms(Config, [MibStorage]).
+%% init_size_check_mse(Config) when is_list(Config) ->
+%% MibStorage = {mib_storage, [{module, snmpa_mib_storage_ets}]},
+%% init_size_check_ms(Config, [MibStorage]).
-init_size_check_msd(Config) when is_list(Config) ->
- AgentDbDir = ?GCONF(agent_db_dir, Config),
- MibStorage = {mib_storage, [{module, snmpa_mib_storage_dets},
- {options, [{dir, AgentDbDir}]}]},
- init_size_check_ms(Config, [MibStorage]).
+%% init_size_check_msd(Config) when is_list(Config) ->
+%% AgentDbDir = ?GCONF(agent_db_dir, Config),
+%% MibStorage = {mib_storage, [{module, snmpa_mib_storage_dets},
+%% {options, [{dir, AgentDbDir}]}]},
+%% init_size_check_ms(Config, [MibStorage]).
init_size_check_msm(Config) when is_list(Config) ->
?line AgentNode = ?GCONF(snmp_master, Config),
@@ -5146,12 +5146,21 @@ snmp_framework_mib_3(Config) when is_list(Config) ->
%% Req. SNMP-FRAMEWORK-MIB
snmp_framework_mib_test() ->
?line ["agentEngine"] = get_req(1, [[snmpEngineID,0]]),
+ T1 = snmp_misc:now(ms),
?line [EngineTime] = get_req(2, [[snmpEngineTime,0]]),
+ T2 = snmp_misc:now(ms),
?SLEEP(5000),
+ T3 = snmp_misc:now(ms),
?line [EngineTime2] = get_req(3, [[snmpEngineTime,0]]),
- ?DBG("snmp_framework_mib -> time(s): "
- "~n EngineTime 1 = ~p"
- "~n EngineTime 2 = ~p", [EngineTime, EngineTime2]),
+ T4 = snmp_misc:now(ms),
+ ?PRINT2("snmp_framework_mib -> time(s): "
+ "~n EngineTime 1: ~p"
+ "~n Time to acquire: ~w ms"
+ "~n EngineTime 2: ~p"
+ "~n Time to acquire: ~w ms"
+ "~n => (5 sec sleep between get(snmpEngineTime))"
+ "~n Total time to acquire: ~w ms",
+ [EngineTime, T2-T1, EngineTime2, T4-T3, T4-T1]),
if
(EngineTime+7) < EngineTime2 ->
?line ?FAIL({too_large_diff, EngineTime, EngineTime2});
@@ -5160,11 +5169,18 @@ snmp_framework_mib_test() ->
true ->
ok
end,
+ T5 = snmp_misc:now(ms),
?line case get_req(4, [[snmpEngineBoots,0]]) of
[Boots] when is_integer(Boots) ->
+ T6 = snmp_misc:now(ms),
+ ?PRINT2("snmp_framework_mib -> "
+ "~n boots: ~p"
+ "~n Time to acquire: ~w ms", [Boots, T6-T5]),
ok;
Else ->
- ?FAIL(Else)
+ ?PRINT2("snmp_framework_mib -> failed get proper boots:"
+ "~n ~p", [Else]),
+ ?FAIL({invalid_boots, Else})
end,
ok.
diff --git a/lib/snmp/test/snmp_agent_test_lib.erl b/lib/snmp/test/snmp_agent_test_lib.erl
index c19c88528f..6defdadb5a 100644
--- a/lib/snmp/test/snmp_agent_test_lib.erl
+++ b/lib/snmp/test/snmp_agent_test_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -358,22 +358,22 @@ run(Mod, Func, Args, Opts) ->
"~n StdM: ~p",
[M,Vsn,Dir,User,SecLevel,EngineID,CtxEngineID,Community,StdM]),
case snmp_test_mgr:start([%% {agent, snmp_test_lib:hostname()},
- {packet_server_debug,true},
- {debug,true},
- {agent, get(master_host)},
- {ipfamily, get(ipfamily)},
- {agent_udp, 4000},
- {trap_udp, 5000},
- {recbuf,65535},
+ {packet_server_debug, true},
+ {debug, true},
+ {agent, get(master_host)},
+ {ipfamily, get(ipfamily)},
+ {agent_udp, 4000},
+ {trap_udp, 5000},
+ {recbuf, 65535},
quiet,
Vsn,
- {community, Community},
- {user, User},
- {sec_level, SecLevel},
- {engine_id, EngineID},
- {context_engine_id, CtxEngineID},
- {dir, Dir},
- {mibs, mibs(StdM, M)}]) of
+ {community, Community},
+ {user, User},
+ {sec_level, SecLevel},
+ {engine_id, EngineID},
+ {context_engine_id, CtxEngineID},
+ {dir, Dir},
+ {mibs, mibs(StdM, M)}]) of
{ok, _Pid} ->
case (catch apply(Mod, Func, Args)) of
{'EXIT', Reason} ->
@@ -383,10 +383,18 @@ run(Mod, Func, Args, Opts) ->
catch snmp_test_mgr:stop(),
Res
end;
+
+ {error, Reason} ->
+ ?EPRINT2("Failed starting (test) manager: "
+ "~n ~p", [Reason]),
+ catch snmp_test_mgr:stop(),
+ ?line ?FAIL({mgr_start_error, Reason});
+
Err ->
- io:format("Error starting manager: ~p\n", [Err]),
+ ?EPRINT2("Failed starting (test) manager: "
+ "~n ~p", [Err]),
catch snmp_test_mgr:stop(),
- ?line ?FAIL({mgr_start, Err})
+ ?line ?FAIL({mgr_start_failure, Err})
end.
@@ -464,20 +472,24 @@ start_agent(Config, Vsns, Opts) ->
process_flag(trap_exit,true),
+ ?PRINT2("start_agent -> try start snmp app supervisor", []),
{ok, AppSup} = snmp_app_sup:start_link(),
unlink(AppSup),
?DBG("start_agent -> snmp app supervisor: ~p", [AppSup]),
- ?DBG("start_agent -> start master agent",[]),
+ ?PRINT2("start_agent -> try start master agent",[]),
?line Sup = start_sup(Env),
-
- ?DBG("start_agent -> unlink from supervisor", []),
?line unlink(Sup),
+ ?DBG("start_agent -> snmp supervisor: ~p", [Sup]),
+
+ ?PRINT2("start_agent -> try (rpc) start sub agent on ~p", [SaNode]),
?line SaDir = ?config(sa_dir, Config),
- ?DBG("start_agent -> (rpc) start sub on ~p", [SaNode]),
?line {ok, Sub} = start_sub_sup(SaNode, SaDir),
- ?DBG("start_agent -> done",[]),
- ?line [{snmp_sup, {Sup, self()}}, {snmp_sub, Sub} | Config].
+ ?DBG("start_agent -> done", []),
+
+ ?line [{snmp_app_sup, AppSup},
+ {snmp_sup, {Sup, self()}},
+ {snmp_sub, Sub} | Config].
app_agent_env_init(Env0, Opts) ->
@@ -670,35 +682,52 @@ merge_agent_options([{Key, _Value} = Opt|Opts], Options) ->
stop_agent(Config) when is_list(Config) ->
- ?LOG("stop_agent -> entry with"
- "~n Config: ~p",[Config]),
-
- {Sup, Par} = ?config(snmp_sup, Config),
- ?DBG("stop_agent -> attempt to stop (sup) ~p"
- "~n Sup: ~p"
- "~n Par: ~p",
- [Sup,
- (catch process_info(Sup)),
- (catch process_info(Par))]),
-
- _Info = agent_info(Sup),
- ?DBG("stop_agent -> Agent info: "
- "~n ~p", [_Info]),
-
- stop_sup(Sup, Par),
-
- {Sup2, Par2} = ?config(snmp_sub, Config),
- ?DBG("stop_agent -> attempt to stop (sub) ~p"
- "~n Sup2: ~p"
- "~n Par2: ~p",
- [Sup2,
- (catch process_info(Sup2)),
- (catch process_info(Par2))]),
- stop_sup(Sup2, Par2),
-
- ?DBG("stop_agent -> done - now cleanup config", []),
- C1 = lists:keydelete(snmp_sup, 1, Config),
- lists:keydelete(snmp_sub, 1, C1).
+ ?PRINT2("stop_agent -> entry with"
+ "~n Config: ~p",[Config]),
+
+
+ %% Stop the sub-agent (the agent supervisor)
+ {SubSup, SubPar} = ?config(snmp_sub, Config),
+ ?PRINT2("stop_agent -> attempt to stop sub agent (~p)"
+ "~n Sub Sup info: "
+ "~n ~p"
+ "~n Sub Par info: "
+ "~n ~p",
+ [SubSup,
+ (catch process_info(SubSup)),
+ (catch process_info(SubPar))]),
+ stop_sup(SubSup, SubPar),
+ Config2 = lists:keydelete(snmp_sub, 1, Config),
+
+
+ %% Stop the master-agent (the top agent supervisor)
+ {MasterSup, MasterPar} = ?config(snmp_sup, Config),
+ ?PRINT2("stop_agent -> attempt to stop master agent (~p)"
+ "~n Master Sup: "
+ "~n ~p"
+ "~n Master Par: "
+ "~n ~p"
+ "~n Agent Info: "
+ "~n ~p",
+ [MasterSup,
+ (catch process_info(MasterSup)),
+ (catch process_info(MasterPar)),
+ agent_info(MasterSup)]),
+ stop_sup(MasterSup, MasterPar),
+ Config3 = lists:keydelete(snmp_sup, 1, Config2),
+
+
+ %% Stop the top supervisor (of the snmp app)
+ AppSup = ?config(snmp_app_sup, Config),
+ ?PRINT2("stop_agent -> attempt to app sup ~p"
+ "~n App Sup: ~p",
+ [AppSup,
+ (catch process_info(AppSup))]),
+ Config4 = lists:keydelete(snmp_app_sup, 1, Config3),
+
+
+ ?PRINT2("stop_agent -> done", []),
+ Config4.
start_sup(Env) ->
@@ -728,7 +757,6 @@ stop_sup(Pid, _) ->
?LOG("stop_sup -> attempt to stop ~p", [Pid]),
Ref = erlang:monitor(process, Pid),
?LOG("stop_sup -> Ref: ~p", [Ref]),
- %% Pid ! {'EXIT', Parent, shutdown}, % usch
exit(Pid, kill),
await_stopped(Pid, Ref).
@@ -864,13 +892,15 @@ expect(Mod, Line, Type, Enterp, Generic, Specific, ExpVBs) ->
expect2(Mod, Line, Fun).
expect2(Mod, Line, F) ->
- io:format("EXPECT for ~w:~w~n", [Mod, Line]),
+ io_format_expect("for ~w:~w", [Mod, Line]),
case F() of
{error, Reason} ->
- io:format("EXPECT failed at ~w:~w => ~n~p~n", [Mod, Line, Reason]),
+ io_format_expect("failed at ~w:~w => "
+ "~n ~p", [Mod, Line, Reason]),
throw({error, {expect, Mod, Line, Reason}});
Else ->
- io:format("EXPECT result for ~w:~w => ~n~p~n", [Mod, Line, Else]),
+ io_format_expect("result for ~w:~w => "
+ "~n ~p", [Mod, Line, Else]),
Else
end.
@@ -899,20 +929,27 @@ receive_trap(To) ->
end.
+io_format_expect(F) ->
+ io_format_expect(F, []).
+
+io_format_expect(F, A) ->
+ ?PRINT2("EXPECT " ++ F, A).
+
+
do_expect(Expect) when is_atom(Expect) ->
do_expect({Expect, get_timeout()});
do_expect({any_pdu, To})
when is_integer(To) orelse (To =:= infinity) ->
- io:format("EXPECT any PDU~n", []),
+ io_format_expect("any PDU"),
receive_pdu(To);
do_expect({any_trap, To}) ->
- io:format("EXPECT any TRAP within ~w~n", [To]),
+ io_format_expect("any TRAP within ~w", [To]),
receive_trap(To);
do_expect({timeout, To}) ->
- io:format("EXPECT nothing within ~w~n", [To]),
+ io_format_expect("nothing within ~w", [To]),
receive
X ->
{error, {unexpected, X}}
@@ -924,16 +961,16 @@ do_expect({timeout, To}) ->
do_expect({Err, To})
when (is_atom(Err) andalso
((is_integer(To) andalso To > 0) orelse (To =:= infinity))) ->
- io:format("EXPECT error ~w within ~w~n", [Err, To]),
+ io_format_expect("error ~w within ~w", [Err, To]),
do_expect({{error, Err}, To});
do_expect({error, Err}) when is_atom(Err) ->
Check = fun(_, R) -> R end,
- io:format("EXPECT error ~w~n", [Err]),
+ io_format_expect("error ~w", [Err]),
do_expect2(Check, any, Err, any, any, get_timeout());
do_expect({{error, Err}, To}) ->
Check = fun(_, R) -> R end,
- io:format("EXPECT error ~w within ~w~n", [Err, To]),
+ io_format_expect("error ~w within ~w", [Err, To]),
do_expect2(Check, any, Err, any, any, To);
%% exp_varbinds() -> [exp_varbind()]
@@ -943,25 +980,23 @@ do_expect({{error, Err}, To}) ->
%% ExpVBs -> exp_varbinds() | {VbsCondition, exp_varbinds()}
do_expect(ExpVBs) ->
Check = fun(_, R) -> R end,
- io:format("EXPECT 'get-response'"
- "~n with"
- "~n Varbinds: ~p~n", [ExpVBs]),
+ io_format_expect("'get-response'"
+ "~n with"
+ "~n Varbinds: ~p", [ExpVBs]),
do_expect2(Check, 'get-response', noError, 0, ExpVBs, get_timeout()).
do_expect(v2trap, ExpVBs) ->
Check = fun(_, R) -> R end,
- io:format("EXPECT 'snmpv2-trap'"
- "~n with"
- "~n Varbinds: ~p~n", [ExpVBs]),
+ io_format_expect("'snmpv2-trap' with"
+ "~n Varbinds: ~p", [ExpVBs]),
do_expect2(Check, 'snmpv2-trap', noError, 0, ExpVBs, get_timeout());
do_expect(report, ExpVBs) ->
Check = fun(_, R) -> R end,
- io:format("EXPECT 'report'"
- "~n with"
- "~n Varbinds: ~p~n", [ExpVBs]),
+ io_format_expect("'report' with"
+ "~n Varbinds: ~p", [ExpVBs]),
do_expect2(Check, 'report', noError, 0, ExpVBs, get_timeout());
@@ -970,9 +1005,8 @@ do_expect(inform, ExpVBs) ->
do_expect({inform, false}, ExpVBs) ->
Check = fun(_, R) -> R end,
- io:format("EXPECT 'inform-request' (false)"
- "~n with"
- "~n Varbinds: ~p~n", [ExpVBs]),
+ io_format_expect("'inform-request' (false) with"
+ "~n Varbinds: ~p", [ExpVBs]),
do_expect2(Check, 'inform-request', noError, 0, ExpVBs, get_timeout());
do_expect({inform, true}, ExpVBs) ->
@@ -986,9 +1020,8 @@ do_expect({inform, true}, ExpVBs) ->
(_, Err) ->
Err
end,
- io:format("EXPECT 'inform-request' (true)"
- "~n with"
- "~n Varbinds: ~p~n", [ExpVBs]),
+ io_format_expect("'inform-request' (true) with"
+ "~n Varbinds: ~p", [ExpVBs]),
do_expect2(Check, 'inform-request', noError, 0, ExpVBs, get_timeout());
do_expect({inform, {error, EStat, EIdx}}, ExpVBs)
@@ -1003,11 +1036,10 @@ do_expect({inform, {error, EStat, EIdx}}, ExpVBs)
(_, Err) ->
Err
end,
- io:format("EXPECT 'inform-request' (error)"
- "~n with"
- "~n Error Status: ~p"
- "~n Error Index: ~p"
- "~n Varbinds: ~p~n", [EStat, EIdx, ExpVBs]),
+ io_format_expect("'inform-request' (error) with"
+ "~n Error Status: ~p"
+ "~n Error Index: ~p"
+ "~n Varbinds: ~p", [EStat, EIdx, ExpVBs]),
do_expect2(Check, 'inform-request', noError, 0, ExpVBs, get_timeout()).
@@ -1018,26 +1050,23 @@ do_expect(Err, Idx, ExpVBs, To)
when is_atom(Err) andalso
(is_integer(Idx) orelse is_list(Idx) orelse (Idx == any)) ->
Check = fun(_, R) -> R end,
- io:format("EXPECT 'get-response'"
- "~n with"
- "~n Error: ~p"
- "~n Index: ~p"
- "~n Varbinds: ~p"
- "~n within ~w~n", [Err, Idx, ExpVBs, To]),
+ io_format_expect("'get-response' withing ~w ms with"
+ "~n Error: ~p"
+ "~n Index: ~p"
+ "~n Varbinds: ~p", [To, Err, Idx, ExpVBs]),
do_expect2(Check, 'get-response', Err, Idx, ExpVBs, To).
do_expect(Type, Enterp, Generic, Specific, ExpVBs) ->
- do_expect(Type, Enterp, Generic, Specific, ExpVBs, 3500).
+ do_expect(Type, Enterp, Generic, Specific, ExpVBs, get_timeout()).
do_expect(trap, Enterp, Generic, Specific, ExpVBs, To) ->
- io:format("EXPECT trap"
- "~n with"
- "~n Enterp: ~w"
- "~n Generic: ~w"
- "~n Specific: ~w"
- "~n Varbinds: ~w"
- "~n within ~w~n", [Enterp, Generic, Specific, ExpVBs, To]),
+ io_format_expect("trap within ~w ms with"
+ "~n Enterp: ~w"
+ "~n Generic: ~w"
+ "~n Specific: ~w"
+ "~n Varbinds: ~w",
+ [To, Enterp, Generic, Specific, ExpVBs]),
PureE = purify_oid(Enterp),
case receive_trap(To) of
#trappdu{enterprise = PureE,
@@ -1072,46 +1101,46 @@ do_expect2(Check, Type, Err, Idx, ExpVBs, To)
#pdu{type = Type,
error_status = Err,
error_index = Idx} when ExpVBs =:= any ->
- io:format("EXPECT received expected pdu (1)~n", []),
+ io_format_expect("received expected pdu (1)"),
ok;
#pdu{type = Type,
request_id = ReqId,
error_status = Err2,
error_index = Idx} when ExpVBs =:= any ->
- io:format("EXPECT received expected pdu with "
- "unexpected error status (2): "
- "~n Error Status: ~p~n", [Err2]),
+ io_format_expect("received expected pdu with "
+ "unexpected error status (2): "
+ "~n Error Status: ~p", [Err2]),
{error, {unexpected_error_status, Err, Err2, ReqId}};
#pdu{error_status = Err} when (Type =:= any) andalso
(Idx =:= any) andalso
(ExpVBs =:= any) ->
- io:format("EXPECT received expected pdu (3)~n", []),
+ io_format_expect("received expected pdu (3)"),
ok;
#pdu{request_id = ReqId,
error_status = Err2} when (Type =:= any) andalso
(Idx =:= any) andalso
(ExpVBs =:= any) ->
- io:format("EXPECT received expected pdu with "
- "unexpected error status (4): "
- "~n Error Status: ~p~n", [Err2]),
+ io_format_expect("received expected pdu with "
+ "unexpected error status (4): "
+ "~n Error Status: ~p", [Err2]),
{error, {unexpected_error_status, Err, Err2, ReqId}};
#pdu{type = Type,
error_status = Err} when (Idx =:= any) andalso
(ExpVBs =:= any) ->
- io:format("EXPECT received expected pdu (5)~n", []),
+ io_format_expect("received expected pdu (5)", []),
ok;
#pdu{type = Type,
request_id = ReqId,
error_status = Err2} when (Idx =:= any) andalso
(ExpVBs =:= any) ->
- io:format("EXPECT received expected pdu with "
- "unexpected error status (6): "
- "~n Error Status: ~p~n", [Err2]),
+ io_format_expect("received expected pdu with "
+ "unexpected error status (6): "
+ "~n Error Status: ~p", [Err2]),
{error, {unexpected_error_status, Err, Err2, ReqId}};
#pdu{type = Type,
@@ -1120,13 +1149,13 @@ do_expect2(Check, Type, Err, Idx, ExpVBs, To)
error_index = EI} when is_list(Idx) andalso (ExpVBs =:= any) ->
case lists:member(EI, Idx) of
true ->
- io:format("EXPECT received expected pdu with "
- "expected error index (7)~n", []),
+ io_format_expect("received expected pdu with "
+ "expected error index (7)"),
ok;
false ->
- io:format("EXPECT received expected pdu with "
- "unexpected error index (8): "
- "~n Error Index: ~p~n", [EI]),
+ io_format_expect("received expected pdu with "
+ "unexpected error index (8): "
+ "~n Error Index: ~p", [EI]),
{error, {unexpected_error_index, EI, Idx, ReqId}}
end;
@@ -1136,15 +1165,15 @@ do_expect2(Check, Type, Err, Idx, ExpVBs, To)
error_index = EI} when is_list(Idx) andalso (ExpVBs =:= any) ->
case lists:member(EI, Idx) of
true ->
- io:format("EXPECT received expected pdu with "
- "unexpected error status (9): "
- "~n Error Status: ~p~n", [Err2]),
+ io_format_expect("received expected pdu with "
+ "unexpected error status (9): "
+ "~n Error Status: ~p", [Err2]),
{error, {unexpected_error_status, Err, Err2, ReqId}};
false ->
- io:format("EXPECT received expected pdu with "
- "unexpected error (10): "
- "~n Error Status: ~p"
- "~n Error index: ~p~n", [Err2, EI]),
+ io_format_expect("received expected pdu with "
+ "unexpected error (10): "
+ "~n Error Status: ~p"
+ "~n Error index: ~p", [Err2, EI]),
{error, {unexpected_error, {Err, Idx}, {Err2, EI}, ReqId}}
end;
@@ -1152,12 +1181,12 @@ do_expect2(Check, Type, Err, Idx, ExpVBs, To)
request_id = ReqId,
error_status = Err2,
error_index = Idx2} when ExpVBs =:= any ->
- io:format("EXPECT received unexpected pdu with (11) "
- "~n Type: ~p"
- "~n ReqId: ~p"
- "~n Errot status: ~p"
- "~n Error index: ~p"
- "~n", [Type2, ReqId, Err2, Idx2]),
+ io_format_expect("received unexpected pdu with (11) "
+ "~n Type: ~p"
+ "~n ReqId: ~p"
+ "~n Errot status: ~p"
+ "~n Error index: ~p",
+ [Type2, ReqId, Err2, Idx2]),
{error,
{unexpected_pdu,
{Type, Err, Idx}, {Type2, Err2, Idx2}, ReqId}};
@@ -1166,26 +1195,26 @@ do_expect2(Check, Type, Err, Idx, ExpVBs, To)
error_status = Err,
error_index = Idx,
varbinds = VBs} = PDU ->
- io:format("EXPECT received pdu (12): "
- "~n [exp] Type: ~p"
- "~n [exp] Error Status: ~p"
- "~n [exp] Error Index: ~p"
- "~n VBs: ~p"
- "~nwhen"
- "~n ExpVBs: ~p"
- "~n", [Type, Err, Idx, VBs, ExpVBs]),
+ io_format_expect("received pdu (12): "
+ "~n [exp] Type: ~p"
+ "~n [exp] Error Status: ~p"
+ "~n [exp] Error Index: ~p"
+ "~n VBs: ~p"
+ "~nwhen"
+ "~n ExpVBs: ~p",
+ [Type, Err, Idx, VBs, ExpVBs]),
Check(PDU, check_vbs(purify_oids(ExpVBs), VBs));
#pdu{type = Type,
error_status = Err,
varbinds = VBs} = PDU when Idx =:= any ->
- io:format("EXPECT received pdu (13): "
- "~n [exp] Type: ~p"
- "~n [exp] Error Status: ~p"
- "~n VBs: ~p"
- "~nwhen"
- "~n ExpVBs: ~p"
- "~n", [Type, Err, VBs, ExpVBs]),
+ io_format_expect("received pdu (13): "
+ "~n [exp] Type: ~p"
+ "~n [exp] Error Status: ~p"
+ "~n VBs: ~p"
+ "~nwhen"
+ "~n ExpVBs: ~p",
+ [Type, Err, VBs, ExpVBs]),
Check(PDU, check_vbs(purify_oids(ExpVBs), VBs));
#pdu{type = Type,
@@ -1193,15 +1222,15 @@ do_expect2(Check, Type, Err, Idx, ExpVBs, To)
error_status = Err,
error_index = EI,
varbinds = VBs} = PDU when is_list(Idx) ->
- io:format("EXPECT received pdu (14): "
- "~n [exp] Type: ~p"
- "~n ReqId: ~p"
- "~n [exp] Error Status: ~p"
- "~n [exp] Error Index: ~p"
- "~n VBs: ~p"
- "~nwhen"
- "~n ExpVBs: ~p"
- "~n", [Type, ReqId, Err, EI, VBs, ExpVBs]),
+ io_format_expect("received pdu (14): "
+ "~n [exp] Type: ~p"
+ "~n ReqId: ~p"
+ "~n [exp] Error Status: ~p"
+ "~n [exp] Error Index: ~p"
+ "~n VBs: ~p"
+ "~nwhen"
+ "~n ExpVBs: ~p",
+ [Type, ReqId, Err, EI, VBs, ExpVBs]),
PureVBs = purify_oids(ExpVBs),
case lists:member(EI, Idx) of
true ->
@@ -1215,13 +1244,13 @@ do_expect2(Check, Type, Err, Idx, ExpVBs, To)
error_status = Err2,
error_index = Idx2,
varbinds = VBs2} ->
- io:format("EXPECT received unexpected pdu with (15) "
- "~n Type: ~p"
- "~n ReqId: ~p"
- "~n Errot status: ~p"
- "~n Error index: ~p"
- "~n Varbinds: ~p"
- "~n", [Type2, ReqId, Err2, Idx2, VBs2]),
+ io_format_expect("received unexpected pdu with (15) "
+ "~n Type: ~p"
+ "~n ReqId: ~p"
+ "~n Errot status: ~p"
+ "~n Error index: ~p"
+ "~n Varbinds: ~p",
+ [Type2, ReqId, Err2, Idx2, VBs2]),
{error,
{unexpected_pdu,
{Type, Err, Idx, purify_oids(ExpVBs)},
@@ -1229,9 +1258,8 @@ do_expect2(Check, Type, Err, Idx, ExpVBs, To)
ReqId}};
Error ->
- io:format("EXPECT received error (16): "
- "~n Error: ~p"
- "~n", [Error]),
+ io_format_expect("received error (16): "
+ "~n Error: ~p", [Error]),
Error
end.
diff --git a/lib/snmp/test/snmp_manager_test.erl b/lib/snmp/test/snmp_manager_test.erl
index bb9b05b89f..5b0ebf8647 100644
--- a/lib/snmp/test/snmp_manager_test.erl
+++ b/lib/snmp/test/snmp_manager_test.erl
@@ -204,10 +204,15 @@ init_per_testcase(Case, Config) when is_list(Config) ->
Result =
case lists:member(Case, DeprecatedApiCases) of
true ->
- %% ?SKIP(api_no_longer_supported);
{skip, api_no_longer_supported};
false ->
- init_per_testcase2(Case, Config)
+ try init_per_testcase2(Case, Config)
+ catch
+ C:{skip, _} = E:_ when ((C =:= throw) orelse (C =:= exit)) ->
+ E;
+ C:E:_ when ((C =:= throw) orelse (C =:= exit)) ->
+ {skip, {catched, C, E}}
+ end
end,
p(Case, "init_per_testcase end when"
"~n Nodes: ~p"
@@ -326,9 +331,25 @@ init_per_testcase3(Case, Config) ->
true ->
Config
end,
+ %% We don't need to try catch this (init_agent)
+ %% since we have a try catch "higher up"...
Conf2 = init_agent(Conf1),
- Conf3 = init_manager(AutoInform, Conf2),
- Conf4 = init_mgr_user(Conf3),
+ Conf3 = try init_manager(AutoInform, Conf2)
+ catch AC:AE:_ ->
+ %% Ouch we need to clean up:
+ %% The init_agent starts an agent node!
+ init_per_testcase_fail_agent_cleanup(Conf2),
+ throw({skip, {manager_init_failed, AC, AE}})
+ end,
+ Conf4 = try init_mgr_user(Conf3)
+ catch MC:ME:_ ->
+ %% Ouch we need to clean up:
+ %% The init_agent starts an agent node!
+ %% The init_magager starts an manager node!
+ init_per_testcase_fail_manager_cleanup(Conf3),
+ init_per_testcase_fail_agent_cleanup(Conf3),
+ throw({skip, {manager_user_init_failed, MC, ME}})
+ end,
case lists:member(Case, ApiCases02 ++ ApiCases03) of
true ->
init_mgr_user_data2(Conf4);
@@ -339,6 +360,12 @@ init_per_testcase3(Case, Config) ->
Config
end.
+init_per_testcase_fail_manager_cleanup(Conf) ->
+ (catch fin_manager(Conf)).
+
+init_per_testcase_fail_agent_cleanup(Conf) ->
+ (catch fin_agent(Conf)).
+
end_per_testcase(Case, Config) when is_list(Config) ->
p(Case, "end_per_testcase begin when"
"~n Nodes: ~p~n~n", [erlang:nodes()]),
@@ -993,18 +1020,40 @@ notify_started02(Config) when is_list(Config) ->
{config, [{verbosity, log}, {dir, ConfDir}, {db_dir, DbDir}]}],
p("start snmpm client process"),
- Pid1 = ns02_loop1_start(),
+ NumIterations = 5,
+ Pid1 = ns02_client_start(NumIterations),
+
+ p("start snmpm ctrl (starter) process"),
+ Pid2 = ns02_ctrl_start(Opts, NumIterations),
+
+ %% On a reasonably fast machine, one iteration takes approx 4 seconds.
+ %% We measure the first iteration, and then we wait for the remaining
+ %% ones (4 in this case).
+ ApproxStartTime =
+ case ns02_client_await_approx_runtime(Pid1) of
+ {ok, T} ->
+ T;
+ {error, Reason} ->
+ %% Attempt cleanup just in case
+ exit(Pid1, kill),
+ exit(Pid2, kill),
+ ?FAIL(Reason);
+ {skip, Reason} ->
+ %% Attempt cleanup just in case
+ exit(Pid1, kill),
+ exit(Pid2, kill),
+ ?SKIP(Reason)
+ end,
- p("start snmpm starter process"),
- Pid2 = ns02_loop2_start(Opts),
-
- p("await snmpm client process exit"),
+ p("await snmpm client process exit (max ~p+10000 msec)", [ApproxStartTime]),
receive
{'EXIT', Pid1, normal} ->
ok;
{'EXIT', Pid1, Reason1} ->
- ?FAIL(Reason1)
- after 25000 ->
+ ?FAIL({client, Reason1})
+ after ApproxStartTime + 10000 ->
+ exit(Pid1, kill),
+ exit(Pid2, kill),
?FAIL(timeout)
end,
@@ -1013,8 +1062,9 @@ notify_started02(Config) when is_list(Config) ->
{'EXIT', Pid2, normal} ->
ok;
{'EXIT', Pid2, Reason2} ->
- ?FAIL(Reason2)
+ ?FAIL({ctrl, Reason2})
after 5000 ->
+ exit(Pid2, kill),
?FAIL(timeout)
end,
@@ -1022,26 +1072,63 @@ notify_started02(Config) when is_list(Config) ->
ok.
-ns02_loop1_start() ->
- spawn_link(fun() -> ns02_loop1() end).
+ns02_client_start(N) ->
+ Self = self(),
+ spawn_link(fun() -> ns02_client(Self, N) end).
+
+ns02_client_await_approx_runtime(Pid) ->
+ receive
+ {?MODULE, client_time, Time} ->
+ {ok, Time};
+ {'EXIT', Pid, Reason} ->
+ p("client (~p) failed: "
+ "~n ~p", [Pid, Reason]),
+ {error, Reason}
+
+ after 15000 ->
+ %% Either something is *really* wrong or this machine
+ %% is dog slow. Either way, this is a skip-reason...
+ {skip, approx_runtime_timeout}
+ end.
+
-ns02_loop1() ->
- put(tname,ns02_loop1),
+ns02_client(Parent, N) when is_pid(Parent) ->
+ put(tname, ns02_client),
p("starting"),
- ns02_loop1(dummy, snmpm:notify_started(?NS_TIMEOUT), 5).
+ ns02_client_loop(Parent,
+ dummy, snmpm:notify_started(?NS_TIMEOUT),
+ snmp_misc:now(ms), undefined,
+ N).
-ns02_loop1(_Ref, _Pid, 0) ->
- p("done"),
+ns02_client_loop(_Parent, _Ref, _Pid, _Begin, _End, 0) ->
+ %% p("loop -> done"),
exit(normal);
-ns02_loop1(Ref, Pid, N) ->
- p("entry when"
- "~n Ref: ~p"
- "~n Pid: ~p"
- "~n N: ~p", [Ref, Pid, N]),
+ns02_client_loop(Parent, Ref, Pid, Begin, End, N)
+ when is_pid(Parent) andalso is_integer(Begin) andalso is_integer(End) ->
+ %% p("loop -> [~w] inform parent: ~w, ~w => ~w", [N, Begin, End, End-Begin]),
+ Parent ! {?MODULE, client_time, N*(End-Begin)},
+ ns02_client_loop(undefined, Ref, Pid, snmp_misc:now(ms), undefined, N);
+ns02_client_loop(Parent, Ref, Pid, Begin, End, N)
+ when is_integer(Begin) andalso is_integer(End) ->
+ %% p("loop -> [~w] entry when"
+ %% "~n Ref: ~p"
+ %% "~n Pid: ~p"
+ %% "~n Begin: ~p"
+ %% "~n End: ~p", [N, Ref, Pid, Begin, End]),
+ ns02_client_loop(Parent, Ref, Pid, snmp_misc:now(ms), undefined, N);
+ns02_client_loop(Parent, Ref, Pid, Begin, End, N) ->
+ %% p("loop(await message) -> [~w] entry when"
+ %% "~n Ref: ~p"
+ %% "~n Pid: ~p"
+ %% "~n Begin: ~p"
+ %% "~n End: ~p", [N, Ref, Pid, Begin, End]),
receive
{snmpm_started, Pid} ->
p("received expected started message (~w)", [N]),
- ns02_loop1(snmpm:monitor(), dummy, N);
+ ns02_client_loop(Parent,
+ snmpm:monitor(), dummy,
+ Begin, End,
+ N);
{snmpm_start_timeout, Pid} ->
p("unexpected timout"),
?FAIL({unexpected_start_timeout, Pid});
@@ -1049,24 +1136,24 @@ ns02_loop1(Ref, Pid, N) ->
p("received expected DOWN message (~w) with"
"~n Obj: ~p"
"~n Reason: ~p", [N, Obj, Reason]),
- ns02_loop1(dummy, snmpm:notify_started(?NS_TIMEOUT), N-1)
- after 10000 ->
- ?FAIL(timeout)
+ ns02_client_loop(Parent,
+ dummy, snmpm:notify_started(?NS_TIMEOUT),
+ Begin, snmp_misc:now(ms),
+ N-1)
end.
-
-ns02_loop2_start(Opts) ->
- spawn_link(fun() -> ns02_loop2(Opts) end).
+ns02_ctrl_start(Opts, N) ->
+ spawn_link(fun() -> ns02_ctrl(Opts, N) end).
-ns02_loop2(Opts) ->
- put(tname,ns02_loop2),
+ns02_ctrl(Opts, N) ->
+ put(tname, ns02_ctrl),
p("starting"),
- ns02_loop2(Opts, 5).
+ ns02_ctrl_loop(Opts, N).
-ns02_loop2(_Opts, 0) ->
+ns02_ctrl_loop(_Opts, 0) ->
p("done"),
exit(normal);
-ns02_loop2(Opts, N) ->
+ns02_ctrl_loop(Opts, N) ->
p("entry when N: ~p", [N]),
?SLEEP(2000),
p("start manager"),
@@ -1074,7 +1161,7 @@ ns02_loop2(Opts, N) ->
?SLEEP(2000),
p("stop manager"),
snmpm:stop(),
- ns02_loop2(Opts, N-1).
+ ns02_ctrl_loop(Opts, N-1).
%%======================================================================
@@ -5416,15 +5503,14 @@ init_manager(AutoInform, Config) ->
start_manager(Node, Vsns, Conf)
end
catch
- T:E ->
- StackTrace = ?STACK(),
+ C:E:S ->
p("Failure during manager start: "
- "~n Error Type: ~p"
- "~n Error: ~p"
- "~n StackTrace: ~p", [T, E, StackTrace]),
+ "~n Error Class: ~p"
+ "~n Error: ~p"
+ "~n StackTrace: ~p", [C, E, S]),
%% And now, *try* to cleanup
(catch stop_node(Node)),
- ?FAIL({failed_starting_manager, T, E, StackTrace})
+ ?FAIL({failed_starting_manager, C, E, S})
end.
fin_manager(Config) ->
@@ -5432,7 +5518,7 @@ fin_manager(Config) ->
StopMgrRes = stop_manager(Node),
StopCryptoRes = fin_crypto(Node),
StopNode = stop_node(Node),
- p("fin_agent -> stop apps and (mgr node ~p) node results: "
+ p("fin_manager -> stop apps and (mgr node ~p) node results: "
"~n SNMP Mgr: ~p"
"~n Crypto: ~p"
"~n Node: ~p",
@@ -5498,15 +5584,14 @@ init_agent(Config) ->
start_agent(Node, Vsns, Conf)
end
catch
- T:E ->
- StackTrace = ?STACK(),
+ C:E:S ->
p("Failure during agent start: "
- "~n Error Type: ~p"
- "~n Error: ~p"
- "~n StackTrace: ~p", [T, E, StackTrace]),
+ "~n Error Class: ~p"
+ "~n Error: ~p"
+ "~n StackTrace: ~p", [C, E, S]),
%% And now, *try* to cleanup
(catch stop_node(Node)),
- ?FAIL({failed_starting_agent, T, E, StackTrace})
+ ?FAIL({failed_starting_agent, C, E, S})
end.
diff --git a/lib/snmp/test/snmp_test_lib.erl b/lib/snmp/test/snmp_test_lib.erl
index 290f1bc31a..a483690653 100644
--- a/lib/snmp/test/snmp_test_lib.erl
+++ b/lib/snmp/test/snmp_test_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -41,7 +41,7 @@
-export([watchdog/3, watchdog_start/1, watchdog_start/2, watchdog_stop/1]).
-export([del_dir/1]).
-export([cover/1]).
--export([p/2, print/5, formated_timestamp/0]).
+-export([p/2, print1/2, print2/2, print/5, formated_timestamp/0]).
%% ----------------------------------------------------------------------
@@ -58,12 +58,67 @@ from(H, [H | T]) -> T;
from(H, [_ | T]) -> from(H, T);
from(_H, []) -> [].
+%% localhost() ->
+%% {ok, Ip} = snmp_misc:ip(net_adm:localhost()),
+%% Ip.
+%% localhost(Family) ->
+%% {ok, Ip} = snmp_misc:ip(net_adm:localhost(), Family),
+%% Ip.
+
localhost() ->
- {ok, Ip} = snmp_misc:ip(net_adm:localhost()),
- Ip.
+ localhost(inet).
+
localhost(Family) ->
- {ok, Ip} = snmp_misc:ip(net_adm:localhost(), Family),
- Ip.
+ case inet:getaddr(net_adm:localhost(), Family) of
+ {ok, {127, _, _, _}} when (Family =:= inet) ->
+ %% Ouch, we need to use something else
+ case inet:getifaddrs() of
+ {ok, IfList} ->
+ which_addr(Family, IfList);
+ {error, Reason1} ->
+ fail({getifaddrs, Reason1}, ?MODULE, ?LINE)
+ end;
+ {ok, {0, _, _, _, _, _, _, _}} when (Family =:= inet6) ->
+ %% Ouch, we need to use something else
+ case inet:getifaddrs() of
+ {ok, IfList} ->
+ which_addr(Family, IfList);
+ {error, Reason1} ->
+ fail({getifaddrs, Reason1}, ?MODULE, ?LINE)
+ end;
+ {ok, Addr} ->
+ Addr;
+ {error, Reason2} ->
+ fail({getaddr, Reason2}, ?MODULE, ?LINE)
+ end.
+
+which_addr(_Family, []) ->
+ fail(no_valid_addr, ?MODULE, ?LINE);
+which_addr(Family, [{"lo", _} | IfList]) ->
+ which_addr(Family, IfList);
+which_addr(Family, [{"docker" ++ _, _} | IfList]) ->
+ which_addr(Family, IfList);
+which_addr(Family, [{"br-" ++ _, _} | IfList]) ->
+ which_addr(Family, IfList);
+which_addr(Family, [{_Name, IfOpts} | IfList]) ->
+ case which_addr2(Family, IfOpts) of
+ {ok, Addr} ->
+ Addr;
+ {error, _} ->
+ which_addr(Family, IfList)
+ end.
+
+which_addr2(_Family, []) ->
+ {error, not_found};
+which_addr2(Family, [{addr, Addr}|_])
+ when (Family =:= inet) andalso (size(Addr) =:= 4) ->
+ {ok, Addr};
+which_addr2(Family, [{addr, Addr}|_])
+ when (Family =:= inet6) andalso (size(Addr) =:= 8) ->
+ {ok, Addr};
+which_addr2(Family, [_|IfOpts]) ->
+ which_addr2(Family, IfOpts).
+
sz(L) when is_list(L) ->
length(L);
@@ -605,19 +660,30 @@ p(Mod, Case) when is_atom(Mod) andalso is_atom(Case) ->
p(F, A) when is_list(F) andalso is_list(A) ->
io:format(user, F ++ "~n", A).
+%% This is just a bog standard printout, with a (formatted) timestamp
+%% prefix and a newline after.
+%% print1 - prints to both standard_io and user.
+%% print2 - prints to just standard_io.
+
+print_format(F, A) ->
+ FTS = snmp_test_lib:formated_timestamp(),
+ io_lib:format("[~s] " ++ F ++ "~n", [FTS | A]).
+
+print1(F, A) ->
+ S = print_format(F, A),
+ io:format("~s", [S]),
+ io:format(user, "~s", [S]).
+
+print2(F, A) ->
+ S = print_format(F, A),
+ io:format("~s", [S]).
+
+
print(Prefix, Module, Line, Format, Args) ->
io:format("*** [~s] ~s ~p ~p ~p:~p *** " ++ Format ++ "~n",
[formated_timestamp(),
Prefix, node(), self(), Module, Line|Args]).
formated_timestamp() ->
- format_timestamp(os:timestamp()).
-
-format_timestamp({_N1, _N2, N3} = Now) ->
- {Date, Time} = calendar:now_to_datetime(Now),
- {YYYY,MM,DD} = Date,
- {Hour,Min,Sec} = Time,
- FormatDate =
- io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w ~w",
- [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]),
- lists:flatten(FormatDate).
+ snmp_misc:formated_timestamp().
+
diff --git a/lib/snmp/test/snmp_test_lib.hrl b/lib/snmp/test/snmp_test_lib.hrl
index 7acebee1f1..335f3fff3c 100644
--- a/lib/snmp/test/snmp_test_lib.hrl
+++ b/lib/snmp/test/snmp_test_lib.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -127,24 +127,31 @@
-endif.
-ifdef(snmp_debug).
--define(DBG(F,A),?PRINT("DBG",F,A)).
+-define(DBG(F,A), ?PRINT("DBG", F, A)).
-else.
--define(DBG(F,A),ok).
+-define(DBG(F,A), ok).
-endif.
-ifdef(snmp_log).
--define(LOG(F,A),?PRINT("LOG",F,A)).
+-define(LOG(F,A), ?PRINT("LOG", F, A)).
-else.
--define(LOG(F,A),ok).
+-define(LOG(F,A), ok).
-endif.
-ifdef(snmp_error).
--define(ERR(F,A),?PRINT("ERR",F,A)).
+-define(ERR(F,A), ?PRINT("ERR", F, A)).
-else.
--define(ERR(F,A),ok).
+-define(ERR(F,A), ok).
-endif.
--define(INF(F,A),?PRINT("INF",F,A)).
+-define(INF(F,A), ?PRINT("INF", F, A)).
-define(PRINT(P,F,A),
- snmp_test_lib:print(P,?MODULE,?LINE,F,A)).
+ snmp_test_lib:print(P, ?MODULE, ?LINE, F, A)).
+
+-define(PRINT1(F, A), snmp_test_lib:print1(F, A)).
+-define(EPRINT1(F, A), ?PRINT1("<ERROR> " ++ F, A)).
+
+-define(PRINT2(F, A), snmp_test_lib:print2(F, A)).
+-define(EPRINT2(F, A), ?PRINT2("<ERROR> " ++ F, A)).
+
diff --git a/lib/snmp/test/snmp_test_mgr.erl b/lib/snmp/test/snmp_test_mgr.erl
index 36637d5cf4..73a4d56084 100644
--- a/lib/snmp/test/snmp_test_mgr.erl
+++ b/lib/snmp/test/snmp_test_mgr.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2015. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -20,8 +20,10 @@
-module(snmp_test_mgr).
+
%%----------------------------------------------------------------------
-%% This module implements a simple SNMP manager for Erlang.
+%% This module implements a simple SNMP manager for Erlang. Its used
+%% during by the agent test suite.
%%----------------------------------------------------------------------
%% c(snmp_test_mgr).
@@ -49,16 +51,17 @@
-include_lib("snmp/include/snmp_types.hrl").
-include_lib("snmp/include/STANDARD-MIB.hrl").
-
--record(state,{dbg = true,
- quiet,
- parent,
- timeout = 3500,
- print_traps = true,
- mini_mib,
- packet_server,
- last_sent_pdu,
- last_received_pdu}).
+-include("snmp_test_lib.hrl").
+
+-record(state, {dbg = true,
+ quiet,
+ parent,
+ timeout = 3500,
+ print_traps = true,
+ mini_mib,
+ packet_server,
+ last_sent_pdu,
+ last_received_pdu}).
-define(SERVER, ?MODULE).
-define(PACK_SERV, snmp_test_mgr_misc).
@@ -197,27 +200,28 @@ init({Options, CallerPid}) ->
put(debug, get_value(debug, Options, false)),
d("init -> (~p) extract options",[self()]),
PacksDbg = get_value(packet_server_debug, Options, false),
- io:format("[~w] ~p -> PacksDbg: ~p~n", [?MODULE, self(), PacksDbg]),
+ print("[~w] ~p -> PacksDbg: ~p~n", [?MODULE, self(), PacksDbg]),
RecBufSz = get_value(recbuf, Options, 1024),
- io:format("[~w] ~p -> RecBufSz: ~p~n", [?MODULE, self(), RecBufSz]),
+ print("[~w] ~p -> RecBufSz: ~p~n", [?MODULE, self(), RecBufSz]),
Mibs = get_value(mibs, Options, []),
- io:format("[~w] ~p -> Mibs: ~p~n", [?MODULE, self(), Mibs]),
+ print("[~w] ~p -> Mibs: ~p~n", [?MODULE, self(), Mibs]),
Udp = get_value(agent_udp, Options, 4000),
- io:format("[~w] ~p -> Udp: ~p~n", [?MODULE, self(), Udp]),
+ print("[~w] ~p -> Udp: ~p~n", [?MODULE, self(), Udp]),
User = get_value(user, Options, "initial"),
- io:format("[~w] ~p -> User: ~p~n", [?MODULE, self(), User]),
+ print("[~w] ~p -> User: ~p~n", [?MODULE, self(), User]),
EngineId = get_value(engine_id, Options, "agentEngine"),
- io:format("[~w] ~p -> EngineId: ~p~n", [?MODULE, self(), EngineId]),
+ print("[~w] ~p -> EngineId: ~p~n", [?MODULE, self(), EngineId]),
CtxEngineId = get_value(context_engine_id, Options, EngineId),
- io:format("[~w] ~p -> CtxEngineId: ~p~n", [?MODULE, self(), CtxEngineId]),
+ print("[~w] ~p -> CtxEngineId: ~p~n", [?MODULE, self(), CtxEngineId]),
TrapUdp = get_value(trap_udp, Options, 5000),
- io:format("[~w] ~p -> TrapUdp: ~p~n", [?MODULE, self(), TrapUdp]),
+ print("[~w] ~p -> TrapUdp: ~p~n", [?MODULE, self(), TrapUdp]),
Dir = get_value(dir, Options, "."),
- io:format("[~w] ~p -> Dir: ~p~n", [?MODULE, self(), Dir]),
+ print("[~w] ~p -> Dir: ~p~n", [?MODULE, self(), Dir]),
SecLevel = get_value(sec_level, Options, noAuthNoPriv),
- io:format("[~w] ~p -> SecLevel: ~p~n", [?MODULE, self(), SecLevel]),
+ print("[~w] ~p -> SecLevel: ~p~n", [?MODULE, self(), SecLevel]),
MiniMIB = snmp_mini_mib:create(Mibs),
- io:format("[~w] ~p -> MiniMIB: ~p~n", [?MODULE, self(), MiniMIB]),
+ d("[~w] ~p -> MiniMIB: "
+ "~n ~p", [?MODULE, self(), MiniMIB]),
Version = case lists:member(v2, Options) of
true -> 'version-2';
false ->
@@ -226,19 +230,19 @@ init({Options, CallerPid}) ->
false -> 'version-1'
end
end,
- io:format("[~w] ~p -> Version: ~p~n", [?MODULE, self(), Version]),
+ print("[~w] ~p -> Version: ~p~n", [?MODULE, self(), Version]),
Com = case Version of
'version-3' ->
get_value(context, Options, "");
_ ->
get_value(community, Options, "public")
end,
- io:format("[~w] ~p -> Com: ~p~n", [?MODULE, self(), Com]),
+ print("[~w] ~p -> Com: ~p~n", [?MODULE, self(), Com]),
VsnHdrD =
{Com, User, EngineId, CtxEngineId, mk_seclevel(SecLevel)},
- io:format("[~w] ~p -> VsnHdrD: ~p~n", [?MODULE, self(), VsnHdrD]),
+ print("[~w] ~p -> VsnHdrD: ~p~n", [?MODULE, self(), VsnHdrD]),
IpFamily = get_value(ipfamily, Options, inet),
- io:format("[~w] ~p -> IpFamily: ~p~n", [?MODULE, self(), IpFamily]),
+ print("[~w] ~p -> IpFamily: ~p~n", [?MODULE, self(), IpFamily]),
AgIp = case snmp_misc:assq(agent, Options) of
{value, Tuple4} when is_tuple(Tuple4) andalso
(size(Tuple4) =:= 4) ->
@@ -247,9 +251,9 @@ init({Options, CallerPid}) ->
{ok, Ip} = snmp_misc:ip(Host, IpFamily),
Ip
end,
- io:format("[~w] ~p -> AgIp: ~p~n", [?MODULE, self(), AgIp]),
+ print("[~w] ~p -> AgIp: ~p~n", [?MODULE, self(), AgIp]),
Quiet = lists:member(quiet, Options),
- io:format("[~w] ~p -> Quiet: ~p~n", [?MODULE, self(), Quiet]),
+ print("[~w] ~p -> Quiet: ~p~n", [?MODULE, self(), Quiet]),
PackServ =
start_packet_server(
Quiet, Options, CallerPid, AgIp, Udp, TrapUdp,
@@ -443,7 +447,8 @@ handle_cast({bulk, Args}, State) ->
{noreply, execute_request(bulk, Args, State)};
handle_cast({response, RespPdu}, State) ->
- d("handle_cast -> response request with ~p", [RespPdu]),
+ d("handle_cast -> response request with "
+ "~n ~p", [RespPdu]),
?PACK_SERV:send_pdu(RespPdu, State#state.packet_server),
{noreply, State};
@@ -1126,14 +1131,15 @@ sizeOf(L) when is_list(L) ->
sizeOf(B) when is_binary(B) ->
size(B).
-d(F,A) -> d(get(debug),F,A).
+d(F, A) -> d(get(debug), F, A).
-d(true,F,A) ->
- io:format("*** [~s] MGR_DBG *** " ++ F ++ "~n",
- [formated_timestamp()|A]);
+d(true, F, A) ->
+ print(F, A);
d(_,_F,_A) ->
ok.
+print(F, A) ->
+ ?PRINT2("MGR " ++ F, A).
formated_timestamp() ->
snmp_test_lib:formated_timestamp().
diff --git a/lib/snmp/test/snmp_test_mgr_misc.erl b/lib/snmp/test/snmp_test_mgr_misc.erl
index 274fb5be26..315e3ebd9e 100644
--- a/lib/snmp/test/snmp_test_mgr_misc.erl
+++ b/lib/snmp/test/snmp_test_mgr_misc.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -38,11 +38,14 @@
-define(SNMP_USE_V3, true).
-include_lib("snmp/include/snmp_types.hrl").
+-include_lib("snmp/src/misc/snmp_verbosity.hrl").
+-include("snmp_test_lib.hrl").
%%----------------------------------------------------------------------
%% The InHandler process will receive messages on the form {snmp_pdu, Pdu}.
%%----------------------------------------------------------------------
+
start_link_packet(
InHandler, AgentIp, UdpPort, TrapUdp, VsnHdr, Version, Dir, BufSz) ->
start_link_packet(
@@ -101,11 +104,11 @@ init_packet(
DbgOptions, IpFamily) ->
put(sname, mgr_misc),
init_debug(DbgOptions),
- {ok, UdpId} =
- gen_udp:open(TrapUdp, [{recbuf,BufSz}, {reuseaddr, true}, IpFamily]),
+ UdpOpts = [{recbuf,BufSz}, {reuseaddr, true}, IpFamily],
+ {ok, UdpId} = gen_udp:open(TrapUdp, UdpOpts),
put(msg_id, 1),
- proc_lib:init_ack(Parent, self()),
init_usm(Version, Dir),
+ proc_lib:init_ack(Parent, self()),
packet_loop(SnmpMgr, UdpId, AgentIp, UdpPort, VsnHdr, Version, []).
init_debug(Dbg) when is_atom(Dbg) ->
@@ -200,86 +203,24 @@ packet_loop(SnmpMgr, UdpId, AgentIp, UdpPort, VsnHdr, Version, MsgData) ->
handle_udp_packet(_V, undefined,
UdpId, Ip, UdpPort,
Bytes, SnmpMgr, AgentIp) ->
- M = (catch snmp_pdus:dec_message_only(Bytes)),
- MsgData3 =
- case M of
- Message when Message#message.version =:= 'version-3' ->
- d("handle_udp_packet -> version 3"),
- case catch handle_v3_msg(Bytes, Message) of
- {ok, NewData, MsgData2} ->
- Msg = Message#message{data = NewData},
- case SnmpMgr of
- {pdu, Pid} ->
- Pdu = get_pdu(Msg),
- d("packet_loop -> "
- "send pdu to manager (~w): ~p", [Pid, Pdu]),
- Pid ! {snmp_pdu, Pdu};
- {msg, Pid} ->
- d("packet_loop -> "
- "send msg to manager (~w): ~p", [Pid, Msg]),
- Pid ! {snmp_msg, Msg, Ip, UdpPort}
- end,
- MsgData2;
- {error, Reason, B} ->
- udp_send(UdpId, AgentIp, UdpPort, B),
- error("Decoding error. Auto-sending Report.\n"
- "Reason: ~w "
- "(UDPport: ~w, Ip: ~w)",
- [Reason, UdpPort, Ip]),
- [];
- {error, Reason} ->
- error("Decoding error. "
- "Bytes: ~w ~n Reason: ~w "
- "(UDPport: ~w, Ip: ~w)",
- [Bytes, Reason, UdpPort, Ip]),
- []
- end;
- Message when is_record(Message, message) ->
- %% v1 or v2c
- d("handle_udp_packet -> version v1 or v2c"),
- case catch snmp_pdus:dec_pdu(Message#message.data) of
- Pdu when is_record(Pdu, pdu) ->
- case SnmpMgr of
- {pdu, Pid} ->
- d("handle_udp_packet -> "
- "send pdu to manager (~w): ~p",
- [Pid, Pdu]),
- Pid ! {snmp_pdu, Pdu};
- {msg, Pid} ->
- d("handle_udp_packet -> "
- "send pdu-msg to manager (~w): ~p",
- [Pid, Pdu]),
- Msg = Message#message{data = Pdu},
- Pid ! {snmp_msg, Msg, Ip, UdpPort}
- end;
- Pdu when is_record(Pdu, trappdu) ->
- case SnmpMgr of
- {pdu, Pid} ->
- d("handle_udp_packet -> "
- "send trap to manager (~w): ~p",
- [Pid, Pdu]),
- Pid ! {snmp_pdu, Pdu};
- {msg, Pid} ->
- d("handle_udp_packet -> "
- "send trap-msg to manager (~w): ~p",
- [Pid, Pdu]),
- Msg = Message#message{data = Pdu},
- Pid ! {snmp_msg, Msg, Ip, UdpPort}
- end;
- Reason ->
- error("Decoding error. "
- "Bytes: ~w ~n Reason: ~w "
- "(UDPport: ~w, Ip: ~w)",
- [Bytes, Reason, UdpPort, Ip])
- end,
- [];
- Reason ->
- error("Decoding error. Bytes: ~w ~n Reason: ~w "
- "(UDPport: ~w, Ip: ~w)",
- [Bytes, Reason, UdpPort, Ip]),
- []
- end,
- MsgData3;
+ try snmp_pdus:dec_message_only(Bytes) of
+ Message when Message#message.version =:= 'version-3' ->
+ d("handle_udp_packet -> version 3"),
+ handle_v3_message(SnmpMgr, UdpId, Ip, UdpPort, AgentIp,
+ Bytes, Message);
+
+ Message when is_record(Message, message) ->
+ d("handle_udp_packet -> version 1 or 2"),
+ handle_v1_or_v2_message(SnmpMgr, UdpId, Ip, UdpPort, AgentIp,
+ Bytes, Message)
+
+ catch
+ Class:Error:_ ->
+ error("Decoding error (~w). Bytes: ~w ~n Error: ~w "
+ "(UDPport: ~w, Ip: ~w)",
+ [Class, Bytes, Error, UdpPort, Ip]),
+ []
+ end;
handle_udp_packet(V, {DiscoReqMsg, From}, _UdpId, _Ip, _UdpPort,
Bytes, _, _AgentIp) ->
DiscoRspMsg = (catch snmp_pdus:dec_message(Bytes)),
@@ -297,6 +238,88 @@ handle_udp_packet(V, {DiscoReqMsg, From}, _UdpId, _Ip, _UdpPort,
[]
end.
+handle_v3_message(Mgr, UdpId, Ip, UdpPort, AgentIp,
+ Bytes, Message) ->
+ try handle_v3_msg(Bytes, Message) of
+ {ok, NewData, MsgData} ->
+ Msg = Message#message{data = NewData},
+ case Mgr of
+ {pdu, Pid} ->
+ Pdu = get_pdu(Msg),
+ d("handle_v3_message -> send pdu to manager (~p): "
+ "~n ~p", [Pid, Pdu]),
+ Pid ! {snmp_pdu, Pdu};
+ {msg, Pid} ->
+ d("handle_v3_message -> send msg to manager (~p): "
+ "~n ~p", [Pid, Msg]),
+ Pid ! {snmp_msg, Msg, Ip, UdpPort}
+ end,
+ MsgData
+
+ catch
+ throw:{error, Reason, B}:_ ->
+ udp_send(UdpId, AgentIp, UdpPort, B),
+ error("Decoding (v3) error. Auto-sending Report.\n"
+ "~n Reason: ~w "
+ "(UDPport: ~w, Ip: ~w)",
+ [Reason, UdpPort, Ip]),
+ [];
+
+ throw:{error, Reason}:_ ->
+ error("Decoding (v3) error. "
+ "~n Bytes: ~w"
+ "~n Reason: ~w "
+ "(UDPport: ~w, Ip: ~w)",
+ [Bytes, Reason, UdpPort, Ip]),
+ [];
+
+ Class:Error:_ ->
+ error("Decoding (v3) error (~w). "
+ "~n Bytes: ~w"
+ "~n Error: ~w "
+ "(UDPport: ~w, Ip: ~w)",
+ [Class, Bytes, Error, UdpPort, Ip]),
+ []
+
+ end.
+
+handle_v1_or_v2_message(Mgr, _UdpId, Ip, UdpPort, _AgentIp,
+ Bytes, Message) ->
+ try snmp_pdus:dec_pdu(Message#message.data) of
+ Pdu when is_record(Pdu, pdu) ->
+ case Mgr of
+ {pdu, Pid} ->
+ d("handle_v1_or_v2_message -> send pdu to manager (~p): "
+ "~n ~p", [Pid, Pdu]),
+ Pid ! {snmp_pdu, Pdu};
+ {msg, Pid} ->
+ d("handle_v1_or_v2_message -> send msg to manager (~p): "
+ "~n ~p", [Pid, Pdu]),
+ Msg = Message#message{data = Pdu},
+ Pid ! {snmp_msg, Msg, Ip, UdpPort}
+ end;
+ Pdu when is_record(Pdu, trappdu) ->
+ case Mgr of
+ {pdu, Pid} ->
+ d("handle_v1_or_v2_message -> send trap-pdu to manager (~p): "
+ "~n ~p", [Pid, Pdu]),
+ Pid ! {snmp_pdu, Pdu};
+ {msg, Pid} ->
+ d("handle_v1_or_v2_message -> send trap-msg to manager (~p): "
+ "~n ~p", [Pid, Pdu]),
+ Msg = Message#message{data = Pdu},
+ Pid ! {snmp_msg, Msg, Ip, UdpPort}
+ end
+
+ catch
+ Class:Error:_ ->
+ error("Decoding (v1 or v2) error (~w): "
+ "~n Bytes: ~w"
+ "~n Error: ~w "
+ "(UDPport: ~w, Ip: ~w)",
+ [Class, Bytes, Error, UdpPort, Ip])
+ end.
+
%% This function assumes that the agent and the manager (thats us)
%% has the same version.
@@ -578,18 +601,100 @@ set_pdu(Msg, RePdu) ->
init_usm('version-3', Dir) ->
+ ?vlog("init_usm -> create (and init) fake \"agent\" table", []),
ets:new(snmp_agent_table, [set, public, named_table]),
ets:insert(snmp_agent_table, {agent_mib_storage, persistent}),
- snmpa_local_db:start_link(normal, Dir, [{verbosity,trace}]),
+ %% The local-db process may *still* be running (from a previous
+ %% test case), on the way down, but not yet dead.
+ %% Either way, before we start it, make sure its dead and *gone*!
+ %% How do we do that without getting hung up? Calling the stop
+ %% function, will not do since it uses Timeout=infinity.
+ ?vlog("init_usm -> ensure (old) fake local-db is dead", []),
+ ensure_local_db_dead(),
+ ?vlog("init_usm -> try start fake local-db", []),
+ case snmpa_local_db:start_link(normal, Dir,
+ [{sname, "MGR-LOCAL-DB"},
+ {verbosity, trace}]) of
+ {ok, Pid} ->
+ ?vlog("started: ~p"
+ "~n ~p", [Pid, process_info(Pid)]);
+ {error, {already_started, Pid}} ->
+ LDBInfo = process_info(Pid),
+ ?vlog("already started: ~p"
+ "~n ~p", [Pid, LDBInfo]),
+ ?FAIL({still_running, snmpa_local_db, LDBInfo});
+ {error, Reason} ->
+ ?FAIL({failed_starting, snmpa_local_db, Reason})
+ end,
NameDb = snmpa_agent:db(snmpEngineID),
+ ?vlog("init_usm -> try set manager engine-id", []),
R = snmp_generic:variable_set(NameDb, "mgrEngine"),
- io:format("~w:init_usm -> engine-id set result: ~p~n", [?MODULE,R]),
+ snmp_verbosity:print(info, info, "init_usm -> engine-id set result: ~p", [R]),
+ ?vlog("init_usm -> try set engine boots (framework-mib)", []),
snmp_framework_mib:set_engine_boots(1),
+ ?vlog("init_usm -> try set engine time (framework-mib)", []),
snmp_framework_mib:set_engine_time(1),
- snmp_user_based_sm_mib:reconfigure(Dir);
+ ?vlog("init_usm -> try usm (mib) reconfigure", []),
+ snmp_user_based_sm_mib:reconfigure(Dir),
+ ?vlog("init_usm -> done", []),
+ ok;
init_usm(_Vsn, _Dir) ->
ok.
+ensure_local_db_dead() ->
+ ensure_dead(whereis(snmpa_local_db), 2000).
+
+ensure_dead(Pid, Timeout) when is_pid(Pid) ->
+ MRef = erlang:monitor(process, Pid),
+ try
+ begin
+ ensure_dead_wait(Pid, MRef, Timeout),
+ ensure_dead_stop(Pid, MRef, Timeout),
+ ensure_dead_kill(Pid, MRef, Timeout),
+ exit(failed_stop_local_db)
+ end
+ catch
+ throw:ok ->
+ ok
+ end;
+ensure_dead(_, _) ->
+ ?vlog("ensure_dead -> already dead", []),
+ ok.
+
+ensure_dead_wait(Pid, MRef, Timeout) ->
+ receive
+ {'DOWN', MRef, process, Pid, _Info} ->
+ ?vlog("ensure_dead_wait -> died peacefully", []),
+ throw(ok)
+ after Timeout ->
+ ?vlog("ensure_dead_wait -> giving up", []),
+ ok
+ end.
+
+ensure_dead_stop(Pid, MRef, Timeout) ->
+ StopPid = spawn(fun() -> snmpa_local_db:stop() end),
+ receive
+ {'DOWN', MRef, process, Pid, _Info} ->
+ ?vlog("ensure_dead -> dead (stopped)", []),
+ throw(ok)
+ after Timeout ->
+ ?vlog("ensure_dead_stop -> giving up", []),
+ exit(StopPid, kill),
+ ok
+ end.
+
+ensure_dead_kill(Pid, MRef, Timeout) ->
+ exit(Pid, kill),
+ receive
+ {'DOWN', MRef, process, Pid, _Info} ->
+ ?vlog("ensure_dead -> dead (killed)", []),
+ throw(ok)
+ after Timeout ->
+ ?vlog("ensure_dead_kill -> giving up", []),
+ ok
+ end.
+
+
display_incomming_message(M) ->
display_message("Incomming",M).
@@ -782,13 +887,13 @@ sz(O) ->
{unknown_size, O}.
d(F) -> d(F, []).
-d(F,A) -> d(get(debug),F,A).
+d(F,A) -> d(get(debug), F, A).
-d(true,F,A) ->
- io:format("*** [~s] MGR_PS_DBG *** " ++ F ++ "~n",
- [formated_timestamp()|A]);
+d(true, F, A) ->
+ print(F, A);
d(_,_F,_A) ->
ok.
-formated_timestamp() ->
- snmp_test_lib:formated_timestamp().
+print(F, A) ->
+ ?PRINT2("MGR_PS " ++ F, A).
+
diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml
index 72af24d2fa..bad5815f40 100644
--- a/lib/ssh/doc/src/notes.xml
+++ b/lib/ssh/doc/src/notes.xml
@@ -350,6 +350,24 @@
</section>
</section>
+<section><title>Ssh 4.6.9.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ If a client was connected to an server on an already open
+ socket, the callback <c>fun(PeerName,FingerPrint)</c> in
+ the <c>accept_callback</c> option passed the local name
+ in the argument PeerName instead of the remote name.</p>
+ <p>
+ Own Id: OTP-15763</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 4.6.9.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/stdlib/doc/src/qlc.xml b/lib/stdlib/doc/src/qlc.xml
index fe60c2e9bb..34f7c5bab9 100644
--- a/lib/stdlib/doc/src/qlc.xml
+++ b/lib/stdlib/doc/src/qlc.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2004</year><year>2016</year>
+ <year>2004</year><year>2019</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -581,11 +581,13 @@ gb_iter(I0, N, EFun) ->
<input>{K} &lt;- ets:table(E1),</input>
<input>K == 2.71 orelse K == a]),</input>
<input>io:format("~s~n", [qlc:info(Q1)]).</input>
-ets:match_spec_run(lists:flatmap(fun(V) ->
- ets:lookup(20493, V)
- end,
- [a,2.71]),
- ets:match_spec_compile([{{'$1'},[],['$1']}]))</pre>
+ets:match_spec_run(
+ lists:flatmap(fun(V) ->
+ ets:lookup(#Ref&lt;0.3098908599.2283929601.256025>,
+ V)
+ end,
+ [a, 2.71]),
+ ets:match_spec_compile([{{'$1'}, [], ['$1']}]))</pre>
<p>In the example, operator <c>==/2</c> has been handled
exactly as <c>=:=/2</c> would have been handled. However,
@@ -607,9 +609,10 @@ ets:match_spec_run(lists:flatmap(fun(V) ->
<input>end,</input>
<input>Q2 = F2({2,2}),</input>
<input>io:format("~s~n", [qlc:info(Q2)]).</input>
-ets:table(53264,
+ets:table(#Ref&lt;0.3098908599.2283929601.256125>,
[{traverse,
- {select,[{{'$1','$2'},[{'==','$1',{const,{2,2}}}],['$2']}]}}])
+ {select,
+ [{{'$1', '$2'}, [{'==', '$1', {const, {2, 2}}}], ['$2']}]}}])
3> <input>lists:sort(qlc:e(Q2)).</input>
[a,b,c]</pre>
@@ -629,8 +632,9 @@ ets:table(53264,
<input>end,</input>
<input>Q3 = F3({2,2}),</input>
<input>io:format("~s~n", [qlc:info(Q3)]).</input>
-ets:match_spec_run(ets:lookup(86033, {2,2}),
- ets:match_spec_compile([{{'$1','$2'},[],['$2']}]))
+ets:match_spec_run(ets:lookup(#Ref&lt;0.3098908599.2283929601.256211>,
+ {2, 2}),
+ ets:match_spec_compile([{{'$1', '$2'}, [], ['$2']}]))
5> <input>qlc:e(Q3).</input>
[b]</pre>
@@ -892,21 +896,21 @@ begin
V1 =
qlc:q([
SQV ||
- SQV &lt;- [x,y]
+ SQV &lt;- [x, y]
],
- [{unique,true}]),
+ [{unique, true}]),
V2 =
qlc:q([
SQV ||
- SQV &lt;- [a,b]
+ SQV &lt;- [a, b]
],
- [{unique,true}]),
+ [{unique, true}]),
qlc:q([
{X,Y} ||
X &lt;- V1,
Y &lt;- V2
],
- [{unique,true}])
+ [{unique, true}])
end</pre>
<p>In the following example QLC <c>V2</c> has
been inserted to show the joined generators and the join
@@ -927,19 +931,21 @@ begin
V1 =
qlc:q([
P0 ||
- P0 = {W,Y} &lt;- ets:table(17)
+ P0 = {W, Y} &lt;-
+ ets:table(#Ref&lt;0.3098908599.2283929601.256549>)
]),
V2 =
qlc:q([
- [G1|G2] ||
+ [G1 | G2] ||
G2 &lt;- V1,
- G1 &lt;- ets:table(16),
+ G1 &lt;-
+ ets:table(#Ref&lt;0.3098908599.2283929601.256548>),
element(2, G1) =:= element(1, G2)
],
- [{join,lookup}]),
+ [{join, lookup}]),
qlc:q([
- {X,Z,W} ||
- [{X,Z}|{W,Y}] &lt;- V2
+ {X, Z, W} ||
+ [{X, Z} | {W, Y}] &lt;- V2
])
end</pre>
</desc>
@@ -1080,27 +1086,27 @@ begin
V1 =
qlc:q([
P0 ||
- P0 = {X,Z} &lt;-
- qlc:keysort(1, [{a,1},{b,4},{c,6}], [])
+ P0 = {X, Z} &lt;-
+ qlc:keysort(1, [{a, 1}, {b, 4}, {c, 6}], [])
]),
V2 =
qlc:q([
P0 ||
- P0 = {W,Y} &lt;-
- qlc:keysort(2, [{2,a},{3,b},{4,c}], [])
+ P0 = {W, Y} &lt;-
+ qlc:keysort(2, [{2, a}, {3, b}, {4, c}], [])
]),
V3 =
qlc:q([
- [G1|G2] ||
+ [G1 | G2] ||
G1 &lt;- V1,
G2 &lt;- V2,
element(1, G1) == element(2, G2)
],
- [{join,merge},{cache,list}]),
+ [{join, merge}, {cache, list}]),
qlc:q([
- {A,X,Z,W} ||
- A &lt;- [a,b,c],
- [{X,Z}|{W,Y}] &lt;- V3,
+ {A, X, Z, W} ||
+ A &lt;- [a, b, c],
+ [{X, Z} | {W, Y}] &lt;- V3,
X =:= Y
])
end</pre>
@@ -1141,14 +1147,21 @@ ets:match_spec_run(
gb_trees:lookup(K,
gb_trees:from_orddict([]))
of
- {value,V} ->
- [{K,V}];
+ {value, V} ->
+ [{K, V}];
none ->
[]
end
end,
- [{1,a},{1,b},{1,c},{2,a},{2,b},{2,c}]),
- ets:match_spec_compile([{{{'$1','$2'},'_'},[],['$1']}]))</pre>
+ [{1, a},
+ {1, b},
+ {1, c},
+ {2, a},
+ {2, b},
+ {2, c}]),
+ ets:match_spec_compile([{{{'$1', '$2'}, '_'},
+ [],
+ ['$1']}]))</pre>
<p>Options:</p>
<list type="bulleted">
<item>
diff --git a/lib/stdlib/src/erl_pp.erl b/lib/stdlib/src/erl_pp.erl
index 2630c60859..255c0ae81f 100644
--- a/lib/stdlib/src/erl_pp.erl
+++ b/lib/stdlib/src/erl_pp.erl
@@ -26,7 +26,7 @@
attribute/1,attribute/2,function/1,function/2,
guard/1,guard/2,exprs/1,exprs/2,exprs/3,expr/1,expr/2,expr/3,expr/4]).
--import(lists, [append/1,foldr/3,mapfoldl/3,reverse/1,reverse/2]).
+-import(lists, [append/1,foldr/3,map/2,mapfoldl/3,reverse/1,reverse/2]).
-import(io_lib, [write/1,format/2]).
-import(erl_parse, [inop_prec/1,preop_prec/1,func_prec/0,max_prec/0,
type_inop_prec/1, type_preop_prec/1]).
@@ -382,7 +382,12 @@ binary_type(I1, I2) ->
P = max_prec(),
E1 = [[leaf("_:"),lexpr(I1, P, options(none))] || B],
E2 = [[leaf("_:_*"),lexpr(I2, P, options(none))] || U],
- {seq,'<<','>>',[$,],E1++E2}.
+ case E1++E2 of
+ [] ->
+ leaf("<<>>");
+ Es ->
+ {seq,'<<','>>',[$,],Es}
+ end.
map_type(Fs) ->
{first,[$#],map_pair_types(Fs)}.
@@ -408,6 +413,8 @@ typed(B, Type) ->
{_L,_P,R} = type_inop_prec('::'),
{list,[{cstep,[B,' ::'],ltype(Type, R)}]}.
+tuple_type([], _) ->
+ leaf("{}");
tuple_type(Ts, F) ->
{seq,${,$},[$,],ltypes(Ts, F, 0)}.
@@ -476,7 +483,7 @@ pname(A) when is_atom(A) ->
write(A).
falist([]) ->
- [leaf("[]")];
+ ['[]'];
falist(Falist) ->
L = [begin
{Name,Arity} = Fa,
@@ -584,22 +591,22 @@ lexpr({map, _, Map, Fs}, Prec, Opts) ->
El = {first,[Rl,$#],map_fields(Fs, Opts)},
maybe_paren(P, Prec, El);
lexpr({block,_,Es}, _, Opts) ->
- {list,[{step,'begin',body(Es, Opts)},'end']};
+ {list,[{step,'begin',body(Es, Opts)},{reserved,'end'}]};
lexpr({'if',_,Cs}, _, Opts) ->
- {list,[{step,'if',if_clauses(Cs, Opts)},'end']};
+ {list,[{step,'if',if_clauses(Cs, Opts)},{reserved,'end'}]};
lexpr({'case',_,Expr,Cs}, _, Opts) ->
- {list,[{step,{list,[{step,'case',lexpr(Expr, Opts)},'of']},
+ {list,[{step,{list,[{step,'case',lexpr(Expr, Opts)},{reserved,'of'}]},
cr_clauses(Cs, Opts)},
- 'end']};
+ {reserved,'end'}]};
lexpr({'cond',_,Cs}, _, Opts) ->
- {list,[{step,leaf("cond"),cond_clauses(Cs, Opts)},'end']};
+ {list,[{step,leaf("cond"),cond_clauses(Cs, Opts)},{reserved,'end'}]};
lexpr({'receive',_,Cs}, _, Opts) ->
- {list,[{step,'receive',cr_clauses(Cs, Opts)},'end']};
+ {list,[{step,'receive',cr_clauses(Cs, Opts)},{reserved,'end'}]};
lexpr({'receive',_,Cs,To,ToOpt}, _, Opts) ->
Al = {list,[{step,[lexpr(To, Opts),' ->'],body(ToOpt, Opts)}]},
{list,[{step,'receive',cr_clauses(Cs, Opts)},
{step,'after',Al},
- 'end']};
+ {reserved,'end'}]};
lexpr({'fun',_,{function,F,A}}, _Prec, _Opts) ->
[leaf("fun "),{atom,F},leaf(format("/~w", [A]))];
lexpr({'fun',L,{function,_,_}=Func,Extra}, Prec, Opts) ->
@@ -618,15 +625,17 @@ lexpr({'fun',_,{function,M,F,A}}, _Prec, Opts) ->
ArityItem = lexpr(A, Opts),
["fun ",NameItem,$:,CallItem,$/,ArityItem];
lexpr({'fun',_,{clauses,Cs}}, _Prec, Opts) ->
- {list,[{first,'fun',fun_clauses(Cs, Opts, unnamed)},'end']};
+ {list,[{first,'fun',fun_clauses(Cs, Opts, unnamed)},{reserved,'end'}]};
lexpr({named_fun,_,Name,Cs}, _Prec, Opts) ->
- {list,[{first,['fun', " "],fun_clauses(Cs, Opts, {named, Name})},'end']};
+ {list,[{first,['fun', " "],fun_clauses(Cs, Opts, {named, Name})},
+ {reserved,'end'}]};
lexpr({'fun',_,{clauses,Cs},Extra}, _Prec, Opts) ->
{force_nl,fun_info(Extra),
- {list,[{first,'fun',fun_clauses(Cs, Opts, unnamed)},'end']}};
+ {list,[{first,'fun',fun_clauses(Cs, Opts, unnamed)},{reserved,'end'}]}};
lexpr({named_fun,_,Name,Cs,Extra}, _Prec, Opts) ->
{force_nl,fun_info(Extra),
- {list,[{first,['fun', " "],fun_clauses(Cs, Opts, {named, Name})},'end']}};
+ {list,[{first,['fun', " "],fun_clauses(Cs, Opts, {named, Name})},
+ {reserved,'end'}]}};
lexpr({call,_,{remote,_,{atom,_,M},{atom,_,F}=N}=Name,Args}, Prec, Opts) ->
case erl_internal:bif(M, F, length(Args)) of
true ->
@@ -641,7 +650,7 @@ lexpr({'try',_,Es,Scs,Ccs,As}, _, Opts) ->
Scs =:= [] ->
{step,'try',body(Es, Opts)};
true ->
- {step,{list,[{step,'try',body(Es, Opts)},'of']},
+ {step,{list,[{step,'try',body(Es, Opts)},{reserved,'of'}]},
cr_clauses(Scs, Opts)}
end,
if
@@ -656,7 +665,7 @@ lexpr({'try',_,Es,Scs,Ccs,As}, _, Opts) ->
true ->
{step,'after',body(As, Opts)}
end,
- 'end']};
+ {reserved,'end'}]};
lexpr({'catch',_,Expr}, Prec, Opts) ->
{P,R} = preop_prec('catch'),
El = {list,[{step,'catch',lexpr(Expr, R, Opts)}]},
@@ -669,7 +678,7 @@ lexpr({match,_,Lhs,Rhs}, Prec, Opts) ->
maybe_paren(P, Prec, El);
lexpr({op,_,Op,Arg}, Prec, Opts) ->
{P,R} = preop_prec(Op),
- Ol = leaf(format("~s ", [Op])),
+ Ol = {reserved, leaf(format("~s ", [Op]))},
El = [Ol,lexpr(Arg, R, Opts)],
maybe_paren(P, Prec, El);
lexpr({op,_,Op,Larg,Rarg}, Prec, Opts) when Op =:= 'orelse';
@@ -677,14 +686,14 @@ lexpr({op,_,Op,Larg,Rarg}, Prec, Opts) when Op =:= 'orelse';
%% Breaks lines since R12B.
{L,P,R} = inop_prec(Op),
Ll = lexpr(Larg, L, Opts),
- Ol = leaf(format("~s", [Op])),
+ Ol = {reserved, leaf(format("~s", [Op]))},
Lr = lexpr(Rarg, R, Opts),
El = {prefer_nl,[[]],[Ll,Ol,Lr]},
maybe_paren(P, Prec, El);
lexpr({op,_,Op,Larg,Rarg}, Prec, Opts) ->
{L,P,R} = inop_prec(Op),
Ll = lexpr(Larg, L, Opts),
- Ol = leaf(format("~s", [Op])),
+ Ol = {reserved, leaf(format("~s", [Op]))},
Lr = lexpr(Rarg, R, Opts),
El = {list,[Ll,Ol,Lr]},
maybe_paren(P, Prec, El);
@@ -830,6 +839,12 @@ cr_clause({clause,_,[T],G,B}, Opts) ->
try_clauses(Cs, Opts) ->
clauses(fun try_clause/2, Opts, Cs).
+try_clause({clause,_,[{tuple,_,[{atom,_,throw},V,S]}],G,B}, Opts) ->
+ El = lexpr(V, 0, Opts),
+ Sl = stack_backtrace(S, [El], Opts),
+ Gl = guard_when(Sl, G, Opts),
+ Bl = body(B, Opts),
+ {step,Gl,Bl};
try_clause({clause,_,[{tuple,_,[C,V,S]}],G,B}, Opts) ->
Cs = lexpr(C, 0, Opts),
El = lexpr(V, 0, Opts),
@@ -898,16 +913,18 @@ lc_qual(Q, Opts) ->
lexpr(Q, 0, Opts).
proper_list(Es, Opts) ->
- {seq,$[,$],$,,lexprs(Es, Opts)}.
+ {seq,$[,$],[$,],lexprs(Es, Opts)}.
improper_list(Es, Opts) ->
- {seq,$[,$],{$,,$|},lexprs(Es, Opts)}.
+ {seq,$[,$],[{$,,' |'}],lexprs(Es, Opts)}.
tuple(L, Opts) ->
tuple(L, fun lexpr/2, Opts).
+tuple([], _F, _Opts) ->
+ leaf("{}");
tuple(Es, F, Opts) ->
- {seq,${,$},$,,lexprs(Es, F, Opts)}.
+ {seq,${,$},[$,],lexprs(Es, F, Opts)}.
args(As, Opts) ->
{seq,$(,$),[$,],lexprs(As, Opts)}.
@@ -1000,8 +1017,10 @@ f({seq,Before,After,Sep,LItems}, I0, ST, WT, PP) ->
end,
{BCharsL++Chars,Size};
no ->
- {BCharsL++insert_newlines(CharsSizeL, I, ST),
- nsz(lists:last(Sizes), I0)}
+ CharsList = handle_step(CharsSizeL, I, ST),
+ {LChars, LSize} =
+ maybe_newlines(CharsList, LItems, I, NSepChars, ST),
+ {[BCharsL,LChars],nsz(LSize, I0)}
end;
f({force_nl,_ExtraInfoItem,Item}, I, ST, WT, PP) when I < 0 ->
%% Extra info is a comment; cannot have that on the same line
@@ -1017,7 +1036,8 @@ f({prefer_nl,Sep,LItems}, I0, ST, WT, PP) ->
Sizes =:= [] ->
{[], 0};
true ->
- {insert_newlines(CharsSize2L, I0, ST),nsz(lists:last(Sizes), I0)}
+ {insert_newlines(CharsSize2L, I0, ST),
+ nsz(lists:last(Sizes), I0)}
end;
f({value,V}, I, ST, WT, PP) ->
f(write_a_value(V, PP), I, ST, WT, PP);
@@ -1029,13 +1049,15 @@ f({char,C}, I, ST, WT, PP) ->
f(write_a_char(C, PP), I, ST, WT, PP);
f({string,S}, I, ST, WT, PP) ->
f(write_a_string(S, I, PP), I, ST, WT, PP);
+f({reserved,R}, I, ST, WT, PP) ->
+ f(R, I, ST, WT, PP);
f({hook,HookExpr,Precedence,Func,Options}, I, _ST, _WT, _PP) ->
Chars = Func(HookExpr, I, Precedence, Options),
{Chars,indentation(Chars, I)};
f({ehook,HookExpr,Precedence,{Mod,Func,Eas}=ModFuncEas}, I, _ST, _WT, _PP) ->
Chars = apply(Mod, Func, [HookExpr,I,Precedence,ModFuncEas|Eas]),
{Chars,indentation(Chars, I)};
-f(WordName, _I, _ST, WT, _PP) -> % when is_atom(WordName)
+f(WordName, _I, _ST, WT, _PP) when is_atom(WordName) ->
word(WordName, WT).
-define(IND, 4).
@@ -1057,12 +1079,18 @@ fl(CItems, Sep0, I0, After, ST, WT, PP) ->
true ->
[CharSize1,f([Item2,S], incr(I0, ?IND), ST, WT, PP)]
end;
+ ({reserved,Word}, S) ->
+ [f([Word,S], I0, ST, WT, PP),{[],0}];
(Item, S) ->
[f([Item,S], I0, ST, WT, PP),{[],0}]
end,
- {Sep,LastSep} = case Sep0 of {_,_} -> Sep0; _ -> {Sep0,Sep0} end,
+ {Sep,LastSep} = sep(Sep0),
fl1(CItems, F, Sep, LastSep, After).
+sep([{S,LS}]) -> {[S],[LS]};
+sep({_,_}=Sep) -> Sep;
+sep(S) -> {S, S}.
+
fl1([CItem], F, _Sep, _LastSep, After) ->
[F(CItem,After)];
fl1([CItem1,CItem2], F, _Sep, LastSep, After) ->
@@ -1088,20 +1116,64 @@ unz1(CharSizes) ->
nonzero(CharSizes) ->
lists:filter(fun({_,Sz}) -> Sz =/= 0 end, CharSizes).
-insert_newlines(CharsSizesL, I, ST) when I >= 0 ->
- insert_nl(foldr(fun([{_C1,0},{_C2,0}], A) ->
- A;
- ([{C1,_Sz1},{_C2,0}], A) ->
- [C1|A];
- ([{C1,_Sz1},{C2,Sz2}], A) when Sz2 > 0 ->
- [insert_nl([C1,C2], I+?IND, ST)|A]
- end, [], CharsSizesL), I, ST).
+maybe_newlines([{Chars,Size}], [], _I, _NSepChars, _ST) ->
+ {Chars,Size};
+maybe_newlines(CharsSizeList, Items, I, NSepChars, ST) when I >= 0 ->
+ maybe_sep(CharsSizeList, Items, I, NSepChars, nl_indent(I, ST)).
+
+maybe_sep([{Chars1,Size1}|CharsSizeL], [Item|Items], I0, NSepChars, Sep) ->
+ I1 = case classify_item(Item) of
+ atomic ->
+ I0 + Size1;
+ _ ->
+ ?MAXLINE+1
+ end,
+ maybe_sep1(CharsSizeL, Items, I0, I1, Sep, NSepChars, Size1, [Chars1]).
+
+maybe_sep1([{Chars,Size}|CharsSizeL], [Item|Items],
+ I0, I, Sep, NSepChars, Sz0, A) ->
+ case classify_item(Item) of
+ atomic when is_integer(Size) ->
+ Size1 = Size + 1,
+ I1 = I + Size1,
+ if
+ I1 =< ?MAXLINE ->
+ A1 = if
+ NSepChars > 0 -> [Chars,$\s|A];
+ true -> [Chars|A]
+ end,
+ maybe_sep1(CharsSizeL, Items, I0, I1, Sep, NSepChars,
+ Sz0 + Size1, A1);
+ true ->
+ A1 = [Chars,Sep|A],
+ maybe_sep1(CharsSizeL, Items, I0, I0 + Size, Sep,
+ NSepChars, Size1, A1)
+ end;
+ _ ->
+ A1 = [Chars,Sep|A],
+ maybe_sep1(CharsSizeL, Items, I0, ?MAXLINE+1, Sep, NSepChars,
+ 0, A1)
+ end;
+maybe_sep1(_CharsSizeL, _Items, _Io, _I, _Sep, _NSepChars, Sz, A) ->
+ {lists:reverse(A), Sz}.
+insert_newlines(CharsSizesL, I, ST) when I >= 0 ->
+ {CharsL, _} = unz1(handle_step(CharsSizesL, I, ST)),
+ insert_nl(CharsL, I, ST).
+
+handle_step(CharsSizesL, I, ST) ->
+ map(fun([{_C1,0},{_C2,0}]) ->
+ {[], 0};
+ ([{C1,Sz1},{_C2,0}]) ->
+ {C1, Sz1};
+ ([{C1,Sz1},{C2,Sz2}]) when Sz2 > 0 ->
+ {insert_nl([C1,C2], I+?IND, ST),line_size([Sz1,Sz2])}
+ end, CharsSizesL).
insert_nl(CharsL, I, ST) ->
insert_sep(CharsL, nl_indent(I, ST)).
-insert_sep([Chars1 | CharsL], Sep) ->
+insert_sep([Chars1|CharsL], Sep) ->
[Chars1 | [[Sep,Chars] || Chars <- CharsL]].
nl_indent(0, _T) ->
@@ -1109,6 +1181,12 @@ nl_indent(0, _T) ->
nl_indent(I, T) when I > 0 ->
[$\n|spaces(I, T)].
+classify_item({atom, _}) -> atomic;
+classify_item({singleton_atom_type, _}) -> atomic;
+classify_item(Atom) when is_atom(Atom) -> atomic;
+classify_item({leaf, _, _}) -> atomic;
+classify_item(_) -> complex.
+
same_line(I0, SizeL, NSepChars) ->
try
Size = lists:sum(SizeL) + NSepChars,
diff --git a/lib/stdlib/test/erl_pp_SUITE.erl b/lib/stdlib/test/erl_pp_SUITE.erl
index c79e29eb11..3eb1670806 100644
--- a/lib/stdlib/test/erl_pp_SUITE.erl
+++ b/lib/stdlib/test/erl_pp_SUITE.erl
@@ -52,7 +52,7 @@
otp_6321/1, otp_6911/1, otp_6914/1, otp_8150/1, otp_8238/1,
otp_8473/1, otp_8522/1, otp_8567/1, otp_8664/1, otp_9147/1,
otp_10302/1, otp_10820/1, otp_11100/1, otp_11861/1, pr_1014/1,
- otp_13662/1, otp_14285/1, otp_15592/1, otp_15751/1]).
+ otp_13662/1, otp_14285/1, otp_15592/1, otp_15751/1, otp_15755/1]).
%% Internal export.
-export([ehook/6]).
@@ -82,7 +82,7 @@ groups() ->
[otp_6321, otp_6911, otp_6914, otp_8150, otp_8238,
otp_8473, otp_8522, otp_8567, otp_8664, otp_9147,
otp_10302, otp_10820, otp_11100, otp_11861, pr_1014, otp_13662,
- otp_14285, otp_15592, otp_15751]}].
+ otp_14285, otp_15592, otp_15751, otp_15755]}].
init_per_suite(Config) ->
Config.
@@ -474,10 +474,10 @@ cond1(Config) when is_list(Config) ->
[{tuple,5,[{atom,5,x},{atom,5,y}]}]}]},
CChars = flat_expr1(C),
"cond\n"
- " {foo,bar} ->\n"
- " [a,b];\n"
+ " {foo, bar} ->\n"
+ " [a, b];\n"
" true ->\n"
- " {x,y}\n"
+ " {x, y}\n"
"end" = CChars,
ok.
@@ -712,7 +712,7 @@ otp_6321(Config) when is_list(Config) ->
Str = "S = hopp, {hej, S}. ",
{done, {ok, Tokens, _EndLine}, ""} = erl_scan:tokens("", Str, _L=1),
{ok, Exprs} = erl_parse:parse_exprs(Tokens),
- "S = hopp, {hej,S}" = lists:flatten(erl_pp:exprs(Exprs)),
+ "S = hopp, {hej, S}" = lists:flatten(erl_pp:exprs(Exprs)),
ok.
%% OTP_6911. More newlines.
@@ -1112,7 +1112,7 @@ otp_11861(Config) when is_list(Config) ->
A3 = erl_anno:new(3),
"-optional_callbacks([bar/0]).\n" =
pf({attribute,A3,optional_callbacks,[{bar,0}]}),
- "-optional_callbacks([{bar,1,bad}]).\n" =
+ "-optional_callbacks([{bar, 1, bad}]).\n" =
pf({attribute,A3,optional_callbacks,[{bar,1,bad}]}),
ok.
@@ -1221,6 +1221,46 @@ otp_15751(_Config) ->
end">>),
ok.
+otp_15755(_Config) ->
+ "[{a, b}, c, {d, e} | t]" =
+ flat_parse_and_pp_expr("[{a, b}, c, {d, e} | t]", 0, []),
+ "[{a, b},\n c, d,\n {d, e},\n 1, 2.0,\n {d, e},\n <<>>, {},\n {d, e},\n"
+ " [], [],\n {d, e} |\n t]" =
+ flat_parse_and_pp_expr("[{a,b},c,d,{d,e},1,2.0,{d,e},<<>>,"
+ "{},{d,e},[],[],{d,e}|t]", 0, []),
+ "[{a, b},\n c, d,\n {d, e},\n 1, 2.0,\n {d, e},\n <<>>, {},\n {d, e},\n"
+ " [], [], d, e | t]" =
+ flat_parse_and_pp_expr("[{a,b},c,d,{d,e},1,2.0,{d,e},<<>>,"
+ "{},{d,e},[],[],d,e|t]", 0, []),
+
+ "-type t() ::
+ a | b | c | a | b | a | b | a | b | a | b | a | b | a | b |
+ a | b | a | b | a | b.\n" =
+ lists:flatten(parse_and_pp_forms(
+ "-type t() :: a | b | c| a | b | a | b | a | b | a |"
+ " b | a | b | a | b | a | b | a | b |a | b.", [])),
+
+ "-type t() ::
+ {dict, 0, 16, 16, 8, 80, 48,
+ {[], [], [], [], [], [], [], [], [], [], [], [], [], [], [],
+ []},
+ {{[], [], [], [], [], [], [], [], [], [], [], [], [], [], []}}}.\n" =
+ lists:flatten(parse_and_pp_forms(
+ "-type t() :: {dict,0,16,16,8,80,48,"
+ "{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},"
+ "{{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]}}}.", [])),
+
+ "-type t() ::
+ {{a},
+ 0, 16,
+ {16},
+ 8, 80, 48, a, b, e, f, 'sf s sdf', [], {},
+ {[]}}.\n" =
+ lists:flatten(parse_and_pp_forms(
+ "-type t() :: {{a}, 0, 16, {16}, 8, 80, 48, a, b, e, f,"
+ " 'sf s sdf', [], {}, {[]}}.", [])),
+ ok.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
compile(Config, Tests) ->
@@ -1352,6 +1392,9 @@ pp_expr(List, Options) when is_list(List) ->
not_ok
end.
+flat_parse_and_pp_expr(String, Indent, Options) ->
+ lists:flatten(parse_and_pp_expr(String, Indent, Options)).
+
parse_and_pp_expr(String, Indent, Options) ->
StringDot = lists:flatten(String) ++ ".",
erl_pp:expr(parse_expr(StringDot), Indent, Options).
diff --git a/lib/stdlib/test/qlc_SUITE.erl b/lib/stdlib/test/qlc_SUITE.erl
index 2354a08f78..8a43f15d2c 100644
--- a/lib/stdlib/test/qlc_SUITE.erl
+++ b/lib/stdlib/test/qlc_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -2436,7 +2436,7 @@ info(Config) when is_list(Config) ->
<<"{'EXIT', {badarg, _}} =
(catch qlc:info([X || {X} <- []], {n_elements, 0})),
L = lists:seq(1, 1000),
- \"[1,2,3,4,5,6,7,8,9,10|'...']\" = qlc:info(L, {n_elements, 10}),
+ \"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10 | '...']\" = qlc:info(L, {n_elements, 10}),
{cons,A1,{integer,A2,1},{atom,A3,'...'}} =
qlc:info(L, [{n_elements, 1},{format,abstract_code}]),
1 = erl_anno:line(A1),
@@ -2447,8 +2447,8 @@ info(Config) when is_list(Config) ->
{atom,_,'...'}}}},
{call,_,_,_}]} =
qlc:info(Q, [{n_elements, 3},{format,abstract_code}]),
- \"ets:match_spec_run([a,b,c,d,e,f],\n\"
- \" ets:match_spec_compile([{'$1',[true],\"
+ \"ets:match_spec_run([a, b, c, d, e, f],\n\"
+ \" ets:match_spec_compile([{'$1', [true], \"
\"[{{'$1'}}]}]))\" =
qlc:info(Q, [{n_elements, infinity}])">>,
@@ -6547,7 +6547,7 @@ otp_7114(Config) when is_list(Config) ->
otp_7232(Config) when is_list(Config) ->
Ts = [<<"L = [fun math:sqrt/1, list_to_pid(\"<0.4.1>\"),
erlang:make_ref()],
- \"[fun math:sqrt/1,<0.4.1>,#Ref<\" ++ _ = qlc:info(L),
+ \"[fun math:sqrt/1, <0.4.1>, #Ref<\" ++ _ = qlc:info(L),
{call,_,
{remote,_,{atom,_,qlc},{atom,_,sort}},
[{cons,_,
@@ -6563,7 +6563,7 @@ otp_7232(Config) when is_list(Config) ->
\"qlc:sort([55296,56296],[{order,fun'-function/0-fun-2-'/2}])\" =
format_info(Q, true),
AC = qlc:info(Q, {format, abstract_code}),
- \"qlc:sort([55296,56296], [{order,fun '-function/0-fun-2-'/2}])\" =
+ \"qlc:sort([55296, 56296], [{order, fun '-function/0-fun-2-'/2}])\" =
binary_to_list(iolist_to_binary(erl_pp:expr(AC)))">>,
%% OTP-7234. erl_parse:abstract() handles bit strings
@@ -7088,21 +7088,21 @@ manpage(Config) when is_list(Config) ->
\" V1 =\n\"
\" qlc:q([ \n\"
\" SQV ||\n\"
- \" SQV <- [x,y]\n\"
+ \" SQV <- [x, y]\n\"
\" ],\n\"
- \" [{unique,true}]),\n\"
+ \" [{unique, true}]),\n\"
\" V2 =\n\"
\" qlc:q([ \n\"
\" SQV ||\n\"
- \" SQV <- [a,b]\n\"
+ \" SQV <- [a, b]\n\"
\" ],\n\"
- \" [{unique,true}]),\n\"
+ \" [{unique, true}]),\n\"
\" qlc:q([ \n\"
- \" {X,Y} ||\n\"
+ \" {X, Y} ||\n\"
\" X <- V1,\n\"
\" Y <- V2\n\"
\" ],\n\"
- \" [{unique,true}])\n\"
+ \" [{unique, true}])\n\"
\"end\",
true = B =:= qlc:info(QH, unique_all)">>,
@@ -7118,19 +7118,19 @@ manpage(Config) when is_list(Config) ->
\" V1 =\n\"
\" qlc:q([ \n\"
\" P0 ||\n\"
- \" P0 = {W,Y} <- ets:table(_)\n\"
+ \" P0 = {W, Y} <- ets:table(_)\n\"
\" ]),\n\"
\" V2 =\n\"
\" qlc:q([ \n\"
- \" [G1|G2] ||\n\"
+ \" [G1 | G2] ||\n\"
\" G2 <- V1,\n\"
\" G1 <- ets:table(_),\n\"
\" element(2, G1) =:= element(1, G2)\n\"
\" ],\n\"
- \" [{join,lookup}]),\n\"
+ \" [{join, lookup}]),\n\"
\" qlc:q([ \n\"
- \" {X,Z,W} ||\n\"
- \" [{X,Z}|{W,Y}] <- V2\n\"
+ \" {X, Z, W} ||\n\"
+ \" [{X, Z} | {W, Y}] <- V2\n\"
\" ])\n\"
\"end\",
Info1 =
@@ -7155,25 +7155,28 @@ manpage(Config) when is_list(Config) ->
\" V1 =\n\"
\" qlc:q([ \n\"
\" P0 ||\n\"
- \" P0 = {X,Z} <- qlc:keysort(1, [{a,1},{b,4},{c,6}], [])\n\"
+ \" P0 = {X, Z} <-\n\"
+ \" qlc:keysort(1, [{a, 1}, {b, 4}, {c, 6}], [])\n\"
\" ]),\n\"
\" V2 =\n\"
\" qlc:q([ \n\"
\" P0 ||\n\"
- \" P0 = {W,Y} <- qlc:keysort(2, [{2,a},{3,b},{4,c}], [])\n\"
+ \" P0 = {W, Y} <-\n\"
+ \" qlc:keysort(2, [{2, a}, {3, b}, {4, c}], [])\n\"
+
\" ]),\n\"
\" V3 =\n\"
\" qlc:q([ \n\"
- \" [G1|G2] ||\n\"
+ \" [G1 | G2] ||\n\"
\" G1 <- V1,\n\"
\" G2 <- V2,\n\"
\" element(1, G1) == element(2, G2)\n\"
\" ],\n\"
- \" [{join,merge},{cache,list}]),\n\"
+ \" [{join, merge}, {cache, list}]),\n\"
\" qlc:q([ \n\"
- \" {A,X,Z,W} ||\n\"
- \" A <- [a,b,c],\n\"
- \" [{X,Z}|{W,Y}] <- V3,\n\"
+ \" {A, X, Z, W} ||\n\"
+ \" A <- [a, b, c],\n\"
+ \" [{X, Z} | {W, Y}] <- V3,\n\"
\" X =:= Y\n\"
\" ])\n\"
\"end\",
@@ -7215,14 +7218,21 @@ manpage(Config) when is_list(Config) ->
gb_trees:lookup(K,
gb_trees:from_orddict([]))
of
- {value,V} ->
- [{K,V}];
+ {value, V} ->
+ [{K, V}];
none ->
[]
end
end,
- [{1,a},{1,b},{1,c},{2,a},{2,b},{2,c}]),
- ets:match_spec_compile([{{{'$1','$2'},'_'},[],['$1']}]))\",
+ [{1, a},
+ {1, b},
+ {1, c},
+ {2, a},
+ {2, b},
+ {2, c}]),
+ ets:match_spec_compile([{{{'$1', '$2'}, '_'},
+ [],
+ ['$1']}]))\",
L = qlc:info(QH)">>
],
run(Config, Ts),
diff --git a/lib/stdlib/test/shell_SUITE.erl b/lib/stdlib/test/shell_SUITE.erl
index 22136d687c..cdb6031b07 100644
--- a/lib/stdlib/test/shell_SUITE.erl
+++ b/lib/stdlib/test/shell_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -2591,7 +2591,7 @@ otp_7184(Config) when is_list(Config) ->
otp_7232(Config) when is_list(Config) ->
Info = <<"qlc:info(qlc:sort(qlc:q([X || X <- [55296,56296]]), "
"{order, fun(A,B)-> A>B end})).">>,
- "qlc:sort([55296,56296],\n"
+ "qlc:sort([55296, 56296],\n"
" [{order,\n"
" fun(A, B) ->\n"
" A > B\n"
@@ -2752,7 +2752,7 @@ otp_10302(Config) when is_list(Config) ->
h().">>,
"ok.\n\"\x{400}\"\nA = \"\x{400}\".\nok.\n"
- "1: io:setopts([{encoding,utf8}])\n-> ok.\n"
+ "1: io:setopts([{encoding, utf8}])\n-> ok.\n"
"2: A = [1024] = \"\x{400}\"\n-> \"\x{400}\"\n"
"3: b()\n-> ok.\nok.\n" = t({Node,Test4}),
diff --git a/lib/syntax_tools/doc/demo.erl b/lib/syntax_tools/doc/demo.erl
new file mode 120000
index 0000000000..fe40fb65ec
--- /dev/null
+++ b/lib/syntax_tools/doc/demo.erl
@@ -0,0 +1 @@
+../examples/demo.erl \ No newline at end of file
diff --git a/lib/syntax_tools/doc/overview.edoc b/lib/syntax_tools/doc/overview.edoc
index 3111633a99..7be96f1a55 100644
--- a/lib/syntax_tools/doc/overview.edoc
+++ b/lib/syntax_tools/doc/overview.edoc
@@ -26,7 +26,7 @@ library module {@link prettypr}: this is a powerful and flexible generic
pretty printing library, which is also distributed separately.
For a short demonstration of parsing and pretty-printing, simply
-compile the included module <a href="../examples/demo.erl">`demo.erl'</a>,
+compile the included module <a href="demo.erl">`demo.erl'</a>,
and execute `demo:run()' from the Erlang shell. It will compile the
remaining modules and give you further instructions.
diff --git a/lib/syntax_tools/doc/src/Makefile b/lib/syntax_tools/doc/src/Makefile
index d953287bad..b799c76177 100644
--- a/lib/syntax_tools/doc/src/Makefile
+++ b/lib/syntax_tools/doc/src/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2006-2018. All Rights Reserved.
+# Copyright Ericsson AB 2006-2019. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -82,6 +82,9 @@ HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
+EXAMPLES_DIR = ../../examples
+EXAMPLES = $(EXAMPLES_DIR)/demo.erl
+
SPECS_FILES = $(XML_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml)
TOP_SPECS_FILE = specs.xml
@@ -146,5 +149,7 @@ release_docs_spec: docs
$(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
$(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
$(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
+ $(INSTALL_DIR) "$(RELSYSDIR)/examples"
+ $(INSTALL_DATA) $(EXAMPLES) "$(RELSYSDIR)/doc/html"
release_spec:
diff --git a/lib/tools/doc/src/notes.xml b/lib/tools/doc/src/notes.xml
index 28f8346a19..2191ebe2df 100644
--- a/lib/tools/doc/src/notes.xml
+++ b/lib/tools/doc/src/notes.xml
@@ -128,6 +128,21 @@
</section>
+<section><title>Tools 2.11.2.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Minor fixes for <c>make clean</c>.</p>
+ <p>
+ Own Id: OTP-15657</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Tools 2.11.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -1905,4 +1920,3 @@
</section>
</section>
</chapter>
-