aboutsummaryrefslogtreecommitdiffstats
path: root/lib/common_test/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/common_test/src')
-rw-r--r--lib/common_test/src/Makefile4
-rw-r--r--lib/common_test/src/common_test.app.src5
-rw-r--r--lib/common_test/src/ct.erl969
-rw-r--r--lib/common_test/src/ct_config.erl19
-rw-r--r--lib/common_test/src/ct_config_plain.erl4
-rw-r--r--lib/common_test/src/ct_conn_log_h.erl22
-rw-r--r--lib/common_test/src/ct_cover.erl47
-rw-r--r--lib/common_test/src/ct_default_gl.erl3
-rw-r--r--lib/common_test/src/ct_event.erl11
-rw-r--r--lib/common_test/src/ct_framework.erl439
-rw-r--r--lib/common_test/src/ct_ftp.erl167
-rw-r--r--lib/common_test/src/ct_gen_conn.erl148
-rw-r--r--lib/common_test/src/ct_groups.erl97
-rw-r--r--lib/common_test/src/ct_hooks.erl112
-rw-r--r--lib/common_test/src/ct_hooks_lock.erl15
-rw-r--r--lib/common_test/src/ct_logs.erl463
-rw-r--r--lib/common_test/src/ct_make.erl54
-rw-r--r--lib/common_test/src/ct_master.erl144
-rw-r--r--lib/common_test/src/ct_master_event.erl13
-rw-r--r--lib/common_test/src/ct_master_logs.erl37
-rw-r--r--lib/common_test/src/ct_master_status.erl10
-rw-r--r--lib/common_test/src/ct_netconfc.erl934
-rw-r--r--lib/common_test/src/ct_netconfc.hrl6
-rw-r--r--lib/common_test/src/ct_property_test.erl67
-rw-r--r--lib/common_test/src/ct_release_test.erl199
-rw-r--r--lib/common_test/src/ct_repeat.erl20
-rw-r--r--lib/common_test/src/ct_rpc.erl112
-rw-r--r--lib/common_test/src/ct_run.erl212
-rw-r--r--lib/common_test/src/ct_slave.erl197
-rw-r--r--lib/common_test/src/ct_snmp.erl289
-rw-r--r--lib/common_test/src/ct_ssh.erl723
-rw-r--r--lib/common_test/src/ct_telnet.erl581
-rw-r--r--lib/common_test/src/ct_telnet_client.erl11
-rw-r--r--lib/common_test/src/ct_testspec.erl21
-rw-r--r--lib/common_test/src/ct_util.erl216
-rw-r--r--lib/common_test/src/ct_util.hrl1
-rw-r--r--lib/common_test/src/ct_webtool.erl37
-rw-r--r--lib/common_test/src/ct_webtool_sup.erl3
-rw-r--r--lib/common_test/src/cth_conn_log.erl25
-rw-r--r--lib/common_test/src/cth_log_redirect.erl266
-rw-r--r--lib/common_test/src/cth_surefire.erl25
-rw-r--r--lib/common_test/src/test_server.erl419
-rw-r--r--lib/common_test/src/test_server_ctrl.erl421
-rw-r--r--lib/common_test/src/test_server_gl.erl13
-rw-r--r--lib/common_test/src/test_server_io.erl17
-rw-r--r--lib/common_test/src/test_server_node.erl157
-rw-r--r--lib/common_test/src/test_server_sup.erl37
-rw-r--r--lib/common_test/src/unix_telnet.erl75
-rw-r--r--lib/common_test/src/vts.erl10
49 files changed, 2927 insertions, 4950 deletions
diff --git a/lib/common_test/src/Makefile b/lib/common_test/src/Makefile
index 9d751996ad..80eaed70bd 100644
--- a/lib/common_test/src/Makefile
+++ b/lib/common_test/src/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2003-2016. All Rights Reserved.
+# Copyright Ericsson AB 2003-2018. 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.
@@ -165,3 +165,5 @@ release_tests_spec: opt
release_docs_spec: docs
+# Include dependencies -- list below added by Kostis Sagonas
+$(EBIN)/cth_log_redirect.beam: ../../kernel/include/logger.hrl ../../kernel/src/logger_internal.hrl
diff --git a/lib/common_test/src/common_test.app.src b/lib/common_test/src/common_test.app.src
index 430a4fa2fb..efebea896c 100644
--- a/lib/common_test/src/common_test.app.src
+++ b/lib/common_test/src/common_test.app.src
@@ -1,7 +1,7 @@
% This is an -*- erlang -*- file.
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2009-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2009-2018. 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.
@@ -85,6 +85,7 @@
"crypto-3.6",
"debugger-4.1",
"erts-7.0",
+ "ftp-1.0.0",
"inets-6.0",
"kernel-4.0",
"observer-2.1",
@@ -92,7 +93,7 @@
"sasl-2.4.2",
"snmp-5.1.2",
"ssh-4.0",
- "stdlib-2.5",
+ "stdlib-3.5",
"syntax_tools-1.7",
"tools-2.8",
"xmerl-1.3.8"
diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl
index 43abb91819..bfa7b25862 100644
--- a/lib/common_test/src/ct.erl
+++ b/lib/common_test/src/ct.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2003-2018. 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.
@@ -18,38 +18,6 @@
%% %CopyrightEnd%
%%
-%%% @doc Main user interface for the Common Test framework.
-%%%
-%%% <p> This module implements the command line interface for running
-%%% tests and some basic functions for common test case issues
-%%% such as configuration and logging. </p>
-%%%
-%%% <p><strong>Test Suite Support Macros</strong></p>
-%%%
-%%% <p>The <c>config</c> macro is defined in <c>ct.hrl</c>. This
-%%% macro should be used to retrieve information from the
-%%% <c>Config</c> variable sent to all test cases. It is used with two
-%%% arguments, where the first is the name of the configuration
-%%% variable you wish to retrieve, and the second is the <c>Config</c>
-%%% variable supplied to the test case.</p>
-%%%
-%%% <p>Possible configuration variables include:</p>
-%%% <ul>
-%%% <li><c>data_dir</c> - Data file directory.</li>
-%%% <li><c>priv_dir</c> - Scratch file directory.</li>
-%%% <li>Whatever added by <c>init_per_suite/1</c> or
-%%% <c>init_per_testcase/2</c> in the test suite.</li>
-%%% </ul>
-
-%%% @type var_name() = atom(). A variable name which is specified when
-%%% <c>ct:require/2</c> is called,
-%%% e.g. <c>ct:require(mynodename,{node,[telnet]})</c>
-%%%
-%%% @type target_name() = var_name(). The name of a target.
-%%%
-%%% @type handle() = ct_gen_conn:handle() | term(). The identity of a
-%%% specific connection.
-
-module(ct).
-include("ct.hrl").
@@ -87,210 +55,68 @@
decrypt_config_file/2, decrypt_config_file/3]).
-export([get_target_name/1]).
+-export([get_progname/0]).
-export([parse_table/1, listenv/1]).
-%%%-----------------------------------------------------------------
-%%% @spec install(Opts) -> ok | {error,Reason}
-%%% Opts = [Opt]
-%%% Opt = {config,ConfigFiles} | {event_handler,Modules} |
-%%% {decrypt,KeyOrFile}
-%%% ConfigFiles = [ConfigFile]
-%%% ConfigFile = string()
-%%% Modules = [atom()]
-%%% KeyOrFile = {key,Key} | {file,KeyFile}
-%%% Key = string()
-%%% KeyFile = string()
-%%% @doc Install config files and event handlers.
-%%%
-%%% <p>Run this function once before first test.</p>
-%%%
-%%% <p>Example:<br/>
-%%% <c>install([{config,["config_node.ctc","config_user.ctc"]}])</c>.</p>
-%%%
-%%% <p>Note that this function is automatically run by the
-%%% <c>ct_run</c> program.</p>
+-export([remaining_test_procs/0]).
+
+%%----------------------------------------------------------------------
+%% Exported types
+%%----------------------------------------------------------------------
+%% For ct_gen_conn
+-export_type([config_key/0,
+ target_name/0,
+ key_or_name/0]).
+
+%% For cth_conn_log
+-export_type([conn_log_options/0,
+ conn_log_type/0,
+ conn_log_mod/0]).
+
+%%------------------------------------------------------------------
+%% Type declarations
+%% ------------------------------------------------------------------
+-type config_key() :: atom(). % Config key which exists in a config file
+-type target_name() :: atom().% Name associated to a config_key() though 'require'
+-type key_or_name() :: config_key() | target_name().
+
+%% Types used when logging connections with the 'cth_conn_log' hook
+-type conn_log_options() :: [conn_log_option()].
+-type conn_log_option() :: {log_type,conn_log_type()} |
+ {hosts,[key_or_name()]}.
+-type conn_log_type() :: raw | pretty | html | silent.
+-type conn_log_mod() :: ct_netconfc | ct_telnet.
+%%----------------------------------------------------------------------
+
+
install(Opts) ->
ct_run:install(Opts).
-%%%-----------------------------------------------------------------
-%%% @spec run(TestDir,Suite,Cases) -> Result
-%%% TestDir = string()
-%%% Suite = atom()
-%%% Cases = atom() | [atom()]
-%%% Result = [TestResult] | {error,Reason}
-%%%
-%%% @doc Run the given test case(s).
-%%%
-%%% <p>Requires that <c>ct:install/1</c> has been run first.</p>
-%%%
-%%% <p>Suites (*_SUITE.erl) files must be stored in
-%%% <c>TestDir</c> or <c>TestDir/test</c>. All suites
-%%% will be compiled when test is run.</p>
run(TestDir,Suite,Cases) ->
ct_run:run(TestDir,Suite,Cases).
-%%%-----------------------------------------------------------------
-%%% @spec run(TestDir,Suite) -> Result
-%%%
-%%% @doc Run all test cases in the given suite.
-%%% @see run/3.
run(TestDir,Suite) ->
ct_run:run(TestDir,Suite).
-%%%-----------------------------------------------------------------
-%%% @spec run(TestDirs) -> Result
-%%% TestDirs = TestDir | [TestDir]
-%%%
-%%% @doc Run all test cases in all suites in the given directories.
-%%% @see run/3.
run(TestDirs) ->
ct_run:run(TestDirs).
-%%%-----------------------------------------------------------------
-%%% @spec run_test(Opts) -> Result
-%%% Opts = [OptTuples]
-%%% OptTuples = {dir,TestDirs} | {suite,Suites} | {group,Groups} |
-%%% {testcase,Cases} | {spec,TestSpecs} | {join_specs,Bool} |
-%%% {label,Label} | {config,CfgFiles} | {userconfig, UserConfig} |
-%%% {allow_user_terms,Bool} | {logdir,LogDir} |
-%%% {silent_connections,Conns} | {stylesheet,CSSFile} |
-%%% {cover,CoverSpecFile} | {cover_stop,Bool} | {step,StepOpts} |
-%%% {event_handler,EventHandlers} | {include,InclDirs} |
-%%% {auto_compile,Bool} | {abort_if_missing_suites,Bool} |
-%%% {create_priv_dir,CreatePrivDir} |
-%%% {multiply_timetraps,M} | {scale_timetraps,Bool} |
-%%% {repeat,N} | {duration,DurTime} | {until,StopTime} |
-%%% {force_stop,ForceStop} | {decrypt,DecryptKeyOrFile} |
-%%% {refresh_logs,LogDir} | {logopts,LogOpts} |
-%%% {verbosity,VLevels} | {basic_html,Bool} |
-%%% {esc_chars,Bool} | {ct_hooks, CTHs} |
-%%% {enable_builtin_hooks,Bool} | {release_shell,Bool}
-%%% TestDirs = [string()] | string()
-%%% Suites = [string()] | [atom()] | string() | atom()
-%%% Cases = [atom()] | atom()
-%%% Groups = GroupNameOrPath | [GroupNameOrPath]
-%%% GroupNameOrPath = [atom()] | atom() | all
-%%% TestSpecs = [string()] | string()
-%%% Label = string() | atom()
-%%% CfgFiles = [string()] | string()
-%%% UserConfig = [{CallbackMod,CfgStrings}] | {CallbackMod,CfgStrings}
-%%% CallbackMod = atom()
-%%% CfgStrings = [string()] | string()
-%%% LogDir = string()
-%%% Conns = all | [atom()]
-%%% CSSFile = string()
-%%% CoverSpecFile = string()
-%%% StepOpts = [StepOpt] | []
-%%% StepOpt = config | keep_inactive
-%%% EventHandlers = EH | [EH]
-%%% EH = atom() | {atom(),InitArgs} | {[atom()],InitArgs}
-%%% InitArgs = [term()]
-%%% InclDirs = [string()] | string()
-%%% CreatePrivDir = auto_per_run | auto_per_tc | manual_per_tc
-%%% M = integer()
-%%% N = integer()
-%%% DurTime = string(HHMMSS)
-%%% StopTime = string(YYMoMoDDHHMMSS) | string(HHMMSS)
-%%% ForceStop = skip_rest | Bool
-%%% DecryptKeyOrFile = {key,DecryptKey} | {file,DecryptFile}
-%%% DecryptKey = string()
-%%% DecryptFile = string()
-%%% LogOpts = [LogOpt]
-%%% LogOpt = no_nl | no_src
-%%% VLevels = VLevel | [{Category,VLevel}]
-%%% VLevel = integer()
-%%% Category = atom()
-%%% CTHs = [CTHModule | {CTHModule, CTHInitArgs}]
-%%% CTHModule = atom()
-%%% CTHInitArgs = term()
-%%% Result = {Ok,Failed,{UserSkipped,AutoSkipped}} | TestRunnerPid | {error,Reason}
-%%% Ok = integer()
-%%% Failed = integer()
-%%% UserSkipped = integer()
-%%% AutoSkipped = integer()
-%%% TestRunnerPid = pid()
-%%% Reason = term()
-%%% @doc <p>Run tests as specified by the combination of options in <c>Opts</c>.
-%%% The options are the same as those used with the
-%%% <seealso marker="ct_run#ct_run"><c>ct_run</c></seealso> program.
-%%% Note that here a <c>TestDir</c> can be used to point out the path to
-%%% a <c>Suite</c>. Note also that the option <c>testcase</c>
-%%% corresponds to the <c>-case</c> option in the <c>ct_run</c>
-%%% program. Configuration files specified in <c>Opts</c> will be
-%%% installed automatically at startup.</p>
-%%% <p><c>TestRunnerPid</c> is returned if <c>release_shell == true</c>
-%%% (see <c>break/1</c> for details).</p>
-%%% <p><c>Reason</c> indicates what type of error has been encountered.</p>
run_test(Opts) ->
ct_run:run_test(Opts).
-%%%-----------------------------------------------------------------
-%%% @spec run_testspec(TestSpec) -> Result
-%%% TestSpec = [term()]
-%%% Result = {Ok,Failed,{UserSkipped,AutoSkipped}} | {error,Reason}
-%%% Ok = integer()
-%%% Failed = integer()
-%%% UserSkipped = integer()
-%%% AutoSkipped = integer()
-%%% Reason = term()
-%%% @doc Run test specified by <c>TestSpec</c>. The terms are
-%%% the same as those used in test specification files.
-%%% <p><c>Reason</c> indicates what type of error has been encountered.</p>
run_testspec(TestSpec) ->
ct_run:run_testspec(TestSpec).
-%%%-----------------------------------------------------------------
-%%% @spec step(TestDir,Suite,Case) -> Result
-%%% Case = atom()
-%%%
-%%% @doc Step through a test case with the debugger.
-%%% @see run/3
step(TestDir,Suite,Case) ->
ct_run:step(TestDir,Suite,Case).
-%%%-----------------------------------------------------------------
-%%% @spec step(TestDir,Suite,Case,Opts) -> Result
-%%% Case = atom()
-%%% Opts = [Opt] | []
-%%% Opt = config | keep_inactive
-%%%
-%%% @doc Step through a test case with the debugger. If the
-%%% <c>config</c> option has been given, breakpoints will
-%%% be set also on the configuration functions in <c>Suite</c>.
-%%% @see run/3
step(TestDir,Suite,Case,Opts) ->
ct_run:step(TestDir,Suite,Case,Opts).
-%%%-----------------------------------------------------------------
-%%% @spec start_interactive() -> ok
-%%%
-%%% @doc Start CT in interactive mode.
-%%%
-%%% <p>From this mode all test case support functions can be executed
-%%% directly from the erlang shell. The interactive mode can also be
-%%% started from the OS command line with <c>ct_run -shell
-%%% [-config File...]</c>.</p>
-%%%
-%%% <p>If any functions using "required config data" (e.g. telnet or
-%%% ftp functions) are to be called from the erlang shell, config data
-%%% must first be required with <c>ct:require/2</c>.</p>
-%%%
-%%% <p>Example:<br/>
-%%% <c>&gt; ct:require(unix_telnet, unix).</c><br/>
-%%% <c>ok</c><br/>
-%%% <c>&gt; ct_telnet:open(unix_telnet).</c><br/>
-%%% <c>{ok,&lt;0.105.0&gt;}</c><br/>
-%%% <c>&gt; ct_telnet:cmd(unix_telnet, "ls .").</c><br/>
-%%% <c>{ok,["ls","file1 ...",...]}</c></p>
start_interactive() ->
_ = ct_util:start(interactive),
ok.
-%%%-----------------------------------------------------------------
-%%% @spec stop_interactive() -> ok
-%%%
-%%% @doc Exit the interactive mode.
-%%% @see start_interactive/0
stop_interactive() ->
ct_util:stop(normal),
ok.
@@ -299,181 +125,24 @@ stop_interactive() ->
%%% MISC INTERFACE
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%%-----------------------------------------------------------------
-%%% @spec require(Required) -> ok | {error,Reason}
-%%% Required = Key | {Key,SubKeys} | {Key,SubKey,SubKeys}
-%%% Key = atom()
-%%% SubKeys = SubKey | [SubKey]
-%%% SubKey = atom()
-%%%
-%%% @doc Check if the required configuration is available. It is possible
-%%% to specify arbitrarily deep tuples as <c>Required</c>. Note that it is
-%%% only the last element of the tuple which can be a list of <c>SubKey</c>s.
-%%%
-%%% <p>Example 1: require the variable <c>myvar</c>:</p>
-%%% <pre>ok = ct:require(myvar).</pre>
-%%%
-%%% <p>In this case the config file must at least contain:</p>
-%%% <pre>{myvar,Value}.</pre>
-%%%
-%%% <p>Example 2: require the key <c>myvar</c> with
-%%% subkeys <c>sub1</c> and <c>sub2</c>:</p>
-%%% <pre>ok = ct:require({myvar,[sub1,sub2]}).</pre>
-%%%
-%%% <p>In this case the config file must at least contain:</p>
-%%% <pre>{myvar,[{sub1,Value},{sub2,Value}]}.</pre>
-%%%
-%%% <p>Example 3: require the key <c>myvar</c> with
-%%% subkey <c>sub1</c> with <c>subsub1</c>:</p>
-%%% <pre>ok = ct:require({myvar,sub1,sub2}).</pre>
-%%%
-%%% <p>In this case the config file must at least contain:</p>
-%%% <pre>{myvar,[{sub1,[{sub2,Value}]}]}.</pre>
-%%%
-%%% @see require/2
-%%% @see get_config/1
-%%% @see get_config/2
-%%% @see get_config/3
require(Required) ->
ct_config:require(Required).
-%%%-----------------------------------------------------------------
-%%% @spec require(Name,Required) -> ok | {error,Reason}
-%%% Name = atom()
-%%% Required = Key | {Key,SubKey} | {Key,SubKey,SubKey}
-%%% SubKey = Key
-%%% Key = atom()
-%%%
-%%% @doc Check if the required configuration is available, and give it
-%%% a name. The semantics for <c>Required</c> is the same as in
-%%% <c>required/1</c> except that it is not possible to specify a list
-%%% of <c>SubKey</c>s.
-%%%
-%%% <p>If the requested data is available, the sub entry will be
-%%% associated with <c>Name</c> so that the value of the element
-%%% can be read with <c>get_config/1,2</c> provided
-%%% <c>Name</c> instead of the whole <c>Required</c> term.</p>
-%%%
-%%% <p>Example: Require one node with a telnet connection and an
-%%% ftp connection. Name the node <c>a</c>:
-%%% <pre>ok = ct:require(a,{machine,node}).</pre>
-%%% All references to this node may then use the node name.
-%%% E.g. you can fetch a file over ftp like this:</p>
-%%% <pre>ok = ct:ftp_get(a,RemoteFile,LocalFile).</pre>
-%%%
-%%% <p>For this to work, the config file must at least contain:</p>
-%%% <pre>{machine,[{node,[{telnet,IpAddr},{ftp,IpAddr}]}]}.</pre>
-%%%
-%%% <note><p>The behaviour of this function changed radically in common_test
-%%% 1.6.2. In order too keep some backwards compatability it is still possible
-%%% to do: <br/><c>ct:require(a,{node,[telnet,ftp]}).</c><br/>
-%%% This will associate the name <c>a</c> with the top level <c>node</c> entry.
-%%% For this to work, the config file must at least contain:<br/>
-%%% <c>{node,[{telnet,IpAddr},{ftp,IpAddr}]}.</c></p></note>
-%%%
-%%% @see require/1
-%%% @see get_config/1
-%%% @see get_config/2
-%%% @see get_config/3
require(Name,Required) ->
ct_config:require(Name,Required).
-%%%-----------------------------------------------------------------
-%%% @spec get_config(Required) -> Value
-%%% @equiv get_config(Required,undefined,[])
get_config(Required) ->
ct_config:get_config(Required,undefined,[]).
-%%%-----------------------------------------------------------------
-%%% @spec get_config(Required,Default) -> Value
-%%% @equiv get_config(Required,Default,[])
get_config(Required,Default) ->
ct_config:get_config(Required,Default,[]).
-%%%-----------------------------------------------------------------
-%%% @spec get_config(Required,Default,Opts) -> ValueOrElement
-%%% Required = KeyOrName | {KeyOrName,SubKey} | {KeyOrName,SubKey,SubKey}
-%%% KeyOrName = atom()
-%%% SubKey = atom()
-%%% Default = term()
-%%% Opts = [Opt] | []
-%%% Opt = element | all
-%%% ValueOrElement = term() | Default
-%%%
-%%% @doc Read config data values.
-%%%
-%%% <p>This function returns the matching value(s) or config element(s),
-%%% given a config variable key or its associated name
-%%% (if one has been specified with <c>require/2</c> or a
-%%% require statement).</p>
-%%%
-%%% <p>Example, given the following config file:</p>
-%%% <pre>
-%%% {unix,[{telnet,IpAddr},
-%%% {user,[{username,Username},
-%%% {password,Password}]}]}.</pre>
-%%% <p><c>ct:get_config(unix,Default) ->
-%%% [{telnet,IpAddr},
-%%% {user, [{username,Username},
-%%% {password,Password}]}]</c><br/>
-%%% <c>ct:get_config({unix,telnet},Default) -> IpAddr</c><br/>
-%%% <c>ct:get_config({unix,user,username},Default) -> Username</c><br/>
-%%% <c>ct:get_config({unix,ftp},Default) -> Default</c><br/>
-%%% <c>ct:get_config(unknownkey,Default) -> Default</c></p>
-%%%
-%%% <p>If a config variable key has been associated with a name (by
-%%% means of <c>require/2</c> or a require statement), the name
-%%% may be used instead of the key to read the value:</p>
-%%%
-%%% <p><c>ct:require(myuser,{unix,user}) -> ok.</c><br/>
-%%% <c>ct:get_config(myuser,Default) ->
-%%% [{username,Username},
-%%% {password,Password}]</c></p>
-%%%
-%%% <p>If a config variable is defined in multiple files and you want to
-%%% access all possible values, use the <c>all</c> option. The
-%%% values will be returned in a list and the order of the elements
-%%% corresponds to the order that the config files were specified at
-%%% startup.</p>
-%%%
-%%% <p>If you want config elements (key-value tuples) returned as result
-%%% instead of values, use the <c>element</c> option.
-%%% The returned elements will then be on the form <c>{Required,Value}</c></p>
-%%%
-%%% @see get_config/1
-%%% @see get_config/2
-%%% @see require/1
-%%% @see require/2
get_config(Required,Default,Opts) ->
ct_config:get_config(Required,Default,Opts).
-%%%-----------------------------------------------------------------
-%%% @spec reload_config(Required) -> ValueOrElement
-%%% Required = KeyOrName | {KeyOrName,SubKey} | {KeyOrName,SubKey,SubKey}
-%%% KeyOrName = atom()
-%%% SubKey = atom()
-%%% ValueOrElement = term()
-%%%
-%%% @doc Reload config file which contains specified configuration key.
-%%%
-%%% <p>This function performs updating of the configuration data from which the
-%%% given configuration variable was read, and returns the (possibly) new
-%%% value of this variable.</p>
-%%% <p>Note that if some variables were present in the configuration but are not loaded
-%%% using this function, they will be removed from the configuration table together
-%%% with their aliases.</p>
-%%%
reload_config(Required)->
ct_config:reload_config(Required).
-%%%-----------------------------------------------------------------
-%%% @spec get_testspec_terms() -> TestSpecTerms | undefined
-%%% TestSpecTerms = [{Tag,Value}]
-%%% Value = [term()]
-%%%
-%%% @doc Get a list of all test specification terms used to
-%%% configure and run this test.
-%%%
get_testspec_terms() ->
case ct_util:get_testdata(testspec) of
undefined ->
@@ -482,25 +151,6 @@ get_testspec_terms() ->
ct_testspec:testspec_rec2list(CurrSpecRec)
end.
-%%%-----------------------------------------------------------------
-%%% @spec get_testspec_terms(Tags) -> TestSpecTerms | undefined
-%%% Tags = [Tag] | Tag
-%%% Tag = atom()
-%%% TestSpecTerms = [{Tag,Value}] | {Tag,Value}
-%%% Value = [{Node,term()}] | [term()]
-%%% Node = atom()
-%%%
-%%% @doc Read one or more terms from the test specification used
-%%% to configure and run this test. Tag is any valid test specification
-%%% tag, such as e.g. <c>label</c>, <c>config</c>, <c>logdir</c>.
-%%% User specific terms are also available to read if the
-%%% <c>allow_user_terms</c> option has been set. Note that all value tuples
-%%% returned, except user terms, will have the node name as first element.
-%%% Note also that in order to read test terms, use <c>Tag = tests</c>
-%%% (rather than <c>suites</c>, <c>groups</c> or <c>cases</c>). Value is
-%%% then the list of *all* tests on the form:
-%%% <c>[{Node,Dir,[{TestSpec,GroupsAndCases1},...]},...], where
-%%% GroupsAndCases = [{Group,[Case]}] | [Case]</c>.
get_testspec_terms(Tags) ->
case ct_util:get_testdata(testspec) of
undefined ->
@@ -509,24 +159,9 @@ get_testspec_terms(Tags) ->
ct_testspec:testspec_rec2list(Tags, CurrSpecRec)
end.
-
-%%%-----------------------------------------------------------------
-%%% @spec escape_chars(IoList1) -> IoList2 | {error,Reason}
-%%% IoList1 = iolist()
-%%% IoList2 = iolist()
-%%%
-%%% @doc Escape special characters to be printed in html log
-%%%
escape_chars(IoList) ->
ct_logs:escape_chars(IoList).
-%%%-----------------------------------------------------------------
-%%% @spec escape_chars(Format, Args) -> IoList | {error,Reason}
-%%% Format = string()
-%%% Args = list()
-%%%
-%%% @doc Escape special characters to be printed in html log
-%%%
escape_chars(Format, Args) ->
try io_lib:format(Format, Args) of
IoList ->
@@ -536,17 +171,9 @@ escape_chars(Format, Args) ->
{error,Reason}
end.
-%%%-----------------------------------------------------------------
-%%% @spec log(Format) -> ok
-%%% @equiv log(default,50,Format,[],[])
log(Format) ->
log(default,?STD_IMPORTANCE,Format,[],[]).
-%%%-----------------------------------------------------------------
-%%% @spec log(X1,X2) -> ok
-%%% X1 = Category | Importance | Format
-%%% X2 = Format | Args
-%%% @equiv log(Category,Importance,Format,Args,[])
log(X1,X2) ->
{Category,Importance,Format,Args} =
if is_atom(X1) -> {X1,?STD_IMPORTANCE,X2,[]};
@@ -555,12 +182,6 @@ log(X1,X2) ->
end,
log(Category,Importance,Format,Args,[]).
-%%%-----------------------------------------------------------------
-%%% @spec log(X1,X2,X3) -> ok
-%%% X1 = Category | Importance | Format
-%%% X2 = Importance | Format | Args
-%%% X3 = Format | Args | Opts
-%%% @equiv log(Category,Importance,Format,Args,Opts)
log(X1,X2,X3) ->
{Category,Importance,Format,Args,Opts} =
if is_atom(X1), is_integer(X2) -> {X1,X2,X3,[],[]};
@@ -570,13 +191,6 @@ log(X1,X2,X3) ->
end,
log(Category,Importance,Format,Args,Opts).
-%%%-----------------------------------------------------------------
-%%% @spec log(X1,X2,X3,X4) -> ok
-%%% X1 = Category | Importance
-%%% X2 = Importance | Format
-%%% X3 = Format | Args
-%%% X4 = Args | Opts
-%%% @equiv log(Category,Importance,Format,Args,Opts)
log(X1,X2,X3,X4) ->
{Category,Importance,Format,Args,Opts} =
if is_atom(X1), is_integer(X2) -> {X1,X2,X3,X4,[]};
@@ -585,40 +199,12 @@ log(X1,X2,X3,X4) ->
end,
log(Category,Importance,Format,Args,Opts).
-%%%-----------------------------------------------------------------
-%%% @spec log(Category,Importance,Format,Args,Opts) -> ok
-%%% Category = atom()
-%%% Importance = integer()
-%%% Format = string()
-%%% Args = list()
-%%% Opts = [Opt]
-%%% Opt = {heading,string()} | esc_chars | no_css
-%%%
-%%% @doc Printout from a test case to the log file.
-%%%
-%%% <p>This function is meant for printing a string directly from a
-%%% test case to the test case log file.</p>
-%%%
-%%% <p>Default <c>Category</c> is <c>default</c>,
-%%% default <c>Importance</c> is <c>?STD_IMPORTANCE</c>,
-%%% and default value for <c>Args</c> is <c>[]</c>.</p>
-%%% <p>Please see the User's Guide for details on <c>Category</c>
-%%% and <c>Importance</c>.</p>
log(Category,Importance,Format,Args,Opts) ->
ct_logs:tc_log(Category,Importance,Format,Args,Opts).
-
-%%%-----------------------------------------------------------------
-%%% @spec print(Format) -> ok
-%%% @equiv print(default,50,Format,[],[])
print(Format) ->
print(default,?STD_IMPORTANCE,Format,[],[]).
-%%%-----------------------------------------------------------------
-%%% @spec print(X1,X2) -> ok
-%%% X1 = Category | Importance | Format
-%%% X2 = Format | Args
-%%% @equiv print(Category,Importance,Format,Args,[])
print(X1,X2) ->
{Category,Importance,Format,Args} =
if is_atom(X1) -> {X1,?STD_IMPORTANCE,X2,[]};
@@ -627,12 +213,6 @@ print(X1,X2) ->
end,
print(Category,Importance,Format,Args,[]).
-%%%-----------------------------------------------------------------
-%%% @spec print(X1,X2,X3) -> ok
-%%% X1 = Category | Importance | Format
-%%% X2 = Importance | Format | Args
-%%% X3 = Format | Args | Opts
-%%% @equiv print(Category,Importance,Format,Args,Opts)
print(X1,X2,X3) ->
{Category,Importance,Format,Args,Opts} =
if is_atom(X1), is_integer(X2) -> {X1,X2,X3,[],[]};
@@ -642,13 +222,6 @@ print(X1,X2,X3) ->
end,
print(Category,Importance,Format,Args,Opts).
-%%%-----------------------------------------------------------------
-%%% @spec print(X1,X2,X3,X4) -> ok
-%%% X1 = Category | Importance
-%%% X2 = Importance | Format
-%%% X3 = Format | Args
-%%% X4 = Args | Opts
-%%% @equiv print(Category,Importance,Format,Args,Opts)
print(X1,X2,X3,X4) ->
{Category,Importance,Format,Args,Opts} =
if is_atom(X1), is_integer(X2) -> {X1,X2,X3,X4,[]};
@@ -657,40 +230,12 @@ print(X1,X2,X3,X4) ->
end,
print(Category,Importance,Format,Args,Opts).
-%%%-----------------------------------------------------------------
-%%% @spec print(Category,Importance,Format,Args,Opts) -> ok
-%%% Category = atom()
-%%% Importance = integer()
-%%% Format = string()
-%%% Args = list()
-%%% Opts = [Opt]
-%%% Opt = {heading,string()}
-%%%
-%%% @doc Printout from a test case to the console.
-%%%
-%%% <p>This function is meant for printing a string from a test case
-%%% to the console.</p>
-%%%
-%%% <p>Default <c>Category</c> is <c>default</c>,
-%%% default <c>Importance</c> is <c>?STD_IMPORTANCE</c>,
-%%% and default value for <c>Args</c> is <c>[]</c>.</p>
-%%% <p>Please see the User's Guide for details on <c>Category</c>
-%%% and <c>Importance</c>.</p>
print(Category,Importance,Format,Args,Opts) ->
ct_logs:tc_print(Category,Importance,Format,Args,Opts).
-
-%%%-----------------------------------------------------------------
-%%% @spec pal(Format) -> ok
-%%% @equiv pal(default,50,Format,[],[])
pal(Format) ->
pal(default,?STD_IMPORTANCE,Format,[]).
-%%%-----------------------------------------------------------------
-%%% @spec pal(X1,X2) -> ok
-%%% X1 = Category | Importance | Format
-%%% X2 = Format | Args
-%%% @equiv pal(Category,Importance,Format,Args,[])
pal(X1,X2) ->
{Category,Importance,Format,Args} =
if is_atom(X1) -> {X1,?STD_IMPORTANCE,X2,[]};
@@ -699,12 +244,6 @@ pal(X1,X2) ->
end,
pal(Category,Importance,Format,Args,[]).
-%%%-----------------------------------------------------------------
-%%% @spec pal(X1,X2,X3) -> ok
-%%% X1 = Category | Importance | Format
-%%% X2 = Importance | Format | Args
-%%% X3 = Format | Args | Opts
-%%% @equiv pal(Category,Importance,Format,Args,Opts)
pal(X1,X2,X3) ->
{Category,Importance,Format,Args,Opts} =
if is_atom(X1), is_integer(X2) -> {X1,X2,X3,[],[]};
@@ -714,13 +253,6 @@ pal(X1,X2,X3) ->
end,
pal(Category,Importance,Format,Args,Opts).
-%%%-----------------------------------------------------------------
-%%% @spec pal(X1,X2,X3,X4) -> ok
-%%% X1 = Category | Importance
-%%% X2 = Importance | Format
-%%% X3 = Format | Args
-%%% X4 = Args | Opts
-%%% @equiv pal(Category,Importance,Format,Args,Opts)
pal(X1,X2,X3,X4) ->
{Category,Importance,Format,Args,Opts} =
if is_atom(X1), is_integer(X2) -> {X1,X2,X3,X4,[]};
@@ -729,96 +261,31 @@ pal(X1,X2,X3,X4) ->
end,
pal(Category,Importance,Format,Args,Opts).
-%%%-----------------------------------------------------------------
-%%% @spec pal(Category,Importance,Format,Args,Opts) -> ok
-%%% Category = atom()
-%%% Importance = integer()
-%%% Format = string()
-%%% Args = list()
-%%% Opts = [Opt]
-%%% Opt = {heading,string()} | no_css
-%%%
-%%% @doc Print and log from a test case.
-%%%
-%%% <p>This function is meant for printing a string from a test case,
-%%% both to the test case log file and to the console.</p>
-%%%
-%%% <p>Default <c>Category</c> is <c>default</c>,
-%%% default <c>Importance</c> is <c>?STD_IMPORTANCE</c>,
-%%% and default value for <c>Args</c> is <c>[]</c>.</p>
-%%% <p>Please see the User's Guide for details on <c>Category</c>
-%%% and <c>Importance</c>.</p>
pal(Category,Importance,Format,Args,Opts) ->
ct_logs:tc_pal(Category,Importance,Format,Args,Opts).
-%%%-----------------------------------------------------------------
-%%% @spec set_verbosity(Category, Level) -> ok
-%%% Category = default | atom()
-%%% Level = integer()
-%%%
-%%% @doc Set the verbosity level for a category
set_verbosity(Category, Level) ->
ct_util:set_verbosity({Category,Level}).
-%%%-----------------------------------------------------------------
-%%% @spec get_verbosity(Category) -> Level | undefined
-%%% Category = default | atom()
-%%% Level = integer()
-%%%
-%%% @doc Read the verbosity level for a category
get_verbosity(Category) ->
ct_util:get_verbosity(Category).
-%%%-----------------------------------------------------------------
-%%% @spec capture_start() -> ok
-%%%
-%%% @doc Start capturing all text strings printed to stdout during
-%%% execution of the test case.
-%%%
-%%% @see capture_stop/0
-%%% @see capture_get/1
capture_start() ->
test_server:capture_start().
-%%%-----------------------------------------------------------------
-%%% @spec capture_stop() -> ok
-%%%
-%%% @doc Stop capturing text strings (a session started with
-%%% <c>capture_start/0</c>).
-%%%
-%%% @see capture_start/0
-%%% @see capture_get/1
capture_stop() ->
test_server:capture_stop().
-%%%-----------------------------------------------------------------
-%%% @spec capture_get() -> ListOfStrings
-%%% ListOfStrings = [string()]
-%%%
-%%% @equiv capture_get([default])
capture_get() ->
%% remove default log printouts (e.g. ct:log/2 printouts)
capture_get([default]).
-%%%-----------------------------------------------------------------
-%%% @spec capture_get(ExclCategories) -> ListOfStrings
-%%% ExclCategories = [atom()]
-%%% ListOfStrings = [string()]
-%%%
-%%% @doc Return and purge the list of text strings buffered
-%%% during the latest session of capturing printouts to stdout.
-%%% With <c>ExclCategories</c> it's possible to specify
-%%% log categories that should be ignored in <c>ListOfStrings</c>.
-%%% If <c>ExclCategories = []</c>, no filtering takes place.
-%%%
-%%% @see capture_start/0
-%%% @see capture_stop/0
-%%% @see log/3
capture_get([ExclCat | ExclCategories]) ->
Strs = test_server:capture_get(),
CatsStr = [atom_to_list(ExclCat) |
[[$| | atom_to_list(EC)] || EC <- ExclCategories]],
- {ok,MP} = re:compile("<div class=\"(" ++ lists:flatten(CatsStr) ++ ")\">.*"),
+ {ok,MP} = re:compile("<div class=\"(" ++ lists:flatten(CatsStr) ++ ")\">.*",
+ [unicode]),
lists:flatmap(fun(Str) ->
case re:run(Str, MP) of
{match,_} -> [];
@@ -829,40 +296,26 @@ capture_get([ExclCat | ExclCategories]) ->
capture_get([]) ->
test_server:capture_get().
-%%%-----------------------------------------------------------------
-%%% @spec fail(Reason) -> ok
-%%% Reason = term()
-%%%
-%%% @doc Terminate a test case with the given error
-%%% <c>Reason</c>.
fail(Reason) ->
try
exit({test_case_failed,Reason})
catch
- Class:R ->
- case erlang:get_stacktrace() of
+ Class:R:S ->
+ case S of
[{?MODULE,fail,1,_}|Stk] -> ok;
Stk -> ok
end,
erlang:raise(Class, R, Stk)
end.
-%%%-----------------------------------------------------------------
-%%% @spec fail(Format, Args) -> ok
-%%% Format = string()
-%%% Args = list()
-%%%
-%%% @doc Terminate a test case with an error message specified
-%%% by a format string and a list of values (used as arguments to
-%%% <c>io_lib:format/2</c>).
fail(Format, Args) ->
try io_lib:format(Format, Args) of
Str ->
try
exit({test_case_failed,lists:flatten(Str)})
catch
- Class:R ->
- case erlang:get_stacktrace() of
+ Class:R:S ->
+ case S of
[{?MODULE,fail,2,_}|Stk] -> ok;
Stk -> ok
end,
@@ -873,42 +326,19 @@ fail(Format, Args) ->
exit({BadArgs,{?MODULE,fail,[Format,Args]}})
end.
-%%%-----------------------------------------------------------------
-%%% @spec comment(Comment) -> ok
-%%% Comment = term()
-%%%
-%%% @doc Print the given <c>Comment</c> in the comment field in
-%%% the table on the test suite result page.
-%%%
-%%% <p>If called several times, only the last comment is printed.
-%%% The test case return value <c>{comment,Comment}</c>
-%%% overwrites the string set by this function.</p>
comment(Comment) when is_list(Comment) ->
Formatted =
case (catch io_lib:format("~ts",[Comment])) of
{'EXIT',_} -> % it's a list not a string
- io_lib:format("~p",[Comment]);
+ io_lib:format("~tp",[Comment]);
String ->
String
end,
send_html_comment(lists:flatten(Formatted));
comment(Comment) ->
- Formatted = io_lib:format("~p",[Comment]),
+ Formatted = io_lib:format("~tp",[Comment]),
send_html_comment(lists:flatten(Formatted)).
-%%%-----------------------------------------------------------------
-%%% @spec comment(Format, Args) -> ok
-%%% Format = string()
-%%% Args = list()
-%%%
-%%% @doc Print the formatted string in the comment field in
-%%% the table on the test suite result page.
-%%%
-%%% <p>The <c>Format</c> and <c>Args</c> arguments are
-%%% used in call to <c>io_lib:format/2</c> in order to create
-%%% the comment string. The behaviour of <c>comment/2</c> is
-%%% otherwise the same as the <c>comment/1</c> function (see
-%%% above for details).</p>
comment(Format, Args) when is_list(Format), is_list(Args) ->
Formatted =
case (catch io_lib:format(Format, Args)) of
@@ -924,64 +354,28 @@ send_html_comment(Comment) ->
ct_util:set_testdata({{comment,group_leader()},Html}),
test_server:comment(Html).
-%%%-----------------------------------------------------------------
-%%% @spec make_priv_dir() -> ok | {error,Reason}
-%%% Reason = term()
-%%% @doc If the test has been started with the create_priv_dir
-%%% option set to manual_per_tc, in order for the test case to use
-%%% the private directory, it must first create it by calling
-%%% this function.
make_priv_dir() ->
test_server:make_priv_dir().
-%%%-----------------------------------------------------------------
-%%% @spec get_target_name(Handle) -> {ok,TargetName} | {error,Reason}
-%%% Handle = handle()
-%%% TargetName = target_name()
-%%% @doc Return the name of the target that the given connection
-%%% belongs to.
get_target_name(Handle) ->
ct_util:get_target_name(Handle).
-
-%%%-----------------------------------------------------------------
-%%% @spec parse_table(Data) -> {Heading,Table}
-%%% Data = [string()]
-%%% Heading = tuple()
-%%% Table = [tuple()]
-%%%
-%%% @doc Parse the printout from an SQL table and return a list of tuples.
-%%%
-%%% <p>The printout to parse would typically be the result of a
-%%% <c>select</c> command in SQL. The returned
-%%% <c>Table</c> is a list of tuples, where each tuple is a row
-%%% in the table.</p>
-%%%
-%%% <p><c>Heading</c> is a tuple of strings representing the
-%%% headings of each column in the table.</p>
+
+-spec get_progname() -> string().
+
+get_progname() ->
+ case init:get_argument(progname) of
+ {ok, [[Prog]]} ->
+ Prog;
+ _Other ->
+ "no_prog_name"
+ end.
+
parse_table(Data) ->
ct_util:parse_table(Data).
-%%%-----------------------------------------------------------------
-%%% @spec listenv(Telnet) -> [Env]
-%%% Telnet = term()
-%%% Env = {Key,Value}
-%%% Key = string()
-%%% Value = string()
-%%%
-%%% @doc Performs the listenv command on the given telnet connection
-%%% and returns the result as a list of Key-Value pairs.
listenv(Telnet) ->
ct_util:listenv(Telnet).
-
-%%%-----------------------------------------------------------------
-%%% @spec testcases(TestDir, Suite) -> Testcases | {error,Reason}
-%%% TestDir = string()
-%%% Suite = atom()
-%%% Testcases = list()
-%%% Reason = term()
-%%%
-%%% @doc Returns all test cases in the specified suite.
testcases(TestDir, Suite) ->
case make_and_load(TestDir, Suite) of
E = {error,_} ->
@@ -996,11 +390,7 @@ testcases(TestDir, Suite) ->
end.
make_and_load(Dir, Suite) ->
- EnvInclude =
- case os:getenv("CT_INCLUDE_PATH") of
- false -> [];
- CtInclPath -> string:tokens(CtInclPath, [$:,$ ,$,])
- end,
+ EnvInclude = string:lexemes(os:getenv("CT_INCLUDE_PATH", ""), [$:,$ ,$,]),
StartInclude =
case init:get_argument(include) of
{ok,[Dirs]} -> Dirs;
@@ -1021,15 +411,6 @@ make_and_load(Dir, Suite) ->
end
end.
-%%%-----------------------------------------------------------------
-%%% @spec userdata(TestDir, Suite) -> SuiteUserData | {error,Reason}
-%%% TestDir = string()
-%%% Suite = atom()
-%%% SuiteUserData = [term()]
-%%% Reason = term()
-%%%
-%%% @doc Returns any data specified with the tag <c>userdata</c>
-%%% in the list of tuples returned from <c>Suite:suite/0</c>.
userdata(TestDir, Suite) ->
case make_and_load(TestDir, Suite) of
E = {error,_} ->
@@ -1055,18 +436,6 @@ get_userdata(List, _) when is_list(List) ->
get_userdata(_BadTerm, Spec) ->
{error,list_to_atom(Spec ++ " must return a list")}.
-%%%-----------------------------------------------------------------
-%%% @spec userdata(TestDir, Suite, GroupOrCase) -> TCUserData | {error,Reason}
-%%% TestDir = string()
-%%% Suite = atom()
-%%% GroupOrCase = {group,GroupName} | atom()
-%%% GroupName = atom()
-%%% TCUserData = [term()]
-%%% Reason = term()
-%%%
-%%% @doc Returns any data specified with the tag <c>userdata</c>
-%%% in the list of tuples returned from <c>Suite:group(GroupName)</c>
-%%% or <c>Suite:Case()</c>.
userdata(TestDir, Suite, {group,GroupName}) ->
case make_and_load(TestDir, Suite) of
E = {error,_} ->
@@ -1085,27 +454,6 @@ userdata(TestDir, Suite, Case) when is_atom(Case) ->
get_userdata(Info, atom_to_list(Case)++"/0")
end.
-
-%%%-----------------------------------------------------------------
-%%% @spec get_status() -> TestStatus | {error,Reason} | no_tests_running
-%%% TestStatus = [StatusElem]
-%%% StatusElem = {current,TestCaseInfo} | {successful,Successful} |
-%%% {failed,Failed} | {skipped,Skipped} | {total,Total}
-%%% TestCaseInfo = {Suite,TestCase} | [{Suite,TestCase}]
-%%% Suite = atom()
-%%% TestCase = atom()
-%%% Successful = integer()
-%%% Failed = integer()
-%%% Skipped = {UserSkipped,AutoSkipped}
-%%% UserSkipped = integer()
-%%% AutoSkipped = integer()
-%%% Total = integer()
-%%% Reason = term()
-%%%
-%%% @doc Returns status of ongoing test. The returned list contains info about
-%%% which test case is currently executing (a list of cases when a
-%%% parallel test case group is executing), as well as counters for
-%%% successful, failed, skipped, and total test cases so far.
get_status() ->
case get_testdata(curr_tc) of
{ok,TestCase} ->
@@ -1137,176 +485,37 @@ get_testdata(Key) ->
{ok,Data}
end.
-%%%-----------------------------------------------------------------
-%%% @spec abort_current_testcase(Reason) -> ok | {error,ErrorReason}
-%%% Reason = term()
-%%% ErrorReason = no_testcase_running | parallel_group
-%%%
-%%% @doc <p>When calling this function, the currently executing test case will be aborted.
-%%% It is the user's responsibility to know for sure which test case is currently
-%%% executing. The function is therefore only safe to call from a function which
-%%% has been called (or synchronously invoked) by the test case.</p>
-%%%
-%%% <p><c>Reason</c>, the reason for aborting the test case, is printed
-%%% in the test case log.</p>
abort_current_testcase(Reason) ->
test_server_ctrl:abort_current_testcase(Reason).
-%%%-----------------------------------------------------------------
-%%% @spec get_event_mgr_ref() -> EvMgrRef
-%%% EvMgrRef = atom()
-%%%
-%%% @doc <p>Call this function in order to get a reference to the
-%%% CT event manager. The reference can be used to e.g. add
-%%% a user specific event handler while tests are running.
-%%% Example:
-%%% <c>gen_event:add_handler(ct:get_event_mgr_ref(), my_ev_h, [])</c></p>
get_event_mgr_ref() ->
?CT_EVMGR_REF.
-%%%-----------------------------------------------------------------
-%%% @spec encrypt_config_file(SrcFileName, EncryptFileName) ->
-%%% ok | {error,Reason}
-%%% SrcFileName = string()
-%%% EncryptFileName = string()
-%%% Reason = term()
-%%%
-%%% @doc <p>This function encrypts the source config file with DES3 and
-%%% saves the result in file <c>EncryptFileName</c>. The key,
-%%% a string, must be available in a text file named
-%%% <c>.ct_config.crypt</c> in the current directory, or the
-%%% home directory of the user (it is searched for in that order).</p>
-%%% <p>See the Common Test User's Guide for information about using
-%%% encrypted config files when running tests.</p>
-%%% <p>See the <c>crypto</c> application for details on DES3
-%%% encryption/decryption.</p>
encrypt_config_file(SrcFileName, EncryptFileName) ->
ct_config:encrypt_config_file(SrcFileName, EncryptFileName).
-%%%-----------------------------------------------------------------
-%%% @spec encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile) ->
-%%% ok | {error,Reason}
-%%% SrcFileName = string()
-%%% EncryptFileName = string()
-%%% KeyOrFile = {key,string()} | {file,string()}
-%%% Reason = term()
-%%%
-%%% @doc <p>This function encrypts the source config file with DES3 and
-%%% saves the result in the target file <c>EncryptFileName</c>.
-%%% The encryption key to use is either the value in
-%%% <c>{key,Key}</c> or the value stored in the file specified
-%%% by <c>{file,File}</c>.</p>
-%%% <p>See the Common Test User's Guide for information about using
-%%% encrypted config files when running tests.</p>
-%%% <p>See the <c>crypto</c> application for details on DES3
-%%% encryption/decryption.</p>
encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile) ->
ct_config:encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile).
-%%%-----------------------------------------------------------------
-%%% @spec decrypt_config_file(EncryptFileName, TargetFileName) ->
-%%% ok | {error,Reason}
-%%% EncryptFileName = string()
-%%% TargetFileName = string()
-%%% Reason = term()
-%%%
-%%% @doc <p>This function decrypts <c>EncryptFileName</c>, previously
-%%% generated with <c>encrypt_config_file/2/3</c>. The original
-%%% file contents is saved in the target file. The encryption key, a
-%%% string, must be available in a text file named
-%%% <c>.ct_config.crypt</c> in the current directory, or the
-%%% home directory of the user (it is searched for in that order).</p>
decrypt_config_file(EncryptFileName, TargetFileName) ->
ct_config:decrypt_config_file(EncryptFileName, TargetFileName).
-%%%-----------------------------------------------------------------
-%%% @spec decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile) ->
-%%% ok | {error,Reason}
-%%% EncryptFileName = string()
-%%% TargetFileName = string()
-%%% KeyOrFile = {key,string()} | {file,string()}
-%%% Reason = term()
-%%%
-%%% @doc <p>This function decrypts <c>EncryptFileName</c>, previously
-%%% generated with <c>encrypt_config_file/2/3</c>. The original
-%%% file contents is saved in the target file. The key must have the
-%%% the same value as that used for encryption.</p>
decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile) ->
ct_config:decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile).
-
-%%%-----------------------------------------------------------------
-%%% @spec add_config(Callback, Config) -> ok | {error, Reason}
-%%% Callback = atom()
-%%% Config = string()
-%%% Reason = term()
-%%%
-%%% @doc <p>This function loads configuration variables using the
-%%% given callback module and configuration string. Callback module
-%%% should be either loaded or present in the code part. Loaded
-%%% configuration variables can later be removed using
-%%% <c>remove_config/2</c> function.</p>
add_config(Callback, Config)->
ct_config:add_config(Callback, Config).
-%%%-----------------------------------------------------------------
-%%% @spec remove_config(Callback, Config) -> ok
-%%% Callback = atom()
-%%% Config = string()
-%%% Reason = term()
-%%%
-%%% @doc <p>This function removes configuration variables (together with
-%%% their aliases) which were loaded with specified callback module and
-%%% configuration string.</p>
remove_config(Callback, Config) ->
ct_config:remove_config(Callback, Config).
-%%%-----------------------------------------------------------------
-%%% @spec timetrap(Time) -> ok
-%%% Time = {hours,Hours} | {minutes,Mins} | {seconds,Secs} | Millisecs | infinity | Func
-%%% Hours = integer()
-%%% Mins = integer()
-%%% Secs = integer()
-%%% Millisecs = integer() | float()
-%%% Func = {M,F,A} | fun()
-%%% M = atom()
-%%% F = atom()
-%%% A = list()
-%%%
-%%% @doc <p>Use this function to set a new timetrap for the running test case.
-%%% If the argument is <c>Func</c>, the timetrap will be triggered
-%%% when this function returns. <c>Func</c> may also return a new
-%%% <c>Time</c> value, which in that case will be the value for the
-%%% new timetrap.</p>
timetrap(Time) ->
test_server:timetrap_cancel(),
test_server:timetrap(Time).
-%%%-----------------------------------------------------------------
-%%% @spec get_timetrap_info() -> {Time,Scale}
-%%% Time = integer() | infinity
-%%% Scale = true | false
-%%%
-%%% @doc <p>Read info about the timetrap set for the current test case.
-%%% <c>Scale</c> indicates if Common Test will attempt to automatically
-%%% compensate timetraps for runtime delays introduced by e.g. tools like
-%%% cover.</p>
get_timetrap_info() ->
test_server:get_timetrap_info().
-%%%-----------------------------------------------------------------
-%%% @spec sleep(Time) -> ok
-%%% Time = {hours,Hours} | {minutes,Mins} | {seconds,Secs} | Millisecs | infinity
-%%% Hours = integer()
-%%% Mins = integer()
-%%% Secs = integer()
-%%% Millisecs = integer() | float()
-%%%
-%%% @doc <p>This function, similar to <c>timer:sleep/1</c>, suspends the test
-%%% case for specified time. However, this function also multiplies
-%%% <c>Time</c> with the 'multiply_timetraps' value (if set) and under
-%%% certain circumstances also scales up the time automatically
-%%% if 'scale_timetraps' is set to true (default is false).</p>
sleep({hours,Hs}) ->
sleep(trunc(Hs * 1000 * 60 * 60));
sleep({minutes,Ms}) ->
@@ -1316,52 +525,12 @@ sleep({seconds,Ss}) ->
sleep(Time) ->
test_server:adjusted_sleep(Time).
-%%%-----------------------------------------------------------------
-%%% @spec notify(Name,Data) -> ok
-%%% Name = atom()
-%%% Data = term()
-%%%
-%%% @doc <p>Sends a asynchronous notification of type <c>Name</c> with
-%%% <c>Data</c>to the common_test event manager. This can later be
-%%% caught by any installed event manager. </p>
-%%% @see //stdlib/gen_event
notify(Name,Data) ->
ct_event:notify(Name, Data).
-%%%-----------------------------------------------------------------
-%%% @spec sync_notify(Name,Data) -> ok
-%%% Name = atom()
-%%% Data = term()
-%%%
-%%% @doc <p>Sends a synchronous notification of type <c>Name</c> with
-%%% <c>Data</c>to the common_test event manager. This can later be
-%%% caught by any installed event manager. </p>
-%%% @see //stdlib/gen_event
sync_notify(Name,Data) ->
ct_event:sync_notify(Name, Data).
-%%%-----------------------------------------------------------------
-%%% @spec break(Comment) -> ok | {error,Reason}
-%%% Comment = string()
-%%% Reason = {multiple_cases_running,TestCases} |
-%%% 'enable break with release_shell option'
-%%% TestCases = [atom()]
-%%%
-%%% @doc <p>This function will cancel any active timetrap and pause the
-%%% execution of the current test case until the user calls the
-%%% <c>continue/0</c> function. It gives the user the opportunity
-%%% to interact with the erlang node running the tests, e.g. for
-%%% debugging purposes or for manually executing a part of the
-%%% test case. If a parallel group is executing, <c>break/2</c>
-%%% should be called instead.</p>
-%%% <p>A cancelled timetrap will not be automatically
-%%% reactivated after the break, but must be started exlicitly with
-%%% <c>ct:timetrap/1</c></p>
-%%% <p>In order for the break/continue functionality to work,
-%%% Common Test must release the shell process controlling stdin.
-%%% This is done by setting the <c>release_shell</c> start option
-%%% to <c>true</c>. See the User's Guide for more information.</p>
-
break(Comment) ->
case {ct_util:get_testdata(starter),
ct_util:get_testdata(release_shell)} of
@@ -1384,19 +553,6 @@ break(Comment) ->
end
end.
-%%%-----------------------------------------------------------------
-%%% @spec break(TestCase, Comment) -> ok | {error,Reason}
-%%% TestCase = atom()
-%%% Comment = string()
-%%% Reason = 'test case not running' |
-%%% 'enable break with release_shell option'
-%%%
-%%% @doc <p>This function works the same way as <c>break/1</c>,
-%%% only the <c>TestCase</c> argument makes it possible to
-%%% pause a test case executing in a parallel group. The
-%%% <c>continue/1</c> function should be used to resume
-%%% execution of <c>TestCase</c>.</p>
-%%% <p>See <c>break/1</c> for more details.</p>
break(TestCase, Comment) ->
case {ct_util:get_testdata(starter),
ct_util:get_testdata(release_shell)} of
@@ -1423,23 +579,12 @@ break(TestCase, Comment) ->
end
end.
-%%%-----------------------------------------------------------------
-%%% @spec continue() -> ok
-%%%
-%%% @doc <p>This function must be called in order to continue after a
-%%% test case (not executing in a parallel group) has called
-%%% <c>break/1</c>.</p>
continue() ->
test_server:continue().
-%%%-----------------------------------------------------------------
-%%% @spec continue(TestCase) -> ok
-%%% TestCase = atom()
-%%%
-%%% @doc <p>This function must be called in order to continue after a
-%%% test case has called <c>break/2</c>. If the paused test case,
-%%% <c>TestCase</c>, executes in a parallel group, this
-%%% function - rather than <c>continue/0</c> - must be used
-%%% in order to let the test case proceed.</p>
continue(TestCase) ->
test_server:continue(TestCase).
+
+
+remaining_test_procs() ->
+ ct_util:remaining_test_procs().
diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl
index 99de311570..a07e61199b 100644
--- a/lib/common_test/src/ct_config.erl
+++ b/lib/common_test/src/ct_config.erl
@@ -1,7 +1,7 @@
%%--------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2018. 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.
@@ -81,6 +81,7 @@ start(Mode) ->
do_start(Parent) ->
process_flag(trap_exit,true),
+ ct_util:mark_process(),
register(ct_config_server,self()),
ct_util:create_table(?attr_table,bag,#ct_conf.key),
{ok,StartDir} = file:get_cwd(),
@@ -171,8 +172,8 @@ reload_config(KeyOrName) ->
process_default_configs(Opts) ->
lists:flatmap(fun({config,[_|_] = FileOrFiles}) ->
- case {io_lib:printable_list(FileOrFiles),
- io_lib:printable_list(hd(FileOrFiles))} of
+ case {io_lib:printable_unicode_list(FileOrFiles),
+ io_lib:printable_unicode_list(hd(FileOrFiles))} of
{false,true} ->
FileOrFiles;
{true,false} ->
@@ -591,7 +592,7 @@ encrypt_config_file(SrcFileName, EncryptFileName, {file,KeyFile}) ->
encrypt_config_file(SrcFileName, EncryptFileName, {key,Key}) ->
_ = crypto:start(),
- {Key,IVec} = make_crypto_key(Key),
+ {CryptoKey,IVec} = make_crypto_key(Key),
case file:read_file(SrcFileName) of
{ok,Bin0} ->
Bin1 = term_to_binary({SrcFileName,Bin0}),
@@ -599,7 +600,7 @@ encrypt_config_file(SrcFileName, EncryptFileName, {key,Key}) ->
0 -> Bin1;
N -> list_to_binary([Bin1,random_bytes(8-N)])
end,
- EncBin = crypto:block_encrypt(des3_cbc, Key, IVec, Bin2),
+ EncBin = crypto:block_encrypt(des3_cbc, CryptoKey, IVec, Bin2),
case file:write_file(EncryptFileName, EncBin) of
ok ->
io:format("~ts --(encrypt)--> ~ts~n",
@@ -630,10 +631,10 @@ decrypt_config_file(EncryptFileName, TargetFileName, {file,KeyFile}) ->
decrypt_config_file(EncryptFileName, TargetFileName, {key,Key}) ->
_ = crypto:start(),
- {Key,IVec} = make_crypto_key(Key),
+ {CryptoKey,IVec} = make_crypto_key(Key),
case file:read_file(EncryptFileName) of
{ok,Bin} ->
- DecBin = crypto:block_decrypt(des3_cbc, Key, IVec, Bin),
+ DecBin = crypto:block_decrypt(des3_cbc, CryptoKey, IVec, Bin),
case catch binary_to_term(DecBin) of
{'EXIT',_} ->
{error,bad_file};
@@ -659,7 +660,7 @@ decrypt_config_file(EncryptFileName, TargetFileName, {key,Key}) ->
get_crypt_key_from_file(File) ->
case file:read_file(File) of
{ok,Bin} ->
- case catch string:tokens(binary_to_list(Bin), [$\n,$\r]) of
+ case catch string:lexemes(binary_to_list(Bin), [$\n, [$\r,$\n]]) of
[Key] ->
Key;
_ ->
@@ -693,7 +694,7 @@ get_crypt_key_from_file() ->
noent ->
Result;
_ ->
- case catch string:tokens(binary_to_list(Result), [$\n,$\r]) of
+ case catch string:lexemes(binary_to_list(Result), [$\n, [$\r,$\n]]) of
[Key] ->
io:format("~nCrypt key file: ~ts~n", [FullName]),
Key;
diff --git a/lib/common_test/src/ct_config_plain.erl b/lib/common_test/src/ct_config_plain.erl
index e72b55971b..7b68ac6597 100644
--- a/lib/common_test/src/ct_config_plain.erl
+++ b/lib/common_test/src/ct_config_plain.erl
@@ -1,7 +1,7 @@
%%--------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2018. 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.
@@ -106,7 +106,7 @@ read_config_terms1({done,{eof,EL},_}, L, _, _) ->
read_config_terms1({done,{error,Info,EL},_}, L, _, _) ->
{error,{Info,{L,EL}}};
read_config_terms1({more,_}, L, Terms, Rest) ->
- case string:tokens(Rest, [$\n,$\r,$\t]) of
+ case string:lexemes(Rest, [$\n,[$\r,$\n],$\t]) of
[] ->
lists:reverse(Terms);
_ ->
diff --git a/lib/common_test/src/ct_conn_log_h.erl b/lib/common_test/src/ct_conn_log_h.erl
index 93e64c65fe..3e83020f45 100644
--- a/lib/common_test/src/ct_conn_log_h.erl
+++ b/lib/common_test/src/ct_conn_log_h.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2012-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2012-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.
@@ -186,7 +186,7 @@ format_head(ConnMod,_,Time,Text) ->
io_lib:format("~n~ts",[Head]).
format_title(raw,#conn_log{client=Client}=Info) ->
- io_lib:format("Client ~w ~s ~ts",[Client,actionstr(Info),serverstr(Info)]);
+ io_lib:format("Client ~tw ~s ~ts",[Client,actionstr(Info),serverstr(Info)]);
format_title(_,Info) ->
Title = pad_char_end(?WIDTH,pretty_title(Info),$=),
io_lib:format("~n~ts", [Title]).
@@ -197,9 +197,9 @@ format_data(ConnMod,LogType,Data) ->
ConnMod:format_data(LogType,Data).
format_error(raw,Report) ->
- io_lib:format("~n~p~n",[Report]);
+ io_lib:format("~n~tp~n",[Report]);
format_error(pretty,Report) ->
- [io_lib:format("~n ~p: ~p",[K,V]) || {K,V} <- Report].
+ [io_lib:format("~n ~tp: ~tp",[K,V]) || {K,V} <- Report].
%%%-----------------------------------------------------------------
@@ -224,13 +224,13 @@ now_to_time({_,_,MicroS}=Now) ->
{calendar:now_to_local_time(Now),MicroS}.
pretty_head({{{Y,Mo,D},{H,Mi,S}},MicroS},ConnMod,Text0) ->
- Text = string:to_upper(atom_to_list(ConnMod) ++ Text0),
+ Text = string:uppercase(atom_to_list(ConnMod) ++ Text0),
io_lib:format("= ~s ==== ~s-~s-~w::~s:~s:~s,~s ",
[Text,t(D),month(Mo),Y,t(H),t(Mi),t(S),
micro2milli(MicroS)]).
pretty_title(#conn_log{client=Client}=Info) ->
- io_lib:format("= Client ~w ~s ~ts ",
+ io_lib:format("= Client ~tw ~s ~ts ",
[Client,actionstr(Info),serverstr(Info)]).
actionstr(#conn_log{action=send}) -> "----->";
@@ -238,16 +238,18 @@ actionstr(#conn_log{action=cmd}) -> "----->";
actionstr(#conn_log{action=recv}) -> "<-----";
actionstr(#conn_log{action=open}) -> "opened session to";
actionstr(#conn_log{action=close}) -> "closed session to";
+actionstr(#conn_log{action=connect}) -> "connected to";
+actionstr(#conn_log{action=disconnect}) -> "disconnected from";
actionstr(_) -> "<---->".
serverstr(#conn_log{name=undefined,address={undefined,_}}) ->
io_lib:format("server",[]);
serverstr(#conn_log{name=undefined,address=Address}) ->
- io_lib:format("~p",[Address]);
+ io_lib:format("~tp",[Address]);
serverstr(#conn_log{name=Alias,address={undefined,_}}) ->
- io_lib:format("~w",[Alias]);
+ io_lib:format("~tw",[Alias]);
serverstr(#conn_log{name=Alias,address=Address}) ->
- io_lib:format("~w(~p)",[Alias,Address]).
+ io_lib:format("~tw(~tp)",[Alias,Address]).
month(1) -> "Jan";
month(2) -> "Feb";
@@ -273,7 +275,7 @@ pad0(N,Str) ->
lists:duplicate(N-M,$0) ++ Str.
pad_char_end(N,Str,Char) ->
- case length(lists:flatten(Str)) of
+ case string:length(Str) of
M when M<N -> Str ++ lists:duplicate(N-M,Char);
_ -> Str
end.
diff --git a/lib/common_test/src/ct_cover.erl b/lib/common_test/src/ct_cover.erl
index c258516915..bcd98dcc58 100644
--- a/lib/common_test/src/ct_cover.erl
+++ b/lib/common_test/src/ct_cover.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2018. 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.
@@ -18,11 +18,6 @@
%% %CopyrightEnd%
%%
-%%% @doc Common Test Framework code coverage support module.
-%%%
-%%% <p>This module exports help functions for performing code
-%%% coverage analysis.</p>
-
-module(ct_cover).
-export([get_spec/1, add_nodes/1, remove_nodes/1, cross_cover_analyse/2]).
@@ -31,16 +26,6 @@
-include_lib("kernel/include/file.hrl").
-%%%-----------------------------------------------------------------
-%%% @spec add_nodes(Nodes) -> {ok,StartedNodes} | {error,Reason}
-%%% Nodes = [atom()]
-%%% StartedNodes = [atom()]
-%%% Reason = cover_not_running | not_main_node
-%%%
-%%% @doc Add nodes to current cover test (only works if cover support
-%%% is active!). To have effect, this function should be called
-%%% from init_per_suite/1 before any actual tests are performed.
-%%%
add_nodes([]) ->
{ok,[]};
add_nodes(Nodes) ->
@@ -67,17 +52,6 @@ add_nodes(Nodes) ->
end
end.
-
-%%%-----------------------------------------------------------------
-%%% @spec remove_nodes(Nodes) -> ok | {error,Reason}
-%%% Nodes = [atom()]
-%%% Reason = cover_not_running | not_main_node
-%%%
-%%% @doc Remove nodes from current cover test. Call this function
-%%% to stop cover test on nodes previously added with add_nodes/1.
-%%% Results on the remote node are transferred to the Common Test
-%%% node.
-%%%
remove_nodes([]) ->
ok;
remove_nodes(Nodes) ->
@@ -103,25 +77,11 @@ remove_nodes(Nodes) ->
end
end.
-
-%%%-----------------------------------------------------------------
-%%% @spec cross_cover_analyse(Level,Tests) -> ok
-%%% Level = overview | details
-%%% Tests = [{Tag,Dir}]
-%%% Tag = atom()
-%%% Dir = string()
-%%%
-%%% @doc Accumulate cover results over multiple tests.
-%%% See the chapter about <seealso
-%%% marker="cover_chapter#cross_cover">cross cover
-%%% analysis</seealso> in the users's guide.
-%%%
cross_cover_analyse(Level,Tests) ->
test_server_ctrl:cross_cover_analyse(Level,Tests).
%%%-----------------------------------------------------------------
-%%% @hidden
%% Read cover specification file and return the parsed info.
%% -> CoverSpec: {CoverFile,Nodes,Import,Export,AppCoverInfo}
@@ -302,6 +262,11 @@ get_app_info(App=#cover{app=Name}, [{src_files,Name,Src1}|Terms], Dir) ->
Src = App#cover.src,
get_app_info(App#cover{src=Src++Src1},Terms,Dir);
+get_app_info(App=#cover{app=none}, [{local_only,Bool}|Terms], Dir) ->
+ get_app_info(App, [{local_only,none,Bool}|Terms], Dir);
+get_app_info(App=#cover{app=Name}, [{local_only,Name,Bool}|Terms], Dir) ->
+ get_app_info(App#cover{local_only=Bool},Terms,Dir);
+
get_app_info(App, [_|Terms], Dir) ->
get_app_info(App, Terms, Dir);
diff --git a/lib/common_test/src/ct_default_gl.erl b/lib/common_test/src/ct_default_gl.erl
index d1b52e5f4f..cf1bcc058d 100644
--- a/lib/common_test/src/ct_default_gl.erl
+++ b/lib/common_test/src/ct_default_gl.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2012-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2012-2018. 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.
@@ -55,6 +55,7 @@ stop() ->
init([ParentGL]) ->
register(?MODULE, self()),
+ ct_util:mark_process(),
{ok,#{parent_gl_pid => ParentGL,
parent_gl_monitor => erlang:monitor(process,ParentGL)}}.
diff --git a/lib/common_test/src/ct_event.erl b/lib/common_test/src/ct_event.erl
index 5fa9f410bf..3689c6bc45 100644
--- a/lib/common_test/src/ct_event.erl
+++ b/lib/common_test/src/ct_event.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2018. 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.
@@ -18,14 +18,14 @@
%% %CopyrightEnd%
%%
-%%% @doc Common Test Framework Event Handler
+%%% doc Common Test Framework Event Handler
%%%
-%%% <p>This module implements an event handler that CT uses to
+%%% This module implements an event handler that CT uses to
%%% handle status and progress notifications during test runs.
%%% The notifications are handled locally (per node) and passed
%%% on to ct_master when CT runs in distributed mode. This
%%% module may be used as a template for other event handlers
-%%% that can be plugged in to handle local logging and reporting.</p>
+%%% that can be plugged in to handle local logging and reporting.
-module(ct_event).
-behaviour(gen_event).
@@ -137,6 +137,7 @@ is_alive() ->
%% this function is called to initialize the event handler.
%%--------------------------------------------------------------------
init(RecvPids) ->
+ ct_util:mark_process(),
%% RecvPids = [{RecvTag,Pid}]
{ok,#state{receivers=RecvPids}}.
@@ -151,7 +152,7 @@ init(RecvPids) ->
%%--------------------------------------------------------------------
handle_event(Event,State=#state{receivers=RecvPids}) ->
print("~n=== ~w ===~n", [?MODULE]),
- print("~w: ~w~n", [Event#event.name,Event#event.data]),
+ print("~tw: ~tw~n", [Event#event.name,Event#event.data]),
lists:foreach(fun(Recv) -> report_event(Recv,Event) end, RecvPids),
{ok,State}.
diff --git a/lib/common_test/src/ct_framework.erl b/lib/common_test/src/ct_framework.erl
index 141c7f5b0a..bce6420042 100644
--- a/lib/common_test/src/ct_framework.erl
+++ b/lib/common_test/src/ct_framework.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2018. 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.
@@ -18,10 +18,10 @@
%% %CopyrightEnd%
%%
-%%% @doc Common Test Framework callback module.
+%%% Common Test Framework callback module.
%%%
-%%% <p>This module exports framework callback functions which are
-%%% called from the test_server.</p>
+%%% This module exports framework callback functions which are
+%%% called from the test_server.
-module(ct_framework).
@@ -42,7 +42,7 @@
-define(rev(L), lists:reverse(L)).
%%%-----------------------------------------------------------------
-%%% @spec init_tc(Mod,Func,Args) -> {ok,NewArgs} | {error,Reason} |
+%%% -spec init_tc(Mod,Func,Args) -> {ok,NewArgs} | {error,Reason} |
%%% {skip,Reason} | {auto_skip,Reason}
%%% Mod = atom()
%%% Func = atom()
@@ -50,7 +50,7 @@
%%% NewArgs = list()
%%% Reason = term()
%%%
-%%% @doc Test server framework callback, called by the test_server
+%%% Test server framework callback, called by the test_server
%%% when a new test case is started.
init_tc(_,{end_per_testcase_not_run,_},[Config]) ->
%% Testcase is completed (skipped or failed), but end_per_testcase
@@ -312,10 +312,10 @@ add_defaults(Mod,Func, GroupPath) ->
end;
{'EXIT',Reason} ->
ErrStr = io_lib:format("~n*** ERROR *** "
- "~w:suite/0 failed: ~p~n",
+ "~w:suite/0 failed: ~tp~n",
[Suite,Reason]),
- io:format(ErrStr, []),
- io:format(?def_gl, ErrStr, []),
+ io:format("~ts", [ErrStr]),
+ io:format(?def_gl, "~ts", [ErrStr]),
{suite0_failed,{exited,Reason}};
SuiteInfo when is_list(SuiteInfo) ->
case lists:all(fun(E) when is_tuple(E) -> true;
@@ -335,18 +335,18 @@ add_defaults(Mod,Func, GroupPath) ->
false ->
ErrStr = io_lib:format("~n*** ERROR *** "
"Invalid return value from "
- "~w:suite/0: ~p~n",
+ "~w:suite/0: ~tp~n",
[Suite,SuiteInfo]),
- io:format(ErrStr, []),
- io:format(?def_gl, ErrStr, []),
+ io:format("~ts", [ErrStr]),
+ io:format(?def_gl, "~ts", [ErrStr]),
{suite0_failed,bad_return_value}
end;
SuiteInfo ->
ErrStr = io_lib:format("~n*** ERROR *** "
"Invalid return value from "
- "~w:suite/0: ~p~n", [Suite,SuiteInfo]),
- io:format(ErrStr, []),
- io:format(?def_gl, ErrStr, []),
+ "~w:suite/0: ~tp~n", [Suite,SuiteInfo]),
+ io:format("~ts", [ErrStr]),
+ io:format(?def_gl, "~ts", [ErrStr]),
{suite0_failed,bad_return_value}
end.
@@ -371,10 +371,10 @@ add_defaults1(Mod,Func, GroupPath, SuiteInfo) ->
{value,{error,BadGr0Val,GrName}} ->
Gr0ErrStr = io_lib:format("~n*** ERROR *** "
"Invalid return value from "
- "~w:group(~w): ~p~n",
+ "~w:group(~tw): ~tp~n",
[Mod,GrName,BadGr0Val]),
- io:format(Gr0ErrStr, []),
- io:format(?def_gl, Gr0ErrStr, []),
+ io:format("~ts", [Gr0ErrStr]),
+ io:format(?def_gl, "~ts", [Gr0ErrStr]),
{group0_failed,bad_return_value};
_ ->
Args = if Func == init_per_group ; Func == end_per_group ->
@@ -393,10 +393,10 @@ add_defaults1(Mod,Func, GroupPath, SuiteInfo) ->
{error,BadTC0Val} ->
TC0ErrStr = io_lib:format("~n*** ERROR *** "
"Invalid return value from "
- "~w:~w/0: ~p~n",
+ "~w:~tw/0: ~tp~n",
[Mod,Func,BadTC0Val]),
- io:format(TC0ErrStr, []),
- io:format(?def_gl, TC0ErrStr, []),
+ io:format("~ts", [TC0ErrStr]),
+ io:format(?def_gl, "~ts", [TC0ErrStr]),
{testcase0_failed,bad_return_value};
_ ->
%% let test case info (also for all config funcs) override
@@ -649,7 +649,7 @@ try_set_default(Name,Key,Info,Where) ->
%%%-----------------------------------------------------------------
-%%% @spec end_tc(Mod,Func,Args) -> {ok,NewArgs}| {error,Reason} |
+%%% -spec end_tc(Mod,Func,Args) -> {ok,NewArgs}| {error,Reason} |
%%% {skip,Reason} | {auto_skip,Reason}
%%% Mod = atom()
%%% Func = atom()
@@ -657,7 +657,7 @@ try_set_default(Name,Key,Info,Where) ->
%%% NewArgs = list()
%%% Reason = term()
%%%
-%%% @doc Test server framework callback, called by the test_server
+%%% Test server framework callback, called by the test_server
%%% when a test case is finished.
end_tc(Mod, Fun, Args) ->
%% Have to keep end_tc/3 for backwards compatibility issues
@@ -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
@@ -903,15 +914,15 @@ tag(_Other) ->
ok.
%%%-----------------------------------------------------------------
-%%% @spec error_notification(Mod,Func,Args,Error) -> ok
+%%% -spec error_notification(Mod,Func,Args,Error) -> ok
%%% Mod = atom()
%%% Func = atom()
%%% Args = list()
%%% Error = term()
%%%
-%%% @doc This function is called as the result of testcase
-%%% <code>Func</code> in suite <code>Mod</code> crashing.
-%%% <code>Error</code> specifies the reason for failing.
+%%% This function is called as the result of testcase
+%%% Func in suite Mod crashing.
+%%% Error specifies the reason for failing.
error_notification(Mod,Func,_Args,{Error,Loc}) ->
ErrorSpec = case Error of
{What={_E,_R},Trace} when is_list(Trace) ->
@@ -921,9 +932,10 @@ error_notification(Mod,Func,_Args,{Error,Loc}) ->
end,
ErrorStr = case ErrorSpec of
{badmatch,Descr} ->
- Descr1 = lists:flatten(io_lib:format("~P",[Descr,10])),
- if length(Descr1) > 50 ->
- Descr2 = string:substr(Descr1,1,50),
+ Descr1 = io_lib:format("~tP",[Descr,10]),
+ DescrLength = string:length(Descr1),
+ if DescrLength > 50 ->
+ Descr2 = string:slice(Descr1,0,50),
io_lib:format("{badmatch,~ts...}",[Descr2]);
true ->
io_lib:format("{badmatch,~ts}",[Descr1])
@@ -931,15 +943,15 @@ error_notification(Mod,Func,_Args,{Error,Loc}) ->
{test_case_failed,Reason} ->
case (catch io_lib:format("{test_case_failed,~ts}", [Reason])) of
{'EXIT',_} ->
- io_lib:format("{test_case_failed,~p}", [Reason]);
+ io_lib:format("{test_case_failed,~tp}", [Reason]);
Result -> Result
end;
{'EXIT',_Reason} = EXIT ->
- io_lib:format("~P", [EXIT,5]);
+ io_lib:format("~tP", [EXIT,5]);
{Spec,_Reason} when is_atom(Spec) ->
- io_lib:format("~w", [Spec]);
+ io_lib:format("~tw", [Spec]);
Other ->
- io_lib:format("~P", [Other,5])
+ io_lib:format("~tP", [Other,5])
end,
ErrorHtml =
"<font color=\"brown\">" ++ ct_logs:escape_chars(ErrorStr) ++ "</font>",
@@ -971,11 +983,10 @@ error_notification(Mod,Func,_Args,{Error,Loc}) ->
end,
PrintError = fun(ErrorFormat, ErrorArgs) ->
- Div = "~n- - - - - - - - - - - - - - - - - - - "
- "- - - - - - - - - - - - - - - - - - - - -~n",
+ Div = "\n- - - - - - - - - - - - - - - - - - - "
+ "- - - - - - - - - - - - - - - - - - - - -\n",
ErrorStr2 = io_lib:format(ErrorFormat, ErrorArgs),
- io:format(?def_gl, lists:concat([Div,ErrorStr2,Div,"~n"]),
- []),
+ io:format(?def_gl, "~ts~n", [lists:concat([Div,ErrorStr2,Div])]),
Link =
"\n\n<a href=\"#end\">"
"Full error description and stacktrace"
@@ -984,7 +995,8 @@ error_notification(Mod,Func,_Args,{Error,Loc}) ->
ct_logs:tc_log(ct_error_notify,
?MAX_IMPORTANCE,
"CT Error Notification",
- ErrorHtml2++Link, [], [])
+ "~ts", [ErrorHtml2++Link],
+ [])
end,
case Loc of
[{?MODULE,error_in_suite}] ->
@@ -996,16 +1008,16 @@ error_notification(Mod,Func,_Args,{Error,Loc}) ->
%% if a function specified by all/0 does not exist, we
%% pick up undef here
[{LastMod,LastFunc}|_] when ErrorStr == "undef" ->
- PrintError("~w:~w could not be executed~nReason: ~ts",
+ PrintError("~w:~tw could not be executed~nReason: ~ts",
[LastMod,LastFunc,ErrorStr]);
[{LastMod,LastFunc}|_] ->
- PrintError("~w:~w failed~nReason: ~ts", [LastMod,LastFunc,ErrorStr]);
+ PrintError("~w:~tw failed~nReason: ~ts", [LastMod,LastFunc,ErrorStr]);
[{LastMod,LastFunc,LastLine}|_] ->
%% print error to console, we are only
%% interested in the last executed expression
- PrintError("~w:~w failed on line ~w~nReason: ~ts",
+ PrintError("~w:~tw failed on line ~w~nReason: ~ts",
[LastMod,LastFunc,LastLine,ErrorStr]),
case ct_util:read_suite_data({seq,Mod,Func}) of
@@ -1048,28 +1060,47 @@ group_or_func(Func, _Config) ->
Func.
%%%-----------------------------------------------------------------
-%%% @spec get_suite(Mod, Func) -> Tests
+%%% -spec get_suite(Mod, Func) -> Tests
%%%
-%%% @doc Called from test_server for every suite (<code>Func==all</code>)
-%%% and every test case. If the former, all test cases in the suite
-%%% should be returned.
+%%% Called from test_server for every suite (Func==all)
+%%% and every test case. If the former, all test cases in the suite
+%%% 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;
%%!============================================================
@@ -1079,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).
%%%-----------------------------------------------------------------
@@ -1160,27 +1211,54 @@ 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)++
- " can not 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 *** "
- "~w:all/0 failed: ~p~n",
+ "~w:all/0 failed: ~tp~n",
[Mod,ExitReason]),
- io:format(?def_gl, ErrStr, []),
+ io:format(?def_gl, "~ts", [ErrStr]),
%% save the error info so it doesn't get printed twice
ct_util:set_testdata_async({{error_in_suite,Mod},
ExitReason});
@@ -1191,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.
%%!============================================================
@@ -1294,8 +1352,8 @@ save_seq(Mod,Seq,SeqTCs,All) ->
check_private(Seq,TCs,All) ->
Bad = lists:filter(fun(TC) -> lists:member(TC,All) end, TCs),
if Bad /= [] ->
- Reason = io_lib:format("regular test cases not allowed in sequence ~p: "
- "~p",[Seq,Bad]),
+ Reason = io_lib:format("regular test cases not allowed in sequence ~tp: "
+ "~tp",[Seq,Bad]),
throw({error,list_to_atom(lists:flatten(Reason))});
true ->
ok
@@ -1312,7 +1370,7 @@ check_multiple(Mod,Seq,TCs) ->
end,TCs),
if Bad /= [] ->
Reason = io_lib:format("test cases found in multiple sequences: "
- "~p",[Bad]),
+ "~tp",[Bad]),
throw({error,list_to_atom(lists:flatten(Reason))});
true ->
ok
@@ -1340,21 +1398,21 @@ end_per_suite(_Config) ->
%% if the group config functions are missing in the suite,
%% use these instead
init_per_group(GroupName, Config) ->
- ct:comment(io_lib:format("start of ~p", [GroupName])),
- ct_logs:log("TEST INFO", "init_per_group/2 for ~w missing "
+ ct:comment(io_lib:format("start of ~tp", [GroupName])),
+ ct_logs:log("TEST INFO", "init_per_group/2 for ~tw missing "
"in suite, using default.",
[GroupName]),
Config.
end_per_group(GroupName, _) ->
- ct:comment(io_lib:format("end of ~p", [GroupName])),
- ct_logs:log("TEST INFO", "end_per_group/2 for ~w missing "
+ ct:comment(io_lib:format("end of ~tp", [GroupName])),
+ ct_logs:log("TEST INFO", "end_per_group/2 for ~tw missing "
"in suite, using default.",
[GroupName]),
ok.
%%%-----------------------------------------------------------------
-%%% @spec report(What,Data) -> ok
+%%% -spec report(What,Data) -> ok
report(What,Data) ->
case What of
loginfo ->
@@ -1518,14 +1576,14 @@ add_to_stats(Result) ->
ct_util:update_testdata(stats, Update).
%%%-----------------------------------------------------------------
-%%% @spec warn(What) -> true | false
+%%% -spec warn(What) -> true | false
warn(What) when What==nodes; What==processes ->
false;
warn(_What) ->
true.
%%%-----------------------------------------------------------------
-%%% @spec add_data_dir(File0, Config) -> File1
+%%% -spec add_data_dir(File0, Config) -> File1
add_data_dir(File,Config) when is_atom(File) ->
add_data_dir(atom_to_list(File),Config);
@@ -1544,7 +1602,7 @@ add_data_dir(File,Config) when is_list(File) ->
end.
%%%-----------------------------------------------------------------
-%%% @spec get_logopts() -> [LogOpt]
+%%% -spec get_logopts() -> [LogOpt]
get_logopts() ->
case ct_util:get_testdata(logopts) of
undefined ->
@@ -1554,12 +1612,12 @@ get_logopts() ->
end.
%%%-----------------------------------------------------------------
-%%% @spec format_comment(Comment) -> HtmlComment
+%%% -spec format_comment(Comment) -> HtmlComment
format_comment(Comment) ->
"<font color=\"green\">" ++ Comment ++ "</font>".
%%%-----------------------------------------------------------------
-%%% @spec get_html_wrapper(TestName, PrintLabel, Cwd) -> Header
+%%% -spec get_html_wrapper(TestName, PrintLabel, Cwd) -> Header
get_html_wrapper(TestName, PrintLabel, Cwd, TableCols) ->
get_html_wrapper(TestName, PrintLabel, Cwd, TableCols, utf8).
@@ -1567,6 +1625,77 @@ get_html_wrapper(TestName, PrintLabel, Cwd, TableCols, Encoding) ->
ct_logs:get_ts_html_wrapper(TestName, PrintLabel, Cwd, TableCols, Encoding).
%%%-----------------------------------------------------------------
-%%% @spec get_log_dir() -> {ok,LogDir}
+%%% -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_ftp.erl b/lib/common_test/src/ct_ftp.erl
index 84e664b387..93d1f88041 100644
--- a/lib/common_test/src/ct_ftp.erl
+++ b/lib/common_test/src/ct_ftp.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2003-2018. 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.
@@ -18,12 +18,6 @@
%% %CopyrightEnd%
%%
-%%% @doc FTP client module (based on the FTP support of the INETS application).
-%%%
-%%% @type connection() = handle() | ct:target_name()
-%%% @type handle() = ct_gen_conn:handle(). Handle for a specific
-%%% ftp connection.
-
-module(ct_ftp).
%% API
@@ -42,76 +36,14 @@
%%%=================================================================
%%% API
-%%%-----------------------------------------------------------------
-%%% @spec put(KeyOrName,LocalFile,RemoteFile) -> ok | {error,Reason}
-%%% KeyOrName = Key | Name
-%%% Key = atom()
-%%% Name = ct:target_name()
-%%% LocalFile = string()
-%%% RemoteFile = string()
-%%%
-%%% @doc Open a ftp connection and send a file to the remote host.
-%%%
-%%% <p><code>LocalFile</code> and <code>RemoteFile</code> must be
-%%% absolute paths.</p>
-%%%
-%%% <p>If the target host is a "special" node, the ftp address must be
-%%% specified in the config file like this:</p>
-%%% <pre>
-%%% {node,[{ftp,IpAddr}]}.</pre>
-%%%
-%%% <p>If the target host is something else, e.g. a unix host, the
-%%% config file must also include the username and password (both
-%%% strings):</p>
-%%% <pre>
-%%% {unix,[{ftp,IpAddr},
-%%% {username,Username},
-%%% {password,Password}]}.</pre>
-%%% @see ct:require/2
put(KeyOrName,LocalFile,RemoteFile) ->
Fun = fun(Ftp) -> send(Ftp,LocalFile,RemoteFile) end,
open_and_do(KeyOrName,Fun).
-%%%-----------------------------------------------------------------
-%%% @spec get(KeyOrName,RemoteFile,LocalFile) -> ok | {error,Reason}
-%%% KeyOrName = Key | Name
-%%% Key = atom()
-%%% Name = ct:target_name()
-%%% RemoteFile = string()
-%%% LocalFile = string()
-%%%
-%%% @doc Open a ftp connection and fetch a file from the remote host.
-%%%
-%%% <p><code>RemoteFile</code> and <code>LocalFile</code> must be
-%%% absolute paths.</p>
-%%%
-%%% <p>The config file must be as for put/3.</p>
-%%% @see put/3
-%%% @see ct:require/2
get(KeyOrName,RemoteFile,LocalFile) ->
Fun = fun(Ftp) -> recv(Ftp,RemoteFile,LocalFile) end,
open_and_do(KeyOrName,Fun).
-
-%%%-----------------------------------------------------------------
-%%% @spec open(KeyOrName) -> {ok,Handle} | {error,Reason}
-%%% KeyOrName = Key | Name
-%%% Key = atom()
-%%% Name = ct:target_name()
-%%% Handle = handle()
-%%%
-%%% @doc Open an FTP connection to the specified node.
-%%% <p>You can open one connection for a particular <code>Name</code> and
-%%% use the same name as reference for all subsequent operations. If you
-%%% want the connection to be associated with <code>Handle</code> instead
-%%% (in case you need to open multiple connections to a host for example),
-%%% simply use <code>Key</code>, the configuration variable name, to
-%%% specify the target. Note that a connection that has no associated target
-%%% name can only be closed with the handle value.</p>
-%%%
-%%% <p>See <c>ct:require/2</c> for how to create a new <c>Name</c></p>
-%%%
-%%% @see ct:require/2
open(KeyOrName) ->
case ct_util:get_key_from_name(KeyOrName) of
{ok,node} ->
@@ -119,19 +51,19 @@ open(KeyOrName) ->
_ ->
case ct:get_config(KeyOrName) of
undefined ->
- log(heading(open,KeyOrName),"Failed: ~p\n",
+ log(heading(open,KeyOrName),"Failed: ~tp\n",
[{not_available,KeyOrName}]),
{error,{not_available,KeyOrName}};
_ ->
case ct:get_config({KeyOrName,username}) of
undefined ->
- log(heading(open,KeyOrName),"Failed: ~p\n",
+ log(heading(open,KeyOrName),"Failed: ~tp\n",
[{not_available,{KeyOrName,username}}]),
{error,{not_available,{KeyOrName,username}}};
Username ->
case ct:get_config({KeyOrName,password}) of
undefined ->
- log(heading(open,KeyOrName),"Failed: ~p\n",
+ log(heading(open,KeyOrName),"Failed: ~tp\n",
[{not_available,{KeyOrName,password}}]),
{error,{not_available,{KeyOrName,password}}};
Password ->
@@ -145,32 +77,16 @@ open(KeyOrName,Username,Password) ->
log(heading(open,KeyOrName),"",[]),
case ct:get_config({KeyOrName,ftp}) of
undefined ->
- log(heading(open,KeyOrName),"Failed: ~p\n",
+ log(heading(open,KeyOrName),"Failed: ~tp\n",
[{not_available,{KeyOrName,ftp}}]),
{error,{not_available,{KeyOrName,ftp}}};
Addr ->
ct_gen_conn:start(KeyOrName,full_addr(Addr),{Username,Password},?MODULE)
end.
-
-%%%-----------------------------------------------------------------
-%%% @spec send(Connection,LocalFile) -> ok | {error,Reason}
-%%%
-%%% @doc Send a file over FTP.
-%%% <p>The file will get the same name on the remote host.</p>
-%%% @see send/3
send(Connection,LocalFile) ->
send(Connection,LocalFile,filename:basename(LocalFile)).
-%%%-----------------------------------------------------------------
-%%% @spec send(Connection,LocalFile,RemoteFile) -> ok | {error,Reason}
-%%% Connection = connection()
-%%% LocalFile = string()
-%%% RemoteFile = string()
-%%%
-%%% @doc Send a file over FTP.
-%%%
-%%% <p>The file will be named <code>RemoteFile</code> on the remote host.</p>
send(Connection,LocalFile,RemoteFile) ->
case get_handle(Connection) of
{ok,Pid} ->
@@ -179,24 +95,9 @@ send(Connection,LocalFile,RemoteFile) ->
Error
end.
-%%%-----------------------------------------------------------------
-%%% @spec recv(Connection,RemoteFile) -> ok | {error,Reason}
-%%%
-%%% @doc Fetch a file over FTP.
-%%% <p>The file will get the same name on the local host.</p>
-%%% @see recv/3
recv(Connection,RemoteFile) ->
recv(Connection,RemoteFile,filename:basename(RemoteFile)).
-%%%-----------------------------------------------------------------
-%%% @spec recv(Connection,RemoteFile,LocalFile) -> ok | {error,Reason}
-%%% Connection = connection()
-%%% RemoteFile = string()
-%%% LocalFile = string()
-%%%
-%%% @doc Fetch a file over FTP.
-%%%
-%%% <p>The file will be named <code>LocalFile</code> on the local host.</p>
recv(Connection,RemoteFile,LocalFile) ->
case get_handle(Connection) of
{ok,Pid} ->
@@ -205,12 +106,6 @@ recv(Connection,RemoteFile,LocalFile) ->
Error
end.
-%%%-----------------------------------------------------------------
-%%% @spec cd(Connection,Dir) -> ok | {error,Reason}
-%%% Connection = connection()
-%%% Dir = string()
-%%%
-%%% @doc Change directory on remote host.
cd(Connection,Dir) ->
case get_handle(Connection) of
{ok,Pid} ->
@@ -219,13 +114,6 @@ cd(Connection,Dir) ->
Error
end.
-%%%-----------------------------------------------------------------
-%%% @spec ls(Connection,Dir) -> {ok,Listing} | {error,Reason}
-%%% Connection = connection()
-%%% Dir = string()
-%%% Listing = string()
-%%%
-%%% @doc List the directory Dir.
ls(Connection,Dir) ->
case get_handle(Connection) of
{ok,Pid} ->
@@ -234,12 +122,6 @@ ls(Connection,Dir) ->
Error
end.
-%%%-----------------------------------------------------------------
-%%% @spec type(Connection,Type) -> ok | {error,Reason}
-%%% Connection = connection()
-%%% Type = ascii | binary
-%%%
-%%% @doc Change file transfer type
type(Connection,Type) ->
case get_handle(Connection) of
{ok,Pid} ->
@@ -248,12 +130,6 @@ type(Connection,Type) ->
Error
end.
-%%%-----------------------------------------------------------------
-%%% @spec delete(Connection,File) -> ok | {error,Reason}
-%%% Connection = connection()
-%%% File = string()
-%%%
-%%% @doc Delete a file on remote host
delete(Connection,File) ->
case get_handle(Connection) of
{ok,Pid} ->
@@ -262,11 +138,6 @@ delete(Connection,File) ->
Error
end.
-%%%-----------------------------------------------------------------
-%%% @spec close(Connection) -> ok | {error,Reason}
-%%% Connection = connection()
-%%%
-%%% @doc Close the FTP connection.
close(Connection) ->
case get_handle(Connection) of
{ok,Pid} ->
@@ -279,21 +150,20 @@ close(Connection) ->
%%%=================================================================
%%% Callback functions
-%% @hidden
init(KeyOrName,{IP,Port},{Username,Password}) ->
case ftp_connect(IP,Port,Username,Password) of
{ok,FtpPid} ->
log(heading(init,KeyOrName),
- "Opened ftp connection:\nIP: ~p\nUsername: ~p\nPassword: ~p\n",
- [IP,Username,lists:duplicate(length(Password),$*)]),
+ "Opened ftp connection:\nIP: ~tp\nUsername: ~tp\nPassword: ~p\n",
+ [IP,Username,lists:duplicate(string:length(Password),$*)]),
{ok,FtpPid,#state{ftp_pid=FtpPid,target_name=KeyOrName}};
Error ->
Error
end.
ftp_connect(IP,Port,Username,Password) ->
- _ = inets:start(),
- case inets:start(ftpc,[{host,IP},{port,Port}]) of
+ _ = ftp:start(),
+ case ftp:start_service([{host,IP},{port,Port}]) of
{ok,FtpPid} ->
case ftp:user(FtpPid,Username,Password) of
ok ->
@@ -305,43 +175,40 @@ ftp_connect(IP,Port,Username,Password) ->
{error,{open,Reason}}
end.
-%% @hidden
handle_msg({send,LocalFile,RemoteFile},State) ->
log(heading(send,State#state.target_name),
- "LocalFile: ~p\nRemoteFile: ~p\n",[LocalFile,RemoteFile]),
+ "LocalFile: ~tp\nRemoteFile: ~tp\n",[LocalFile,RemoteFile]),
Result = ftp:send(State#state.ftp_pid,LocalFile,RemoteFile),
{Result,State};
handle_msg({recv,RemoteFile,LocalFile},State) ->
log(heading(recv,State#state.target_name),
- "RemoteFile: ~p\nLocalFile: ~p\n",[RemoteFile,LocalFile]),
+ "RemoteFile: ~tp\nLocalFile: ~tp\n",[RemoteFile,LocalFile]),
Result = ftp:recv(State#state.ftp_pid,RemoteFile,LocalFile),
{Result,State};
handle_msg({cd,Dir},State) ->
- log(heading(cd,State#state.target_name),"Dir: ~p\n",[Dir]),
+ log(heading(cd,State#state.target_name),"Dir: ~tp\n",[Dir]),
Result = ftp:cd(State#state.ftp_pid,Dir),
{Result,State};
handle_msg({ls,Dir},State) ->
- log(heading(ls,State#state.target_name),"Dir: ~p\n",[Dir]),
+ log(heading(ls,State#state.target_name),"Dir: ~tp\n",[Dir]),
Result = ftp:ls(State#state.ftp_pid,Dir),
{Result,State};
handle_msg({type,Type},State) ->
- log(heading(type,State#state.target_name),"Type: ~p\n",[Type]),
+ log(heading(type,State#state.target_name),"Type: ~tp\n",[Type]),
Result = ftp:type(State#state.ftp_pid,Type),
{Result,State};
handle_msg({delete,File},State) ->
- log(heading(delete,State#state.target_name),"Delete file: ~p\n",[File]),
+ log(heading(delete,State#state.target_name),"Delete file: ~tp\n",[File]),
Result = ftp:delete(State#state.ftp_pid,File),
{Result,State}.
-%% @hidden
reconnect(_Addr,_State) ->
{error,no_reconnection_of_ftp}.
-%% @hidden
terminate(FtpPid,State) ->
log(heading(terminate,State#state.target_name),
"Closing FTP connection.\nHandle: ~p\n",[FtpPid]),
- inets:stop(ftpc,FtpPid).
+ ftp:stop_service(FtpPid).
%%%=================================================================
@@ -368,7 +235,7 @@ call(Pid,Msg) ->
heading(Function,Name) ->
- io_lib:format("ct_ftp:~w ~p",[Function,Name]).
+ io_lib:format("ct_ftp:~tw ~tp",[Function,Name]).
log(Heading,Str,Args) ->
ct_gen_conn:log(Heading,Str,Args).
diff --git a/lib/common_test/src/ct_gen_conn.erl b/lib/common_test/src/ct_gen_conn.erl
index 8b59d3ab23..1ab9946d96 100644
--- a/lib/common_test/src/ct_gen_conn.erl
+++ b/lib/common_test/src/ct_gen_conn.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2003-2018. 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.
@@ -18,9 +18,9 @@
%% %CopyrightEnd%
%%
-%%% @doc Generic connection owner process.
+%%% Generic connection owner process.
%%%
-%%% @type handle() = pid(). A handle for using a connection implemented
+%%% -type handle() = pid(). A handle for using a connection implemented
%%% with ct_gen_conn.erl.
-module(ct_gen_conn).
@@ -29,13 +29,6 @@
-export([call/2, call/3, return/2, do_within_time/2]).
-export([log/3, start_log/1, cont_log/2, cont_log_no_timestamp/2, end_log/0]).
-%%----------------------------------------------------------------------
-%% Exported types
-%%----------------------------------------------------------------------
--export_type([server_id/0,
- target_name/0,
- key_or_name/0]).
-
-ifdef(debug).
-define(dbg,true).
-else.
@@ -54,20 +47,8 @@
cb_state,
ct_util_server}).
-%%------------------------------------------------------------------
-%% Type declarations
-%%------------------------------------------------------------------
--type server_id() :: atom().
-%% A `ServerId' which exists in a configuration file.
--type target_name() :: atom().
-%% A name which is associated to a `server_id()' via a
-%% `require' statement or a call to {@link ct:require/2} in the
-%% test suite.
--type key_or_name() :: server_id() | target_name().
-
-
%%%-----------------------------------------------------------------
-%%% @spec start(Address,InitData,CallbackMod,Opts) ->
+%%% -spec start(Address,InitData,CallbackMod,Opts) ->
%%% {ok,Handle} | {error,Reason}
%%% Name = term()
%%% CallbackMod = atom()
@@ -77,57 +58,52 @@
%%% Opt = {name,Name} | {use_existing_connection,boolean()} |
%%% {reconnect,boolean()} | {forward_messages,boolean()}
%%%
-%%% @doc Open a connection and start the generic connection owner process.
+%%% Open a connection and start the generic connection owner process.
%%%
-%%% <p>The <code>CallbackMod</code> is a specific callback module for
+%%% The CallbackMod is a specific callback module for
%%% each type of connection (e.g. telnet, ftp,...). It must export the
-%%% function <code>init/3</code> which takes the arguments
-%%% <code>Name</code>, <code>Addresse</code>) and
-%%% <code>InitData</code> and returna
-%%% <code>{ok,ConnectionPid,State}</code> or
-%%% <code>{error,Reason}</code>.</p>
+%%% function init/3 which takes the arguments
+%%% Name, Addresse) and
+%%% InitData and returna
+%%% {ok,ConnectionPid,State} or
+%%% {error,Reason}.
%%%
-%%% If no name is given, the <code>Name</code> argument in init/3 will
-%%% have the value <code>undefined</code>.
+%%% If no name is given, the Name argument in init/3 will
+%%% have the value undefined.
%%%
%%% The callback modules must also export
-%%% ```
+%%%
%%% handle_msg(Msg,From,State) -> {reply,Reply,State} |
%%% {noreply,State} |
%%% {stop,Reply,State}
%%% terminate(ConnectionPid,State) -> term()
%%% close(Handle) -> term()
-%%% '''
%%%
-%%% The <code>close/1</code> callback function is actually a callback
+%%% The close/1 callback function is actually a callback
%%% for ct_util, for closing registered connections when
-%%% ct_util_server is terminated. <code>Handle</code> is the Pid of
+%%% ct_util_server is terminated. Handle is the Pid of
%%% the ct_gen_conn process.
%%%
-%%% If option <code>reconnect</code> is <code>true</code>, then the
+%%% If option reconnect is true, then the
%%% callback must also export
-%%% ```
+%%%
%%% reconnect(Address,State) -> {ok,ConnectionPid,State}
-%%% '''
%%%
-%%% If option <code>forward_messages</code> is <ocde>true</code>, then
+%%% If option forward_messages is <ocde>true, then
%%% the callback must also export
-%%% ```
+%%%
%%% handle_msg(Msg,State) -> {noreply,State} | {stop,State}
-%%% '''
%%%
%%% An old interface still exists. This is used by ct_telnet, ct_ftp
%%% and ct_ssh. The start function then has an explicit
-%%% <code>Name</code> argument, and no <code>Opts</code> argument. The
+%%% Name argument, and no Opts argument. The
%%% callback must export:
%%%
-%%% ```
%%% init(Name,Address,InitData) -> {ok,ConnectionPid,State}
%%% handle_msg(Msg,State) -> {Reply,State}
%%% reconnect(Address,State) -> {ok,ConnectionPid,State}
%%% terminate(ConnectionPid,State) -> term()
%%% close(Handle) -> term()
-%%% '''
%%%
start(Address,InitData,CallbackMod,Opts) when is_list(Opts) ->
do_start(Address,InitData,CallbackMod,Opts);
@@ -135,79 +111,81 @@ start(Name,Address,InitData,CallbackMod) ->
do_start(Address,InitData,CallbackMod,[{name,Name},{old,true}]).
%%%-----------------------------------------------------------------
-%%% @spec stop(Handle) -> ok
+%%% -spec stop(Handle) -> ok
%%% Handle = handle()
%%%
-%%% @doc Close the connection and stop the process managing it.
+%%% Close the connection and stop the process managing it.
stop(Handle) ->
call(Handle,stop,5000).
%%%-----------------------------------------------------------------
-%%% @spec get_conn_pid(Handle) -> ok
+%%% -spec get_conn_pid(Handle) -> ok
%%% Handle = handle()
%%%
-%%% @doc Return the connection pid associated with Handle
+%%% Return the connection pid associated with Handle
get_conn_pid(Handle) ->
call(Handle,get_conn_pid).
%%%-----------------------------------------------------------------
-%%% @spec log(Heading,Format,Args) -> ok
+%%% -spec log(Heading,Format,Args) -> ok
%%%
-%%% @doc Log activities on the current connection (tool-internal use only).
-%%% @see ct_logs:log/3
+%%% Log activities on the current connection (tool-internal use only).
+%%% See ct_logs:log/3
log(Heading,Format,Args) ->
log(log,[Heading,Format,Args]).
%%%-----------------------------------------------------------------
-%%% @spec start_log(Heading) -> ok
+%%% -spec start_log(Heading) -> ok
%%%
-%%% @doc Log activities on the current connection (tool-internal use only).
-%%% @see ct_logs:start_log/1
+%%% Log activities on the current connection (tool-internal use only).
+%%% See ct_logs:start_log/1
start_log(Heading) ->
log(start_log,[Heading]).
%%%-----------------------------------------------------------------
-%%% @spec cont_log(Format,Args) -> ok
+%%% -spec cont_log(Format,Args) -> ok
%%%
-%%% @doc Log activities on the current connection (tool-internal use only).
-%%% @see ct_logs:cont_log/2
+%%% Log activities on the current connection (tool-internal use only).
+%%% See ct_logs:cont_log/2
cont_log(Format,Args) ->
log(cont_log,[Format,Args]).
%%%-----------------------------------------------------------------
-%%% @spec cont_log_no_timestamp(Format,Args) -> ok
+%%% -spec cont_log_no_timestamp(Format,Args) -> ok
%%%
-%%% @doc Log activities on the current connection (tool-internal use only).
-%%% @see ct_logs:cont_log/2
+%%% Log activities on the current connection (tool-internal use only).
+%%% See ct_logs:cont_log/2
cont_log_no_timestamp(Format,Args) ->
log(cont_log_no_timestamp,[Format,Args]).
%%%-----------------------------------------------------------------
-%%% @spec end_log() -> ok
+%%% -spec end_log() -> ok
%%%
-%%% @doc Log activities on the current connection (tool-internal use only).
-%%% @see ct_logs:end_log/0
+%%% Log activities on the current connection (tool-internal use only).
+%%% See ct_logs:end_log/0
end_log() ->
log(end_log,[]).
%%%-----------------------------------------------------------------
-%%% @spec do_within_time(Fun,Timeout) -> FunResult | {error,Reason}
+%%% -spec do_within_time(Fun,Timeout) -> FunResult | {error,Reason}
%%% Fun = function()
%%% Timeout = integer()
%%%
-%%% @doc Execute a function within a limited time (tool-internal use only).
+%%% Execute a function within a limited time (tool-internal use only).
%%%
-%%% <p>Execute the given <code>Fun</code>, but interrupt if it takes
-%%% more than <code>Timeout</code> milliseconds.</p>
+%%% Execute the given Fun, but interrupt if it takes
+%%% more than Timeout milliseconds.
%%%
-%%% <p>The execution is also interrupted if the connection is
-%%% closed.</p>
+%%% The execution is also interrupted if the connection is
+%%% closed.
do_within_time(Fun,Timeout) ->
Self = self(),
Silent = get(silent),
- TmpPid = spawn_link(fun() -> put(silent,Silent),
- R = Fun(),
- Self ! {self(),R}
+ TmpPid = spawn_link(fun() ->
+ ct_util:mark_process(),
+ put(silent,Silent),
+ R = Fun(),
+ Self ! {self(),R}
end),
ConnPid = get(conn_pid),
receive
@@ -266,7 +244,7 @@ do_start(Opts) ->
Error;
{'DOWN',MRef,process,_,Reason} ->
log("ct_gen_conn:start",
- "Connection process died: ~p\n",
+ "Connection process died: ~tp\n",
[Reason]),
{error,{connection_process_died,Reason}}
end.
@@ -320,6 +298,7 @@ return({To,Ref},Result) ->
init_gen(Parent,Opts) ->
process_flag(trap_exit,true),
+ ct_util:mark_process(),
put(silent,false),
try (Opts#gen_opts.callback):init(Opts#gen_opts.name,
Opts#gen_opts.address,
@@ -346,7 +325,7 @@ loop(Opts) ->
case Opts#gen_opts.reconnect of
true ->
log("Connection down!\nOpening new!",
- "Reason: ~p\nAddress: ~p\n",
+ "Reason: ~tp\nAddress: ~tp\n",
[Reason,Opts#gen_opts.address]),
case reconnect(Opts) of
{ok, NewPid, NewState} ->
@@ -357,12 +336,12 @@ loop(Opts) ->
Error ->
ct_util:unregister_connection(self()),
log("Reconnect failed. Giving up!",
- "Reason: ~p\n",
+ "Reason: ~tp\n",
[Error])
end;
false ->
ct_util:unregister_connection(self()),
- log("Connection closed!","Reason: ~p\n",[Reason])
+ log("Connection closed!","Reason: ~tp\n",[Reason])
end;
{'EXIT',Pid,Reason} ->
case Opts#gen_opts.ct_util_server of
@@ -373,8 +352,9 @@ loop(Opts) ->
end;
{stop, From} ->
ct_util:unregister_connection(self()),
- (Opts#gen_opts.callback):terminate(Opts#gen_opts.conn_pid,
- Opts#gen_opts.cb_state),
+ ConnPid = Opts#gen_opts.conn_pid,
+ unlink(ConnPid),
+ (Opts#gen_opts.callback):terminate(ConnPid,Opts#gen_opts.cb_state),
return(From,ok),
ok;
{{retry,{Error,_Name,CPid,_Msg}}, From} when
@@ -411,8 +391,9 @@ loop(Opts) ->
loop(Opts#gen_opts{cb_state=NewState});
{stop,Reply,NewState} ->
ct_util:unregister_connection(self()),
- (Opts#gen_opts.callback):terminate(Opts#gen_opts.conn_pid,
- NewState),
+ ConnPid = Opts#gen_opts.conn_pid,
+ unlink(ConnPid),
+ (Opts#gen_opts.callback):terminate(ConnPid,NewState),
return(From,Reply)
end;
Msg when Opts#gen_opts.forward==true ->
@@ -422,8 +403,9 @@ loop(Opts) ->
loop(Opts#gen_opts{cb_state=NewState});
{stop,NewState} ->
ct_util:unregister_connection(self()),
- (Opts#gen_opts.callback):terminate(Opts#gen_opts.conn_pid,
- NewState)
+ ConnPid = Opts#gen_opts.conn_pid,
+ unlink(ConnPid),
+ (Opts#gen_opts.callback):terminate(ConnPid,NewState)
end
end.
diff --git a/lib/common_test/src/ct_groups.erl b/lib/common_test/src/ct_groups.erl
index 333151ee1b..f4b12c41c0 100644
--- a/lib/common_test/src/ct_groups.erl
+++ b/lib/common_test/src/ct_groups.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2018. 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.
@@ -18,11 +18,9 @@
%% %CopyrightEnd%
%%
-%%% @doc Common Test Framework callback module.
-%%%
-%%% <p>This module contains CT internal help functions for searching
-%%% through groups specification trees and producing resulting
-%%% tests.</p>
+%%% This module contains CT internal help functions for searching
+%%% through groups specification trees and producing resulting
+%%% tests.
-module(ct_groups).
@@ -103,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
@@ -174,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 ->
@@ -190,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 -> []
@@ -210,7 +237,7 @@ find(Mod, _GrNames, _TCs, [BadTerm | _Gs], Known, _Defs, _FindAll) ->
"group "++atom_to_list(lists:last(Known))++
" in "++atom_to_list(Mod)++":groups/0"
end,
- Term = io_lib:format("~p", [BadTerm]),
+ Term = io_lib:format("~tp", [BadTerm]),
E = "Bad term "++lists:flatten(Term)++" in "++Where,
throw({error,list_to_atom(E)});
@@ -291,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,_}) ->
@@ -447,7 +484,7 @@ make_conf(Mod, Name, Props, TestSpec) ->
{false,false} ->
ct_logs:log("TEST INFO", "init_per_group/2 and "
"end_per_group/2 missing for group "
- "~w in ~w, using default.",
+ "~tw in ~w, using default.",
[Name,Mod]),
{{ct_framework,init_per_group},
{ct_framework,end_per_group},
diff --git a/lib/common_test/src/ct_hooks.erl b/lib/common_test/src/ct_hooks.erl
index 8cdc6d8c75..94551d6815 100644
--- a/lib/common_test/src/ct_hooks.erl
+++ b/lib/common_test/src/ct_hooks.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2018. 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.
@@ -18,14 +18,12 @@
%% %CopyrightEnd%
%%
-%%% @doc Common Test Framework test execution control module.
-%%%
-%%% <p>This module is a proxy for calling and handling common test hooks.</p>
-
-module(ct_hooks).
%% API Exports
-export([init/1]).
+-export([groups/2]).
+-export([all/2]).
-export([init_tc/3]).
-export([end_tc/5]).
-export([terminate/1]).
@@ -41,20 +39,61 @@
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
%% -------------------------------------------------------------------------
-%% @doc Called before any suites are started
-spec init(State :: term()) -> ok |
{fail, Reason :: term()}.
init(Opts) ->
call(get_builtin_hooks(Opts) ++ get_new_hooks(Opts, undefined),
ok, init, []).
-%% @doc Called after all suites are done.
+%% 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) ->
@@ -63,8 +102,6 @@ terminate(Hooks) ->
ct_hooks_terminate_dummy, terminate, Hooks),
ok.
-%% @doc Called as each test case is started. This includes all configuration
-%% tests.
-spec init_tc(Mod :: atom(),
FuncSpec :: atom() |
{ConfigFunc :: init_per_testcase | end_per_testcase,
@@ -88,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) ->
@@ -104,8 +142,6 @@ init_tc(Mod, {end_per_testcase,TC}, Config) ->
init_tc(Mod, TC = error_in_suite, Config) ->
call(fun call_generic_fallback/3, Config, [pre_init_per_testcase, Mod, TC]).
-%% @doc Called as each test case is completed. This includes all configuration
-%% tests.
-spec end_tc(Mod :: atom(),
FuncSpec :: atom() |
{ConfigFunc :: init_per_testcase | end_per_testcase,
@@ -163,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 } };
@@ -194,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],
@@ -228,17 +276,22 @@ 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}]}
end,
call(resort(NewRest,NewHooks,Meta), Config, Meta, NewHooks)
- catch Error:Reason ->
- Trace = erlang:get_stacktrace(),
- ct_logs:log("Suite Hook","Failed to start a CTH: ~p:~p",
+ 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);
@@ -278,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.
@@ -306,7 +363,16 @@ terminate_if_scope_ends(HookId, Function0, Hooks) ->
Function = strip_config(Function0),
case lists:keyfind(HookId, #ct_hook_config.id, Hooks) of
#ct_hook_config{ id = HookId, scope = Function} = Hook ->
- terminate([Hook]),
+ case Function of
+ [AllOrGroup,_] when AllOrGroup=:=post_all;
+ AllOrGroup=:=post_groups ->
+ %% The scope only contains one function (post_all
+ %% or post_groups), and init has not been called,
+ %% so skip terminate as well.
+ ok;
+ _ ->
+ terminate([Hook])
+ end,
lists:keydelete(HookId, #ct_hook_config.id, Hooks);
_ ->
Hooks
@@ -364,6 +430,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).
@@ -422,13 +489,12 @@ catch_apply(M,F,A, Default, Fallback) ->
catch_apply(M,F,A) ->
try
erlang:apply(M,F,A)
- catch _:Reason ->
- Trace = erlang:get_stacktrace(),
- ct_logs:log("Suite Hook","Call to CTH failed: ~w:~p",
+ catch _:Reason:Trace ->
+ ct_logs:log("Suite Hook","Call to CTH failed: ~w:~tp",
[error,{Reason,Trace}]),
throw({error_in_cth_call,
lists:flatten(
- io_lib:format("~w:~w/~w CTH call failed",
+ io_lib:format("~w:~tw/~w CTH call failed",
[M,F,length(A)]))})
end.
diff --git a/lib/common_test/src/ct_hooks_lock.erl b/lib/common_test/src/ct_hooks_lock.erl
index fea298e535..be50a33e01 100644
--- a/lib/common_test/src/ct_hooks_lock.erl
+++ b/lib/common_test/src/ct_hooks_lock.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2018. 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.
@@ -18,11 +18,6 @@
%% %CopyrightEnd%
%%
-%%% @doc Common Test Framework test execution control module.
-%%%
-%%% <p>This module is a proxy for calling and handling locks in
-%%% common test hooks.</p>
-
-module(ct_hooks_lock).
-behaviour(gen_server).
@@ -42,7 +37,6 @@
%%% API
%%%===================================================================
-%% @doc Starts the server
start(Id) ->
case gen_server:start({local, ?SERVER}, ?MODULE, Id, []) of
{error,{already_started, Pid}} ->
@@ -76,11 +70,10 @@ release() ->
%%% gen_server callbacks
%%%===================================================================
-%% @doc Initiates the server
init(Id) ->
+ ct_util:mark_process(),
{ok, #state{ id = Id }}.
-%% @doc Handling call messages
handle_call({stop,Id}, _From, #state{ id = Id, requests = Reqs } = State) ->
_ = [gen_server:reply(Req, locker_stopped) || {Req,_ReqId} <- Reqs],
{stop, normal, stopped, State};
@@ -107,11 +100,9 @@ handle_call({release, Pid}, _From,
handle_call({release, _Pid}, _From, State) ->
{reply, not_locked, State}.
-%% @doc Handling cast messages
handle_cast(_Msg, State) ->
{noreply, State}.
-%% @doc Handling all non call/cast messages
handle_info({'DOWN',Ref,process,Pid,_},
#state{ locked = {true, Pid, Ref},
requests = [{NextFrom,NextPid}|Rest] } = State) ->
@@ -120,11 +111,9 @@ handle_info({'DOWN',Ref,process,Pid,_},
{noreply,State#state{ locked = {true, NextPid, NextRef},
requests = Rest } }.
-%% @doc This function is called by a gen_server when it is about to terminate.
terminate(_Reason, _State) ->
ok.
-%% @doc Convert process state when code is changed
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl
index 09ad709da5..ca262b350f 100644
--- a/lib/common_test/src/ct_logs.erl
+++ b/lib/common_test/src/ct_logs.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2003-2018. 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.
@@ -18,14 +18,12 @@
%% %CopyrightEnd%
%%
-%%% @doc Logging functionality for Common Test Framework.
+%%% Logging functionality for Common Test Framework.
%%%
-%%% <p>This module implements
-%%% <ul>
-%%% <li>Internal logging of activities in Common Test Framework</li>
-%%% <li>Compilation of test results into index pages on several levels</li>
-%%% </ul>
-%%% </p>
+%%% This module implements:
+%%%
+%%% Internal logging of activities in Common Test Framework, and
+%%% Compilation of test results into index pages on several levels
-module(ct_logs).
@@ -41,6 +39,7 @@
-export([xhtml/2, locate_priv_file/1, make_relative/1]).
-export([insert_javascript/1]).
-export([uri/1]).
+-export([parse_keep_logs/1]).
%% Logging stuff directly from testcase
-export([tc_log/3, tc_log/4, tc_log/5, tc_log/6,
@@ -83,17 +82,17 @@
tests = []}).
%%%-----------------------------------------------------------------
-%%% @spec init(Mode, Verbosity) -> Result
+%%% -spec init(Mode, Verbosity) -> Result
%%% Mode = normal | interactive
%%% Result = {StartTime,LogDir}
%%% StartTime = term()
%%% LogDir = string()
%%%
-%%% @doc Initiate the logging mechanism (tool-internal use only).
+%%% Initiate the logging mechanism (tool-internal use only).
%%%
-%%% <p>This function is called by ct_util.erl when testing is
-%%% started. A new directory named ct_run.&lt;timestamp&gt; is created
-%%% and all logs are stored under this directory.</p>
+%%% This function is called by ct_util.erl when testing is
+%%% started. A new directory named ct_run.<timestamp> is created
+%%% and all logs are stored under this directory.
%%%
init(Mode, Verbosity) ->
Self = self(),
@@ -128,9 +127,9 @@ datestr_from_dirname([]) ->
"".
%%%-----------------------------------------------------------------
-%%% @spec close(Info, StartDir) -> ok
+%%% -spec close(Info, StartDir) -> ok
%%%
-%%% @doc Create index pages with test results and close the CT Log
+%%% Create index pages with test results and close the CT Log
%%% (tool-internal use only).
close(Info, StartDir) ->
%% close executes on the ct_util process, not on the logger process
@@ -138,7 +137,7 @@ close(Info, StartDir) ->
LogCacheBin =
case make_last_run_index() of
{error, Reason} -> % log server not responding
- io:format("Warning! ct_logs not responding: ~p~n", [Reason]),
+ io:format("Warning! ct_logs not responding: ~tp~n", [Reason]),
undefined;
LCB ->
LCB
@@ -150,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,
@@ -174,7 +173,7 @@ close(Info, StartDir) ->
ok ->
ok;
Error ->
- io:format("Warning! Cleanup failed: ~p~n", [Error])
+ io:format("Warning! Cleanup failed: ~tp~n", [Error])
end,
_ = make_all_suites_index(stop),
make_all_runs_index(stop),
@@ -203,22 +202,22 @@ close(Info, StartDir) ->
ok.
%%%-----------------------------------------------------------------
-%%% @spec set_stylesheet(TC,SSFile) -> ok
+%%% -spec set_stylesheet(TC,SSFile) -> ok
set_stylesheet(TC, SSFile) ->
cast({set_stylesheet,TC,SSFile}).
%%%-----------------------------------------------------------------
-%%% @spec clear_stylesheet(TC) -> ok
+%%% -spec clear_stylesheet(TC) -> ok
clear_stylesheet(TC) ->
cast({clear_stylesheet,TC}).
%%%-----------------------------------------------------------------
-%%% @spec get_log_dir() -> {ok,Dir} | {error,Reason}
+%%% -spec get_log_dir() -> {ok,Dir} | {error,Reason}
get_log_dir() ->
get_log_dir(false).
%%%-----------------------------------------------------------------
-%%% @spec get_log_dir(ReturnAbsName) -> {ok,Dir} | {error,Reason}
+%%% -spec get_log_dir(ReturnAbsName) -> {ok,Dir} | {error,Reason}
get_log_dir(ReturnAbsName) ->
case call({get_log_dir,ReturnAbsName}) of
{error,does_not_exist} when ReturnAbsName == true ->
@@ -277,58 +276,58 @@ get_format_args(Content) ->
end, Content).
%%%-----------------------------------------------------------------
-%%% @spec init_tc(RefreshLog) -> ok
+%%% -spec init_tc(RefreshLog) -> ok
%%%
-%%% @doc Test case initiation (tool-internal use only).
+%%% Test case initiation (tool-internal use only).
%%%
-%%% <p>This function is called by ct_framework:init_tc/3</p>
+%%% This function is called by ct_framework:init_tc/3
init_tc(RefreshLog) ->
call({init_tc,self(),group_leader(),RefreshLog}),
tc_io_format(group_leader(), xhtml("", "<br />"), []),
ok.
%%%-----------------------------------------------------------------
-%%% @spec end_tc(TCPid) -> ok
+%%% -spec end_tc(TCPid) -> ok
%%%
-%%% @doc Test case clean up (tool-internal use only).
+%%% Test case clean up (tool-internal use only).
%%%
-%%% <p>This function is called by ct_framework:end_tc/3</p>
+%%% This function is called by ct_framework:end_tc/3
end_tc(TCPid) ->
%% use call here so that the TC process will wait and receive
%% possible exit signals from ct_logs before end_tc returns ok
call({end_tc,TCPid}).
%%%-----------------------------------------------------------------
-%%% @spec register_groupleader(Pid,GroupLeader) -> ok
+%%% -spec register_groupleader(Pid,GroupLeader) -> ok
%%%
-%%% @doc To enable logging to a group leader (tool-internal use only).
+%%% To enable logging to a group leader (tool-internal use only).
%%%
-%%% <p>This function is called by ct_framework:report/2</p>
+%%% This function is called by ct_framework:report/2
register_groupleader(Pid,GroupLeader) ->
call({register_groupleader,Pid,GroupLeader}),
ok.
%%%-----------------------------------------------------------------
-%%% @spec unregister_groupleader(Pid) -> ok
+%%% -spec unregister_groupleader(Pid) -> ok
%%%
-%%% @doc To disable logging to a group leader (tool-internal use only).
+%%% To disable logging to a group leader (tool-internal use only).
%%%
-%%% <p>This function is called by ct_framework:report/2</p>
+%%% This function is called by ct_framework:report/2
unregister_groupleader(Pid) ->
call({unregister_groupleader,Pid}),
ok.
%%%-----------------------------------------------------------------
-%%% @spec log(Heading,Format,Args) -> ok
+%%% -spec log(Heading,Format,Args) -> ok
%%%
-%%% @doc Log internal activity (tool-internal use only).
+%%% Log internal activity (tool-internal use only).
%%%
-%%% <p>This function writes an entry to the currently active log,
-%%% i.e. either the CT log or a test case log.</p>
+%%% This function writes an entry to the currently active log,
+%%% i.e. either the CT log or a test case log.
%%%
-%%% <p><code>Heading</code> is a short string indicating what type of
-%%% activity it is. <code>Format</code> and <code>Args</code> is the
-%%% data to log (as in <code>io:format(Format,Args)</code>).</p>
+%%% Heading is a short string indicating what type of
+%%% activity it is. Format and Args is the
+%%% data to log (as in io:format(Format,Args)).
log(Heading,Format,Args) ->
cast({log,sync,self(),group_leader(),ct_internal,?MAX_IMPORTANCE,
[{hd,int_header(),[log_timestamp(?now),Heading]},
@@ -338,32 +337,29 @@ log(Heading,Format,Args) ->
ok.
%%%-----------------------------------------------------------------
-%%% @spec start_log(Heading) -> ok
+%%% -spec start_log(Heading) -> ok
%%%
-%%% @doc Starts the logging of an activity (tool-internal use only).
+%%% Starts the logging of an activity (tool-internal use only).
%%%
-%%% <p>This function must be used in combination with
-%%% <code>cont_log/2</code> and <code>end_log/0</code>. The intention
-%%% is to call <code>start_log</code> once, then <code>cont_log</code>
-%%% any number of times and finally <code>end_log</code> once.</p>
+%%% This function must be used in combination with
+%%% cont_log/2 and end_log/0. The intention
+%%% is to call start_log once, then cont_log
+%%% any number of times and finally end_log once.
%%%
-%%% <p>For information about the parameters, see <code>log/3</code>.</p>
+%%% For information about the parameters, see log/3.
%%%
-%%% @see log/3
-%%% @see cont_log/2
-%%% @see end_log/0
+%%% See log/3, cont_log/2, and end_log/0.
start_log(Heading) ->
cast({log,sync,self(),group_leader(),ct_internal,?MAX_IMPORTANCE,
[{hd,int_header(),[log_timestamp(?now),Heading]}],false}),
ok.
%%%-----------------------------------------------------------------
-%%% @spec cont_log(Format,Args) -> ok
+%%% -spec cont_log(Format,Args) -> ok
%%%
-%%% @doc Adds information about an activity (tool-internal use only).
+%%% Adds information about an activity (tool-internal use only).
%%%
-%%% @see start_log/1
-%%% @see end_log/0
+%%% See start_log/1 and end_log/0.
cont_log([],[]) ->
ok;
cont_log(Format,Args) ->
@@ -373,12 +369,11 @@ cont_log(Format,Args) ->
ok.
%%%-----------------------------------------------------------------
-%%% @spec cont_log_no_timestamp(Format,Args) -> ok
+%%% -spec cont_log_no_timestamp(Format,Args) -> ok
%%%
-%%% @doc Adds information about an activity (tool-internal use only).
+%%% Adds information about an activity (tool-internal use only).
%%%
-%%% @see start_log/1
-%%% @see end_log/0
+%%% See start_log/1 and end_log/0.
cont_log_no_timestamp([],[]) ->
ok;
cont_log_no_timestamp(Format,Args) ->
@@ -387,12 +382,11 @@ cont_log_no_timestamp(Format,Args) ->
ok.
%%%-----------------------------------------------------------------
-%%% @spec end_log() -> ok
+%%% -spec end_log() -> ok
%%%
-%%% @doc Ends the logging of an activity (tool-internal use only).
+%%% Ends the logging of an activity (tool-internal use only).
%%%
-%%% @see start_log/1
-%%% @see cont_log/2
+%%% See start_log/1 and cont_log/2.
end_log() ->
cast({log,sync,self(),group_leader(),ct_internal,?MAX_IMPORTANCE,
[{ft,int_footer(), []}],false}),
@@ -400,15 +394,15 @@ end_log() ->
%%%-----------------------------------------------------------------
-%%% @spec add_external_logs(Logs) -> ok
+%%% -spec add_external_logs(Logs) -> ok
%%% Logs = [Log]
%%% Log = string()
%%%
-%%% @doc Print a link to each given <code>Log</code> in the test case
+%%% Print a link to each given Log in the test case
%%% log.
%%%
-%%% <p>The given <code>Logs</code> must exist in the priv dir of the
-%%% calling test suite.</p>
+%%% The given Logs must exist in the priv dir of the
+%%% calling test suite.
add_external_logs(Logs) ->
start_log("External Logs"),
[cont_log("<a href=\"~ts\">~ts</a>\n",
@@ -416,38 +410,38 @@ add_external_logs(Logs) ->
end_log().
%%%-----------------------------------------------------------------
-%%% @spec add_link(Heading,File,Type) -> ok
+%%% -spec add_link(Heading,File,Type) -> ok
%%% Heading = string()
%%% File = string()
%%% Type = string()
%%%
-%%% @doc Print a link to a given file stored in the priv_dir of the
+%%% Print a link to a given file stored in the priv_dir of the
%%% calling test suite.
add_link(Heading,File,Type) ->
- log(Heading,"<a href=\"~ts\" type=~p>~ts</a>\n",
+ log(Heading,"<a href=\"~ts\" type=~tp>~ts</a>\n",
[uri(filename:join("log_private",File)),Type,File]).
%%%-----------------------------------------------------------------
-%%% @spec tc_log(Category,Format,Args) -> ok
-%%% @equiv tc_log(Category,?STD_IMPORTANCE,Format,Args)
+%%% -spec tc_log(Category,Format,Args) -> ok
+%%% Equivalent to tc_log(Category,?STD_IMPORTANCE,Format,Args)
tc_log(Category,Format,Args) ->
tc_log(Category,?STD_IMPORTANCE,"User",Format,Args,[]).
%%%-----------------------------------------------------------------
-%%% @spec tc_log(Category,Importance,Format,Args) -> ok
-%%% @equiv tc_log(Category,Importance,"User",Format,Args)
+%%% -spec tc_log(Category,Importance,Format,Args) -> ok
+%%% Equivalent to tc_log(Category,Importance,"User",Format,Args)
tc_log(Category,Importance,Format,Args) ->
tc_log(Category,Importance,"User",Format,Args,[]).
%%%-----------------------------------------------------------------
-%%% @spec tc_log(Category,Importance,Format,Args) -> ok
-%%% @equiv tc_log(Category,Importance,"User",Format,Args)
+%%% -spec tc_log(Category,Importance,Format,Args) -> ok
+%%% Equivalent to tc_log(Category,Importance,"User",Format,Args)
tc_log(Category,Importance,Format,Args,Opts) ->
tc_log(Category,Importance,"User",Format,Args,Opts).
%%%-----------------------------------------------------------------
-%%% @spec tc_log(Category,Importance,Heading,Format,Args,Opts) -> ok
+%%% -spec tc_log(Category,Importance,Heading,Format,Args,Opts) -> ok
%%% Category = atom()
%%% Importance = integer()
%%% Heading = string()
@@ -455,11 +449,11 @@ tc_log(Category,Importance,Format,Args,Opts) ->
%%% Args = list()
%%% Opts = list()
%%%
-%%% @doc Printout from a testcase.
+%%% Printout from a testcase.
%%%
-%%% <p>This function is called by <code>ct</code> when logging
+%%% This function is called by ct when logging
%%% stuff directly from a testcase (i.e. not from within the CT
-%%% framework).</p>
+%%% framework).
tc_log(Category,Importance,Heading,Format,Args,Opts) ->
Data =
case lists:member(no_css, Opts) of
@@ -480,26 +474,26 @@ tc_log(Category,Importance,Heading,Format,Args,Opts) ->
ok.
%%%-----------------------------------------------------------------
-%%% @spec tc_log_async(Category,Format,Args) -> ok
-%%% @equiv tc_log_async(Category,?STD_IMPORTANCE,"User",Format,Args)
+%%% -spec tc_log_async(Category,Format,Args) -> ok
+%%% Equivalent to tc_log_async(Category,?STD_IMPORTANCE,"User",Format,Args)
tc_log_async(Category,Format,Args) ->
tc_log_async(Category,?STD_IMPORTANCE,"User",Format,Args).
%%%-----------------------------------------------------------------
-%%% @spec tc_log_async(Category,Importance,Format,Args) -> ok
+%%% -spec tc_log_async(Category,Importance,Format,Args) -> ok
%%% Category = atom()
%%% Importance = integer()
%%% Heading = string()
%%% Format = string()
%%% Args = list()
%%%
-%%% @doc Internal use only.
+%%% Internal use only.
%%%
-%%% <p>This function is used to perform asynchronous printouts
+%%% This function is used to perform asynchronous printouts
%%% towards the test server IO handler. This is necessary in order
%%% to avoid deadlocks when e.g. the hook that handles SASL printouts
%%% prints to the test case log file at the same time test server
-%%% asks ct_logs for an html wrapper.</p>
+%%% asks ct_logs for an html wrapper.
tc_log_async(Category,Importance,Heading,Format,Args) ->
cast({log,async,self(),group_leader(),Category,Importance,
[{hd,div_header(Category,Heading),[]},
@@ -508,29 +502,29 @@ tc_log_async(Category,Importance,Heading,Format,Args) ->
true}),
ok.
%%%-----------------------------------------------------------------
-%%% @spec tc_print(Category,Format,Args)
-%%% @equiv tc_print(Category,?STD_IMPORTANCE,Format,Args,[])
+%%% -spec tc_print(Category,Format,Args)
+%%% Equivalent to tc_print(Category,?STD_IMPORTANCE,Format,Args,[])
tc_print(Category,Format,Args) ->
tc_print(Category,?STD_IMPORTANCE,Format,Args,[]).
%%%-----------------------------------------------------------------
-%%% @spec tc_print(Category,Importance,Format,Args)
-%%% @equiv tc_print(Category,Importance,Format,Args,[])
+%%% -spec tc_print(Category,Importance,Format,Args)
+%%% Equivalent to tc_print(Category,Importance,Format,Args,[])
tc_print(Category,Importance,Format,Args) ->
tc_print(Category,Importance,Format,Args,[]).
%%%-----------------------------------------------------------------
-%%% @spec tc_print(Category,Importance,Format,Args,Opts) -> ok
+%%% -spec tc_print(Category,Importance,Format,Args,Opts) -> ok
%%% Category = atom()
%%% Importance = integer()
%%% Format = string()
%%% Args = list()
%%% Opts = list()
%%%
-%%% @doc Console printout from a testcase.
+%%% Console printout from a testcase.
%%%
-%%% <p>This function is called by <code>ct</code> when printing
-%%% stuff from a testcase on the user console.</p>
+%%% This function is called by ct when printing
+%%% stuff from a testcase on the user console.
tc_print(Category,Importance,Format,Args,Opts) ->
VLvl = case ct_util:get_verbosity(Category) of
undefined ->
@@ -548,7 +542,7 @@ tc_print(Category,Importance,Format,Args,Opts) ->
undefined -> atom_to_list(Category);
Hd -> Hd
end,
- Str = lists:concat([get_header(Heading),Format,"\n\n"]),
+ Str = lists:flatten([get_header(Heading),Format,"\n\n"]),
try
io:format(?def_gl, Str, Args)
catch
@@ -566,49 +560,49 @@ get_header("default") ->
[log_timestamp(?now)]);
get_header(Heading) ->
io_lib:format("\n-----------------------------"
- "-----------------------\n~s ~s\n",
+ "-----------------------\n~ts ~s\n",
[Heading,log_timestamp(?now)]).
%%%-----------------------------------------------------------------
-%%% @spec tc_pal(Category,Format,Args) -> ok
-%%% @equiv tc_pal(Category,?STD_IMPORTANCE,Format,Args,[]) -> ok
+%%% -spec tc_pal(Category,Format,Args) -> ok
+%%% Equivalent to tc_pal(Category,?STD_IMPORTANCE,Format,Args,[]) -> ok
tc_pal(Category,Format,Args) ->
tc_pal(Category,?STD_IMPORTANCE,Format,Args,[]).
%%%-----------------------------------------------------------------
-%%% @spec tc_pal(Category,Importance,Format,Args) -> ok
-%%% @equiv tc_pal(Category,Importance,Format,Args,[]) -> ok
+%%% -spec tc_pal(Category,Importance,Format,Args) -> ok
+%%% Equivalent to tc_pal(Category,Importance,Format,Args,[]) -> ok
tc_pal(Category,Importance,Format,Args) ->
tc_pal(Category,Importance,Format,Args,[]).
%%%-----------------------------------------------------------------
-%%% @spec tc_pal(Category,Importance,Format,Args,Opts) -> ok
+%%% -spec tc_pal(Category,Importance,Format,Args,Opts) -> ok
%%% Category = atom()
%%% Importance = integer()
%%% Format = string()
%%% Args = list()
%%% Opts = list()
%%%
-%%% @doc Print and log from a testcase.
+%%% Print and log from a testcase.
%%%
-%%% <p>This function is called by <code>ct</code> when logging
+%%% This function is called by ct when logging
%%% stuff directly from a testcase. The info is written both in the
-%%% log and on the console.</p>
+%%% log and on the console.
tc_pal(Category,Importance,Format,Args,Opts) ->
tc_print(Category,Importance,Format,Args,Opts),
tc_log(Category,Importance,"User",Format,Args,[esc_chars|Opts]).
%%%-----------------------------------------------------------------
-%%% @spec ct_log(Category,Format,Args) -> ok
+%%% -spec ct_log(Category,Format,Args) -> ok
%%% Category = atom()
%%% Format = string()
%%% Args = list()
%%%
-%%% @doc Print to the ct framework log
+%%% Print to the ct framework log
%%%
-%%% <p>This function is called by internal ct functions to
-%%% force logging to the ct framework log</p>
+%%% This function is called by internal ct functions to
+%%% force logging to the ct framework log
ct_log(Category,Format,Args) ->
cast({ct_log,[{hd,div_header(Category),[]},
{Format,Args},
@@ -665,6 +659,7 @@ log_timestamp({MS,S,US}) ->
logger(Parent, Mode, Verbosity) ->
register(?MODULE,self()),
+ ct_util:mark_process(),
%%! Below is a temporary workaround for the limitation of
%%! max one test run per second.
%%! --->
@@ -703,8 +698,8 @@ logger(Parent, Mode, Verbosity) ->
case copy_priv_files(PrivFilesSrc, PrivFilesDestTop) of
{error,Src1,Dest1,Reason1} ->
io:format(?def_gl, "ERROR! "++
- "Priv file ~p could not be copied to ~p. "++
- "Reason: ~p~n",
+ "Priv file ~tp could not be copied to ~tp. "++
+ "Reason: ~tp~n",
[Src1,Dest1,Reason1]),
exit({priv_file_error,Dest1});
ok ->
@@ -712,8 +707,8 @@ logger(Parent, Mode, Verbosity) ->
{error,Src2,Dest2,Reason2} ->
io:format(?def_gl,
"ERROR! "++
- "Priv file ~p could not be copied to ~p. "
- ++"Reason: ~p~n",
+ "Priv file ~tp could not be copied to ~tp. "
+ ++"Reason: ~tp~n",
[Src2,Dest2,Reason2]),
exit({priv_file_error,Dest2});
ok ->
@@ -890,7 +885,7 @@ logger_loop(State) ->
logger_loop(State);
{set_stylesheet,TC,SSFile} ->
Fd = State#logger_state.ct_log_fd,
- io:format(Fd, "~p loading external style sheet: ~ts~n",
+ io:format(Fd, "~tp loading external style sheet: ~ts~n",
[TC,SSFile]),
logger_loop(State#logger_state{stylesheet = SSFile});
{clear_stylesheet,_} when State#logger_state.stylesheet == undefined ->
@@ -940,7 +935,7 @@ create_io_fun(FromPid, CtLogFd, EscChars) ->
{_HdOrFt,S,A} -> {false,S,A};
{S,A} -> {true,S,A}
end,
- try io_lib:format(Str, Args) of
+ try io_lib:format(lists:flatten(Str), Args) of
IoStr when Escapable, EscChars, IoList == [] ->
escape_chars(IoStr);
IoStr when Escapable, EscChars ->
@@ -951,7 +946,7 @@ create_io_fun(FromPid, CtLogFd, EscChars) ->
[IoList,"\n",IoStr]
catch
_:_Reason ->
- io:format(CtLogFd, "Logging fails! Str: ~p, Args: ~p~n",
+ io:format(CtLogFd, "Logging fails! Str: ~tp, Args: ~tp~n",
[Str,Args]),
%% stop the testcase, we need to see the fault
exit(FromPid, {log_printout_error,Str,Args}),
@@ -1003,6 +998,7 @@ print_to_log(async, FromPid, Category, TCGL, Content, EscChars, State) ->
if FromPid /= TCGL ->
IoFun = create_io_fun(FromPid, CtLogFd, EscChars),
fun() ->
+ ct_util:mark_process(),
test_server:permit_io(TCGL, self()),
%% Since asynchronous io gets can get buffered if
@@ -1034,6 +1030,7 @@ print_to_log(async, FromPid, Category, TCGL, Content, EscChars, State) ->
end;
true ->
fun() ->
+ ct_util:mark_process(),
unexpected_io(FromPid, Category, ?MAX_IMPORTANCE,
Content, CtLogFd, EscChars)
end
@@ -1141,16 +1138,16 @@ set_evmgr_gl(GL) ->
open_ctlog(MiscIoName) ->
{ok,Fd} = file:open(?ct_log_name,[write,{encoding,utf8}]),
- io:format(Fd, header("Common Test Framework Log", {[],[1,2],[]}), []),
+ io:format(Fd, "~ts", [header("Common Test Framework Log", {[],[1,2],[]})]),
case file:consult(ct_run:variables_file_name("../")) of
{ok,Vars} ->
- io:format(Fd, config_table(Vars), []);
+ io:format(Fd, "~ts", [config_table(Vars)]);
{error,Reason} ->
{ok,Cwd} = file:get_cwd(),
Dir = filename:dirname(Cwd),
Variables = ct_run:variables_file_name(Dir),
io:format(Fd,
- "Can not read the file \'~ts\' Reason: ~w\n"
+ "Can not read the file \'~ts\' Reason: ~tw\n"
"No configuration found for test!!\n",
[Variables,Reason])
end,
@@ -1187,39 +1184,36 @@ print_style(Fd, IoFormat, StyleSheet) ->
case file:read_file(StyleSheet) of
{ok,Bin} ->
Str = b2s(Bin,encoding(StyleSheet)),
- Pos0 = case string:str(Str,"<style>") of
- 0 -> string:str(Str,"<STYLE>");
- N0 -> N0
- end,
- Pos1 = case string:str(Str,"</style>") of
- 0 -> string:str(Str,"</STYLE>");
- N1 -> N1
- end,
- if (Pos0 == 0) and (Pos1 /= 0) ->
- print_style_error(Fd, IoFormat,
- StyleSheet, missing_style_start_tag);
- (Pos0 /= 0) and (Pos1 == 0) ->
- print_style_error(Fd, IoFormat,
- StyleSheet,missing_style_end_tag);
- Pos0 /= 0 ->
- Style = string:sub_string(Str,Pos0,Pos1+7),
- IoFormat(Fd,"~ts\n",[Style]);
- Pos0 == 0 ->
- IoFormat(Fd,"<style>\n~ts</style>\n",[Str])
- end;
+ case re:run(Str,"<style>.*</style>",
+ [dotall,caseless,{capture,all,list}]) of
+ nomatch ->
+ case re:run(Str,"</?style>",[caseless,{capture,all,list}]) of
+ nomatch ->
+ IoFormat(Fd,"<style>\n~ts</style>\n",[Str]);
+ {match,["</"++_]} ->
+ print_style_error(Fd, IoFormat,
+ StyleSheet,
+ missing_style_start_tag);
+ {match,[_]} ->
+ print_style_error(Fd, IoFormat,
+ StyleSheet,missing_style_end_tag)
+ end;
+ {match,[Style]} ->
+ IoFormat(Fd,"~ts\n",[Style])
+ end;
{error,Reason} ->
print_style_error(Fd,IoFormat,StyleSheet,Reason)
end.
print_style_error(Fd, IoFormat, StyleSheet, Reason) ->
- IO = io_lib:format("\n<!-- Failed to load stylesheet ~ts: ~p -->\n",
+ IO = io_lib:format("\n<!-- Failed to load stylesheet ~ts: ~tp -->\n",
[StyleSheet,Reason]),
IoFormat(Fd, IO, []),
print_style(Fd, IoFormat, undefined).
close_ctlog(Fd) ->
io:format(Fd, "\n</pre>\n", []),
- io:format(Fd, [xhtml("<br><br>\n", "<br /><br />\n") | footer()], []),
+ io:format(Fd, "~ts", [[xhtml("<br><br>\n", "<br /><br />\n") | footer()]]),
ok = file:close(Fd).
%%%-----------------------------------------------------------------
@@ -1255,11 +1249,11 @@ make_last_run_index(StartTime) ->
case catch make_last_run_index1(StartTime,IndexName) of
{'EXIT', Reason} ->
io:put_chars("CRASHED while updating " ++ AbsIndexName ++ "!\n"),
- io:format("~p~n", [Reason]),
+ io:format("~tp~n", [Reason]),
{error, Reason};
{error, Reason} ->
io:put_chars("FAILED while updating " ++ AbsIndexName ++ "\n"),
- io:format("~p~n", [Reason]),
+ io:format("~tp~n", [Reason]),
{error, Reason};
ok ->
ok;
@@ -1413,9 +1407,9 @@ make_one_index_entry1(SuiteName, Link, Label, Success, Fail, UserSkip, AutoSkip,
{Lbl,Timestamp,Node,AllInfo} =
case All of
{true,OldRuns} ->
- [_Prefix,NodeOrDate|_] = string:tokens(Link,"."),
- Node1 = case string:chr(NodeOrDate,$@) of
- 0 -> "-";
+ [_Prefix,NodeOrDate|_] = string:lexemes(Link,"."),
+ Node1 = case string:find(NodeOrDate,[$@]) of
+ nomatch -> "-";
_ -> NodeOrDate
end,
@@ -1522,7 +1516,7 @@ not_built(BaseName,_LogDir,_All,Missing) ->
%% Top.ObjDir | Top.ObjDir.suites | Top.ObjDir.Suite |
%% Top.ObjDir.Suite.cases | Top.ObjDir.Suite.Case
Failed =
- case string:tokens(BaseName,".") of
+ case string:lexemes(BaseName,".") of
[T,O] when is_list(T) -> % all under Top.ObjDir
locate_info({T,O},all,Missing);
[T,O,"suites"] ->
@@ -1560,7 +1554,7 @@ get_missing_suites(_,_) ->
[].
term_to_text(Term) ->
- lists:flatten(io_lib:format("~p.\n", [Term])).
+ lists:flatten(io_lib:format("~tp.\n", [Term])).
%%% Headers and footers.
@@ -1828,7 +1822,7 @@ count_cases(Dir) ->
Summary
end;
{error, Reason} ->
- io:format("\nFailed to read ~p: ~p (skipped)\n",
+ io:format("\nFailed to read ~tp: ~tp (skipped)\n",
[LogFile,Reason]),
error
end
@@ -1910,10 +1904,10 @@ config_table_header() ->
config_table1([{Key,Value}|Vars]) ->
[xhtml(["<tr><td>", atom_to_list(Key), "</td>\n",
- "<td><pre>",io_lib:format("~p",[Value]),"</pre></td></tr>\n"],
+ "<td><pre>",io_lib:format("~tp",[Value]),"</pre></td></tr>\n"],
["<tr class=\"", odd_or_even(), "\">\n",
"<td>", atom_to_list(Key), "</td>\n",
- "<td>", io_lib:format("~p",[Value]), "</td>\n</tr>\n"]) |
+ "<td>", io_lib:format("~tp",[Value]), "</td>\n</tr>\n"]) |
config_table1(Vars)];
config_table1([]) ->
[xhtml("","</tbody>\n"),"</table>\n"].
@@ -1946,7 +1940,11 @@ make_all_runs_index(When) ->
end,
Dirs = filelib:wildcard(logdir_prefix()++"*.*"),
- DirsSorted = (catch sort_all_runs(Dirs)),
+ DirsSorted0 = (catch sort_all_runs(Dirs)),
+ DirsSorted =
+ if When == start -> DirsSorted0;
+ true -> maybe_delete_old_dirs(DirsSorted0)
+ end,
LogCacheInfo = get_cache_data(UseCache),
@@ -2024,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))
@@ -2038,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) ->
@@ -2046,9 +2044,9 @@ sort_all_runs(Dirs) ->
%% "YYYY-MM-DD_HH.MM.SS"
lists:sort(fun(Dir1,Dir2) ->
[SS1,MM1,HH1,Date1|_] =
- lists:reverse(string:tokens(Dir1,[$.,$_])),
+ lists:reverse(string:lexemes(Dir1,[$.,$_])),
[SS2,MM2,HH2,Date2|_] =
- lists:reverse(string:tokens(Dir2,[$.,$_])),
+ lists:reverse(string:lexemes(Dir2,[$.,$_])),
{Date1,HH1,MM1,SS1} > {Date2,HH2,MM2,SS2}
end, Dirs).
@@ -2058,12 +2056,42 @@ sort_ct_runs(Dirs) ->
lists:sort(
fun(Dir1,Dir2) ->
[SS1,MM1,DateHH1 | _] =
- lists:reverse(string:tokens(filename:dirname(Dir1),[$.])),
+ lists:reverse(string:lexemes(filename:dirname(Dir1),[$.])),
[SS2,MM2,DateHH2 | _] =
- lists:reverse(string:tokens(filename:dirname(Dir2),[$.])),
+ lists:reverse(string:lexemes(filename:dirname(Dir2),[$.])),
{DateHH1,MM1,SS1} =< {DateHH2,MM2,SS2}
end, Dirs).
+parse_keep_logs([Str="all"]) ->
+ parse_keep_logs(list_to_atom(Str));
+parse_keep_logs([NStr]) ->
+ parse_keep_logs(list_to_integer(NStr));
+parse_keep_logs(all) ->
+ all;
+parse_keep_logs(N) when is_integer(N), N>0 ->
+ N.
+
+maybe_delete_old_dirs(Sorted) ->
+ {Keep,Delete} =
+ case application:get_env(common_test, keep_logs) of
+ {ok,MaxN} when is_integer(MaxN), length(Sorted)>MaxN ->
+ lists:split(MaxN,Sorted);
+ _ ->
+ {Sorted,[]}
+ end,
+ delete_old_dirs(Delete),
+ Keep.
+
+delete_old_dirs([]) ->
+ ok;
+delete_old_dirs(Dirs) ->
+ io:put_chars("\n Removing old test directories:\n"),
+ [begin
+ io:put_chars(" " ++ Dir ++ "\n"),
+ rm_dir(Dir)
+ end|| Dir <- Dirs],
+ ok.
+
dir_diff_all_runs(Dirs, LogCache) ->
case LogCache#log_cache.all_runs of
[] ->
@@ -2176,27 +2204,15 @@ runentry(Dir, Totals={Node,Label,Logs,
0 -> "-";
N -> integer_to_list(N)
end,
- StripExt =
- fun(File) ->
- string:sub_string(File,1,
- length(File)-
- length(?logdir_ext)) ++ ", "
- end,
- Polish = fun(S) -> case lists:reverse(S) of
- [32,$,|Rev] -> lists:reverse(Rev);
- [$,|Rev] -> lists:reverse(Rev);
- _ -> S
- end
- end,
- TestNames = Polish(lists:flatten(lists:map(StripExt,Logs))),
+
+ RootNames = lists:map(fun(F) -> filename:rootname(F,?logdir_ext) end, Logs),
+ TestNames = lists:flatten(lists:join(", ", RootNames)),
TestNamesTrunc =
- if TestNames=="" ->
- "";
- length(TestNames) < ?testname_width ->
+ if length(TestNames) < ?testname_width ->
TestNames;
true ->
- Trunc = Polish(string:substr(TestNames,1,
- ?testname_width-3)),
+ Trunc = string:trim(string:slice(TestNames,0,?testname_width-3),
+ trailing,",\s"),
lists:flatten(io_lib:format("~ts...",[Trunc]))
end,
TotMissingStr =
@@ -2339,7 +2355,7 @@ force_rename(From,To,Number) ->
timestamp(Dir) ->
- TsR = lists:reverse(string:tokens(Dir,".-_")),
+ TsR = lists:reverse(string:lexemes(Dir,".-_")),
[S,Min,H,D,M,Y] = [list_to_integer(N) || N <- lists:sublist(TsR,6)],
format_time({{Y,M,D},{H,Min,S}}).
@@ -2439,17 +2455,17 @@ make_all_suites_index(NewTestData = {_TestName,DirName}) ->
LogDirData) of
{'EXIT',Reason} ->
io:put_chars("CRASHED while updating " ++ AbsIndexName ++ "!\n"),
- io:format("~p~n", [Reason]),
+ io:format("~tp~n", [Reason]),
{error,Reason};
{error,Reason} ->
io:put_chars("FAILED while updating " ++ AbsIndexName ++ "\n"),
- io:format("~p~n", [Reason]),
+ io:format("~tp~n", [Reason]),
{error,Reason};
ok ->
ok;
Err ->
io:format("Unknown internal error while updating ~ts. "
- "Please report.\n(Err: ~p, ID: 1)",
+ "Please report.\n(Err: ~tp, ID: 1)",
[AbsIndexName,Err]),
{error, Err}
end,
@@ -2652,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.
%%
@@ -2668,11 +2684,11 @@ make_all_suites_index1(When, AbsIndexName, AllTestLogDirs) ->
case catch make_all_suites_index2(IndexName, AllTestLogDirs) of
{'EXIT', Reason} ->
io:put_chars("CRASHED while updating " ++ AbsIndexName ++ "!\n"),
- io:format("~p~n", [Reason]),
+ io:format("~tp~n", [Reason]),
{error, Reason};
{error, Reason} ->
io:put_chars("FAILED while updating " ++ AbsIndexName ++ "\n"),
- io:format("~p~n", [Reason]),
+ io:format("~tp~n", [Reason]),
{error, Reason};
{ok,TempData} ->
case When of
@@ -2686,7 +2702,7 @@ make_all_suites_index1(When, AbsIndexName, AllTestLogDirs) ->
end;
Err ->
io:format("Unknown internal error while updating ~ts. "
- "Please report.\n(Err: ~p, ID: 1)",
+ "Please report.\n(Err: ~tp, ID: 1)",
[AbsIndexName,Err]),
{error, Err}
end.
@@ -2888,7 +2904,7 @@ cache_vsn() ->
VSNfile = filename:join([EbinDir,"..","vsn.mk"]),
case file:read_file(VSNfile) of
{ok,Bin} ->
- [_,VSN] = string:tokens(binary_to_list(Bin),[$=,$\n,$ ]),
+ [_,VSN] = string:lexemes(binary_to_list(Bin),[$=,$\n,$ ]),
VSN;
_ ->
undefined
@@ -2970,18 +2986,19 @@ rm_files([]) ->
ok.
%%%-----------------------------------------------------------------
-%%% @spec simulate() -> pid()
+%%% -spec simulate() -> pid()
%%%
-%%% @doc Simulate the logger process.
+%%% Simulate the logger process.
%%%
-%%% <p>Simulate the logger process - for use when testing code using
+%%% Simulate the logger process - for use when testing code using
%%% ct_logs logging mechanism without using the ct
-%%% environment. (E.g. when testing code with ts)</p>
+%%% environment. (E.g. when testing code with ts)
simulate() ->
cast(stop),
S = self(),
Pid = spawn(fun() ->
register(?MODULE,self()),
+ ct_util:mark_process(),
S ! {self(),started},
simulate_logger_loop()
end),
@@ -3003,9 +3020,7 @@ simulate_logger_loop() ->
end.
%%%-----------------------------------------------------------------
-%%% @spec notify_and_lock_file(Files) -> ok
-%%%
-%%% @doc
+%%% -spec notify_and_lock_file(Files) -> ok
%%%
notify_and_lock_file(File) ->
case ct_event:is_alive() of
@@ -3018,9 +3033,7 @@ notify_and_lock_file(File) ->
end.
%%%-----------------------------------------------------------------
-%%% @spec notify_and_unlock_file(Files) -> ok
-%%%
-%%% @doc
+%%% -spec notify_and_unlock_file(Files) -> ok
%%%
notify_and_unlock_file(File) ->
case ct_event:is_alive() of
@@ -3033,9 +3046,7 @@ notify_and_unlock_file(File) ->
end.
%%%-----------------------------------------------------------------
-%%% @spec get_run_dirs(Dir) -> [string()] | false
-%%%
-%%% @doc
+%%% -spec get_run_dirs(Dir) -> [string()] | false
%%%
get_run_dirs(Dir) ->
case filelib:wildcard(filename:join(Dir, "run.[1-2]*")) of
@@ -3046,9 +3057,7 @@ get_run_dirs(Dir) ->
end.
%%%-----------------------------------------------------------------
-%%% @spec xhtml(HTML, XHTML) -> HTML | XHTML
-%%%
-%%% @doc
+%%% -spec xhtml(HTML, XHTML) -> HTML | XHTML
%%%
xhtml(HTML, XHTML) when is_function(HTML),
is_function(XHTML) ->
@@ -3063,9 +3072,7 @@ xhtml(HTML, XHTML) ->
end.
%%%-----------------------------------------------------------------
-%%% @spec odd_or_even() -> "odd" | "even"
-%%%
-%%% @doc
+%%% -spec odd_or_even() -> "odd" | "even"
%%%
odd_or_even() ->
case get(odd_or_even) of
@@ -3078,9 +3085,7 @@ odd_or_even() ->
end.
%%%-----------------------------------------------------------------
-%%% @spec basic_html() -> true | false
-%%%
-%%% @doc
+%%% -spec basic_html() -> true | false
%%%
basic_html() ->
case application:get_env(common_test, basic_html) of
@@ -3091,9 +3096,7 @@ basic_html() ->
end.
%%%-----------------------------------------------------------------
-%%% @spec locate_priv_file(FileName) -> PrivFile
-%%%
-%%% @doc
+%%% -spec locate_priv_file(FileName) -> PrivFile
%%%
locate_priv_file(FileName) ->
{ok,CWD} = file:get_cwd(),
@@ -3109,8 +3112,8 @@ locate_priv_file(FileName) ->
filename:join(get(ct_run_dir), FileName);
_ ->
%% executed on other process than ct_logs
- {ok,RunDir} = get_log_dir(true),
- filename:join(RunDir, FileName)
+ {ok,LogDir} = get_log_dir(true),
+ filename:join(LogDir, FileName)
end,
case filelib:is_file(PrivResultFile) of
true ->
@@ -3123,13 +3126,13 @@ locate_priv_file(FileName) ->
end.
%%%-----------------------------------------------------------------
-%%% @spec make_relative(AbsDir, Cwd) -> RelDir
+%%% -spec make_relative(AbsDir, Cwd) -> RelDir
%%%
-%%% @doc Return directory path to File (last element of AbsDir), which
-%%% is the path relative to Cwd. Examples when Cwd == "/ldisk/test/logs":
-%%% make_relative("/ldisk/test/logs/run/trace.log") -> "run/trace.log"
-%%% make_relative("/ldisk/test/trace.log") -> "../trace.log"
-%%% make_relative("/ldisk/test/logs/trace.log") -> "trace.log"
+%%% Return directory path to File (last element of AbsDir), which
+%%% is the path relative to Cwd. Examples when Cwd == "/ldisk/test/logs":
+%%% make_relative("/ldisk/test/logs/run/trace.log") -> "run/trace.log"
+%%% make_relative("/ldisk/test/trace.log") -> "../trace.log"
+%%% make_relative("/ldisk/test/logs/trace.log") -> "trace.log"
make_relative(AbsDir) ->
{ok,Cwd} = file:get_cwd(),
make_relative(AbsDir, Cwd).
@@ -3153,11 +3156,9 @@ make_relative1(DirTs, CwdTs) ->
Ups ++ DirTs.
%%%-----------------------------------------------------------------
-%%% @spec get_ts_html_wrapper(TestName, PrintLabel, Cwd, TableCols, Encoding)
+%%% -spec get_ts_html_wrapper(TestName, PrintLabel, Cwd, TableCols, Encoding)
%%% -> {Mode,Header,Footer}
%%%
-%%% @doc
-%%%
get_ts_html_wrapper(TestName, PrintLabel, Cwd, TableCols, Encoding) ->
get_ts_html_wrapper(TestName, undefined, PrintLabel, Cwd, TableCols, Encoding).
@@ -3165,7 +3166,7 @@ get_ts_html_wrapper(TestName, Logdir, PrintLabel, Cwd, TableCols, Encoding) ->
TestName1 = if is_list(TestName) ->
lists:flatten(TestName);
true ->
- lists:flatten(io_lib:format("~p", [TestName]))
+ lists:flatten(io_lib:format("~tp", [TestName]))
end,
Basic = basic_html(),
LabelStr =
@@ -3192,6 +3193,10 @@ get_ts_html_wrapper(TestName, Logdir, PrintLabel, Cwd, TableCols, Encoding) ->
?all_runs_name), Cwd),
TestIndex = make_relative(filename:join(filename:dirname(CtLogdir),
?index_name), Cwd),
+ LatestTest = make_relative(filename:join(filename:dirname(CtLogdir),
+ ?suitelog_name++".latest.html"),
+ Cwd),
+
case Basic of
true ->
TileFile = filename:join(filename:join(CTPath,"priv"),"tile1.jpg"),
@@ -3218,7 +3223,9 @@ get_ts_html_wrapper(TestName, Logdir, PrintLabel, Cwd, TableCols, Encoding) ->
"<a href=\"", uri(AllRuns),
"\">Test run history\n</a> | ",
"<a href=\"", uri(TestIndex),
- "\">Top level test index\n</a>\n</p>\n",
+ "\">Top level test index\n</a> | ",
+ "<a href=\"", uri(LatestTest),
+ "\">Latest test result</a>\n</p>\n",
Copyright,"</center>\n</body>\n</html>\n"]};
_ ->
Copyright =
@@ -3265,7 +3272,9 @@ get_ts_html_wrapper(TestName, Logdir, PrintLabel, Cwd, TableCols, Encoding) ->
"<a href=\"", uri(AllRuns),
"\">Test run history\n</a> | ",
"<a href=\"", uri(TestIndex),
- "\">Top level test index\n</a>\n</p>\n",
+ "\">Top level test index\n</a> | ",
+ "<a href=\"", uri(LatestTest),
+ "\">Latest test result</a>\n</p>\n",
Copyright,"</center>\n</body>\n</html>\n"]}
end.
@@ -3285,7 +3294,7 @@ insert_javascript({tablesorter,TableName,
end, [{"CTDateSorter",DateCols},
{"CTTextSorter",TextCols},
{"CTValSorter",ValCols}]))),
- Headers1 = string:substr(Headers, 1, length(Headers)-2),
+ Headers1 = string:trim(Headers, trailing, ",\n"),
["<script type=\"text/javascript\">\n",
"// Parser for date format, e.g: Wed Jul 4 2012 11:24:15\n",
@@ -3391,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/ct_make.erl b/lib/common_test/src/ct_make.erl
index f22959d457..220cb0473d 100644
--- a/lib/common_test/src/ct_make.erl
+++ b/lib/common_test/src/ct_make.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2009-2016. All Rights Reserved.
+%% 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.
@@ -96,7 +96,7 @@ read_emakefile(Emakefile,Opts) ->
Mods = [filename:rootname(F) || F <- filelib:wildcard("*.erl")],
[{Mods, Opts}];
{error,Other} ->
- io:format("make: Trouble reading 'Emakefile':~n~p~n",[Other]),
+ io:format("make: Trouble reading 'Emakefile':~n~tp~n",[Other]),
error
end.
@@ -151,7 +151,7 @@ get_opts_from_emakefile(Mods,Emakefile,Opts) ->
{error,enoent} ->
[{Mods, Opts}];
{error,Other} ->
- io:format("make: Trouble reading 'Emakefile':~n~p~n",[Other]),
+ io:format("make: Trouble reading 'Emakefile':~n~tp~n",[Other]),
error
end.
@@ -280,15 +280,47 @@ recompile(File, NoExec, Load, Opts) ->
do_recompile(_File, true, _Load, _Opts) ->
out_of_date;
-do_recompile(File, false, noload, Opts) ->
+do_recompile(File, false, Load, Opts) ->
io:format("Recompile: ~ts\n",[File]),
- compile:file(File, [report_errors, report_warnings, error_summary |Opts]);
-do_recompile(File, false, load, Opts) ->
- io:format("Recompile: ~ts\n",[File]),
- c:c(File, Opts);
-do_recompile(File, false, netload, Opts) ->
- io:format("Recompile: ~ts\n",[File]),
- c:nc(File, Opts).
+ case compile:file(File, [report_errors, report_warnings |Opts]) of
+ Ok when is_tuple(Ok), element(1,Ok)==ok ->
+ maybe_load(element(2,Ok), Load, Opts);
+ _Error ->
+ error
+ end.
+
+maybe_load(_Mod, noload, _Opts) ->
+ ok;
+maybe_load(Mod, Load, Opts) ->
+ %% We have compiled File with options Opts. Find out where the
+ %% output file went to, and load it.
+ case compile:output_generated(Opts) of
+ true ->
+ Dir = proplists:get_value(outdir,Opts,"."),
+ do_load(Dir, Mod, Load);
+ false ->
+ io:format("** Warning: No object file created - nothing loaded **~n"),
+ ok
+ end.
+
+do_load(Dir, Mod, load) ->
+ code:purge(Mod),
+ case code:load_abs(filename:join(Dir, Mod),Mod) of
+ {module,Mod} ->
+ {ok,Mod};
+ Other ->
+ Other
+ end;
+do_load(Dir, Mod, netload) ->
+ Obj = atom_to_list(Mod) ++ code:objfile_extension(),
+ Fname = filename:join(Dir, Obj),
+ case file:read_file(Fname) of
+ {ok,Bin} ->
+ rpc:eval_everywhere(code,load_binary,[Mod,Fname,Bin]),
+ {ok,Mod};
+ Other ->
+ Other
+ end.
exists(File) ->
case file:read_file_info(File) of
diff --git a/lib/common_test/src/ct_master.erl b/lib/common_test/src/ct_master.erl
index 4eef27d2a5..9fc169789c 100644
--- a/lib/common_test/src/ct_master.erl
+++ b/lib/common_test/src/ct_master.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2018. 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.
@@ -18,9 +18,6 @@
%% %CopyrightEnd%
%%
-%%% @doc Distributed test execution control for Common Test.
-%%% <p>This module exports functions for running Common Test nodes
-%%% on multiple hosts in parallel.</p>
-module(ct_master).
-export([run/1,run/3,run/4]).
@@ -45,50 +42,14 @@
blocked=[]
}).
-%%%-----------------------------------------------------------------
-%%% @spec run_test(Node,Opts) -> ok
-%%% Node = atom()
-%%% Opts = [OptTuples]
-%%% OptTuples = {config,CfgFiles} | {dir,TestDirs} | {suite,Suites} |
-%%% {testcase,Cases} | {spec,TestSpecs} | {allow_user_terms,Bool} |
-%%% {logdir,LogDir} | {event_handler,EventHandlers} |
-%%% {silent_connections,Conns} | {cover,CoverSpecFile} |
-%%% {cover_stop,Bool} | {userconfig, UserCfgFiles}
-%%% CfgFiles = string() | [string()]
-%%% TestDirs = string() | [string()]
-%%% Suites = atom() | [atom()]
-%%% Cases = atom() | [atom()]
-%%% TestSpecs = string() | [string()]
-%%% LogDir = string()
-%%% EventHandlers = EH | [EH]
-%%% EH = atom() | {atom(),InitArgs} | {[atom()],InitArgs}
-%%% InitArgs = [term()]
-%%% Conns = all | [atom()]
-%%%
-%%% @doc Tests are spawned on <code>Node</code> using <code>ct:run_test/1</code>.
run_test(Node,Opts) ->
run_test([{Node,Opts}]).
-%%% @hidden
run_test({Node,Opts}) ->
run_test([{Node,Opts}]);
run_test(NodeOptsList) when is_list(NodeOptsList) ->
start_master(NodeOptsList).
-%%%-----------------------------------------------------------------
-%%% @spec run(TestSpecs,AllowUserTerms,InclNodes,ExclNodes) -> ok
-%%% TestSpecs = string() | [SeparateOrMerged]
-%%% SeparateOrMerged = string() | [string()]
-%%% AllowUserTerms = bool()
-%%% InclNodes = [atom()]
-%%% ExclNodes = [atom()]
-%%%
-%%% @doc Tests are spawned on the nodes as specified in <code>TestSpecs</code>.
-%%% Each specification in TestSpec will be handled separately. It is however
-%%% possible to also specify a list of specifications that should be merged
-%%% into one before the tests are executed. Any test without a particular node
-%%% specification will also be executed on the nodes in <code>InclNodes</code>.
-%%% Nodes in the <code>ExclNodes</code> list will be excluded from the test.
run([TS|TestSpecs],AllowUserTerms,InclNodes,ExclNodes) when is_list(TS),
is_list(InclNodes),
is_list(ExclNodes) ->
@@ -128,22 +89,9 @@ run(TS,AllowUserTerms,InclNodes,ExclNodes) when is_list(InclNodes),
is_list(ExclNodes) ->
run([TS],AllowUserTerms,InclNodes,ExclNodes).
-%%%-----------------------------------------------------------------
-%%% @spec run(TestSpecs,InclNodes,ExclNodes) -> ok
-%%% TestSpecs = string() | [SeparateOrMerged]
-%%% SeparateOrMerged = string() | [string()]
-%%% InclNodes = [atom()]
-%%% ExclNodes = [atom()]
-%%%
-%%% @equiv run(TestSpecs,false,InclNodes,ExclNodes)
run(TestSpecs,InclNodes,ExclNodes) ->
run(TestSpecs,false,InclNodes,ExclNodes).
-%%%-----------------------------------------------------------------
-%%% @spec run(TestSpecs) -> ok
-%%% TestSpecs = string() | [SeparateOrMerged]
-%%%
-%%% @equiv run(TestSpecs,false,[],[])
run(TestSpecs=[TS|_]) when is_list(TS) ->
run(TestSpecs,false,[],[]);
run(TS) ->
@@ -156,15 +104,6 @@ exclude_nodes([],RunSkipPerNode) ->
RunSkipPerNode.
-%%%-----------------------------------------------------------------
-%%% @spec run_on_node(TestSpecs,AllowUserTerms,Node) -> ok
-%%% TestSpecs = string() | [SeparateOrMerged]
-%%% SeparateOrMerged = string() | [string()]
-%%% AllowUserTerms = bool()
-%%% Node = atom()
-%%%
-%%% @doc Tests are spawned on <code>Node</code> according to
-%%% <code>TestSpecs</code>.
run_on_node([TS|TestSpecs],AllowUserTerms,Node) when is_list(TS),is_atom(Node) ->
case catch ct_testspec:collect_tests_from_file([TS],[Node],
AllowUserTerms) of
@@ -194,13 +133,6 @@ run_on_node([],_,_) ->
run_on_node(TS,AllowUserTerms,Node) when is_atom(Node) ->
run_on_node([TS],AllowUserTerms,Node).
-%%%-----------------------------------------------------------------
-%%% @spec run_on_node(TestSpecs,Node) -> ok
-%%% TestSpecs = string() | [SeparateOrMerged]
-%%% SeparateOrMerged = string() | [string()]
-%%% Node = atom()
-%%%
-%%% @equiv run_on_node(TestSpecs,false,Node)
run_on_node(TestSpecs,Node) ->
run_on_node(TestSpecs,false,Node).
@@ -264,64 +196,25 @@ run_all([],AllLogDirs,_,AllEvHs,_AllIncludes,
ok.
-%%%-----------------------------------------------------------------
-%%% @spec abort() -> ok
-%%%
-%%% @doc Stops all running tests.
abort() ->
call(abort).
-%%%-----------------------------------------------------------------
-%%% @spec abort(Nodes) -> ok
-%%% Nodes = atom() | [atom()]
-%%%
-%%% @doc Stops tests on specified nodes.
abort(Nodes) when is_list(Nodes) ->
call({abort,Nodes});
abort(Node) when is_atom(Node) ->
abort([Node]).
-%%%-----------------------------------------------------------------
-%%% @spec progress() -> [{Node,Status}]
-%%% Node = atom()
-%%% Status = finished_ok | ongoing | aborted | {error,Reason}
-%%% Reason = term()
-%%%
-%%% @doc Returns test progress. If <code>Status</code> is <code>ongoing</code>,
-%%% tests are running on the node and have not yet finished.
progress() ->
call(progress).
-%%%-----------------------------------------------------------------
-%%% @spec get_event_mgr_ref() -> MasterEvMgrRef
-%%% MasterEvMgrRef = atom()
-%%%
-%%% @doc <p>Call this function in order to get a reference to the
-%%% CT master event manager. The reference can be used to e.g.
-%%% add a user specific event handler while tests are running.
-%%% Example:
-%%% <c>gen_event:add_handler(ct_master:get_event_mgr_ref(), my_ev_h, [])</c></p>
get_event_mgr_ref() ->
?CT_MEVMGR_REF.
-%%%-----------------------------------------------------------------
-%%% @spec basic_html(Bool) -> ok
-%%% Bool = true | false
-%%%
-%%% @doc If set to true, the ct_master logs will be written on a
-%%% primitive html format, not using the Common Test CSS style
-%%% sheet.
basic_html(Bool) ->
application:set_env(common_test_master, basic_html, Bool),
ok.
-%%%-----------------------------------------------------------------
-%%% @spec esc_chars(Bool) -> ok
-%%% Bool = true | false
-%%%
-%%% @doc If set to false, the ct_master logs will be written without
-%%% special characters being escaped in the HTML logs.
esc_chars(Bool) ->
application:set_env(common_test_master, esc_chars, Bool),
ok.
@@ -340,12 +233,12 @@ start_master(NodeOptsList,EvHandlers,MasterLogDir,LogDirs,InitOptions,Specs) ->
{Master,Result} -> Result
end.
-%%% @hidden
init_master(Parent,NodeOptsList,EvHandlers,MasterLogDir,LogDirs,
InitOptions,Specs) ->
case whereis(ct_master) of
undefined ->
register(ct_master,self()),
+ ct_util:mark_process(),
ok;
_Pid ->
io:format("~nWarning: ct_master already running!~n"),
@@ -434,7 +327,7 @@ init_master1(Parent,NodeOptsList,InitOptions,LogDirs) ->
init_master2(Parent,NodeOptsList,LogDirs) ->
process_flag(trap_exit,true),
Cookie = erlang:get_cookie(),
- log(all,"Cookie","~w",[Cookie]),
+ log(all,"Cookie","~tw",[Cookie]),
log(all,"Starting Tests",
"Tests starting on: ~p",[[N || {N,_} <- NodeOptsList]]),
SpawnAndMon =
@@ -454,10 +347,10 @@ master_loop(#state{node_ctrl_pids=[],
results=Finished}) ->
Str =
lists:map(fun({Node,Result}) ->
- io_lib:format("~-40.40.*ts~p\n",
+ io_lib:format("~-40.40.*ts~tp\n",
[$_,atom_to_list(Node),Result])
end,lists:reverse(Finished)),
- log(all,"TEST RESULTS",Str,[]),
+ log(all,"TEST RESULTS","~ts", [Str]),
log(all,"Info","Updating log files",[]),
refresh_logs(LogDirs,[]),
@@ -488,7 +381,7 @@ master_loop(State=#state{node_ctrl_pids=NodeCtrlPids,
Bad
end,
log(all,"Test Info",
- "Test on node ~w failed! Reason: ~p",
+ "Test on node ~w failed! Reason: ~tp",
[Node,Error]),
{Locks1,Blocked1} =
update_queue(exit,Node,Locks,Blocked),
@@ -501,7 +394,7 @@ master_loop(State=#state{node_ctrl_pids=NodeCtrlPids,
undefined ->
%% ignore (but report) exit from master_logger etc
log(all,"Test Info",
- "Warning! Process ~w has terminated. Reason: ~p",
+ "Warning! Process ~w has terminated. Reason: ~tp",
[Pid,Reason]),
master_loop(State)
end;
@@ -584,7 +477,7 @@ update_queue(take,Node,From,Lock={Op,Resource},Locks,Blocked) ->
%% Blocked: [{{Operation,Resource},Node,WaitingPid},...]
case lists:keysearch(Lock,1,Locks) of
{value,{_Lock,Owner}} -> % other node has lock
- log(html,"Lock Info","Node ~w blocked on ~w by ~w. Resource: ~p",
+ log(html,"Lock Info","Node ~w blocked on ~w by ~w. Resource: ~tp",
[Node,Op,Owner,Resource]),
Blocked1 = Blocked ++ [{Lock,Node,From}],
{Locks,Blocked1};
@@ -599,7 +492,7 @@ update_queue(release,Node,_From,Lock={Op,Resource},Locks,Blocked) ->
case lists:keysearch(Lock,1,Blocked) of
{value,E={Lock,SomeNode,WaitingPid}} ->
Blocked1 = lists:delete(E,Blocked),
- log(html,"Lock Info","Node ~w proceeds with ~w. Resource: ~p",
+ log(html,"Lock Info","Node ~w proceeds with ~w. Resource: ~tp",
[SomeNode,Op,Resource]),
reply(ok,WaitingPid), % waiting process may start
{Locks1,Blocked1};
@@ -678,18 +571,18 @@ refresh_logs([D|Dirs],Refreshed) ->
refresh_logs([],Refreshed) ->
Str =
lists:map(fun({D,Result}) ->
- io_lib:format("Refreshing logs in ~p... ~p",
+ io_lib:format("Refreshing logs in ~tp... ~tp",
[D,Result])
end,Refreshed),
- log(all,"Info",Str,[]).
+ log(all,"Info","~ts", [Str]).
%%%-----------------------------------------------------------------
%%% NODE CONTROLLER, runs and controls tests on a test node.
%%%-----------------------------------------------------------------
-%%% @hidden
init_node_ctrl(MasterPid,Cookie,Opts) ->
%% make sure tests proceed even if connection to master is lost
process_flag(trap_exit, true),
+ ct_util:mark_process(),
MasterNode = node(MasterPid),
group_leader(whereis(user),self()),
io:format("~n********** node_ctrl process ~w started on ~w **********~n",
@@ -712,7 +605,7 @@ init_node_ctrl(MasterPid,Cookie,Opts) ->
{ok, _} = start_ct_event(),
ct_event:add_handler([{master,MasterPid}]),
- %% log("Running test with options: ~p~n", [Opts]),
+ %% log("Running test with options: ~tp~n", [Opts]),
Result = case (catch ct:run_test(Opts)) of
ok -> finished_ok;
Other -> Other
@@ -740,7 +633,6 @@ start_ct_event() ->
%%%-----------------------------------------------------------------
%%% Event handling
%%%-----------------------------------------------------------------
-%%% @hidden
status(MasterPid,Event=#event{name=start_make}) ->
call(MasterPid,Event);
status(MasterPid,Event=#event{name=finished_make}) ->
@@ -807,7 +699,7 @@ filter_accessible(InitOptions, Inaccessible)->
start_nodes(InitOptions)->
lists:foreach(fun({NodeName, Options})->
- [NodeS,HostS]=string:tokens(atom_to_list(NodeName), "@"),
+ [NodeS,HostS]=string:lexemes(atom_to_list(NodeName), "@"),
Node=list_to_atom(NodeS),
Host=list_to_atom(HostS),
HasNodeStart = lists:keymember(node_start, 1, Options),
@@ -828,7 +720,7 @@ start_nodes(InitOptions)->
"with callback ~w~n", [NodeName,Callback]);
{error, Reason, _NodeName} ->
io:format("Failed to start node ~w with callback ~w! "
- "Reason: ~p~n", [NodeName, Callback, Reason])
+ "Reason: ~tp~n", [NodeName, Callback, Reason])
end;
{true, true}->
io:format("WARNING: Node ~w is alive but has node_start "
@@ -857,10 +749,10 @@ eval_on_nodes(InitOptions)->
evaluate(Node, [{M,F,A}|MFAs])->
case rpc:call(Node, M, F, A) of
{badrpc,Reason}->
- io:format("WARNING: Failed to call ~w:~w/~w on node ~w "
- "due to ~p~n", [M,F,length(A),Node,Reason]);
+ io:format("WARNING: Failed to call ~w:~tw/~w on node ~w "
+ "due to ~tp~n", [M,F,length(A),Node,Reason]);
Result->
- io:format("Called ~w:~w/~w on node ~w, result: ~p~n",
+ io:format("Called ~w:~tw/~w on node ~w, result: ~tp~n",
[M,F,length(A),Node,Result])
end,
evaluate(Node, MFAs);
diff --git a/lib/common_test/src/ct_master_event.erl b/lib/common_test/src/ct_master_event.erl
index d28ef42c20..a6ec5a7a75 100644
--- a/lib/common_test/src/ct_master_event.erl
+++ b/lib/common_test/src/ct_master_event.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2018. 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.
@@ -18,13 +18,13 @@
%% %CopyrightEnd%
%%
-%%% @doc Common Test Framework Event Handler
+%%% Common Test Framework Event Handler
%%%
-%%% <p>This module implements an event handler that the CT Master
+%%% This module implements an event handler that the CT Master
%%% uses to handle status and progress notifications sent to the
%%% master node during test runs. This module may be used as a
%%% template for other event handlers that can be plugged in to
-%%% handle logging and reporting on the master node.</p>
+%%% handle logging and reporting on the master node.
-module(ct_master_event).
-behaviour(gen_event).
@@ -71,7 +71,7 @@ stop() ->
{error,Reason} ->
ct_master_logs:log("Error",
"No response from CT Master Event.\n"
- "Reason = ~p\n"
+ "Reason = ~tp\n"
"Terminating now!\n",[Reason]),
%% communication with event manager fails, kill it
catch exit(whereis(?CT_MEVMGR_REF), kill);
@@ -116,6 +116,7 @@ sync_notify(Event) ->
%% this function is called to initialize the event handler.
%%--------------------------------------------------------------------
init(_) ->
+ ct_util:mark_process(),
ct_master_logs:log("CT Master Event Handler started","",[]),
{ok,#state{}}.
@@ -135,7 +136,7 @@ handle_event(#event{name=start_logging,node=Node,data=RunDir},State) ->
handle_event(#event{name=Name,node=Node,data=Data},State) ->
print("~n=== ~w ===~n", [?MODULE]),
- print("~w on ~w: ~p~n", [Name,Node,Data]),
+ print("~tw on ~w: ~tp~n", [Name,Node,Data]),
{ok,State}.
%%--------------------------------------------------------------------
diff --git a/lib/common_test/src/ct_master_logs.erl b/lib/common_test/src/ct_master_logs.erl
index 52003f752d..ea6f93b565 100644
--- a/lib/common_test/src/ct_master_logs.erl
+++ b/lib/common_test/src/ct_master_logs.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2018. 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.
@@ -18,10 +18,10 @@
%% %CopyrightEnd%
%%
-%%% @doc Logging functionality for Common Test Master.
+%%% Logging functionality for Common Test Master.
%%%
-%%% <p>This module implements a logger for the master
-%%% node.</p>
+%%% This module implements a logger for the master
+%%% node.
-module(ct_master_logs).
-export([start/2, make_all_runs_index/0, log/3, nodedir/2,
@@ -88,6 +88,7 @@ stop() ->
init(Parent,LogDir,Nodes) ->
register(?MODULE,self()),
+ ct_util:mark_process(),
Time = calendar:local_time(),
RunDir = make_dirname(Time),
RunDirAbs = filename:join(LogDir,RunDir),
@@ -110,16 +111,16 @@ init(Parent,LogDir,Nodes) ->
case copy_priv_files(PrivFilesSrc, PrivFilesDestTop) of
{error,Src1,Dest1,Reason1} ->
io:format(user, "ERROR! "++
- "Priv file ~p could not be copied to ~p. "++
- "Reason: ~p~n",
+ "Priv file ~tp could not be copied to ~tp. "++
+ "Reason: ~tp~n",
[Src1,Dest1,Reason1]),
exit({priv_file_error,Dest1});
ok ->
case copy_priv_files(PrivFilesSrc, PrivFilesDestRun) of
{error,Src2,Dest2,Reason2} ->
io:format(user, "ERROR! "++
- "Priv file ~p could not be copied to ~p. "++
- "Reason: ~p~n",
+ "Priv file ~tp could not be copied to ~tp. "++
+ "Reason: ~tp~n",
[Src2,Dest2,Reason2]),
exit({priv_file_error,Dest2});
ok ->
@@ -170,7 +171,7 @@ loop(State) ->
case catch io:format(Fd,Str++"\n",Args) of
{'EXIT',Reason} ->
io:format(Fd,
- "Logging fails! Str: ~p, Args: ~p~n",
+ "Logging fails! Str: ~tp, Args: ~tp~n",
[Str,Args]),
exit({logging_failed,Reason}),
ok;
@@ -297,7 +298,7 @@ sort_all_runs(Dirs) ->
%% "YYYY-MM-DD_HH.MM.SS"
KeyList =
lists:map(fun(Dir) ->
- case lists:reverse(string:tokens(Dir,[$.,$_])) of
+ case lists:reverse(string:lexemes(Dir,[$.,$_])) of
[SS,MM,HH,Date|_] ->
{{Date,HH,MM,SS},Dir};
_Other ->
@@ -312,18 +313,8 @@ runentry(Dir) ->
{MasterStr,NodesStr} =
case read_details_file(Dir) of
{Master,Nodes} when is_list(Nodes) ->
- [_,Host] = string:tokens(atom_to_list(Master),"@"),
- NodesList =
- lists:reverse(lists:map(fun(N) ->
- atom_to_list(N) ++ ", "
- end,Nodes)),
- case NodesList of
- [N|NListR] ->
- N1 = string:sub_string(N,1,length(N)-2),
- {Host,lists:flatten(lists:reverse([N1|NListR]))};
- [] ->
- {Host,""}
- end;
+ [_,Host] = string:lexemes(atom_to_list(Master),"@"),
+ {Host,lists:concat(lists:join(", ",Nodes))};
_Error ->
{"unknown",""}
end,
@@ -348,7 +339,7 @@ all_runs_header() ->
xhtml("", "</tr></thead>\n<tbody>\n")]].
timestamp(Dir) ->
- [S,Min,H,D,M,Y|_] = lists:reverse(string:tokens(Dir,".-_")),
+ [S,Min,H,D,M,Y|_] = lists:reverse(string:lexemes(Dir,".-_")),
[S1,Min1,H1,D1,M1,Y1] = [list_to_integer(N) || N <- [S,Min,H,D,M,Y]],
format_time({{Y1,M1,D1},{H1,Min1,S1}}).
diff --git a/lib/common_test/src/ct_master_status.erl b/lib/common_test/src/ct_master_status.erl
index 7d3e54e645..cb01220121 100644
--- a/lib/common_test/src/ct_master_status.erl
+++ b/lib/common_test/src/ct_master_status.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2018. 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.
@@ -18,12 +18,12 @@
%% %CopyrightEnd%
%%
-%%% @doc Logging functionality for Common Test Master.
+%%% doc Logging functionality for Common Test Master.
%%%
-%%% <p>This module keeps a list of <code>{Node,Status}</code>
+%%% This module keeps a list of {Node,Status}
%%% tuples. It is possible to anytime during a test run get
%%% a snapshot of the test status. The module is an event
-%%% handler for the master event manager.</p>
+%%% handler for the master event manager.
-module(ct_master_status).
-behaviour(gen_event).
@@ -71,7 +71,7 @@ init(_) ->
%%
handle_event(#event{name=Name,node=Node,data=Data},State) ->
print("~n=== ~w ===~n", [?MODULE]),
- print("~w on ~w: ~p~n", [Name,Node,Data]),
+ print("~tw on ~w: ~tp~n", [Name,Node,Data]),
{ok,State}.
%%--------------------------------------------------------------------
diff --git a/lib/common_test/src/ct_netconfc.erl b/lib/common_test/src/ct_netconfc.erl
index ff45407fe0..6a758c4ea3 100644
--- a/lib/common_test/src/ct_netconfc.erl
+++ b/lib/common_test/src/ct_netconfc.erl
@@ -1,7 +1,7 @@
%%----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2012-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2012-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.
@@ -23,24 +23,15 @@
%% Description:
%% This file contains the Netconf client interface
%%
-%% @author Support
+%% Netconf servers can be configured by adding the following statement
+%% to a configuration file:
%%
-%% @doc Netconf client module.
+%% {server_id(),options()}.
%%
-%% <p>The Netconf client is compliant with RFC4741 and RFC4742.</p>
+%% The server_id() or an associated ct:target_name() shall then be
+%% used in calls to open/2 connect/2.
%%
-%% <p> For each server to test against, the following entry can be
-%% added to a configuration file:</p>
-%%
-%% <p>`{server_id(),options()}.'</p>
-%%
-%% <p> The `server_id()' or an associated `target_name()' (see
-%% {@link ct}) shall then be used in calls to {@link open/2}.</p>
-%%
-%% <p>If no configuration exists for a server, a session can still be
-%% opened by calling {@link open/2} with all necessary options given
-%% in the call. The first argument to {@link open/2} can then be any
-%% atom.</p>
+%% If no configuration exists for a server, use open/1 and connect/1.
%%
%% == Logging ==
%%
@@ -49,102 +40,15 @@
%% `ct_conn_log_h'. To use this error handler, add the `cth_conn_log'
%% hook in your test suite, e.g.
%%
-%% ```
%% suite() ->
-%% [{ct_hooks, [{cth_conn_log, [{conn_mod(),hook_options()}]}]}].
-%%'''
-%%
-%% The `conn_mod()' is the name of the common_test module implementing
-%% the connection protocol, e.g. `ct_netconfc'.
-%%
-%% The hook option `log_type' specifies the type of logging:
-%%
-%% <dl>
-%% <dt>`raw'</dt>
-%% <dd>The sent and received netconf data is logged to a separate
-%% text file as is without any formatting. A link to the file is
-%% added to the test case HTML log.</dd>
-%%
-%% <dt>`pretty'</dt>
-%% <dd>The sent and received netconf data is logged to a separate
-%% text file with XML data nicely indented. A link to the file is
-%% added to the test case HTML log.</dd>
-%%
-%% <dt>`html (default)'</dt>
-%% <dd>The sent and received netconf traffic is pretty printed
-%% directly in the test case HTML log.</dd>
-%%
-%% <dt>`silent'</dt>
-%% <dd>Netconf traffic is not logged.</dd>
-%% </dl>
-%%
-%% By default, all netconf traffic is logged in one single log
-%% file. However, it is possible to have different connections logged
-%% in separate files. To do this, use the hook option `hosts' and
-%% list the names of the servers/connections that will be used in the
-%% suite. Note that the connections must be named for this to work,
-%% i.e. they must be opened with {@link open/2}.
-%%
-%% The `hosts' option has no effect if `log_type' is set to `html' or
-%% `silent'.
-%%
-%% The hook options can also be specified in a configuration file with
-%% the configuration variable `ct_conn_log':
-%%
-%% ```
-%% {ct_conn_log,[{conn_mod(),hook_options()}]}.
-%% '''
+%% [{ct_hooks, [{cth_conn_log, [{ct:conn_log_mod(),ct:conn_log_options()}]}]}].
%%
%% For example:
%%
-%% ```
-%% {ct_conn_log,[{ct_netconfc,[{log_type,pretty},
-%% {hosts,[key_or_name()]}]}]}
-%% '''
-%%
-%% <b>Note</b> that hook options specified in a configuration file
-%% will overwrite the hardcoded hook options in the test suite.
-%%
-%% === Logging example 1 ===
-%%
-%% The following `ct_hooks' statement will cause pretty printing of
-%% netconf traffic to separate logs for the connections named
-%% `nc_server1' and `nc_server2'. Any other connections will be logged
-%% to default netconf log.
-%%
-%% ```
%% suite() ->
-%% [{ct_hooks, [{cth_conn_log, [{ct_netconfc,[{log_type,pretty}},
-%% {hosts,[nc_server1,nc_server2]}]}
-%% ]}]}].
-%%'''
-%%
-%% Connections must be opened like this:
-%%
-%% ```
-%% open(nc_server1,[...]),
-%% open(nc_server2,[...]).
-%% '''
-%%
-%% === Logging example 2 ===
-%%
-%% The following configuration file will cause raw logging of all
-%% netconf traffic into one single text file.
-%%
-%% ```
-%% {ct_conn_log,[{ct_netconfc,[{log_type,raw}]}]}.
-%% '''
-%%
-%% The `ct_hooks' statement must look like this:
-%%
-%% ```
-%% suite() ->
-%% [{ct_hooks, [{cth_conn_log, []}]}].
-%% '''
-%%
-%% The same `ct_hooks' statement without the configuration file would
-%% cause HTML logging of all netconf connections into the test case
-%% HTML log.
+%% [{ct_hooks,
+%% [{cth_conn_log,[{ct_netconfc,[{log_type,pretty},
+%% {hosts,[my_configured_server]}]}]}
%%
%% == Notifications ==
%%
@@ -152,11 +56,9 @@
%% Notifications, which defines a mechanism for an asynchronous
%% message notification delivery service for the netconf protocol.
%%
-%% Specific functions to support this are {@link
-%% create_subscription/6} and {@link get_event_streams/3}. (The
-%% functions also exist with other arities.)
+%% Specific functions to support this are create_subscription/6
+%% get_event_streams/3. (The functions also exist with other arities.)
%%
-%% @end
%%----------------------------------------------------------------------
-module(ct_netconfc).
@@ -167,7 +69,13 @@
%%----------------------------------------------------------------------
%% External exports
%%----------------------------------------------------------------------
--export([open/1,
+-export([connect/1,
+ connect/2,
+ disconnect/1,
+ session/1,
+ session/2,
+ session/3,
+ open/1,
open/2,
only_open/1,
only_open/2,
@@ -205,6 +113,7 @@
create_subscription/4,
create_subscription/5,
create_subscription/6,
+ get_event_streams/1,
get_event_streams/2,
get_event_streams/3,
get_capabilities/1,
@@ -215,7 +124,9 @@
%%----------------------------------------------------------------------
%% Exported types
%%----------------------------------------------------------------------
--export_type([notification/0]).
+-export_type([client/0,
+ handle/0,
+ notification/0]).
%%----------------------------------------------------------------------
%% Internal exports
@@ -273,13 +184,15 @@
host,
port = ?DEFAULT_PORT,
timeout = ?DEFAULT_TIMEOUT,
- name}).
+ name,
+ type}).
%% Connection reference
-record(connection, {reference, % {CM,Ch}
host,
port,
- name}).
+ name,
+ type}).
%% Pending replies from server
-record(pending, {tref, % timer ref (returned from timer:xxx)
@@ -291,17 +204,17 @@
%%----------------------------------------------------------------------
%% Type declarations
%%----------------------------------------------------------------------
--type client() :: handle() | ct_gen_conn:server_id() | ct_gen_conn:target_name().
--type handle() :: term().
-%% An opaque reference for a connection (netconf session). See {@link
-%% ct} for more information.
+-type client() :: handle() | server_id() | ct:target_name().
+-opaque handle() :: pid().
-type options() :: [option()].
-%% Options used for setting up ssh connection to a netconf server.
-
-type option() :: {ssh,host()} | {port,inet:port_number()} | {user,string()} |
{password,string()} | {user_dir,string()} |
{timeout,timeout()}.
+
+-type session_options() :: [session_option()].
+-type session_option() :: {timeout,timeout()}.
+
-type host() :: inet:hostname() | inet:ip_address().
-type notification() :: {notification, xml_attributes(), notification_content()}.
@@ -317,14 +230,13 @@
%% See XML Schema for Event Notifications found in RFC5277 for further
%% detail about the data format for the string values.
-%-type error_handler() :: module().
-type error_reason() :: term().
+-type server_id() :: atom().
+
-type simple_xml() :: {xml_tag(), xml_attributes(), xml_content()} |
{xml_tag(), xml_content()} |
xml_tag().
-%% <p>This type is further described in the documentation for the
-%% <tt>Xmerl</tt> application.</p>
-type xml_tag() :: atom().
-type xml_attributes() :: [{xml_attribute_tag(),xml_attribute_value()}].
-type xml_attribute_tag() :: atom().
@@ -336,69 +248,130 @@
-type xs_datetime() :: string().
%% This date and time identifyer has the same format as the XML type
%% dateTime and compliant to RFC3339. The format is
-%% ```[-]CCYY-MM-DDThh:mm:ss[.s][Z|(+|-)hh:mm]'''
+%% "[-]CCYY-MM-DDThh:mm:ss[.s][Z|(+|-)hh:mm]"
%%----------------------------------------------------------------------
%% External interface functions
%%----------------------------------------------------------------------
%%----------------------------------------------------------------------
--spec open(Options) -> Result when
+%% Open an SSH connection to a Netconf server
+%% If the server options are specified in a configuration file, use
+%% open/2.
+-spec connect(Options) -> Result when
Options :: options(),
Result :: {ok,handle()} | {error,error_reason()}.
-%% @doc Open a netconf session and exchange `hello' messages.
-%%
-%% If the server options are specified in a configuration file, or if
-%% a named client is needed for logging purposes (see {@section
-%% Logging}) use {@link open/2} instead.
-%%
-%% The opaque `handler()' reference which is returned from this
-%% function is required as client identifier when calling any other
-%% function in this module.
-%%
-%% The `timeout' option (milli seconds) is used when setting up
-%% the ssh connection and when waiting for the hello message from the
-%% server. It is not used for any other purposes during the lifetime
-%% of the connection.
-%%
-%% @end
+connect(Options) ->
+ do_connect(Options, #options{type=connection},[]).
+
+-spec connect(KeyOrName,ExtraOptions) -> Result when
+ KeyOrName :: ct:key_or_name(),
+ ExtraOptions :: options(),
+ Result :: {ok,handle()} | {error,error_reason()}.
+connect(KeyOrName, ExtraOptions) ->
+ SortedExtra = lists:keysort(1,ExtraOptions),
+ SortedConfig = lists:keysort(1,ct:get_config(KeyOrName,[])),
+ AllOpts = lists:ukeymerge(1,SortedConfig,SortedExtra),
+ do_connect(AllOpts,#options{name=KeyOrName,type=connection},[{name,KeyOrName}]).
+
+do_connect(OptList,InitOptRec,NameOpt) ->
+ case check_options(OptList,InitOptRec) of
+ {Host,Port,Options} ->
+ ct_gen_conn:start({Host,Port},Options,?MODULE,
+ NameOpt ++ [{reconnect,false},
+ {use_existing_connection,false},
+ {forward_messages,false}]);
+ Error ->
+ Error
+ end.
+
%%----------------------------------------------------------------------
-open(Options) ->
- open(Options,#options{},[],true).
+%% Close the given SSH connection.
+-spec disconnect(Conn) -> ok | {error,error_reason()} when
+ Conn :: handle().
+disconnect(Conn) ->
+ case call(Conn,get_ssh_connection) of
+ {ok,_} ->
+ ct_gen_conn:stop(Conn);
+ Error ->
+ Error
+ end.
%%----------------------------------------------------------------------
+%% Open a netconf session as a channel on the given SSH connection,
+%% and exchange `hello' messages.
+-spec session(Conn) -> Result when
+ Conn :: handle(),
+ Result :: {ok,handle()} | {error,error_reason()}.
+session(Conn) ->
+ do_session(Conn,[],#options{type=channel},[]).
+
+-spec session(Conn,Options) -> Result when
+ Conn :: handle(),
+ Options :: session_options(),
+ Result :: {ok,handle()} | {error,error_reason()};
+ (KeyOrName,Conn) -> Result when
+ KeyOrName :: ct:key_or_name(),
+ Conn :: handle(),
+ Result :: {ok,handle()} | {error,error_reason()}.
+session(Conn,Options) when is_list(Options) ->
+ do_session(Conn,Options,#options{type=channel},[]);
+session(KeyOrName,Conn) ->
+ do_session(Conn,[],#options{name=KeyOrName,type=channel},[{name,KeyOrName}]).
+
+-spec session(KeyOrName,Conn,Options) -> Result when
+ Conn :: handle(),
+ Options :: session_options(),
+ KeyOrName :: ct:key_or_name(),
+ Result :: {ok,handle()} | {error,error_reason()}.
+session(KeyOrName,Conn,ExtraOptions) ->
+ SortedExtra = lists:keysort(1,ExtraOptions),
+ SortedConfig = lists:keysort(1,ct:get_config(KeyOrName,[])),
+ AllOpts = lists:ukeymerge(1,SortedConfig,SortedExtra),
+ do_session(Conn,AllOpts,#options{name=KeyOrName,type=channel},
+ [{name,KeyOrName}]).
+
+do_session(Conn,OptList,InitOptRec,NameOpt) ->
+ case call(Conn,get_ssh_connection) of
+ {ok,SshConn} ->
+ case check_session_options(OptList,InitOptRec) of
+ {ok,Options} ->
+ case ct_gen_conn:start(SshConn,Options,?MODULE,
+ NameOpt ++
+ [{reconnect,false},
+ {use_existing_connection,false},
+ {forward_messages,true}]) of
+ {ok,Client} ->
+ case hello(Client,Options#options.timeout) of
+ ok ->
+ {ok,Client};
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
+ end.
+
+%%----------------------------------------------------------------------
+%% Open a netconf session and exchange 'hello' messages.
+%% If the server options are specified in a configuration file, use
+%% open/2.
+-spec open(Options) -> Result when
+ Options :: options(),
+ Result :: {ok,handle()} | {error,error_reason()}.
+open(Options) ->
+ open(Options,#options{type=connection_and_channel},[],true).
+
-spec open(KeyOrName, ExtraOptions) -> Result when
- KeyOrName :: ct_gen_conn:key_or_name(),
+ KeyOrName :: ct:key_or_name(),
ExtraOptions :: options(),
Result :: {ok,handle()} | {error,error_reason()}.
-%% @doc Open a named netconf session and exchange `hello' messages.
-%%
-%% If `KeyOrName' is a configured `server_id()' or a
-%% `target_name()' associated with such an ID, then the options
-%% for this server will be fetched from the configuration file.
-%
-%% The `ExtraOptions' argument will be added to the options found in
-%% the configuration file. If the same options are given, the values
-%% from the configuration file will overwrite `ExtraOptions'.
-%%
-%% If the server is not specified in a configuration file, use {@link
-%% open/1} instead.
-%%
-%% The opaque `handle()' reference which is returned from this
-%% function can be used as client identifier when calling any other
-%% function in this module. However, if `KeyOrName' is a
-%% `target_name()', i.e. if the server is named via a call to
-%% `ct:require/2' or a `require' statement in the test
-%% suite, then this name may be used instead of the `handle()'.
-%%
-%% The `timeout' option (milli seconds) is used when setting up
-%% the ssh connection and when waiting for the hello message from the
-%% server. It is not used for any other purposes during the lifetime
-%% of the connection.
-%%
-%% @see ct:require/2
-%% @end
-%%----------------------------------------------------------------------
open(KeyOrName, ExtraOpts) ->
open(KeyOrName, ExtraOpts, true).
@@ -406,10 +379,11 @@ open(KeyOrName, ExtraOpts, Hello) ->
SortedExtra = lists:keysort(1,ExtraOpts),
SortedConfig = lists:keysort(1,ct:get_config(KeyOrName,[])),
AllOpts = lists:ukeymerge(1,SortedConfig,SortedExtra),
- open(AllOpts,#options{name=KeyOrName},[{name,KeyOrName}],Hello).
+ open(AllOpts,#options{name=KeyOrName,type=connection_and_channel},
+ [{name,KeyOrName}],Hello).
open(OptList,InitOptRec,NameOpt,Hello) ->
- case check_options(OptList,undefined,undefined,InitOptRec) of
+ case check_options(OptList,InitOptRec) of
{Host,Port,Options} ->
case ct_gen_conn:start({Host,Port},Options,?MODULE,
NameOpt ++ [{reconnect,false},
@@ -431,396 +405,288 @@ open(OptList,InitOptRec,NameOpt,Hello) ->
%%----------------------------------------------------------------------
+%% As open/1,2, except no 'hello' message is sent.
-spec only_open(Options) -> Result when
Options :: options(),
Result :: {ok,handle()} | {error,error_reason()}.
-%% @doc Open a netconf session, but don't send `hello'.
-%%
-%% As {@link open/1} but does not send a `hello' message.
-%%
-%% @end
-%%----------------------------------------------------------------------
only_open(Options) ->
- open(Options,#options{},[],false).
+ open(Options,#options{type=connection_and_channel},[],false).
-%%----------------------------------------------------------------------
-spec only_open(KeyOrName,ExtraOptions) -> Result when
- KeyOrName :: ct_gen_conn:key_or_name(),
+ KeyOrName :: ct:key_or_name(),
ExtraOptions :: options(),
Result :: {ok,handle()} | {error,error_reason()}.
-%% @doc Open a name netconf session, but don't send `hello'.
-%%
-%% As {@link open/2} but does not send a `hello' message.
-%%
-%% @end
-%%----------------------------------------------------------------------
only_open(KeyOrName, ExtraOpts) ->
open(KeyOrName, ExtraOpts, false).
%%----------------------------------------------------------------------
-%% @spec hello(Client) -> Result
-%% @equiv hello(Client, [], infinity)
+%% Send a 'hello' message.
+-spec hello(Client) -> Result when
+ Client :: handle(),
+ Result :: ok | {error,error_reason()}.
hello(Client) ->
hello(Client,[],?DEFAULT_TIMEOUT).
-%%----------------------------------------------------------------------
-spec hello(Client,Timeout) -> Result when
Client :: handle(),
Timeout :: timeout(),
Result :: ok | {error,error_reason()}.
-%% @spec hello(Client, Timeout) -> Result
-%% @equiv hello(Client, [], Timeout)
hello(Client,Timeout) ->
hello(Client,[],Timeout).
-%%----------------------------------------------------------------------
-spec hello(Client,Options,Timeout) -> Result when
Client :: handle(),
Options :: [{capability, [string()]}],
Timeout :: timeout(),
Result :: ok | {error,error_reason()}.
-%% @doc Exchange `hello' messages with the server.
-%%
-%% Adds optional capabilities and sends a `hello' message to the
-%% server and waits for the return.
-%% @end
-%%----------------------------------------------------------------------
hello(Client,Options,Timeout) ->
call(Client, {hello, Options, Timeout}).
%%----------------------------------------------------------------------
-%% @spec get_session_id(Client) -> Result
-%% @equiv get_session_id(Client, infinity)
+%% Get the session id for the session specified by Client.
+-spec get_session_id(Client) -> Result when
+ Client :: client(),
+ Result :: pos_integer() | {error,error_reason()}.
get_session_id(Client) ->
get_session_id(Client, ?DEFAULT_TIMEOUT).
-%%----------------------------------------------------------------------
-spec get_session_id(Client, Timeout) -> Result when
Client :: client(),
Timeout :: timeout(),
Result :: pos_integer() | {error,error_reason()}.
-%% @doc Returns the session id associated with the given client.
-%%
-%% @end
-%%----------------------------------------------------------------------
get_session_id(Client, Timeout) ->
call(Client, get_session_id, Timeout).
%%----------------------------------------------------------------------
-%% @spec get_capabilities(Client) -> Result
-%% @equiv get_capabilities(Client, infinity)
+%% Get the server side capabilities.
+-spec get_capabilities(Client) -> Result when
+ Client :: client(),
+ Result :: [string()] | {error,error_reason()}.
get_capabilities(Client) ->
get_capabilities(Client, ?DEFAULT_TIMEOUT).
-%%----------------------------------------------------------------------
-spec get_capabilities(Client, Timeout) -> Result when
Client :: client(),
Timeout :: timeout(),
Result :: [string()] | {error,error_reason()}.
-%% @doc Returns the server side capabilities
-%%
-%% The following capability identifiers, defined in RFC 4741, can be returned:
-%%
-%% <ul>
-%% <li>`"urn:ietf:params:netconf:base:1.0"'</li>
-%% <li>`"urn:ietf:params:netconf:capability:writable-running:1.0"'</li>
-%% <li>`"urn:ietf:params:netconf:capability:candidate:1.0"'</li>
-%% <li>`"urn:ietf:params:netconf:capability:confirmed-commit:1.0"'</li>
-%% <li>`"urn:ietf:params:netconf:capability:rollback-on-error:1.0"'</li>
-%% <li>`"urn:ietf:params:netconf:capability:startup:1.0"'</li>
-%% <li>`"urn:ietf:params:netconf:capability:url:1.0"'</li>
-%% <li>`"urn:ietf:params:netconf:capability:xpath:1.0"'</li>
-%% </ul>
-%%
-%% Note, additional identifiers may exist, e.g. server side namespace.
-%%
-%% @end
-%%----------------------------------------------------------------------
get_capabilities(Client, Timeout) ->
call(Client, get_capabilities, Timeout).
%%----------------------------------------------------------------------
-%% @spec send(Client, SimpleXml) -> Result
-%% @equiv send(Client, SimpleXml, infinity)
+%% Send an XML document to the server.
+-spec send(Client, SimpleXml) -> Result when
+ Client :: client(),
+ SimpleXml :: simple_xml(),
+ Result :: simple_xml() | {error,error_reason()}.
send(Client, SimpleXml) ->
send(Client, SimpleXml, ?DEFAULT_TIMEOUT).
-%%----------------------------------------------------------------------
-spec send(Client, SimpleXml, Timeout) -> Result when
Client :: client(),
SimpleXml :: simple_xml(),
Timeout :: timeout(),
Result :: simple_xml() | {error,error_reason()}.
-%% @doc Send an XML document to the server.
-%%
-%% The given XML document is sent as is to the server. This function
-%% can be used for sending XML documents that can not be expressed by
-%% other interface functions in this module.
send(Client, SimpleXml, Timeout) ->
call(Client,{send, Timeout, SimpleXml}).
%%----------------------------------------------------------------------
-%% @spec send_rpc(Client, SimpleXml) -> Result
-%% @equiv send_rpc(Client, SimpleXml, infinity)
+%% Wrap the given XML document in a valid netconf 'rpc' request and
+%% send to the server.
+-spec send_rpc(Client, SimpleXml) -> Result when
+ Client :: client(),
+ SimpleXml :: simple_xml(),
+ Result :: [simple_xml()] | {error,error_reason()}.
send_rpc(Client, SimpleXml) ->
send_rpc(Client, SimpleXml, ?DEFAULT_TIMEOUT).
-%%----------------------------------------------------------------------
-spec send_rpc(Client, SimpleXml, Timeout) -> Result when
Client :: client(),
SimpleXml :: simple_xml(),
Timeout :: timeout(),
Result :: [simple_xml()] | {error,error_reason()}.
-%% @doc Send a Netconf <code>rpc</code> request to the server.
-%%
-%% The given XML document is wrapped in a valid Netconf
-%% <code>rpc</code> request and sent to the server. The
-%% <code>message-id</code> and namespace attributes are added to the
-%% <code>rpc</code> element.
-%%
-%% This function can be used for sending <code>rpc</code> requests
-%% that can not be expressed by other interface functions in this
-%% module.
send_rpc(Client, SimpleXml, Timeout) ->
call(Client,{send_rpc, SimpleXml, Timeout}).
%%----------------------------------------------------------------------
-%% @spec lock(Client, Target) -> Result
-%% @equiv lock(Client, Target, infinity)
+%% Send a 'lock' request.
+-spec lock(Client, Target) -> Result when
+ Client :: client(),
+ Target :: netconf_db(),
+ Result :: ok | {error,error_reason()}.
lock(Client, Target) ->
lock(Client, Target,?DEFAULT_TIMEOUT).
-%%----------------------------------------------------------------------
-spec lock(Client, Target, Timeout) -> Result when
Client :: client(),
Target :: netconf_db(),
Timeout :: timeout(),
Result :: ok | {error,error_reason()}.
-%% @doc Unlock configuration target.
-%%
-%% Which target parameters that can be used depends on if
-%% `:candidate' and/or `:startup' are supported by the
-%% server. If successfull, the configuration system of the device is
-%% not available to other clients (Netconf, CORBA, SNMP etc). Locks
-%% are intended to be short-lived.
-%%
-%% The operations {@link kill_session/2} or {@link kill_session/3} can
-%% be used to force the release of a lock owned by another Netconf
-%% session. How this is achieved by the server side is implementation
-%% specific.
-%%
-%% @end
-%%----------------------------------------------------------------------
lock(Client, Target, Timeout) ->
call(Client,{send_rpc_op,lock,[Target],Timeout}).
%%----------------------------------------------------------------------
-%% @spec unlock(Client, Target) -> Result
-%% @equiv unlock(Client, Target, infinity)
+%% Send a 'unlock' request.
+-spec unlock(Client, Target) -> Result when
+ Client :: client(),
+ Target :: netconf_db(),
+ Result :: ok | {error,error_reason()}.
unlock(Client, Target) ->
unlock(Client, Target,?DEFAULT_TIMEOUT).
-%%----------------------------------------------------------------------
-spec unlock(Client, Target, Timeout) -> Result when
Client :: client(),
Target :: netconf_db(),
Timeout :: timeout(),
Result :: ok | {error,error_reason()}.
-%% @doc Unlock configuration target.
-%%
-%% If the client earlier has aquired a lock, via {@link lock/2} or
-%% {@link lock/3}, this operation release the associated lock. To be
-%% able to access another target than `running', the server must
-%% support `:candidate' and/or `:startup'.
-%%
-%% @end
-%%----------------------------------------------------------------------
unlock(Client, Target, Timeout) ->
call(Client, {send_rpc_op, unlock, [Target], Timeout}).
%%----------------------------------------------------------------------
-%% @spec get(Client, Filter) -> Result
-%% @equiv get(Client, Filter, infinity)
+%% Send a 'get' request.
+-spec get(Client, Filter) -> Result when
+ Client :: client(),
+ Filter :: simple_xml() | xpath(),
+ Result :: {ok,[simple_xml()]} | {error,error_reason()}.
get(Client, Filter) ->
get(Client, Filter, ?DEFAULT_TIMEOUT).
-%%----------------------------------------------------------------------
-spec get(Client, Filter, Timeout) -> Result when
Client :: client(),
Filter :: simple_xml() | xpath(),
Timeout :: timeout(),
Result :: {ok,[simple_xml()]} | {error,error_reason()}.
-%% @doc Get data.
-%%
-%% This operation returns both configuration and state data from the
-%% server.
-%%
-%% Filter type `xpath' can only be used if the server supports
-%% `:xpath'.
-%%
-%% @end
-%%----------------------------------------------------------------------
get(Client, Filter, Timeout) ->
call(Client,{send_rpc_op, get, [Filter], Timeout}).
%%----------------------------------------------------------------------
-%% @spec get_config(Client, Source, Filter) -> Result
-%% @equiv get_config(Client, Source, Filter, infinity)
+%% Send a 'get-config' request.
+-spec get_config(Client, Source, Filter) -> Result when
+ Client :: client(),
+ Source :: netconf_db(),
+ Filter :: simple_xml() | xpath(),
+ Result :: {ok,[simple_xml()]} | {error,error_reason()}.
get_config(Client, Source, Filter) ->
get_config(Client, Source, Filter, ?DEFAULT_TIMEOUT).
-%%----------------------------------------------------------------------
-spec get_config(Client, Source, Filter, Timeout) -> Result when
Client :: client(),
Source :: netconf_db(),
Filter :: simple_xml() | xpath(),
Timeout :: timeout(),
Result :: {ok,[simple_xml()]} | {error,error_reason()}.
-%% @doc Get configuration data.
-%%
-%% To be able to access another source than `running', the server
-%% must advertise `:candidate' and/or `:startup'.
-%%
-%% Filter type `xpath' can only be used if the server supports
-%% `:xpath'.
-%%
-%%
-%% @end
-%%----------------------------------------------------------------------
get_config(Client, Source, Filter, Timeout) ->
call(Client, {send_rpc_op, get_config, [Source, Filter], Timeout}).
%%----------------------------------------------------------------------
-%% @spec edit_config(Client, Target, Config) -> Result
-%% @equiv edit_config(Client, Target, Config, [], infinity)
+%% Send a 'edit-config' request.
+-spec edit_config(Client, Target, Config) -> Result when
+ Client :: client(),
+ Target :: netconf_db(),
+ Config :: simple_xml() | [simple_xml()],
+ Result :: ok | {error,error_reason()}.
edit_config(Client, Target, Config) ->
edit_config(Client, Target, Config, ?DEFAULT_TIMEOUT).
-%%----------------------------------------------------------------------
--spec edit_config(Client, Target, Config, OptParamsOrTimeout) -> Result when
+-spec edit_config(Client, Target, Config, OptParams) -> Result when
+ Client :: client(),
+ Target :: netconf_db(),
+ Config :: simple_xml() | [simple_xml()],
+ OptParams :: [simple_xml()],
+ Result :: ok | {error,error_reason()};
+ (Client, Target, Config, Timeout) -> Result when
Client :: client(),
Target :: netconf_db(),
Config :: simple_xml(),
- OptParamsOrTimeout :: [simple_xml()] | timeout(),
+ Timeout :: timeout(),
Result :: ok | {error,error_reason()}.
-%% @doc
-%%
-%% If `OptParamsOrTimeout' is a timeout value, then this is
-%% equivalent to {@link edit_config/5. edit_config(Client, Target,
-%% Config, [], Timeout)}.
-%%
-%% If `OptParamsOrTimeout' is a list of simple XML, then this is
-%% equivalent to {@link edit_config/5. edit_config(Client, Target,
-%% Config, OptParams, infinity)}.
-%%
-%% @end
edit_config(Client, Target, Config, Timeout) when ?is_timeout(Timeout) ->
edit_config(Client, Target, Config, [], Timeout);
edit_config(Client, Target, Config, OptParams) when is_list(OptParams) ->
edit_config(Client, Target, Config, OptParams, ?DEFAULT_TIMEOUT).
-%%----------------------------------------------------------------------
-spec edit_config(Client, Target, Config, OptParams, Timeout) -> Result when
Client :: client(),
Target :: netconf_db(),
- Config :: simple_xml(),
+ Config :: simple_xml() | [simple_xml()],
OptParams :: [simple_xml()],
Timeout :: timeout(),
Result :: ok | {error,error_reason()}.
-%% @doc Edit configuration data.
-%%
-%% Per default only the running target is available, unless the server
-%% include `:candidate' or `:startup' in its list of
-%% capabilities.
-%%
-%% `OptParams' can be used for specifying optional parameters
-%% (`default-operation', `test-option' or `error-option') that will be
-%% added to the `edit-config' request. The value must be a list
-%% containing valid simple XML, for example
-%%
-%% ```
-%% [{'default-operation', ["none"]},
-%% {'error-option', ["rollback-on-error"]}]
-%%'''
-%%
-%% @end
-%%----------------------------------------------------------------------
+edit_config(Client, Target, Config, OptParams, Timeout) when not is_list(Config)->
+ edit_config(Client, Target, [Config], OptParams, Timeout);
edit_config(Client, Target, Config, OptParams, Timeout) ->
call(Client, {send_rpc_op, edit_config, [Target,Config,OptParams], Timeout}).
%%----------------------------------------------------------------------
-%% @spec delete_config(Client, Target) -> Result
-%% @equiv delete_config(Client, Target, infinity)
+%% Send a 'delete-config' request.
+-spec delete_config(Client, Target) -> Result when
+ Client :: client(),
+ Target :: startup | candidate,
+ Result :: ok | {error,error_reason()}.
delete_config(Client, Target) ->
delete_config(Client, Target, ?DEFAULT_TIMEOUT).
-%%----------------------------------------------------------------------
-spec delete_config(Client, Target, Timeout) -> Result when
Client :: client(),
Target :: startup | candidate,
Timeout :: timeout(),
Result :: ok | {error,error_reason()}.
-%% @doc Delete configuration data.
-%%
-%% The running configuration cannot be deleted and `:candidate'
-%% or `:startup' must be advertised by the server.
-%%
-%% @end
-%%----------------------------------------------------------------------
delete_config(Client, Target, Timeout) when Target == startup;
Target == candidate ->
call(Client,{send_rpc_op, delete_config, [Target], Timeout}).
%%----------------------------------------------------------------------
-%% @spec copy_config(Client, Source, Target) -> Result
-%% @equiv copy_config(Client, Source, Target, infinity)
+%% Send a 'copy-config' request.
+-spec copy_config(Client, Target, Source) -> Result when
+ Client :: client(),
+ Target :: netconf_db(),
+ Source :: netconf_db(),
+ Result :: ok | {error,error_reason()}.
copy_config(Client, Source, Target) ->
copy_config(Client, Source, Target, ?DEFAULT_TIMEOUT).
-%%----------------------------------------------------------------------
-spec copy_config(Client, Target, Source, Timeout) -> Result when
Client :: client(),
Target :: netconf_db(),
Source :: netconf_db(),
Timeout :: timeout(),
Result :: ok | {error,error_reason()}.
-%% @doc Copy configuration data.
-%%
-%% Which source and target options that can be issued depends on the
-%% capabilities supported by the server. I.e. `:candidate' and/or
-%% `:startup' are required.
-%%
-%% @end
-%%----------------------------------------------------------------------
copy_config(Client, Target, Source, Timeout) ->
call(Client,{send_rpc_op, copy_config, [Target, Source], Timeout}).
%%----------------------------------------------------------------------
-%% @spec action(Client, Action) -> Result
-%% @equiv action(Client, Action, infinity)
+%% Execute an action.
+-spec action(Client, Action) -> Result when
+ Client :: client(),
+ Action :: simple_xml(),
+ Result :: ok | {ok,[simple_xml()]} | {error,error_reason()}.
action(Client,Action) ->
action(Client,Action,?DEFAULT_TIMEOUT).
-%%----------------------------------------------------------------------
-spec action(Client, Action, Timeout) -> Result when
Client :: client(),
Action :: simple_xml(),
Timeout :: timeout(),
Result :: ok | {ok,[simple_xml()]} | {error,error_reason()}.
-%% @doc Execute an action. If the return type is void, <c>ok</c> will
-%% be returned instead of <c>{ok,[simple_xml()]}</c>.
-%%
-%% @end
-%%----------------------------------------------------------------------
action(Client,Action,Timeout) ->
call(Client,{send_rpc_op, action, [Action], Timeout}).
%%----------------------------------------------------------------------
+%% Send a 'create-subscription' request
+%% See RFC5277, NETCONF Event Notifications
+-spec create_subscription(Client) -> Result when
+ Client :: client(),
+ Result :: ok | {error,error_reason()}.
create_subscription(Client) ->
create_subscription(Client,?DEFAULT_STREAM,?DEFAULT_TIMEOUT).
+-spec create_subscription(Client, Stream | Filter | Timeout) -> Result when
+ Client :: client(),
+ Stream :: stream_name(),
+ Filter :: simple_xml() | [simple_xml()],
+ Timeout :: timeout(),
+ Result :: ok | {error,error_reason()}.
create_subscription(Client,Timeout)
when ?is_timeout(Timeout) ->
create_subscription(Client,?DEFAULT_STREAM,Timeout);
@@ -876,6 +742,22 @@ create_subscription(Client,Stream,Filter,Timeout)
[Stream,Filter,undefined,undefined],
Timeout}).
+-spec create_subscription(Client, Stream, StartTime, StopTime, Timeout) ->
+ Result when
+ Client :: client(),
+ Stream :: stream_name(),
+ StartTime :: xs_datetime(),
+ StopTime :: xs_datetime(),
+ Timeout :: timeout(),
+ Result :: ok | {error,error_reason()};
+ (Client, Stream, Filter,StartTime, StopTime) ->
+ Result when
+ Client :: client(),
+ Stream :: stream_name(),
+ Filter :: simple_xml() | [simple_xml()],
+ StartTime :: xs_datetime(),
+ StopTime :: xs_datetime(),
+ Result :: ok | {error,error_reason()}.
create_subscription(Client,Stream,StartTime,StopTime,Timeout)
when ?is_string(Stream) andalso
?is_string(StartTime) andalso
@@ -891,7 +773,6 @@ create_subscription(Client,Stream,Filter,StartTime,StopTime)
?is_string(StopTime) ->
create_subscription(Client,Stream,Filter,StartTime,StopTime,?DEFAULT_TIMEOUT).
-%%----------------------------------------------------------------------
-spec create_subscription(Client, Stream, Filter,StartTime, StopTime, Timeout) ->
Result when
Client :: client(),
@@ -901,168 +782,75 @@ create_subscription(Client,Stream,Filter,StartTime,StopTime)
StopTime :: xs_datetime(),
Timeout :: timeout(),
Result :: ok | {error,error_reason()}.
-%% @doc Create a subscription for event notifications.
-%%
-%% This function sets up a subscription for netconf event
-%% notifications of the given stream type, matching the given
-%% filter. The calling process will receive notifications as messages
-%% of type `notification()'.
-%%
-%% <dl>
-%% <dt>Stream:</dt>
-%% <dd> An optional parameter that indicates which stream of events
-%% is of interest. If not present, events in the default NETCONF
-%% stream will be sent.</dd>
-%%
-%% <dt>Filter:</dt>
-%% <dd>An optional parameter that indicates which subset of all
-%% possible events is of interest. The format of this parameter is
-%% the same as that of the filter parameter in the NETCONF protocol
-%% operations. If not present, all events not precluded by other
-%% parameters will be sent.</dd>
-%%
-%% <dt>StartTime:</dt>
-%% <dd>An optional parameter used to trigger the replay feature and
-%% indicate that the replay should start at the time specified. If
-%% `StartTime' is not present, this is not a replay subscription.
-%% It is not valid to specify start times that are later than the
-%% current time. If the `StartTime' specified is earlier than the
-%% log can support, the replay will begin with the earliest
-%% available notification. This parameter is of type dateTime and
-%% compliant to [RFC3339]. Implementations must support time
-%% zones.</dd>
-%%
-%% <dt>StopTime:</dt>
-%% <dd>An optional parameter used with the optional replay feature
-%% to indicate the newest notifications of interest. If `StopTime'
-%% is not present, the notifications will continue until the
-%% subscription is terminated. Must be used with and be later than
-%% `StartTime'. Values of `StopTime' in the future are valid. This
-%% parameter is of type dateTime and compliant to [RFC3339].
-%% Implementations must support time zones.</dd>
-%% </dl>
-%%
-%% See RFC5277 for further details about the event notification
-%% mechanism.
-%%
-%% @end
-%%----------------------------------------------------------------------
create_subscription(Client,Stream,Filter,StartTime,StopTime,Timeout) ->
call(Client,{send_rpc_op,{create_subscription, self()},
[Stream,Filter,StartTime,StopTime],
Timeout}).
%%----------------------------------------------------------------------
-%% @spec get_event_streams(Client, Timeout) -> Result
-%% @equiv get_event_streams(Client, [], Timeout)
+%% Send a request to get the given event streams
+%% See RFC5277, NETCONF Event Notifications
+-spec get_event_streams(Client)
+ -> Result when
+ Client :: client(),
+ Result :: {ok,streams()} | {error,error_reason()}.
+get_event_streams(Client) ->
+ get_event_streams(Client,[],?DEFAULT_TIMEOUT).
+
+-spec get_event_streams(Client, Timeout)
+ -> Result when
+ Client :: client(),
+ Timeout :: timeout(),
+ Result :: {ok,streams()} | {error,error_reason()};
+ (Client, Streams) -> Result when
+ Client :: client(),
+ Streams :: [stream_name()],
+ Result :: {ok,streams()} | {error,error_reason()}.
get_event_streams(Client,Timeout) when is_integer(Timeout); Timeout==infinity ->
get_event_streams(Client,[],Timeout);
-
-%%----------------------------------------------------------------------
-%% @spec get_event_streams(Client, Streams) -> Result
-%% @equiv get_event_streams(Client, Streams, infinity)
get_event_streams(Client,Streams) when is_list(Streams) ->
get_event_streams(Client,Streams,?DEFAULT_TIMEOUT).
-%%----------------------------------------------------------------------
-spec get_event_streams(Client, Streams, Timeout)
-> Result when
Client :: client(),
Streams :: [stream_name()],
Timeout :: timeout(),
Result :: {ok,streams()} | {error,error_reason()}.
-%% @doc Send a request to get the given event streams.
-%%
-%% `Streams' is a list of stream names. The following filter will
-%% be sent to the netconf server in a `get' request:
-%%
-%% ```
-%% <netconf xmlns="urn:ietf:params:xml:ns:netmod:notification">
-%% <streams>
-%% <stream>
-%% <name>StreamName1</name>
-%% </stream>
-%% <stream>
-%% <name>StreamName2</name>
-%% </stream>
-%% ...
-%% </streams>
-%% </netconf>
-%% '''
-%%
-%% If `Streams' is an empty list, ALL streams will be requested
-%% by sending the following filter:
-%%
-%% ```
-%% <netconf xmlns="urn:ietf:params:xml:ns:netmod:notification">
-%% <streams/>
-%% </netconf>
-%% '''
-%%
-%% If more complex filtering is needed, a use {@link get/2} or {@link
-%% get/3} and specify the exact filter according to XML Schema for
-%% Event Notifications found in RFC5277.
-%%
-%% @end
-%%----------------------------------------------------------------------
get_event_streams(Client,Streams,Timeout) ->
call(Client,{get_event_streams,Streams,Timeout}).
%%----------------------------------------------------------------------
-%% @spec close_session(Client) -> Result
-%% @equiv close_session(Client, infinity)
+%% Send a 'close-session' request
+-spec close_session(Client) -> Result when
+ Client :: client(),
+ Result :: ok | {error,error_reason()}.
close_session(Client) ->
close_session(Client, ?DEFAULT_TIMEOUT).
-%%----------------------------------------------------------------------
-spec close_session(Client, Timeout) -> Result when
Client :: client(),
Timeout :: timeout(),
Result :: ok | {error,error_reason()}.
-%% @doc Request graceful termination of the session associated with the client.
-%%
-%% When a netconf server receives a `close-session' request, it
-%% will gracefully close the session. The server will release any
-%% locks and resources associated with the session and gracefully
-%% close any associated connections. Any NETCONF requests received
-%% after a `close-session' request will be ignored.
-%%
-%% @end
-%%----------------------------------------------------------------------
close_session(Client, Timeout) ->
call(Client,{send_rpc_op, close_session, [], Timeout}, true).
%%----------------------------------------------------------------------
-%% @spec kill_session(Client, SessionId) -> Result
-%% @equiv kill_session(Client, SessionId, infinity)
+%% Send a 'kill-session' request
+-spec kill_session(Client, SessionId) -> Result when
+ Client :: client(),
+ SessionId :: pos_integer(),
+ Result :: ok | {error,error_reason()}.
kill_session(Client, SessionId) ->
kill_session(Client, SessionId, ?DEFAULT_TIMEOUT).
-%%----------------------------------------------------------------------
-spec kill_session(Client, SessionId, Timeout) -> Result when
Client :: client(),
SessionId :: pos_integer(),
Timeout :: timeout(),
Result :: ok | {error,error_reason()}.
-%% @doc Force termination of the session associated with the supplied
-%% session id.
-%%
-%% The server side shall abort any operations currently in process,
-%% release any locks and resources associated with the session, and
-%% close any associated connections.
-%%
-%% Only if the server is in the confirmed commit phase, the
-%% configuration will be restored to its state before entering the
-%% confirmed commit phase. Otherwise, no configuration roll back will
-%% be performed.
-%%
-%% If the given `SessionId' is equal to the current session id,
-%% an error will be returned.
-%%
-%% @end
-%% ----------------------------------------------------------------------
kill_session(Client, SessionId, Timeout) ->
call(Client,{send_rpc_op, kill_session, [SessionId], Timeout}).
@@ -1071,24 +859,35 @@ kill_session(Client, SessionId, Timeout) ->
%% Callback functions
%%----------------------------------------------------------------------
-%% @private
+init(_KeyOrName,{CM,{Host,Port}},Options) ->
+ case ssh_channel(#connection{reference=CM,host=Host,port=Port},Options) of
+ {ok,Connection} ->
+ {ok, CM, #state{connection = Connection}};
+ {error,Reason}->
+ {error,Reason}
+ end;
+init(_KeyOrName,{_Host,_Port},Options) when Options#options.type==connection ->
+ case ssh_connect(Options) of
+ {ok, Connection} ->
+ ConnPid = Connection#connection.reference,
+ {ok, ConnPid, #state{connection = Connection}};
+ Error ->
+ Error
+ end;
init(_KeyOrName,{_Host,_Port},Options) ->
case ssh_open(Options) of
{ok, Connection} ->
- log(Connection,open),
{ConnPid,_} = Connection#connection.reference,
{ok, ConnPid, #state{connection = Connection}};
{error,Reason}->
{error,Reason}
end.
-%% @private
+
terminate(_, #state{connection=Connection}) ->
ssh_close(Connection),
- log(Connection,close),
ok.
-%% @private
handle_msg({hello, Options, Timeout}, From,
#state{connection=Connection,hello_status=HelloStatus} = State) ->
case do_send(Connection, client_hello(Options)) of
@@ -1107,6 +906,14 @@ handle_msg({hello, Options, Timeout}, From,
Error ->
{stop, Error, State}
end;
+handle_msg(get_ssh_connection, _From, #state{connection=Connection}=State) ->
+ Reply =
+ case Connection#connection.reference of
+ {_,_} -> {error,not_an_ssh_connection};
+ CM -> {ok,{CM,{Connection#connection.host,
+ Connection#connection.port}}}
+ end,
+ {reply, Reply, State};
handle_msg(_, _From, #state{session_id=undefined} = State) ->
%% Hello is not yet excanged - this shall never happen
{reply,{error,waiting_for_hello},State};
@@ -1136,7 +943,6 @@ handle_msg({get_event_streams=Op,Streams,Timeout}, From, State) ->
SimpleXml = encode_rpc_operation(get,[Filter]),
do_send_rpc(Op, SimpleXml, Timeout, From, State).
-%% @private
handle_msg({ssh_cm, CM, {data, Ch, _Type, Data}}, State) ->
ssh_connection:adjust_window(CM,Ch,size(Data)),
handle_data(Data, State);
@@ -1172,7 +978,6 @@ handle_msg({Ref,timeout},#state{pending=Pending} = State) ->
%% the implementation before this patch
{R,State#state{pending=Pending1, no_end_tag_buff= <<>>, buff= <<>>}}.
-%% @private
%% Called by ct_util_server to close registered connections before terminate.
close(Client) ->
case get_handle(Client) of
@@ -1243,15 +1048,18 @@ get_handle(Client) ->
Error
end.
+check_options(OptList,Options) ->
+ check_options(OptList,undefined,undefined,Options).
+
check_options([], undefined, _Port, _Options) ->
{error, no_host_address};
check_options([], _Host, undefined, _Options) ->
{error, no_port};
check_options([], Host, Port, Options) ->
{Host,Port,Options};
-check_options([{ssh, Host}|T], _, Port, #options{} = Options) ->
+check_options([{ssh, Host}|T], _, Port, Options) ->
check_options(T, Host, Port, Options#options{host=Host});
-check_options([{port,Port}|T], Host, _, #options{} = Options) ->
+check_options([{port,Port}|T], Host, _, Options) ->
check_options(T, Host, Port, Options#options{port=Port});
check_options([{timeout, Timeout}|T], Host, Port, Options)
when is_integer(Timeout); Timeout==infinity ->
@@ -1262,6 +1070,15 @@ check_options([Opt|T], Host, Port, #options{ssh=SshOpts}=Options) ->
%% Option verified by ssh
check_options(T, Host, Port, Options#options{ssh=[Opt|SshOpts]}).
+check_session_options([],Options) ->
+ {ok,Options};
+check_session_options([{timeout, Timeout}|T], Options)
+ when is_integer(Timeout); Timeout==infinity ->
+ check_session_options(T, Options#options{timeout = Timeout});
+check_session_options([Opt|_T], _Options) ->
+ {error, {invalid_option, Opt}}.
+
+
%%%-----------------------------------------------------------------
set_request_timer(infinity) ->
{undefined,undefined};
@@ -1298,7 +1115,7 @@ encode_rpc_operation(get,[Filter]) ->
encode_rpc_operation(get_config,[Source,Filter]) ->
{'get-config',[{source,[Source]}] ++ filter(Filter)};
encode_rpc_operation(edit_config,[Target,Config,OptParams]) ->
- {'edit-config',[{target,[Target]}] ++ OptParams ++ [{config,[Config]}]};
+ {'edit-config',[{target,[Target]}] ++ OptParams ++ [{config,Config}]};
encode_rpc_operation(delete_config,[Target]) ->
{'delete-config',[{target,[Target]}]};
encode_rpc_operation(copy_config,[Target,Source]) ->
@@ -1356,7 +1173,6 @@ do_send_rpc(Connection, MsgId, SimpleXml) ->
do_send(Connection, SimpleXml) ->
Xml=to_xml_doc(SimpleXml),
- log(Connection,send,Xml),
ssh_send(Connection, Xml).
to_xml_doc(Simple) ->
@@ -1653,7 +1469,7 @@ decode_data(Other) ->
{error,{unexpected_rpc_reply,Other}}.
get_qualified_name(Tag) ->
- case string:tokens(atom_to_list(Tag),":") of
+ case string:lexemes(atom_to_list(Tag),":") of
[TagStr] -> {[],TagStr};
[PrefixStr,TagStr] -> {PrefixStr,TagStr}
end.
@@ -1766,9 +1582,14 @@ decode_streams([]) ->
log(Connection,Action) ->
log(Connection,Action,<<>>).
-log(#connection{host=Host,port=Port,name=Name},Action,Data) ->
+log(#connection{reference=Ref,host=Host,port=Port,name=Name},Action,Data) ->
+ Address =
+ case Ref of
+ {_,Ch} -> {Host,Port,Ch};
+ _ -> {Host,Port}
+ end,
error_logger:info_report(#conn_log{client=self(),
- address={Host,Port},
+ address=Address,
name=Name,
action=Action,
module=?MODULE},
@@ -1776,7 +1597,6 @@ log(#connection{host=Host,port=Port,name=Name},Action,Data) ->
%% Log callback - called from the error handler process
-%% @private
format_data(How,Data) ->
%% Assuming that the data is encoded as UTF-8. If it is not, then
%% the printout might be wrong, but the format function will not
@@ -1915,42 +1735,84 @@ get_tag([]) ->
%%%-----------------------------------------------------------------
%%% SSH stuff
-
-ssh_open(#options{host=Host,timeout=Timeout,port=Port,ssh=SshOpts,name=Name}) ->
+ssh_connect(#options{host=Host,timeout=Timeout,port=Port,
+ ssh=SshOpts,name=Name,type=Type}) ->
case ssh:connect(Host, Port,
[{user_interaction,false},
- {silently_accept_hosts, true}|SshOpts]) of
+ {silently_accept_hosts, true}|SshOpts],
+ Timeout) of
{ok,CM} ->
- case ssh_connection:session_channel(CM, Timeout) of
- {ok,Ch} ->
- case ssh_connection:subsystem(CM, Ch, "netconf", Timeout) of
- success ->
- {ok, #connection{reference = {CM,Ch},
- host = Host,
- port = Port,
- name = Name}};
- failure ->
- ssh:close(CM),
- {error,{ssh,could_not_execute_netconf_subsystem}};
- {error,timeout} ->
- {error,{ssh,could_not_execute_netconf_subsystem,timeout}}
- end;
- {error, Reason} ->
- ssh:close(CM),
- {error,{ssh,could_not_open_channel,Reason}}
- end;
+ Connection = #connection{reference = CM,
+ host = Host,
+ port = Port,
+ name = Name,
+ type = Type},
+ log(Connection,connect),
+ {ok,Connection};
{error,Reason} ->
{error,{ssh,could_not_connect_to_server,Reason}}
end.
-ssh_send(#connection{reference = {CM,Ch}}, Data) ->
+ssh_channel(#connection{reference=CM}=Connection0,
+ #options{timeout=Timeout,name=Name,type=Type}) ->
+ case ssh_connection:session_channel(CM, Timeout) of
+ {ok,Ch} ->
+ case ssh_connection:subsystem(CM, Ch, "netconf", Timeout) of
+ success ->
+ Connection = Connection0#connection{reference = {CM,Ch},
+ name = Name,
+ type = Type},
+ log(Connection,open),
+ {ok, Connection};
+ failure ->
+ ssh_connection:close(CM,Ch),
+ {error,{ssh,could_not_execute_netconf_subsystem}};
+ {error,timeout} ->
+ ssh_connection:close(CM,Ch),
+ {error,{ssh,could_not_execute_netconf_subsystem,timeout}}
+ end;
+ {error, Reason} ->
+ {error,{ssh,could_not_open_channel,Reason}}
+ end.
+
+
+ssh_open(Options) ->
+ case ssh_connect(Options) of
+ {ok,Connection} ->
+ case ssh_channel(Connection,Options) of
+ {ok,_} = Ok ->
+ Ok;
+ Error ->
+ ssh_close(Connection),
+ Error
+ end;
+ Error ->
+ Error
+ end.
+
+ssh_send(#connection{reference = {CM,Ch}}=Connection, Data) ->
case ssh_connection:send(CM, Ch, Data) of
- ok -> ok;
- {error,Reason} -> {error,{ssh,failed_to_send_data,Reason}}
+ ok ->
+ log(Connection,send,Data),
+ ok;
+ {error,Reason} ->
+ {error,{ssh,failed_to_send_data,Reason}}
end.
-ssh_close(#connection{reference = {CM,_Ch}}) ->
- ssh:close(CM).
+ssh_close(Connection=#connection{reference = {CM,Ch}, type = Type}) ->
+ _ = ssh_connection:close(CM,Ch),
+ log(Connection,close),
+ case Type of
+ connection_and_channel ->
+ ssh_close(Connection#connection{reference = CM});
+ _ ->
+ ok
+ end,
+ ok;
+ssh_close(Connection=#connection{reference = CM}) ->
+ _ = ssh:close(CM),
+ log(Connection,disconnect),
+ ok.
%%----------------------------------------------------------------------
diff --git a/lib/common_test/src/ct_netconfc.hrl b/lib/common_test/src/ct_netconfc.hrl
index 5eeeafa318..1d81c762ba 100644
--- a/lib/common_test/src/ct_netconfc.hrl
+++ b/lib/common_test/src/ct_netconfc.hrl
@@ -1,7 +1,7 @@
%%--------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2012-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2012-2018. 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.
@@ -24,9 +24,7 @@
%% This file defines constant values and records used by the
%% netconf client ct_netconfc.
%%
-%% @author Support
-%% @doc Netconf Client Interface.
-%% @end
+%% Netconf Client Interface.
%%----------------------------------------------------------------------
%%----------------------------------------------------------------------
diff --git a/lib/common_test/src/ct_property_test.erl b/lib/common_test/src/ct_property_test.erl
index 12c3d726d3..f51f454d08 100644
--- a/lib/common_test/src/ct_property_test.erl
+++ b/lib/common_test/src/ct_property_test.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2003-2018. 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.
@@ -28,46 +28,6 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%% @doc EXPERIMENTAL support in common-test for calling property based tests.
-%%%
-%%% <p>This module is a first step towards running Property Based testing in the
-%%% Common Test framework. A property testing tool like QuickCheck or PropEr is
-%%% assumed to be installed.</p>
-%%%
-%%% <p>The idea is to have a common_test testsuite calling a property testing
-%%% tool with special property test suites as defined by that tool. In this manual
-%%% we assume the usual Erlang Application directory structure. The tests are
-%%% collected in the application's <c>test</c> directory. The test directory
-%%% has a sub-directory called <c>property_test</c> where everything needed for
-%%% the property tests are collected.</p>
-%%%
-%%% <p>A typical ct test suite using <c>ct_property_test</c> is organized as follows:
-%%% </p>
-%%% ```
-%%% -include_lib("common_test/include/ct.hrl").
-%%%
-%%% all() -> [prop_ftp_case].
-%%%
-%%% init_per_suite(Config) ->
-%%% ct_property_test:init_per_suite(Config).
-%%%
-%%% %%%---- test case
-%%% prop_ftp_case(Config) ->
-%%% ct_property_test:quickcheck(
-%%% ftp_simple_client_server:prop_ftp(Config),
-%%% Config
-%%% ).
-%%% '''
-%%%
-%%% <warning>
-%%% <p>
-%%% This is experimental code which may be changed or removed
-%%% anytime without any warning.
-%%% </p>
-%%% </warning>
-%%%
-%%% @end
-
-module(ct_property_test).
%% API
@@ -76,19 +36,6 @@
-include_lib("common_test/include/ct.hrl").
-%%%-----------------------------------------------------------------
-%%% @spec init_per_suite(Config) -> Config | {skip,Reason}
-%%%
-%%% @doc Initializes Config for property testing.
-%%%
-%%% <p>The function investigates if support is available for either Quickcheck, PropEr,
-%%% or Triq.
-%%% The options <c>{property_dir,AbsPath}</c> and
-%%% <c>{property_test_tool,Tool}</c> is set in the Config returned.</p>
-%%% <p>The function is intended to be called in the init_per_suite in the test suite.</p>
-%%% <p>The property tests are assumed to be in the subdirectory <c>property_test</c>.</p>
-%%% @end
-
init_per_suite(Config) ->
case which_module_exists([eqc,proper,triq]) of
{ok,ToolModule} ->
@@ -108,14 +55,6 @@ init_per_suite(Config) ->
{skip, "No property testing tool found"}
end.
-%%%-----------------------------------------------------------------
-%%% @spec quickcheck(Property, Config) -> true | {fail,Reason}
-%%%
-%%% @doc Call quickcheck and return the result in a form suitable for common_test.
-%%%
-%%% <p>The function is intended to be called in the test cases in the test suite.</p>
-%%% @end
-
quickcheck(Property, Config) ->
Tool = proplists:get_value(property_test_tool,Config),
F = function_name(quickcheck, Tool),
@@ -134,7 +73,7 @@ mk_ct_return(Other, Tool) ->
try lists:last(hd(Tool:counterexample()))
of
{set,{var,_},{call,M,F,Args}} ->
- {fail, io_lib:format("~p:~p/~p returned bad result",[M,F,length(Args)])}
+ {fail, io_lib:format("~p:~tp/~p returned bad result",[M,F,length(Args)])}
catch
_:_ ->
{fail, Other}
@@ -174,7 +113,7 @@ compile_tests(Path, ToolModule) ->
BeamFiles = [F || F<-FileNames,
filename:extension(F) == ".beam"],
_ = [file:delete(F) || F<-BeamFiles],
- ct:pal("Compiling in ~p:~n Deleted ~p~n MacroDefs=~p",[Path,BeamFiles,MacroDefs]),
+ ct:pal("Compiling in ~tp:~n Deleted ~p~n MacroDefs=~p",[Path,BeamFiles,MacroDefs]),
Result = make:all([load|MacroDefs]),
ok = file:set_cwd(Cwd),
Result.
diff --git a/lib/common_test/src/ct_release_test.erl b/lib/common_test/src/ct_release_test.erl
index d783f8d04e..ac3dcab7c9 100644
--- a/lib/common_test/src/ct_release_test.erl
+++ b/lib/common_test/src/ct_release_test.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2014-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2014-2018. 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.
@@ -18,7 +18,7 @@
%% %CopyrightEnd%
%%
%%-----------------------------------------------------------------
-%% @doc EXPERIMENTAL support for testing of upgrade.
+%% EXPERIMENTAL support for testing of upgrade.
%%
%% This is a library module containing support for test of release
%% related activities in one or more applications. Currenty it
@@ -27,7 +27,7 @@
%% == Configuration ==
%%
%% In order to find version numbers of applications to upgrade from,
-%% `{@module}' needs to access and start old OTP
+%% ct_release_test needs to access and start old OTP
%% releases. A `common_test' configuration file can be used for
%% specifying the location of such releases, for example:
%%
@@ -39,48 +39,45 @@
%% The configuration file should preferably point out the latest patch
%% level on each major release.
%%
-%% If no such configuration file is given, {@link init/1} will return
-%% `{skip,Reason}' and any attempt at running {@link upgrade/4}
+%% If no such configuration file is given, init/1 will return
+%% {skip,Reason} and any attempt at running upgrade/4
%% will fail.
%%
%% == Callback functions ==
%%
-%% The following functions should be exported from a {@module}
+%% The following functions should be exported from a ct_release_test
%% callback module.
%%
%% All callback functions are called on the node where the upgrade is
%% executed.
%%
-%% <dl>
-%% <dt>Module:upgrade_init(CtData,State) -> NewState</dt>
-%% <dd>Types:
+%% Module:upgrade_init(CtData,State) -> NewState
+%% Types:
%%
-%% <b><code>CtData = {@link ct_data()}</code></b><br/>
-%% <b><code>State = NewState = cb_state()</code></b>
+%% CtData = ct_data()
+%% State = NewState = cb_state()
%%
%% Initialyze system before upgrade test starts.
%%
%% This function is called before the upgrade is started. All
-%% applications given in {@link upgrade/4} are already started by
+%% applications given in upgrade/4 are already started by
%% the boot script, so this callback is intended for additional
%% initialization, if necessary.
%%
-%% <code>CtData</code> is an opaque data structure which shall be used
-%% in any call to <code>ct_release_test</code> inside the callback.
+%% CtData is an opaque data structure which shall be used
+%% in any call to ct_release_test inside the callback.
%%
%% Example:
%%
-%% ```
%% upgrade_init(CtData,State) ->
%% {ok,{FromVsn,ToVsn}} = ct_release_test:get_app_vsns(CtData,myapp),
-%% open_connection(State).'''
-%% </dd>
+%% open_connection(State).
%%
-%% <dt>Module:upgrade_upgraded(CtData,State) -> NewState</dt>
-%% <dd>Types:
+%% Module:upgrade_upgraded(CtData,State) -> NewState
+%% Types:
%%
-%% <b><code>CtData = {@link ct_data()}</code></b><br/>
-%% <b><code>State = NewState = cb_state()</code></b>
+%% CtData = ct_data()
+%% State = NewState = cb_state()
%%
%% Check that upgrade was successful.
%%
@@ -89,21 +86,19 @@
%% been made permanent. It allows application specific checks to
%% ensure that the upgrade was successful.
%%
-%% <code>CtData</code> is an opaque data structure which shall be used
-%% in any call to <code>ct_release_test</code> inside the callback.
+%% CtData is an opaque data structure which shall be used
+%% in any call to ct_release_test inside the callback.
%%
%% Example:
%%
-%% ```
%% upgrade_upgraded(CtData,State) ->
-%% check_connection_still_open(State).'''
-%% </dd>
+%% check_connection_still_open(State).
%%
-%% <dt>Module:upgrade_downgraded(CtData,State) -> NewState</dt>
-%% <dd>Types:
+%% Module:upgrade_downgraded(CtData,State) -> NewState
+%% Types:
%%
-%% <b><code>CtData = {@link ct_data()}</code></b><br/>
-%% <b><code>State = NewState = cb_state()</code></b>
+%% CtData = ct_data()
+%% State = NewState = cb_state()
%%
%% Check that downgrade was successful.
%%
@@ -112,17 +107,13 @@
%% made permanent. It allows application specific checks to ensure
%% that the downgrade was successful.
%%
-%% <code>CtData</code> is an opaque data structure which shall be used
-%% in any call to <code>ct_release_test</code> inside the callback.
+%% CtData is an opaque data structure which shall be used
+%% in any call to ct_release_test inside the callback.
%%
%% Example:
%%
-%% ```
%% upgrade_downgraded(CtData,State) ->
-%% check_connection_closed(State).'''
-%% </dd>
-%% </dl>
-%% @end
+%% check_connection_closed(State).
%%-----------------------------------------------------------------
-module(ct_release_test).
@@ -132,7 +123,7 @@
%%-----------------------------------------------------------------
-define(testnode, 'ct_release_test-upgrade').
--define(exclude_apps, [hipe, typer, dialyzer]). % never include these apps
+-define(exclude_apps, [hipe, dialyzer]). % never include these apps
%%-----------------------------------------------------------------
-record(ct_data, {from,to}).
@@ -152,7 +143,7 @@
Config :: config(),
Result :: config() | SkipOrFail,
SkipOrFail :: {skip,Reason} | {fail,Reason}.
-%% @doc Initialize `{@module}'.
+%% Initialize ct_release_test.
%%
%% This function can be called from any of the
%% `init_per_*' functions in the test suite. It updates
@@ -161,16 +152,15 @@
%% returned configuration must therefore also be returned from
%% the calling `init_per_*'.
%%
-%% If the initialization fails, e.g. if a required release can
-%% not be found, the function returns `{skip,Reason}'. In
+%% If the initialization fails, e.g. if a required release
+%% cannot be found, the function returns `{skip,Reason}'. In
%% this case the other test support functions in this mudule
-%% can not be used.
+%% cannot be used.
%%
%% Example:
%%
-%% ```
%% init_per_suite(Config) ->
-%% ct_release_test:init(Config).'''
+%% ct_release_test:init(Config).
%%
init(Config) ->
try init_upgrade_test() of
@@ -194,47 +184,42 @@ init(Config) ->
Callback :: {module(),InitState},
InitState :: cb_state(),
Config :: config().
-%% @doc Test upgrade of the given application(s).
+%% Test upgrade of the given application(s).
%%
%% This function can be called from a test case. It requires that
-%% `Config' has been initialized by calling {@link
-%% init/1} prior to this, for example from `init_per_suite/1'.
+%% `Config' has been initialized by calling
+%% init/1 prior to this, for example from `init_per_suite/1'.
%%
%% Upgrade tests are performed as follows:
%%
-%% <ol>
-%% <li>Figure out which OTP release to test upgrade
+%% Figure out which OTP release to test upgrade
%% from. Start a node running that release and find the
%% application versions on that node. Terminate the
-%% node.</li>
-%% <li>Figure out all dependencies for the applications under
-%% test.</li>
-%% <li>Create a release containing the core
+%% node.
+%% Figure out all dependencies for the applications under
+%% test.
+%% Create a release containing the core
%% applications `kernel', `stdlib' and `sasl'
%% in addition to the application(s) under test and all
%% dependencies of these. The versions of the applications
%% under test will be the ones found on the OTP release to
%% upgrade from. The versions of all other applications will
%% be those found on the current node, i.e. the common_test
-%% node. This is the "From"-release.</li>
-%% <li>Create another release containing the same
+%% node. This is the "From"-release.
+%% Create another release containing the same
%% applications as in the previous step, but with all
%% application versions taken from the current node. This is
-%% the "To"-release.</li>
-%% <li>Install the "From"-release and start a new node
-%% running this release.</li>
-%% <li>Perform the upgrade test and allow customized
+%% the "To"-release.
+%% Install the "From"-release and start a new node
+%% running this release.
+%% Perform the upgrade test and allow customized
%% control by using callbacks:
-%% <ol>
-%% <li>Callback: `upgrade_init/2'</li>
-%% <li>Unpack the new release</li>
-%% <li>Install the new release</li>
-%% <li>Callback: `upgrade_upgraded/2'</li>
-%% <li>Install the original release</li>
-%% <li>Callback: `upgrade_downgraded/2'</li>
-%% </ol>
-%% </li>
-%% </ol>
+%% Callback: `upgrade_init/2'
+%% Unpack the new release
+%% Install the new release
+%% Callback: `upgrade_upgraded/2'
+%% Install the original release
+%% Callback: `upgrade_downgraded/2'
%%
%% `App' or `Apps'
%% specifies the applications under test, i.e. the applications
@@ -244,27 +229,25 @@ init(Config) ->
%%
%% `Level' specifies which OTP release to
%% pick the "From" versions from.
-%% <dl>
-%% <dt>major</dt>
-%% <dd>From verions are picked from the previous major
+%% major
+%% From verions are picked from the previous major
%% release. For example, if the test is run on an OTP-17
-%% node, `{@module}' will pick the application
+%% node, ct_release_test will pick the application
%% "From" versions from an OTP installation running OTP
-%% R16B.</dd>
+%% R16B.
%%
-%% <dt>minor</dt>
-%% <dd>From verions are picked from the current major
+%% minor
+%% From verions are picked from the current major
%% release. For example, if the test is run on an OTP-17
-%% node, `{@module}' will pick the application
+%% node, ct_release_test will pick the application
%% "From" versions from an OTP installation running an
-%% earlier patch level of OTP-17.</dd>
-%% </dl>
+%% earlier patch level of OTP-17.
%%
%% The application "To" versions are allways picked from the
%% current node, i.e. the common_test node.
%%
%% `Callback' specifies the module (normally the
-%% test suite) which implements the {@section Callback functions}, and
+%% test suite) which implements the Callback functions, and
%% the initial value of the `State' variable used in these
%% functions.
%%
@@ -273,10 +256,8 @@ init(Config) ->
%%
%% Example:
%%
-%% ```
%% minor_upgrade(Config) ->
%% ct_release_test:upgrade(ssl,minor,{?MODULE,[]},Config).
-%% '''
%%
upgrade(App,Level,Callback,Config) when is_atom(App) ->
upgrade([App],Level,Callback,Config);
@@ -318,10 +299,10 @@ upgrade(Apps,Level,Callback,Config) ->
-spec cleanup(Config) -> Result when
Config :: config(),
Result :: config().
-%% @doc Clean up after tests.
+%% Clean up after tests.
%%
%% This function shall be called from the `end_per_*' function
-%% complementing the `init_per_*' function where {@link init/1}
+%% complementing the `init_per_*' function where init/1
%% is called.
%%
%% It cleans up after the test, for example kills hanging
@@ -329,9 +310,8 @@ upgrade(Apps,Level,Callback,Config) ->
%%
%% Example:
%%
-%% ```
%% end_per_suite(Config) ->
-%% ct_release_test:cleanup(Config).'''
+%% ct_release_test:cleanup(Config).
%%
cleanup(Config) ->
AllNodes = [node_name(?testnode)|nodes()],
@@ -352,15 +332,15 @@ cleanup(Config) ->
From :: string(),
To :: string(),
Reason :: {app_not_found,App}.
-%% @doc Get versions involved in this upgrade for the given application.
+%% Get versions involved in this upgrade for the given application.
%%
%% This function can be called from inside any of the callback
%% functions. It returns the old (From) and new (To) versions involved
%% in the upgrade/downgrade test for the given application.
%%
-%% <code>CtData</code> must be the first argument received in the
+%% CtData must be the first argument received in the
%% calling callback function - an opaque data structure set by
-%% <code>ct_release_tests</code>.
+%% ct_release_tests.
get_app_vsns(#ct_data{from=FromApps,to=ToApps},App) ->
case {lists:keyfind(App,1,FromApps),lists:keyfind(App,1,ToApps)} of
{{App,FromVsn,_},{App,ToVsn,_}} ->
@@ -380,16 +360,16 @@ get_app_vsns(#ct_data{from=FromApps,to=ToApps},App) ->
Down :: [Instr],
Instr :: term(),
Reason :: {app_not_found,App} | {vsn_not_found,{App,From}}.
-%% @doc Get appup instructions for the given application.
+%% Get appup instructions for the given application.
%%
%% This function can be called from inside any of the callback
%% functions. It reads the appup file for the given application and
%% returns the instructions for upgrade and downgrade for the versions
%% in the test.
%%
-%% <code>CtData</code> must be the first argument received in the
+%% CtData must be the first argument received in the
%% calling callback function - an opaque data structure set by
-%% <code>ct_release_tests</code>.
+%% ct_release_tests.
%%
%% See reference manual for appup files for types definitions for the
%% instructions.
@@ -445,8 +425,8 @@ init_upgrade_test(Level) ->
end,
case OldRel of
false ->
- ct:log("Release ~p is not available."
- " Upgrade on '~p' level can not be tested.",
+ ct:log("Release ~tp is not available."
+ " Upgrade on '~p' level cannot be tested.",
[FromVsn,Level]),
undefined;
_ ->
@@ -522,8 +502,8 @@ upgrade(Apps,Level,Callback,CreateDir,InstallDir,Config) ->
{ToVsn,ToRel,ToAppsVsns} =
upgrade_system(Apps, FromRel, CreateDir,
InstallDir, Data),
- ct:log("Upgrade from: OTP-~ts, ~p",[FromVsn, FromAppsVsns]),
- ct:log("Upgrade to: OTP-~ts, ~p",[ToVsn, ToAppsVsns]),
+ ct:log("Upgrade from: OTP-~ts, ~tp",[FromVsn, FromAppsVsns]),
+ ct:log("Upgrade to: OTP-~ts, ~tp",[ToVsn, ToAppsVsns]),
do_upgrade(Callback, FromVsn, FromAppsVsns, ToRel,
ToAppsVsns, InstallDir)
end
@@ -548,7 +528,7 @@ target_system(Apps,CreateDir,InstallDir,{FromVsn,_,AllAppsVsns,Path}) ->
{path,Path}]]),
%% Unpack the tar to complete the installation
- erl_tar:extract(RelName ++ ".tar.gz", [{cwd, InstallDir}, compressed]),
+ ok = erl_tar:extract(RelName ++ ".tar.gz", [{cwd, InstallDir}, compressed]),
%% Add bin and log dirs
BinDir = filename:join([InstallDir, "bin"]),
@@ -574,11 +554,11 @@ target_system(Apps,CreateDir,InstallDir,{FromVsn,_,AllAppsVsns,Path}) ->
%% create start_erl.data, sys.config and start.src
StartErlData = filename:join([InstallDir, "releases", "start_erl.data"]),
- write_file(StartErlData, io_lib:fwrite("~s ~s~n", [ErtsVsn, FromVsn])),
+ ok = write_file(StartErlData, io_lib:fwrite("~s ~s~n", [ErtsVsn, FromVsn])),
SysConfig = filename:join([InstallDir, "releases", FromVsn, "sys.config"]),
- write_file(SysConfig, "[]."),
+ ok = write_file(SysConfig, "[]."),
StartSrc = filename:join(ErtsBinDir,"start.src"),
- write_file(StartSrc,start_script()),
+ ok = write_file(StartSrc,start_script()),
ok = file:change_mode(StartSrc,8#0755),
%% Make start_erl executable
@@ -640,7 +620,7 @@ upgrade_system(Apps, FromRel, CreateDir, InstallDir, {_,ToVsn,_,_}) ->
[{path,[FromPath]},
{outdir,CreateDir}]]),
SysConfig = filename:join([CreateDir, "sys.config"]),
- write_file(SysConfig, "[]."),
+ ok = write_file(SysConfig, "[]."),
ok = systools(make_tar,[RelName,[{erts,code:root_dir()}]]),
@@ -727,9 +707,9 @@ do_upgrade({Cb,InitState},FromVsn,FromAppsVsns,ToRel,ToAppsVsns,InstallDir) ->
do_callback(Node,Mod,Func,Args) ->
Dir = filename:dirname(code:which(Mod)),
_ = rpc:call(Node,code,add_path,[Dir]),
- ct:log("Calling ~p:~p/1",[Mod,Func]),
+ ct:log("Calling ~p:~tp/1",[Mod,Func]),
R = rpc:call(Node,Mod,Func,Args),
- ct:log("~p:~p/~w returned: ~p",[Mod,Func,length(Args),R]),
+ ct:log("~p:~tp/~w returned: ~tp",[Mod,Func,length(Args),R]),
case R of
{badrpc,Error} ->
throw({fail,{test_upgrade_callback,Mod,Func,Args,Error}});
@@ -817,7 +797,7 @@ get_runtime_deps([App|Apps],StartApps,Acc,Visited) ->
RuntimeDeps =
lists:flatmap(
fun(Str) ->
- [RuntimeAppStr,_] = string:tokens(Str,"-"),
+ [RuntimeAppStr,_] = string:lexemes(Str,"-"),
RuntimeApp = list_to_atom(RuntimeAppStr),
case {lists:keymember(RuntimeApp,1,Acc),
lists:member(RuntimeApp,StartApps)} of
@@ -860,10 +840,7 @@ copy_file(Src, Dest, Opts) ->
end.
write_file(FName, Conts) ->
- Enc = file:native_name_encoding(),
- {ok, Fd} = file:open(FName, [write]),
- ok = file:write(Fd, unicode:characters_to_binary(Conts,Enc,Enc)),
- ok = file:close(Fd).
+ file:write_file(FName, unicode:characters_to_binary(Conts)).
%% Substitute all occurrences of %Var% for Val in the given scripts
subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) ->
@@ -879,9 +856,9 @@ subst_src_script(Script, SrcDir, DestDir, Vars, Opts) ->
subst_file(Src, Dest, Vars, Opts) ->
{ok, Bin} = file:read_file(Src),
- Conts = binary_to_list(Bin),
+ Conts = unicode:characters_to_list(Bin),
NConts = subst(Conts, Vars),
- write_file(Dest, NConts),
+ ok = write_file(Dest, NConts),
case lists:member(preserve, Opts) of
true ->
{ok, FileInfo} = file:read_file_info(Src),
@@ -891,7 +868,7 @@ subst_file(Src, Dest, Vars, Opts) ->
end.
subst(Str, [{Var,Val}|Vars]) ->
- subst(re:replace(Str,"%"++Var++"%",Val,[{return,list}]),Vars);
+ subst(re:replace(Str,"%"++Var++"%",Val,[{return,list},unicode]),Vars);
subst(Str, []) ->
Str.
diff --git a/lib/common_test/src/ct_repeat.erl b/lib/common_test/src/ct_repeat.erl
index dac596a135..b97c6e59e7 100644
--- a/lib/common_test/src/ct_repeat.erl
+++ b/lib/common_test/src/ct_repeat.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2018. 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.
@@ -18,14 +18,14 @@
%% %CopyrightEnd%
%%
-%%% @doc Common Test Framework module that handles repeated test runs
+%%% doc Common Test Framework module that handles repeated test runs
%%%
-%%% <p>This module exports functions for repeating tests. The following
+%%% This module exports functions for repeating tests. The following
%%% start flags (or equivalent ct:run_test/1 options) are supported:
%%% -until <StopTime>, StopTime = YYMoMoDDHHMMSS | HHMMSS
%%% -duration <DurTime>, DurTime = HHMMSS
%%% -force_stop [skip_rest]
-%%% -repeat <N>, N = integer()</p>
+%%% -repeat <N>, N = integer()
-module(ct_repeat).
@@ -43,7 +43,7 @@ loop_test(If,Args) when is_list(Args) ->
no_loop ->
false;
E = {error,_} ->
- io:format("Common Test error: ~p\n\n",[E]),
+ io:format("Common Test error: ~tp\n\n",[E]),
ok = file:set_cwd(Cwd),
E;
{repeat,N} ->
@@ -70,6 +70,7 @@ loop_test(If,Args) when is_list(Args) ->
CtrlPid = self(),
spawn(
fun() ->
+ ct_util:mark_process(),
stop_after(CtrlPid,Secs,ForceStop)
end)
end,
@@ -89,18 +90,18 @@ loop(If,Type,N,Data0,Data1,Args,TPid,AccResult) ->
{'EXIT',Pid,Reason} ->
case Reason of
{user_error,What} ->
- io:format("\nTest run failed!\nReason: ~p\n\n\n", [What]),
+ io:format("\nTest run failed!\nReason: ~tp\n\n\n", [What]),
cancel(TPid),
{error,What};
_ ->
io:format("Test run crashed! This could be an internal error "
"- please report!\n\n"
- "~p\n\n\n",[Reason]),
+ "~tp\n\n\n",[Reason]),
cancel(TPid),
{error,Reason}
end;
{Pid,{error,Reason}} ->
- io:format("\nTest run failed!\nReason: ~p\n\n\n",[Reason]),
+ io:format("\nTest run failed!\nReason: ~tp\n\n\n",[Reason]),
cancel(TPid),
{error,Reason};
{Pid,Result} ->
@@ -134,6 +135,7 @@ spawn_tester(script,Ctrl,Args) ->
spawn_tester(func,Ctrl,Opts) ->
Tester = fun() ->
+ ct_util:mark_process(),
case catch ct_run:run_test2(Opts) of
{'EXIT',Reason} ->
exit(Reason);
@@ -276,7 +278,7 @@ log_loop_info(Args) ->
ForceStop ->
io_lib:format("force_stop is set to: ~w",[ForceStop])
end,
- ct_logs:log("Test loop info",LogStr1++LogStr2++LogStr3++LogStr4,[])
+ ct_logs:log("Test loop info","~ts", [LogStr1++LogStr2++LogStr3++LogStr4])
end.
ts(Secs) ->
diff --git a/lib/common_test/src/ct_rpc.erl b/lib/common_test/src/ct_rpc.erl
index b4a0bc9d74..cef5ad14e5 100644
--- a/lib/common_test/src/ct_rpc.erl
+++ b/lib/common_test/src/ct_rpc.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2018. 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.
@@ -18,8 +18,6 @@
%% %CopyrightEnd%
%%
-%%% @doc Common Test specific layer on Erlang/OTP rpc.
-
-module(ct_rpc).
%%% API
@@ -29,49 +27,12 @@
%%%=========================================================================
%%% API
%%%=========================================================================
-%%% @spec app_node(App, Candidates) -> NodeName
-%%%
-%%% App = atom()
-%%% Candidates = [NodeName]
-%%% NodeName = atom()
-%%%
-%%% @doc From a set of candidate nodes determines which of them is
-%%% running the application App. If none of the candidate nodes
-%%% is running the application the function will make the test case
-%%% calling this function fail. This function is the same as calling
-%%% <code>app_node(App, Candidates, true)</code>.
-%%%
app_node(App, Candidates) ->
app_node(App, Candidates, true, []).
-%%% @spec app_node(App, Candidates, FailOnBadRPC) -> NodeName
-%%%
-%%% App = atom()
-%%% Candidates = [NodeName]
-%%% NodeName = atom()
-%%% FailOnBadRPC = true | false
-%%%
-%%% @doc Same as <code>app_node/2</code> only the <code>FailOnBadRPC</code>
-%%% argument will determine if the search for a candidate node should
-%%% stop or not if <code>badrpc</code> is received at some point.
-%%%
app_node(App, Candidates, FailOnBadRPC) ->
app_node(App, Candidates, FailOnBadRPC, []).
-%%% @spec app_node(App, Candidates, FailOnBadRPC, Cookie) -> NodeName
-%%%
-%%% App = atom()
-%%% Candidates = [NodeName]
-%%% NodeName = atom()
-%%% FailOnBadRPC = true | false
-%%% Cookie = atom()
-%%%
-%%% @doc Same as <code>app_node/2</code> only the <code>FailOnBadRPC</code>
-%%% argument will determine if the search for a candidate node should
-%%% stop or not if <code>badrpc</code> is received at some point.
-%%% The cookie on the client node will be set to <code>Cookie</code>
-%%% for this rpc operation (use to match the server node cookie).
-%%%
app_node(App, [], _, _) ->
ct:fail({application_not_running, App});
@@ -96,49 +57,12 @@ app_node(App, _Candidates = [CandidateNode | Nodes], FailOnBadRPC, Cookie) ->
end
end.
-%%% @spec call(Node, Module, Function, Args) -> term() | {badrpc, Reason}
-%%%
-%%% @doc Same as call(Node, Module, Function, Args, infinity)
-%%%
call(Node, Module, Function, Args) ->
call(Node, Module, Function, Args, infinity, []).
-%%% @spec call(Node, Module, Function, Args, TimeOut) ->
-%%% term() | {badrpc, Reason}
-%%% Node = NodeName | {Fun, FunArgs}
-%%% Fun = fun()
-%%% FunArgs = term()
-%%% NodeName = atom()
-%%% Module = atom()
-%%% Function = atom()
-%%% Args = [term()]
-%%% Reason = timeout | term()
-%%%
-%%% @doc Evaluates apply(Module, Function, Args) on the node Node.
-%%% Returns whatever Function returns or {badrpc, Reason} if the
-%%% remote procedure call fails. If Node is {Fun, FunArgs} applying
-%%% Fun to FunArgs should return a node name.
call(Node, Module, Function, Args, TimeOut) ->
call(Node, Module, Function, Args, TimeOut, []).
-%%% @spec call(Node, Module, Function, Args, TimeOut, Cookie) ->
-%%% term() | {badrpc, Reason}
-%%% Node = NodeName | {Fun, FunArgs}
-%%% Fun = fun()
-%%% FunArgs = term()
-%%% NodeName = atom()
-%%% Module = atom()
-%%% Function = atom()
-%%% Args = [term()]
-%%% Reason = timeout | term()
-%%% Cookie = atom()
-%%%
-%%% @doc Evaluates apply(Module, Function, Args) on the node Node.
-%%% Returns whatever Function returns or {badrpc, Reason} if the
-%%% remote procedure call fails. If Node is {Fun, FunArgs} applying
-%%% Fun to FunArgs should return a node name.
-%%% The cookie on the client node will be set to <code>Cookie</code>
-%%% for this rpc operation (use to match the server node cookie).
call({Fun, FunArgs}, Module, Function, Args, TimeOut, Cookie) ->
Node = Fun(FunArgs),
call(Node, Module, Function, Args, TimeOut, Cookie);
@@ -148,42 +72,9 @@ call(Node, Module, Function, Args, TimeOut, Cookie) when is_atom(Node) ->
_ = set_the_cookie(Cookie0),
Result.
-%%% @spec cast(Node, Module, Function, Args) -> ok
-%%% Node = NodeName | {Fun, FunArgs}
-%%% Fun = fun()
-%%% FunArgs = term()
-%%% NodeName = atom()
-%%% Module = atom()
-%%% Function = atom()
-%%% Args = [term()]
-%%% Reason = timeout | term()
-%%%
-%%% @doc Evaluates apply(Module, Function, Args) on the node Node.
-%%% No response is delivered and the process which makes the call is
-%%% not suspended until the evaluation is compleated as in the case of
-%%% call/[3,4]. If Node is {Fun, FunArgs} applying
-%%% Fun to FunArgs should return a node name.
cast(Node, Module, Function, Args) ->
cast(Node, Module, Function, Args, []).
-%%% @spec cast(Node, Module, Function, Args, Cookie) -> ok
-%%% Node = NodeName | {Fun, FunArgs}
-%%% Fun = fun()
-%%% FunArgs = term()
-%%% NodeName = atom()
-%%% Module = atom()
-%%% Function = atom()
-%%% Args = [term()]
-%%% Reason = timeout | term()
-%%% Cookie = atom()
-%%%
-%%% @doc Evaluates apply(Module, Function, Args) on the node Node.
-%%% No response is delivered and the process which makes the call is
-%%% not suspended until the evaluation is compleated as in the case of
-%%% call/[3,4]. If Node is {Fun, FunArgs} applying
-%%% Fun to FunArgs should return a node name.
-%%% The cookie on the client node will be set to <code>Cookie</code>
-%%% for this rpc operation (use to match the server node cookie).
cast({Fun, FunArgs}, Module, Function, Args, Cookie) ->
Node = Fun(FunArgs),
cast(Node, Module, Function, Args, Cookie);
@@ -196,7 +87,6 @@ cast(Node, Module, Function, Args, Cookie) when is_atom(Node) ->
%%%---------- Internal -----------
-%%% @hidden
set_the_cookie([]) ->
[];
set_the_cookie(Cookie) ->
diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl
index 10e62e18b8..960252a6fe 100644
--- a/lib/common_test/src/ct_run.erl
+++ b/lib/common_test/src/ct_run.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2018. 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.
@@ -18,11 +18,6 @@
%% %CopyrightEnd%
%%
-%%% @doc Common Test Framework test execution control module.
-%%%
-%%% <p>This module exports functions for installing and running tests
-%%% withing the Common Test Framework.</p>
-
-module(ct_run).
%% Script interface
@@ -84,17 +79,6 @@
tests,
starter}).
-%%%-----------------------------------------------------------------
-%%% @spec script_start() -> term()
-%%%
-%%% @doc Start tests via the ct_run program or script.
-%%%
-%%% <p>Example:<br/><code>./ct_run -config config.ctc -dir
-%%% $TEST_DIR</code></p>
-%%%
-%%% <p>Example:<br/><code>./ct_run -config config.ctc -suite
-%%% $SUITE_PATH/$SUITE_NAME [-case $CASE_NAME]</code></p>
-%%%
script_start() ->
process_flag(trap_exit, true),
Init = init:get_arguments(),
@@ -121,13 +105,13 @@ script_start() ->
%% used for purpose of testing the run_test interface
io:format(user, "~n-------------------- START ARGS "
"--------------------~n", []),
- io:format(user, "--- Init args:~n~p~n", [FlagFilter(Init)]),
- io:format(user, "--- CT args:~n~p~n", [FlagFilter(CtArgs)]),
+ io:format(user, "--- Init args:~n~tp~n", [FlagFilter(Init)]),
+ io:format(user, "--- CT args:~n~tp~n", [FlagFilter(CtArgs)]),
EnvArgs = opts2args(EnvStartOpts),
- io:format(user, "--- Env opts -> args:~n~p~n =>~n~p~n",
+ io:format(user, "--- Env opts -> args:~n~tp~n =>~n~tp~n",
[EnvStartOpts,EnvArgs]),
Merged = merge_arguments(CtArgs ++ EnvArgs),
- io:format(user, "--- Merged args:~n~p~n", [FlagFilter(Merged)]),
+ io:format(user, "--- Merged args:~n~tp~n", [FlagFilter(Merged)]),
io:format(user, "-----------------------------------"
"-----------------~n~n", []),
Merged;
@@ -147,7 +131,7 @@ script_start(Args) ->
CTVsn =
case filename:basename(code:lib_dir(common_test)) of
CTBase when is_list(CTBase) ->
- case string:tokens(CTBase, "-") of
+ case string:lexemes(CTBase, "-") of
["common_test",Vsn] -> " v"++Vsn;
_ -> ""
end
@@ -160,18 +144,18 @@ script_start(Args) ->
{'EXIT',Pid,Reason} ->
case Reason of
{user_error,What} ->
- io:format("\nTest run failed!\nReason: ~p\n\n\n",
+ io:format("\nTest run failed!\nReason: ~tp\n\n\n",
[What]),
finish(Tracing, ?EXIT_STATUS_TEST_RUN_FAILED, Args);
_ ->
io:format("Test run crashed! "
"This could be an internal error "
"- please report!\n\n"
- "~p\n\n\n", [Reason]),
+ "~tp\n\n\n", [Reason]),
finish(Tracing, ?EXIT_STATUS_TEST_RUN_FAILED, Args)
end;
{Pid,{error,Reason}} ->
- io:format("\nTest run failed! Reason:\n~p\n\n\n",[Reason]),
+ io:format("\nTest run failed! Reason:\n~tp\n\n\n",[Reason]),
finish(Tracing, ?EXIT_STATUS_TEST_RUN_FAILED, Args);
{Pid,Result} ->
io:nl(),
@@ -219,7 +203,7 @@ analyze_test_result([], _) ->
analyze_test_result(interactive_mode, _) ->
interactive_mode;
analyze_test_result(Unknown, _) ->
- io:format("\nTest run failed! Reason:\n~p\n\n\n",[Unknown]),
+ io:format("\nTest run failed! Reason:\n~tp\n\n\n",[Unknown]),
?EXIT_STATUS_TEST_RUN_FAILED.
finish(Tracing, ExitStatus, Args) ->
@@ -250,6 +234,8 @@ finish(Tracing, ExitStatus, Args) ->
end.
script_start1(Parent, Args) ->
+ %% tag this process
+ ct_util:mark_process(),
%% read general start flags
Label = get_start_opt(label, fun([Lbl]) -> Lbl end, Args),
Profile = get_start_opt(profile, fun([Prof]) -> Prof end, Args),
@@ -315,7 +301,7 @@ script_start1(Parent, Args) ->
{undefined,InclDirs};
CtInclPath ->
AllInclDirs =
- string:tokens(CtInclPath,[$:,$ ,$,]) ++ InclDirs,
+ string:lexemes(CtInclPath,[$:,$ ,$,]) ++ InclDirs,
application:set_env(common_test, include, AllInclDirs),
{undefined,AllInclDirs}
end;
@@ -363,6 +349,12 @@ script_start1(Parent, Args) ->
_ ->
application:set_env(common_test, disable_log_cache, true)
end,
+ %% log_cleanup - used by ct_logs
+ KeepLogs = get_start_opt(keep_logs,
+ fun ct_logs:parse_keep_logs/1,
+ all,
+ Args),
+ application:set_env(common_test, keep_logs, KeepLogs),
Opts = #opts{label = Label, profile = Profile,
vts = Vts, shell = Shell,
@@ -430,13 +422,15 @@ script_start2(Opts = #opts{vts = undefined,
Specs1 = get_start_opt(join_specs, [Specs], Specs, Args),
%% using testspec as input for test
Relaxed = get_start_opt(allow_user_terms, true, false, Args),
- case catch ct_testspec:collect_tests_from_file(Specs1, Relaxed) of
- {E,Reason} when E == error ; E == 'EXIT' ->
- StackTrace = erlang:get_stacktrace(),
- {error,{invalid_testspec,{Reason,StackTrace}}};
- TestSpecData ->
+ try ct_testspec:collect_tests_from_file(Specs1, Relaxed) of
+ TestSpecData ->
execute_all_specs(TestSpecData, Opts, Args, [])
- end;
+ catch
+ throw:{error,Reason}:StackTrace ->
+ {error,{invalid_testspec,{Reason,StackTrace}}};
+ _:Reason:StackTrace ->
+ {error,{invalid_testspec,{Reason,StackTrace}}}
+ end;
[] ->
{error,no_testspec_specified};
_ -> % no testspec used
@@ -750,7 +744,7 @@ script_start4(#opts{label = Label, profile = Profile,
if Config == [] ->
ok;
true ->
- io:format("\nInstalling: ~p\n\n", [Config])
+ io:format("\nInstalling: ~tp\n\n", [Config])
end,
case install([{config,Config},{event_handler,EvHandlers},
{ct_hooks, CTHooks},
@@ -789,9 +783,6 @@ script_start4(#opts{shell = true, cover = Cover}, _) ->
script_start4(Opts = #opts{tests = Tests}, Args) ->
do_run(Tests, [], Opts, Args).
-%%%-----------------------------------------------------------------
-%%% @spec script_usage() -> ok
-%%% @doc Print usage information for <code>ct_run</code>.
script_usage() ->
io:format("\nUsage:\n\n"),
io:format("Run tests from command line:\n\n"
@@ -875,9 +866,6 @@ script_usage() ->
"\n\t [-basic_html]"
"\n\t [-no_esc_chars]\n\n").
-%%%-----------------------------------------------------------------
-%%% @hidden
-%%% @equiv ct:install/1
install(Opts) ->
install(Opts, ".").
@@ -899,9 +887,9 @@ install(Opts, LogDir) ->
case whereis(ct_util_server) of
undefined ->
VarFile = variables_file_name(LogDir),
- case file:open(VarFile, [write]) of
+ case file:open(VarFile, [write, {encoding,utf8}]) of
{ok,Fd} ->
- _ = [io:format(Fd, "~p.\n", [Opt]) || Opt <- ConfOpts],
+ _ = [io:format(Fd, "~tp.\n", [Opt]) || Opt <- ConfOpts],
ok = file:close(Fd);
{error,Reason} ->
io:format("CT failed to install configuration data. Please "
@@ -921,15 +909,6 @@ install(Opts, LogDir) ->
variables_file_name(Dir) ->
filename:join(Dir, "variables-"++atom_to_list(node())).
-%%%-----------------------------------------------------------------
-%%% @spec run_test(Opts) -> Result
-%%% Opts = [tuple()]
-%%% Result = [TestResult] | {error,Reason}
-%%%
-%%% @doc Start tests from the erlang shell or from an erlang program.
-%%% @equiv ct:run_test/1
-%%%-----------------------------------------------------------------
-
run_test(StartOpt) when is_tuple(StartOpt) ->
run_test([StartOpt]);
@@ -946,7 +925,10 @@ run_test(StartOpts) when is_list(StartOpts) ->
-spec run_test1_fun(_) -> fun(() -> no_return()).
run_test1_fun(StartOpts) ->
- fun() -> run_test1(StartOpts) end.
+ fun() ->
+ ct_util:mark_process(),
+ run_test1(StartOpts)
+ end.
run_test1(StartOpts) when is_list(StartOpts) ->
case proplists:get_value(refresh_logs, StartOpts) of
@@ -970,6 +952,12 @@ run_test1(StartOpts) when is_list(StartOpts) ->
stop_trace(Tracing),
exit(Res);
RefreshDir ->
+ %% log_cleanup - used by ct_logs
+ KeepLogs = get_start_opt(keep_logs,
+ fun ct_logs:parse_keep_logs/1,
+ all,
+ StartOpts),
+ application:set_env(common_test, keep_logs, KeepLogs),
ok = refresh_logs(?abs(RefreshDir)),
exit(done)
end.
@@ -1080,7 +1068,7 @@ run_test2(StartOpts) ->
application:set_env(common_test, include, InclDirs),
{undefined,InclDirs};
CtInclPath ->
- InclDirs1 = string:tokens(CtInclPath, [$:,$ ,$,]),
+ InclDirs1 = string:lexemes(CtInclPath, [$:,$ ,$,]),
AllInclDirs = InclDirs1++InclDirs,
application:set_env(common_test, include, AllInclDirs),
{undefined,AllInclDirs}
@@ -1131,6 +1119,12 @@ run_test2(StartOpts) ->
DisableCacheBool ->
application:set_env(common_test, disable_log_cache, DisableCacheBool)
end,
+ %% log_cleanup - used by ct_logs
+ KeepLogs = get_start_opt(keep_logs,
+ fun ct_logs:parse_keep_logs/1,
+ all,
+ StartOpts),
+ application:set_env(common_test, keep_logs, KeepLogs),
%% stepped execution
Step = get_start_opt(step, value, StartOpts),
@@ -1180,12 +1174,14 @@ run_spec_file(Relaxed,
end,
AbsSpecs = lists:map(fun(SF) -> ?abs(SF) end, Specs1),
AbsSpecs1 = get_start_opt(join_specs, [AbsSpecs], AbsSpecs, StartOpts),
- case catch ct_testspec:collect_tests_from_file(AbsSpecs1, Relaxed) of
- {Error,CTReason} when Error == error ; Error == 'EXIT' ->
- StackTrace = erlang:get_stacktrace(),
- exit({error,{invalid_testspec,{CTReason,StackTrace}}});
+ try ct_testspec:collect_tests_from_file(AbsSpecs1, Relaxed) of
TestSpecData ->
run_all_specs(TestSpecData, Opts, StartOpts, [])
+ catch
+ throw:{error,CTReason}:StackTrace ->
+ exit({error,{invalid_testspec,{CTReason,StackTrace}}});
+ _:CTReason:StackTrace ->
+ exit({error,{invalid_testspec,{CTReason,StackTrace}}})
end.
run_all_specs([], _, _, TotResult) ->
@@ -1400,14 +1396,6 @@ run_dir(Opts = #opts{logdir = LogDir,
exit({error,{incorrect_start_options,{Dir,Suite,GsAndCs}}})
end.
-%%%-----------------------------------------------------------------
-%%% @spec run_testspec(TestSpec) -> Result
-%%% TestSpec = [term()]
-%%%
-%%% @doc Run test specified by <code>TestSpec</code>. The terms are
-%%% the same as those used in test specification files.
-%%% @equiv ct:run_testspec/1
-%%%-----------------------------------------------------------------
run_testspec(TestSpec) ->
CTPid = spawn(run_testspec1_fun(TestSpec)),
Ref = monitor(process, CTPid),
@@ -1421,7 +1409,10 @@ run_testspec(TestSpec) ->
-spec run_testspec1_fun(_) -> fun(() -> no_return()).
run_testspec1_fun(TestSpec) ->
- fun() -> run_testspec1(TestSpec) end.
+ fun() ->
+ ct_util:mark_process(),
+ run_testspec1(TestSpec)
+ end.
run_testspec1(TestSpec) ->
{ok,Cwd} = file:get_cwd(),
@@ -1456,7 +1447,7 @@ run_testspec2(TestSpec) ->
false ->
Opts#opts.include;
CtInclPath ->
- EnvInclude = string:tokens(CtInclPath, [$:,$ ,$,]),
+ EnvInclude = string:lexemes(CtInclPath, [$:,$ ,$,]),
EnvInclude++Opts#opts.include
end,
application:set_env(common_test, include, AllInclude),
@@ -1604,9 +1595,6 @@ delistify([E]) -> E;
delistify(E) -> E.
-%%%-----------------------------------------------------------------
-%%% @hidden
-%%% @equiv ct:run/3
run(TestDir, Suite, Cases) ->
case install([]) of
ok ->
@@ -1615,9 +1603,6 @@ run(TestDir, Suite, Cases) ->
Error
end.
-%%%-----------------------------------------------------------------
-%%% @hidden
-%%% @equiv ct:run/2
run(TestDir, Suite) when is_list(TestDir), is_integer(hd(TestDir)) ->
case install([]) of
ok ->
@@ -1626,9 +1611,6 @@ run(TestDir, Suite) when is_list(TestDir), is_integer(hd(TestDir)) ->
Error
end.
-%%%-----------------------------------------------------------------
-%%% @hidden
-%%% @equiv ct:run/1
run(TestDirs) ->
case install([]) of
ok ->
@@ -1802,10 +1784,10 @@ compile_and_run(Tests, Skip, Opts, Args) ->
case lists:member(all, Conns) of
true ->
Conns1 = ct_util:override_silence_all_connections(),
- ct_logs:log("Silent connections", "~p", [Conns1]);
+ ct_logs:log("Silent connections", "~tp", [Conns1]);
false ->
ct_util:override_silence_connections(Conns),
- ct_logs:log("Silent connections", "~p", [Conns])
+ ct_logs:log("Silent connections", "~tp", [Conns])
end
end,
log_ts_names(Opts#opts.testspec_files),
@@ -1880,10 +1862,12 @@ possibly_spawn(true, Tests, Skip, Opts) ->
CTUtilSrv = whereis(ct_util_server),
Supervisor =
fun() ->
+ ct_util:mark_process(),
process_flag(trap_exit, true),
link(CTUtilSrv),
TestRun =
fun() ->
+ ct_util:mark_process(),
TestResult = (catch do_run_test(Tests, Skip, Opts)),
case TestResult of
{EType,_} = Error when EType == user_error;
@@ -1898,7 +1882,7 @@ possibly_spawn(true, Tests, Skip, Opts) ->
TestRunPid = spawn_link(TestRun),
receive
{'EXIT',TestRunPid,{ok,TestResult}} ->
- io:format(user, "~nCommon Test returned ~p~n~n",
+ io:format(user, "~nCommon Test returned ~tp~n~n",
[TestResult]);
{'EXIT',TestRunPid,Error} ->
exit(Error)
@@ -1917,7 +1901,7 @@ auto_compile(TestSuites) ->
case application:get_env(common_test, include) of
{ok,UserInclDirs} when length(UserInclDirs) > 0 ->
io:format("Including the following directories:~n"),
- [begin io:format("~p~n",[UserInclDir]), {i,UserInclDir} end ||
+ [begin io:format("~tp~n",[UserInclDir]), {i,UserInclDir} end ||
UserInclDir <- UserInclDirs];
_ ->
[]
@@ -1925,7 +1909,8 @@ auto_compile(TestSuites) ->
SuiteMakeErrors =
lists:flatmap(fun({TestDir,Suite} = TS) ->
case run_make(suites, TestDir,
- Suite, UserInclude) of
+ Suite, UserInclude,
+ [nowarn_export_all]) of
{error,{make_failed,Bad}} ->
[{TS,Bad}];
{error,_} ->
@@ -1943,7 +1928,7 @@ auto_compile(TestSuites) ->
case lists:member(Dir, Done) of
false ->
Failed1 =
- case run_make(helpmods, Dir, Suite, UserInclude) of
+ case run_make(helpmods, Dir, Suite, UserInclude, []) of
{error,{make_failed,BadMods}} ->
[{{Dir,all},BadMods}|Failed];
{error,_} ->
@@ -2036,16 +2021,9 @@ get_bad_suites([], BadSuites) ->
BadSuites.
-
-%%%-----------------------------------------------------------------
-%%% @hidden
-%%% @equiv ct:step/3
step(TestDir, Suite, Case) ->
step(TestDir, Suite, Case, []).
-%%%-----------------------------------------------------------------
-%%% @hidden
-%%% @equiv ct:step/4
step(TestDir, Suite, Case, Opts) when is_list(TestDir),
is_atom(Suite), is_atom(Case),
Suite =/= all, Case =/= all ->
@@ -2258,7 +2236,7 @@ do_run_test(Tests, Skip, Opts0) ->
NoOfSuites = length(Suites1),
ct_util:warn_duplicates(Suites1),
{ok,Cwd} = file:get_cwd(),
- io:format("~nCWD set to: ~p~n", [Cwd]),
+ io:format("~nCWD set to: ~tp~n", [Cwd]),
if NoOfCases == unknown ->
io:format("~nTEST INFO: ~w test(s), ~w suite(s)~n~n",
[NoOfTests,NoOfSuites]),
@@ -2328,7 +2306,7 @@ do_run_test(Tests, Skip, Opts0) ->
case ct_util:get_testdata(severe_error) of
undefined -> ok;
SevereError ->
- ct_logs:log("SEVERE ERROR", "~p\n", [SevereError]),
+ ct_logs:log("SEVERE ERROR", "~tp\n", [SevereError]),
exit(SevereError)
end,
@@ -2367,18 +2345,24 @@ start_cover(Opts=#opts{coverspec=CovData,cover_stop=CovStop},LogDir) ->
CovImport,
_CovExport,
#cover{app = CovApp,
+ local_only = LocalOnly,
level = CovLevel,
excl_mods = CovExcl,
incl_mods = CovIncl,
cross = CovCross,
src = _CovSrc}} = CovData,
+ case LocalOnly of
+ true -> cover:local_only();
+ false -> ok
+ end,
ct_logs:log("COVER INFO",
"Using cover specification file: ~ts~n"
"App: ~w~n"
+ "Local only: ~w~n"
"Cross cover: ~w~n"
"Including ~w modules~n"
"Excluding ~w modules",
- [CovFile,CovApp,CovCross,
+ [CovFile,CovApp,LocalOnly,CovCross,
length(CovIncl),length(CovExcl)]),
%% Tell test_server to print a link in its coverlog
@@ -2399,7 +2383,7 @@ start_cover(Opts=#opts{coverspec=CovData,cover_stop=CovStop},LogDir) ->
if (CovNodes /= []) and (CovNodes /= undefined) ->
ct_logs:log("COVER INFO",
"Nodes included in cover "
- "session: ~w",
+ "session: ~tw",
[CovNodes]),
cover:start(CovNodes);
true ->
@@ -2413,7 +2397,7 @@ start_cover(Opts=#opts{coverspec=CovData,cover_stop=CovStop},LogDir) ->
{error,Reason} ->
ct_logs:log("COVER INFO",
"Importing cover data from: ~ts fails! "
- "Reason: ~p", [Imp,Reason])
+ "Reason: ~tp", [Imp,Reason])
end
end, CovImport),
{TsCoverInfo,Opts}.
@@ -2692,12 +2676,12 @@ get_name(Dir) ->
run_make(TestDir, Mod, UserInclude) ->
- run_make(suites, TestDir, Mod, UserInclude).
+ run_make(suites, TestDir, Mod, UserInclude, [nowarn_export_all]).
-run_make(Targets, TestDir0, Mod, UserInclude) when is_list(Mod) ->
- run_make(Targets, TestDir0, list_to_atom(Mod), UserInclude);
+run_make(Targets, TestDir0, Mod, UserInclude, COpts) when is_list(Mod) ->
+ run_make(Targets, TestDir0, list_to_atom(Mod), UserInclude, COpts);
-run_make(Targets, TestDir0, Mod, UserInclude) ->
+run_make(Targets, TestDir0, Mod, UserInclude, COpts) ->
case locate_test_dir(TestDir0, Mod) of
{ok,TestDir} ->
%% send a start_make notification which may suspend
@@ -2712,7 +2696,7 @@ run_make(Targets, TestDir0, Mod, UserInclude) ->
XmerlInclude = get_dir(xmerl, "include"),
ErlFlags = UserInclude ++ [{i,CtInclude},
{i,XmerlInclude},
- debug_info],
+ debug_info] ++ COpts,
Result =
if Mod == all ; Targets == helpmods ->
case (catch ct_make:all([noexec|ErlFlags])) of
@@ -2747,7 +2731,7 @@ run_make(Targets, TestDir0, Mod, UserInclude) ->
{up_to_date,_} ->
ok;
{'EXIT',Reason} ->
- io:format("{error,{make_crashed,~p}\n", [Reason]),
+ io:format("{error,{make_crashed,~tp}\n", [Reason]),
{error,{make_crashed,TestDir,Reason}};
{error,ModInfo} ->
io:format("{error,make_failed}\n", []),
@@ -2756,7 +2740,7 @@ run_make(Targets, TestDir0, Mod, UserInclude) ->
{error,{make_failed,Bad}}
end;
{error,_} ->
- io:format("{error,{invalid_directory,~p}}\n", [TestDir0]),
+ io:format("{error,{invalid_directory,~tp}}\n", [TestDir0]),
{error,{invalid_directory,TestDir0}}
end.
@@ -2806,7 +2790,7 @@ maybe_interpret2(Suite, Cases, StepOpts) ->
_ -> ok
catch
_:_Error ->
- io:format(user, "Invalid breakpoint: ~w:~w/1~n",
+ io:format(user, "Invalid breakpoint: ~w:~tw/1~n",
[Suite,Case])
end
end || Case <- Cases, is_atom(Case)],
@@ -2937,7 +2921,7 @@ ct_hooks_args2opts([],Acc) ->
parse_cth_args(String) ->
try
- true = io_lib:printable_list(String),
+ true = io_lib:printable_unicode_list(String),
{ok,Toks,_} = erl_scan:string(String++"."),
{ok, Args} = erl_parse:parse_term(Toks),
Args
@@ -3016,7 +3000,7 @@ rel_to_abs(CtArgs) ->
_ = if Dir /= Abs ->
_ = code:del_path(Dir),
_ = code:del_path(Abs),
- io:format(user, "Converting ~p to ~p and re-inserting "
+ io:format(user, "Converting ~tp to ~tp and re-inserting "
"with add_pathz/1~n",
[Dir, Abs]);
true ->
@@ -3030,7 +3014,7 @@ rel_to_abs(CtArgs) ->
_ = if Dir /= Abs ->
_ = code:del_path(Dir),
_ = code:del_path(Abs),
- io:format(user, "Converting ~p to ~p and re-inserting "
+ io:format(user, "Converting ~tp to ~tp and re-inserting "
"with add_patha/1~n",
[Dir, Abs]);
true ->
@@ -3100,7 +3084,7 @@ opts2args(EnvStartOpts) ->
({group,G}) when is_atom(G) ->
[{group,[atom_to_list(G)]}];
({group,Gs}) when is_list(Gs) ->
- LOfGStrs = [lists:flatten(io_lib:format("~w",[G])) ||
+ LOfGStrs = [lists:flatten(io_lib:format("~tw",[G])) ||
G <- Gs],
[{group,LOfGStrs}];
({testcase,Case}) when is_atom(Case) ->
@@ -3152,10 +3136,10 @@ opts2args(EnvStartOpts) ->
({event_handler,EHs}) when is_list(EHs) ->
[{event_handler,[atom_to_list(EH) || EH <- EHs]}];
({event_handler,{EH,Arg}}) when is_atom(EH) ->
- ArgStr = lists:flatten(io_lib:format("~p", [Arg])),
+ ArgStr = lists:flatten(io_lib:format("~tp", [Arg])),
[{event_handler_init,[atom_to_list(EH),ArgStr]}];
({event_handler,{EHs,Arg}}) when is_list(EHs) ->
- ArgStr = lists:flatten(io_lib:format("~p", [Arg])),
+ ArgStr = lists:flatten(io_lib:format("~tp", [Arg])),
Strs = lists:flatmap(fun(EH) ->
[atom_to_list(EH),
ArgStr,"and"]
@@ -3186,25 +3170,25 @@ opts2args(EnvStartOpts) ->
({ct_hooks,[]}) ->
[];
({ct_hooks,CTHs}) when is_list(CTHs) ->
- io:format(user,"ct_hooks: ~p",[CTHs]),
+ io:format(user,"ct_hooks: ~tp",[CTHs]),
Strs = lists:flatmap(
fun({CTH,Arg,Prio}) ->
[atom_to_list(CTH),
lists:flatten(
- io_lib:format("~p",[Arg])),
+ io_lib:format("~tp",[Arg])),
lists:flatten(
- io_lib:format("~p",[Prio])),
+ io_lib:format("~tp",[Prio])),
"and"];
({CTH,Arg}) ->
[atom_to_list(CTH),
lists:flatten(
- io_lib:format("~p",[Arg])),
+ io_lib:format("~tp",[Arg])),
"and"];
(CTH) when is_atom(CTH) ->
[atom_to_list(CTH),"and"]
end,CTHs),
[_LastAnd|StrsR] = lists:reverse(Strs),
- io:format(user,"return: ~p",[lists:reverse(StrsR)]),
+ io:format(user,"return: ~tp",[lists:reverse(StrsR)]),
[{ct_hooks,lists:reverse(StrsR)}];
({Opt,As=[A|_]}) when is_atom(A) ->
[{Opt,[atom_to_list(Atom) || Atom <- As]}];
@@ -3286,7 +3270,7 @@ start_trace(Args) ->
ok ->
true;
{_,Error} ->
- io:format("Warning! Tracing not started. Reason: ~p~n~n",
+ io:format("Warning! Tracing not started. Reason: ~tp~n~n",
[Error]),
false
end;
diff --git a/lib/common_test/src/ct_slave.erl b/lib/common_test/src/ct_slave.erl
index 571958ca03..dde33440be 100644
--- a/lib/common_test/src/ct_slave.erl
+++ b/lib/common_test/src/ct_slave.erl
@@ -1,7 +1,7 @@
%%--------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2018. 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.
@@ -17,13 +17,6 @@
%%
%% %CopyrightEnd%
-%%% @doc Common Test Framework functions for starting and stopping nodes for
-%%% Large Scale Testing.
-%%%
-%%% <p>This module exports functions which are used by the Common Test Master
-%%% to start and stop "slave" nodes. It is the default callback module for the
-%%% <code>{init, node_start}</code> term of the Test Specification.</p>
-
%%----------------------------------------------------------------------
%% File : ct_slave.erl
%% Description : CT module for starting nodes for large-scale testing.
@@ -38,44 +31,12 @@
-record(options, {username, password, boot_timeout, init_timeout,
startup_timeout, startup_functions, monitor_master,
- kill_if_fail, erl_flags, env, ssh_port, ssh_opts}).
-
-%%%-----------------------------------------------------------------
-%%% @spec start(Node) -> Result
-%%% Node = atom()
-%%% Result = {ok, NodeName} |
-%%% {error, Reason, NodeName}
-%%% Reason = already_started |
-%%% started_not_connected |
-%%% boot_timeout |
-%%% init_timeout |
-%%% startup_timeout |
-%%% not_alive
-%%% NodeName = atom()
-%%% @doc Starts an Erlang node with name <code>Node</code> on the local host.
-%%% @see start/3
+ kill_if_fail, erl_flags, env, ssh_port, ssh_opts,
+ stop_timeout}).
+
start(Node) ->
start(gethostname(), Node).
-%%%-----------------------------------------------------------------
-%%% @spec start(HostOrNode, NodeOrOpts) -> Result
-%%% HostOrNode = atom()
-%%% NodeOrOpts = atom() | list()
-%%% Result = {ok, NodeName} |
-%%% {error, Reason, NodeName}
-%%% Reason = already_started |
-%%% started_not_connected |
-%%% boot_timeout |
-%%% init_timeout |
-%%% startup_timeout |
-%%% not_alive
-%%% NodeName = atom()
-%%% @doc Starts an Erlang node with default options on a specified
-%%% host, or on the local host with specified options. That is,
-%%% the call is interpreted as <code>start(Host, Node)</code> when the
-%%% second argument is atom-valued and <code>start(Node, Opts)</code>
-%%% when it's list-valued.
-%%% @see start/3
start(_HostOrNode = Node, _NodeOrOpts = Opts) %% match to satiate edoc
when is_list(Opts) ->
start(gethostname(), Node, Opts);
@@ -83,104 +44,6 @@ start(_HostOrNode = Node, _NodeOrOpts = Opts) %% match to satiate edoc
start(Host, Node) ->
start(Host, Node, []).
-%%%-----------------------------------------------------------------
-%%% @spec start(Host, Node, Opts) -> Result
-%%% Node = atom()
-%%% Host = atom()
-%%% Opts = [OptTuples]
-%%% OptTuples = {username, Username} |
-%%% {password, Password} |
-%%% {boot_timeout, BootTimeout} | {init_timeout, InitTimeout} |
-%%% {startup_timeout, StartupTimeout} |
-%%% {startup_functions, StartupFunctions} |
-%%% {monitor_master, Monitor} |
-%%% {kill_if_fail, KillIfFail} |
-%%% {erl_flags, ErlangFlags} |
-%%% {env, [{EnvVar,Value}]}
-%%% Username = string()
-%%% Password = string()
-%%% BootTimeout = integer()
-%%% InitTimeout = integer()
-%%% StartupTimeout = integer()
-%%% StartupFunctions = [StartupFunctionSpec]
-%%% StartupFunctionSpec = {Module, Function, Arguments}
-%%% Module = atom()
-%%% Function = atom()
-%%% Arguments = [term]
-%%% Monitor = bool()
-%%% KillIfFail = bool()
-%%% ErlangFlags = string()
-%%% EnvVar = string()
-%%% Value = string()
-%%% Result = {ok, NodeName} |
-%%% {error, Reason, NodeName}
-%%% Reason = already_started |
-%%% started_not_connected |
-%%% boot_timeout |
-%%% init_timeout |
-%%% startup_timeout |
-%%% not_alive
-%%% NodeName = atom()
-%%% @doc Starts an Erlang node with name <code>Node</code> on host
-%%% <code>Host</code> as specified by the combination of options in
-%%% <code>Opts</code>.
-%%%
-%%% <p>Options <code>Username</code> and <code>Password</code> will be used
-%%% to log in onto the remote host <code>Host</code>.
-%%% Username, if omitted, defaults to the current user name,
-%%% and password is empty by default.</p>
-%%%
-%%% <p>A list of functions specified in the <code>Startup</code> option will be
-%%% executed after startup of the node. Note that all used modules should be
-%%% present in the code path on the <code>Host</code>.</p>
-%%%
-%%% <p>The timeouts are applied as follows:</p>
-%%% <list>
-%%% <item>
-%%% <code>BootTimeout</code> - time to start the Erlang node, in seconds.
-%%% Defaults to 3 seconds. If node does not become pingable within this time,
-%%% the result <code>{error, boot_timeout, NodeName}</code> is returned;
-%%% </item>
-%%% <item>
-%%% <code>InitTimeout</code> - time to wait for the node until it calls the
-%%% internal callback function informing master about successfull startup.
-%%% Defaults to one second.
-%%% In case of timed out message the result
-%%% <code>{error, init_timeout, NodeName}</code> is returned;
-%%% </item>
-%%% <item>
-%%% <code>StartupTimeout</code> - time to wait intil the node finishes to run
-%%% the <code>StartupFunctions</code>. Defaults to one second.
-%%% If this timeout occurs, the result
-%%% <code>{error, startup_timeout, NodeName}</code> is returned.
-%%% </item>
-%%% </list>
-%%%
-%%% <p>Option <code>monitor_master</code> specifies, if the slave node should be
-%%% stopped in case of master node stop. Defaults to false.</p>
-%%%
-%%% <p>Option <code>kill_if_fail</code> specifies, if the slave node should be
-%%% killed in case of a timeout during initialization or startup.
-%%% Defaults to true. Note that node also may be still alive it the boot
-%%% timeout occurred, but it will not be killed in this case.</p>
-%%%
-%%% <p>Option <code>erlang_flags</code> specifies, which flags will be added
-%%% to the parameters of the <code>erl</code> executable.</p>
-%%%
-%%% <p>Option <code>env</code> specifies a list of environment variables
-%%% that will extended the environment.</p>
-%%%
-%%% <p>Special return values are:</p>
-%%% <list>
-%%% <item><code>{error, already_started, NodeName}</code> - if the node with
-%%% the given name is already started on a given host;</item>
-%%% <item><code>{error, started_not_connected, NodeName}</code> - if node is
-%%% started, but not connected to the master node.</item>
-%%% <item><code>{error, not_alive, NodeName}</code> - if node on which the
-%%% <code>ct_slave:start/3</code> is called, is not alive. Note that
-%%% <code>NodeName</code> is the name of current node in this case.</item>
-%%% </list>
-%%%
start(Host, Node, Opts) ->
ENode = enodename(Host, Node),
case erlang:is_alive() of
@@ -198,36 +61,22 @@ start(Host, Node, Opts) ->
end
end.
-%%% @spec stop(Node) -> Result
-%%% Node = atom()
-%%% Result = {ok, NodeName} |
-%%% {error, Reason, NodeName}
-%%% Reason = not_started |
-%%% not_connected |
-%%% stop_timeout
-
-%%% NodeName = atom()
-%%% @doc Stops the running Erlang node with name <code>Node</code> on
-%%% the localhost.
stop(Node) ->
stop(gethostname(), Node).
-%%% @spec stop(Host, Node) -> Result
-%%% Host = atom()
-%%% Node = atom()
-%%% Result = {ok, NodeName} |
-%%% {error, Reason, NodeName}
-%%% Reason = not_started |
-%%% not_connected |
-%%% stop_timeout
-%%% NodeName = atom()
-%%% @doc Stops the running Erlang node with name <code>Node</code> on
-%%% host <code>Host</code>.
+stop(_HostOrNode = Node, _NodeOrOpts = Opts) %% match to satiate edoc
+ when is_list(Opts) ->
+ stop(gethostname(), Node, Opts);
+
stop(Host, Node) ->
+ stop(Host, Node, []).
+
+stop(Host, Node, Opts) ->
ENode = enodename(Host, Node),
case is_started(ENode) of
{true, connected}->
- do_stop(ENode);
+ OptionsRec = fetch_options(Opts),
+ do_stop(ENode, OptionsRec);
{true, not_connected}->
{error, not_connected, ENode};
false->
@@ -257,31 +106,31 @@ fetch_options(Options) ->
EnvVars = get_option_value(env, Options, []),
SSHPort = get_option_value(ssh_port, Options, []),
SSHOpts = get_option_value(ssh_opts, Options, []),
+ StopTimeout = get_option_value(stop_timeout, Options, 5),
#options{username=UserName, password=Password,
boot_timeout=BootTimeout, init_timeout=InitTimeout,
startup_timeout=StartupTimeout, startup_functions=StartupFunctions,
monitor_master=Monitor, kill_if_fail=KillIfFail,
- erl_flags=ErlFlags, env=EnvVars, ssh_port=SSHPort, ssh_opts=SSHOpts}.
+ erl_flags=ErlFlags, env=EnvVars, ssh_port=SSHPort, ssh_opts=SSHOpts,
+ stop_timeout=StopTimeout}.
% send a message when slave node is started
-% @hidden
slave_started(ENode, MasterPid) ->
MasterPid ! {node_started, ENode},
ok.
% send a message when slave node has finished startup
-% @hidden
slave_ready(ENode, MasterPid) ->
MasterPid ! {node_ready, ENode},
ok.
% start monitoring of the master node
-% @hidden
monitor_master(MasterNode) ->
spawn(fun() -> monitor_master_int(MasterNode) end).
% code of the masterdeath-waiter process
monitor_master_int(MasterNode) ->
+ ct_util:mark_process(),
erlang:monitor_node(MasterNode, true),
receive
{nodedown, MasterNode}->
@@ -309,7 +158,12 @@ is_started(ENode) ->
% make a Erlang node name from name and hostname
enodename(Host, Node) ->
- list_to_atom(atom_to_list(Node)++"@"++atom_to_list(Host)).
+ case lists:member($@, atom_to_list(Node)) of
+ true ->
+ Node;
+ false ->
+ list_to_atom(atom_to_list(Node)++"@"++atom_to_list(Host))
+ end.
% performs actual start of the "slave" node
do_start(Host, Node, Options) ->
@@ -456,6 +310,8 @@ wait_for_node_alive(Node, N) ->
% call init:stop on a remote node
do_stop(ENode) ->
+ do_stop(ENode, fetch_options([])).
+do_stop(ENode, Options) ->
{Cover,MainCoverNode} =
case test_server:is_cover() of
true ->
@@ -466,7 +322,8 @@ do_stop(ENode) ->
{false,undefined}
end,
spawn(ENode, init, stop, []),
- case wait_for_node_dead(ENode, 5) of
+ StopTimeout = Options#options.stop_timeout,
+ case wait_for_node_dead(ENode, StopTimeout) of
{ok,ENode} ->
if Cover ->
%% To avoid that cover is started again if a node
diff --git a/lib/common_test/src/ct_snmp.erl b/lib/common_test/src/ct_snmp.erl
index 5844909d17..27b74dd04e 100644
--- a/lib/common_test/src/ct_snmp.erl
+++ b/lib/common_test/src/ct_snmp.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2018. 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.
@@ -18,139 +18,8 @@
%% %CopyrightEnd%
%%
-%%% @doc Common Test user interface module for the OTP snmp application
-%%%
-%%% <p>The purpose of this module is to make snmp configuration easier for
-%%% the test case writer. Many test cases can use default values for common
-%%% operations and then no snmp configuration files need to be supplied. When
-%%% it is necessary to change particular configuration parameters, a subset
-%%% of the relevant snmp configuration files may be passed to <code>ct_snmp</code>
-%%% by means of Common Test configuration files.
-%%% For more specialized configuration parameters, it is possible to place a
-%%% "simple snmp configuration file" in the test suite data directory.
-%%% To simplify the test suite, Common Test keeps track
-%%% of some of the snmp manager information. This way the test suite doesn't
-%%% have to handle as many input parameters as it would if it had to interface the
-%%% OTP snmp manager directly.</p>
-%%%
-%%% <p> The following snmp manager and agent parameters are configurable: </p>
-%%%
-%%% <pre>
-%%% {snmp,
-%%% %%% Manager config
-%%% [{start_manager, boolean()} % Optional - default is true
-%%% {users, [{user_name(), [call_back_module(), user_data()]}]}, %% Optional
-%%% {usm_users, [{usm_user_name(), [usm_config()]}]},%% Optional - snmp v3 only
-%%% % managed_agents is optional
-%%% {managed_agents,[{agent_name(), [user_name(), agent_ip(), agent_port(), [agent_config()]]}]},
-%%% {max_msg_size, integer()}, % Optional - default is 484
-%%% {mgr_port, integer()}, % Optional - default is 5000
-%%% {engine _id, string()}, % Optional - default is "mgrEngine"
-%%%
-%%% %%% Agent config
-%%% {start_agent, boolean()}, % Optional - default is false
-%%% {agent_sysname, string()}, % Optional - default is "ct_test"
-%%% {agent_manager_ip, manager_ip()}, % Optional - default is localhost
-%%% {agent_vsns, list()}, % Optional - default is [v2]
-%%% {agent_trap_udp, integer()}, % Optional - default is 5000
-%%% {agent_udp, integer()}, % Optional - default is 4000
-%%% {agent_notify_type, atom()}, % Optional - default is trap
-%%% {agent_sec_type, sec_type()}, % Optional - default is none
-%%% {agent_passwd, string()}, % Optional - default is ""
-%%% {agent_engine_id, string()}, % Optional - default is "agentEngine"
-%%% {agent_max_msg_size, string()},% Optional - default is 484
-%%%
-%%% %% The following parameters represents the snmp configuration files
-%%% %% context.conf, standard.conf, community.conf, vacm.conf,
-%%% %% usm.conf, notify.conf, target_addr.conf and target_params.conf.
-%%% %% Note all values in agent.conf can be altered by the parametes
-%%% %% above. All these configuration files have default values set
-%%% %% up by the snmp application. These values can be overridden by
-%%% %% suppling a list of valid configuration values or a file located
-%%% %% in the test suites data dir that can produce a list
-%%% %% of valid configuration values if you apply file:consult/1 to the
-%%% %% file.
-%%% {agent_contexts, [term()] | {data_dir_file, rel_path()}}, % Optional
-%%% {agent_community, [term()] | {data_dir_file, rel_path()}},% Optional
-%%% {agent_sysinfo, [term()] | {data_dir_file, rel_path()}}, % Optional
-%%% {agent_vacm, [term()] | {data_dir_file, rel_path()}}, % Optional
-%%% {agent_usm, [term()] | {data_dir_file, rel_path()}}, % Optional
-%%% {agent_notify_def, [term()] | {data_dir_file, rel_path()}},% Optional
-%%% {agent_target_address_def, [term()] | {data_dir_file, rel_path()}},% Optional
-%%% {agent_target_param_def, [term()] | {data_dir_file, rel_path()}},% Optional
-%%% ]}.
-%%% </pre>
-%%%
-%%% <p>The <code>MgrAgentConfName</code> parameter in the functions
-%%% should be a name you allocate in your test suite using a
-%%% <code>require</code> statement.
-%%% Example (where <code>MgrAgentConfName = snmp_mgr_agent</code>):</p>
-%%% <pre> suite() -> [{require, snmp_mgr_agent, snmp}].</pre>
-%%% <p>or</p>
-%%% <pre> ct:require(snmp_mgr_agent, snmp).</pre>
-%%%
-%%% <p> Note that Usm users are needed for snmp v3 configuration and are
-%%% not to be confused with users.</p>
-%%%
-%%% <p> Snmp traps, inform and report messages are handled by the
-%%% user callback module. For more information about this see
-%%% the snmp application. </p>
-%%% <p> Note: It is recommended to use the .hrl-files created by the
-%%% Erlang/OTP mib-compiler to define the oids.
-%%% Example for the getting the erlang node name from the erlNodeTable
-%%% in the OTP-MIB:</p>
-%%% <pre>Oid = ?erlNodeEntry ++ [?erlNodeName, 1] </pre>
-%%%
-%%% <p>It is also possible to set values for snmp application configuration
-%%% parameters, such as <code>config</code>, <code>server</code>,
-%%% <code>net_if</code>, etc (see the "Configuring the application" chapter in
-%%% the OTP snmp User's Guide for a list of valid parameters and types). This is
-%%% done by defining a configuration data variable on the following form:</p>
-%%% <pre>
-%%% {snmp_app, [{manager, [snmp_app_manager_params()]},
-%%% {agent, [snmp_app_agent_params()]}]}.</pre>
-%%%
-%%% <p>A name for the data needs to be allocated in the suite using
-%%% <code>require</code> (see example above), and this name passed as
-%%% the <code>SnmpAppConfName</code> argument to <code>start/3</code>.
-%%% <code>ct_snmp</code> specifies default values for some snmp application
-%%% configuration parameters (such as <code>{verbosity,trace}</code> for the
-%%% <code>config</code> parameter). This set of defaults will be
-%%% merged with the parameters specified by the user, and user values
-%%% override <code>ct_snmp</code> defaults.</p>
-
-module(ct_snmp).
-%%% Common Types
-%%% @type agent_ip() = ip()
-%%% @type manager_ip() = ip()
-%%% @type agent_name() = atom()
-%%% @type ip() = string() | {integer(), integer(),
-%%% integer(), integer()}
-%%% @type agent_port() = integer()
-%%% @type agent_config() = {Item, Value}
-%%% @type user_name() = atom()
-%%% @type usm_user_name() = string()
-%%% @type usm_config() = {Item, Value}
-%%% @type call_back_module() = atom()
-%%% @type user_data() = term()
-%%% @type oids() = [oid()]
-%%% @type oid() = [byte()]
-%%% @type snmpreply() = {error_status(), error_index(), varbinds()}
-%%% @type error_status() = noError | atom()
-%%% @type error_index() = integer()
-%%% @type varbinds() = [varbind()]
-%%% @type varbind() = term()
-%%% @type value_type() = o ('OBJECT IDENTIFIER') | i ('INTEGER') |
-%%% u ('Unsigned32') | g ('Unsigned32') | s ('OCTET STRING')
-%%% @type varsandvals() = [var_and_val()]
-%%% @type var_and_val() = {oid(), value_type(), value()}
-%%% @type sec_type() = none | minimum | semi
-%%% @type rel_path() = string()
-%%% @type snmp_app_manager_params() = term()
-%%% @type snmp_app_agent_params() = term()
-
-
-include("snmp_types.hrl").
-include("inet.hrl").
-include("ct.hrl").
@@ -181,31 +50,9 @@
%%% API
%%%=========================================================================
-%%%-----------------------------------------------------------------
-%%% @spec start(Config, MgrAgentConfName) -> ok
-%%% @equiv start(Config, MgrAgentConfName, undefined)
start(Config, MgrAgentConfName) ->
start(Config, MgrAgentConfName, undefined).
-%%% @spec start(Config, MgrAgentConfName, SnmpAppConfName) -> ok
-%%% Config = [{Key, Value}]
-%%% Key = atom()
-%%% Value = term()
-%%% MgrAgentConfName = atom()
-%%% SnmpConfName = atom()
-%%%
-%%% @doc Starts an snmp manager and/or agent. In the manager case,
-%%% registrations of users and agents as specified by the configuration
-%%% <code>MgrAgentConfName</code> will be performed. When using snmp
-%%% v3 also so called usm users will be registered. Note that users,
-%%% usm_users and managed agents may also be registered at a later time
-%%% using ct_snmp:register_users/2, ct_snmp:register_agents/2, and
-%%% ct_snmp:register_usm_users/2. The agent started will be
-%%% called <code>snmp_master_agent</code>. Use ct_snmp:load_mibs/1 to load
-%%% mibs into the agent. With <code>SnmpAppConfName</code> it's possible
-%%% to configure the snmp application with parameters such as <code>config</code>,
-%%% <code>mibs</code>, <code>net_if</code>, etc. The values will be merged
-%%% with (and possibly override) default values set by <code>ct_snmp</code>.
start(Config, MgrAgentConfName, SnmpAppConfName) ->
StartManager= ct:get_config({MgrAgentConfName, start_manager}, true),
StartAgent = ct:get_config({MgrAgentConfName, start_agent}, false),
@@ -233,12 +80,6 @@ start_application(App) ->
Else
end.
-%%% @spec stop(Config) -> ok
-%%% Config = [{Key, Value}]
-%%% Key = atom()
-%%% Value = term()
-%%%
-%%% @doc Stops the snmp manager and/or agent removes all files created.
stop(Config) ->
PrivDir = ?config(priv_dir, Config),
ok = application:stop(snmp),
@@ -251,41 +92,16 @@ stop(Config) ->
catch del_dir(DbDir).
-%%% @spec get_values(Agent, Oids, MgrAgentConfName) -> SnmpReply
-%%%
-%%% Agent = agent_name()
-%%% Oids = oids()
-%%% MgrAgentConfName = atom()
-%%% SnmpReply = snmpreply()
-%%%
-%%% @doc Issues a synchronous snmp get request.
get_values(Agent, Oids, MgrAgentConfName) ->
[Uid | _] = agent_conf(Agent, MgrAgentConfName),
{ok, SnmpReply, _} = snmpm:sync_get2(Uid, target_name(Agent), Oids),
SnmpReply.
-%%% @spec get_next_values(Agent, Oids, MgrAgentConfName) -> SnmpReply
-%%%
-%%% Agent = agent_name()
-%%% Oids = oids()
-%%% MgrAgentConfName = atom()
-%%% SnmpReply = snmpreply()
-%%%
-%%% @doc Issues a synchronous snmp get next request.
get_next_values(Agent, Oids, MgrAgentConfName) ->
[Uid | _] = agent_conf(Agent, MgrAgentConfName),
{ok, SnmpReply, _} = snmpm:sync_get_next2(Uid, target_name(Agent), Oids),
SnmpReply.
-%%% @spec set_values(Agent, VarsAndVals, MgrAgentConfName, Config) -> SnmpReply
-%%%
-%%% Agent = agent_name()
-%%% Oids = oids()
-%%% MgrAgentConfName = atom()
-%%% Config = [{Key, Value}]
-%%% SnmpReply = snmpreply()
-%%%
-%%% @doc Issues a synchronous snmp set request.
set_values(Agent, VarsAndVals, MgrAgentConfName, Config) ->
PrivDir = ?config(priv_dir, Config),
[Uid | _] = agent_conf(Agent, MgrAgentConfName),
@@ -301,19 +117,6 @@ set_values(Agent, VarsAndVals, MgrAgentConfName, Config) ->
end,
SnmpSetReply.
-%%% @spec set_info(Config) -> [{Agent, OldVarsAndVals, NewVarsAndVals}]
-%%%
-%%% Config = [{Key, Value}]
-%%% Agent = agent_name()
-%%% OldVarsAndVals = varsandvals()
-%%% NewVarsAndVals = varsandvals()
-%%%
-%%% @doc Returns a list of all successful set requests performed in
-%%% the test case in reverse order. The list contains the involved
-%%% user and agent, the value prior to the set and the new value. This
-%%% is intended to facilitate the clean up in the end_per_testcase
-%%% function i.e. the undoing of the set requests and its possible
-%%% side-effects.
set_info(Config) ->
PrivDir = ?config(priv_dir, Config),
SetLogFile = filename:join(PrivDir, ?CT_SNMP_LOG_FILE),
@@ -325,18 +128,6 @@ set_info(Config) ->
[]
end.
-%%% @spec register_users(MgrAgentConfName, Users) -> ok | {error, Reason}
-%%%
-%%% MgrAgentConfName = atom()
-%%% Users = [user()]
-%%% Reason = term()
-%%%
-%%% @doc Register the manager entity (=user) responsible for specific agent(s).
-%%% Corresponds to making an entry in users.conf.
-%%%
-%%% <p>This function will try to register the given users, without
-%%% checking if any of them already exist. In order to change an
-%%% already registered user, the user must first be unregistered.</p>
register_users(MgrAgentConfName, Users) ->
case setup_users(Users) of
ok ->
@@ -350,19 +141,6 @@ register_users(MgrAgentConfName, Users) ->
Error
end.
-%%% @spec register_agents(MgrAgentConfName, ManagedAgents) -> ok | {error, Reason}
-%%%
-%%% MgrAgentConfName = atom()
-%%% ManagedAgents = [agent()]
-%%% Reason = term()
-%%%
-%%% @doc Explicitly instruct the manager to handle this agent.
-%%% Corresponds to making an entry in agents.conf
-%%%
-%%% <p>This function will try to register the given managed agents,
-%%% without checking if any of them already exist. In order to change
-%%% an already registered managed agent, the agent must first be
-%%% unregistered.</p>
register_agents(MgrAgentConfName, ManagedAgents) ->
case setup_managed_agents(MgrAgentConfName,ManagedAgents) of
ok ->
@@ -377,18 +155,6 @@ register_agents(MgrAgentConfName, ManagedAgents) ->
Error
end.
-%%% @spec register_usm_users(MgrAgentConfName, UsmUsers) -> ok | {error, Reason}
-%%%
-%%% MgrAgentConfName = atom()
-%%% UsmUsers = [usm_user()]
-%%% Reason = term()
-%%%
-%%% @doc Explicitly instruct the manager to handle this USM user.
-%%% Corresponds to making an entry in usm.conf
-%%%
-%%% <p>This function will try to register the given users, without
-%%% checking if any of them already exist. In order to change an
-%%% already registered user, the user must first be unregistered.</p>
register_usm_users(MgrAgentConfName, UsmUsers) ->
EngineID = ct:get_config({MgrAgentConfName, engine_id}, ?ENGINE_ID),
case setup_usm_users(UsmUsers, EngineID) of
@@ -403,23 +169,10 @@ register_usm_users(MgrAgentConfName, UsmUsers) ->
Error
end.
-%%% @spec unregister_users(MgrAgentConfName) -> ok
-%%%
-%%% MgrAgentConfName = atom()
-%%% Reason = term()
-%%%
-%%% @doc Unregister all users.
unregister_users(MgrAgentConfName) ->
Users = [Id || {Id,_} <- ct:get_config({MgrAgentConfName, users},[])],
unregister_users(MgrAgentConfName,Users).
-%%% @spec unregister_users(MgrAgentConfName,Users) -> ok
-%%%
-%%% MgrAgentConfName = atom()
-%%% Users = [user_name()]
-%%% Reason = term()
-%%%
-%%% @doc Unregister the given users.
unregister_users(MgrAgentConfName,Users) ->
takedown_users(Users),
SnmpVals = ct:get_config(MgrAgentConfName),
@@ -432,25 +185,12 @@ unregister_users(MgrAgentConfName,Users) ->
ct_config:update_config(MgrAgentConfName, NewSnmpVals),
ok.
-%%% @spec unregister_agents(MgrAgentConfName) -> ok
-%%%
-%%% MgrAgentConfName = atom()
-%%% Reason = term()
-%%%
-%%% @doc Unregister all managed agents.
unregister_agents(MgrAgentConfName) ->
ManagedAgents = [AgentName ||
{AgentName, _} <-
ct:get_config({MgrAgentConfName,managed_agents},[])],
unregister_agents(MgrAgentConfName,ManagedAgents).
-%%% @spec unregister_agents(MgrAgentConfName,ManagedAgents) -> ok
-%%%
-%%% MgrAgentConfName = atom()
-%%% ManagedAgents = [agent_name()]
-%%% Reason = term()
-%%%
-%%% @doc Unregister the given managed agents.
unregister_agents(MgrAgentConfName,ManagedAgents) ->
takedown_managed_agents(MgrAgentConfName, ManagedAgents),
SnmpVals = ct:get_config(MgrAgentConfName),
@@ -464,23 +204,10 @@ unregister_agents(MgrAgentConfName,ManagedAgents) ->
ct_config:update_config(MgrAgentConfName, NewSnmpVals),
ok.
-%%% @spec unregister_usm_users(MgrAgentConfName) -> ok
-%%%
-%%% MgrAgentConfName = atom()
-%%% Reason = term()
-%%%
-%%% @doc Unregister all usm users.
unregister_usm_users(MgrAgentConfName) ->
UsmUsers = [Id || {Id,_} <- ct:get_config({MgrAgentConfName, usm_users},[])],
unregister_usm_users(MgrAgentConfName,UsmUsers).
-%%% @spec unregister_usm_users(MgrAgentConfName,UsmUsers) -> ok
-%%%
-%%% MgrAgentConfName = atom()
-%%% UsmUsers = [usm_user_name()]
-%%% Reason = term()
-%%%
-%%% @doc Unregister the given usm users.
unregister_usm_users(MgrAgentConfName,UsmUsers) ->
EngineID = ct:get_config({MgrAgentConfName, engine_id}, ?ENGINE_ID),
takedown_usm_users(UsmUsers,EngineID),
@@ -495,23 +222,9 @@ unregister_usm_users(MgrAgentConfName,UsmUsers) ->
ct_config:update_config(MgrAgentConfName, NewSnmpVals),
ok.
-%%% @spec load_mibs(Mibs) -> ok | {error, Reason}
-%%%
-%%% Mibs = [MibName]
-%%% MibName = string()
-%%% Reason = term()
-%%%
-%%% @doc Load the mibs into the agent 'snmp_master_agent'.
load_mibs(Mibs) ->
snmpa:load_mibs(snmp_master_agent, Mibs).
-%%% @spec unload_mibs(Mibs) -> ok | {error, Reason}
-%%%
-%%% Mibs = [MibName]
-%%% MibName = string()
-%%% Reason = term()
-%%%
-%%% @doc Unload the mibs from the agent 'snmp_master_agent'.
unload_mibs(Mibs) ->
snmpa:unload_mibs(snmp_master_agent, Mibs).
diff --git a/lib/common_test/src/ct_ssh.erl b/lib/common_test/src/ct_ssh.erl
index 6ab3bf036c..79ab122452 100644
--- a/lib/common_test/src/ct_ssh.erl
+++ b/lib/common_test/src/ct_ssh.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2009-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2009-2018. 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.
@@ -18,44 +18,6 @@
%% %CopyrightEnd%
%%
-%%% @doc SSH/SFTP client module.
-%%%
-%%% <p>ct_ssh uses the OTP ssh application and more detailed information
-%%% about e.g. functions, types and options can be found in the
-%%% documentation for this application.</p>
-%%%
-%%% <p>The <code>Server</code> argument in the SFTP functions should
-%%% only be used for SFTP sessions that have been started on existing
-%%% SSH connections (i.e. when the original connection type is
-%%% <code>ssh</code>). Whenever the connection type is
-%%% <code>sftp</code>, use the SSH connection reference only.</p>
-%%%
-%%% <p>The following options are valid for specifying an SSH/SFTP
-%%% connection (i.e. may be used as config elements):</p>
-%%%
-%%% <pre>
-%%%
-%%% [{ConnType, Addr},
-%%% {port, Port},
-%%% {user, UserName}
-%%% {password, Pwd}
-%%% {user_dir, String}
-%%% {public_key_alg, PubKeyAlg}
-%%% {connect_timeout, Timeout}
-%%% {key_cb, KeyCallbackMod}]
-%%% </pre>
-%%%
-%%% <p><code>ConnType = ssh | sftp</code>.</p>
-%%% <p>Please see ssh(3) for other types.</p>
-%%%
-%%% <p>All timeout parameters in ct_ssh functions are values in
-%%% milliseconds.</p>
-%%%
-%%% @type connection() = handle() | ct:target_name()
-%%% @type handle() = ct_gen_conn:handle(). Handle for a specific
-%%% SSH/SFTP connection.
-%%% @type ssh_sftp_return() = term(). A return value from an ssh_sftp function.
-
-module(ct_ssh).
%% SSH Functions
@@ -68,7 +30,8 @@
send_and_receive/3, send_and_receive/4, send_and_receive/5,
send_and_receive/6,
exec/2, exec/3, exec/4,
- subsystem/3, subsystem/4]).
+ subsystem/3, subsystem/4,
+ shell/2, shell/3]).
%% STFP Functions
-export([sftp_connect/1,
@@ -94,72 +57,24 @@
-record(state, {ssh_ref, conn_type, target}).
+-type handle() :: pid().
%%%-----------------------------------------------------------------
%%%------------------------ SSH COMMANDS ---------------------------
-%%%-----------------------------------------------------------------
-%%% @spec connect(KeyOrName) -> {ok,Handle} | {error,Reason}
-%%% @equiv connect(KeyOrName,host,[])
connect(KeyOrName) ->
connect(KeyOrName, host).
-%%%-----------------------------------------------------------------
-%%% @spec connect(KeyOrName,ConnType) -> {ok,Handle} | {error,Reason}
-%%% @equiv connect(KeyOrName,ConnType,[])
connect(KeyOrName, ConnType) when is_atom(ConnType) ->
connect(KeyOrName, ConnType, []);
-%%%-----------------------------------------------------------------
-%%% @spec connect(KeyOrName,ExtraOpts) -> {ok,Handle} | {error,Reason}
-%%% @equiv connect(KeyOrName,host,ExtraOpts)
connect(KeyOrName, ExtraOpts) when is_list(ExtraOpts) ->
connect(KeyOrName, host, ExtraOpts).
-%%%-----------------------------------------------------------------
-%%% @spec connect(KeyOrName,ConnType,ExtraOpts) ->
-%%% {ok,Handle} | {error,Reason}
-%%% KeyOrName = Key | Name
-%%% Key = atom()
-%%% Name = ct:target_name()
-%%% ConnType = ssh | sftp | host
-%%% ExtraOpts = ssh_connect_options()
-%%% Handle = handle()
-%%% Reason = term()
-%%%
-%%% @doc Open an SSH or SFTP connection using the information
-%%% associated with <code>KeyOrName</code>.
-%%%
-%%% <p>If <code>Name</code> (an alias name for <code>Key</code>),
-%%% is used to identify the connection, this name may
-%%% be used as connection reference for subsequent calls.
-%%% It's only possible to have one open connection at a time
-%%% associated with <code>Name</code>. If <code>Key</code> is
-%%% used, the returned handle must be used for subsequent calls
-%%% (multiple connections may be opened using the config
-%%% data specified by <code>Key</code>). See <c>ct:require/2</c>
-%%% for how to create a new <c>Name</c></p>
-%%%
-%%% <p><code>ConnType</code> will always override the type
-%%% specified in the address tuple in the configuration data (and
-%%% in <code>ExtraOpts</code>). So it is possible to for example
-%%% open an sftp connection directly using data originally
-%%% specifying an ssh connection. The value <code>host</code>
-%%% means the connection type specified by the host option
-%%% (either in the configuration data or in <code>ExtraOpts</code>)
-%%% will be used.</p>
-%%%
-%%% <p><code>ExtraOpts</code> (optional) are extra SSH options
-%%% to be added to the config data for <code>KeyOrName</code>.
-%%% The extra options will override any existing options with the
-%%% same key in the config data. For details on valid SSH
-%%% options, see the documentation for the OTP ssh application.</p>
-%%%
-%%% @see ct:require/2
connect(KeyOrName, ConnType, ExtraOpts) ->
case ct:get_config(KeyOrName) of
undefined ->
- log(heading(connect,KeyOrName), "Failed: ~p\n",
+ log(heading(connect,KeyOrName), "Failed: ~tp\n",
[{not_available,KeyOrName}]),
{error,{not_available,KeyOrName}};
SSHData ->
@@ -212,30 +127,24 @@ connect(KeyOrName, ConnType, ExtraOpts) ->
end,
case {Addr,proplists:get_value(port, AllOpts1)} of
{undefined,_} ->
- log(heading(connect,KeyOrName), "Failed: ~p\n",
+ log(heading(connect,KeyOrName), "Failed: ~tp\n",
[{not_available,{KeyOrName,ConnType1}}]),
{error,{not_available,{KeyOrName,ConnType1}}};
{_,undefined} ->
try_log(heading(connect,KeyOrName),
- "Opening ~w connection to ~p:22\n",
+ "Opening ~w connection to ~tp:22\n",
[ConnType1,Addr]),
ct_gen_conn:start(KeyOrName, {ConnType1,Addr,22},
AllOpts1, ?MODULE);
{_,Port} ->
try_log(heading(connect,KeyOrName),
- "Opening ~w connection to ~p:~w\n",
+ "Opening ~w connection to ~tp:~w\n",
[ConnType1,Addr,Port]),
ct_gen_conn:start(KeyOrName, {ConnType1,Addr,Port},
AllOpts1, ?MODULE)
end
end.
-%%%-----------------------------------------------------------------
-%%% @spec disconnect(SSH) -> ok | {error,Reason}
-%%% SSH = connection()
-%%% Reason = term()
-%%%
-%%% @doc Close an SSH/SFTP connection.
disconnect(SSH) ->
case get_handle(SSH) of
{ok,Pid} ->
@@ -250,682 +159,252 @@ disconnect(SSH) ->
Error
end.
-%%%-----------------------------------------------------------------
-%%% @spec session_open(SSH) -> {ok,ChannelId} | {error, Reason}
-%%% @equiv session_open(SSH,DefaultTimeout)
session_open(SSH) ->
call(SSH, {session_open,?DEFAULT_TIMEOUT}).
-%%%-----------------------------------------------------------------
-%%% @spec session_open(SSH,Timeout) -> {ok,ChannelId} | {error, Reason}
-%%% SSH = connection()
-%%% Timeout = integer()
-%%% ChannelId = integer()
-%%% Reason = term()
-%%%
-%%% @doc Opens a channel for an SSH session.
session_open(SSH, Timeout) ->
call(SSH, {session_open,Timeout}).
-%%%-----------------------------------------------------------------
-%%% @spec session_close(SSH,ChannelId) -> ok | {error, Reason}
-%%% SSH = connection()
-%%% ChannelId = integer()
-%%% Reason = term()
-%%%
-%%% @doc Closes an SSH session channel.
session_close(SSH, ChannelId) ->
call(SSH, {session_close,ChannelId}).
-%%%-----------------------------------------------------------------
-%%% @spec exec(SSH,Command) -> {ok,Data} | {error,Reason}
-%%% @equiv exec(SSH,Command,DefaultTimeout)
exec(SSH, Command) ->
exec(SSH, undefined, Command, ?DEFAULT_TIMEOUT).
-%%%-----------------------------------------------------------------
-%%% @spec exec(SSH,Command,Timeout) -> {ok,Data} | {error,Reason}
-%%% SSH = connection()
-%%% Command = string()
-%%% Timeout = integer()
-%%% Data = list()
-%%% Reason = term()
-%%%
-%%% @doc Requests server to perform <code>Command</code>. A session
-%%% channel is opened automatically for the request.
-%%% <code>Data</code> is received from the server as a result
-%%% of the command.
exec(SSH, Command, Timeout) when is_list(Command) ->
exec(SSH, undefined, Command, Timeout);
-%%%-----------------------------------------------------------------
-%%% @spec exec(SSH,ChannelId,Command) -> {ok,Data} | {error,Reason}
-%%% @equiv exec(SSH,ChannelId,Command,DefaultTimeout)
exec(SSH, ChannelId, Command) when is_integer(ChannelId) ->
exec(SSH, ChannelId, Command, ?DEFAULT_TIMEOUT).
-%%%-----------------------------------------------------------------
-%%% @spec exec(SSH,ChannelId,Command,Timeout) -> {ok,Data} | {error,Reason}
-%%% SSH = connection()
-%%% ChannelId = integer()
-%%% Command = string()
-%%% Timeout = integer()
-%%% Data = list()
-%%% Reason = term()
-%%%
-%%% @doc Requests server to perform <code>Command</code>. A previously
-%%% opened session channel is used for the request.
-%%% <code>Data</code> is received from the server as a result
-%%% of the command.
exec(SSH, ChannelId, Command, Timeout) ->
call(SSH, {exec,ChannelId,Command,Timeout}).
-%%%-----------------------------------------------------------------
-%%% @spec receive_response(SSH,ChannelId) -> {ok,Data} | {error,Reason}
-%%% @equiv receive_response(SSH,ChannelId,close)
receive_response(SSH, ChannelId) ->
receive_response(SSH, ChannelId, close, ?DEFAULT_TIMEOUT).
-%%%-----------------------------------------------------------------
-%%% @spec receive_response(SSH,ChannelId,End) -> {ok,Data} | {error,Reason}
-%%% @equiv receive_response(SSH,ChannelId,End,DefaultTimeout)
receive_response(SSH, ChannelId, End) when is_function(End) ->
receive_response(SSH, ChannelId, End, ?DEFAULT_TIMEOUT);
-%%%-----------------------------------------------------------------
-%%% @spec receive_response(SSH,ChannelId,Timeout) -> {ok,Data} | {error,Reason}
-%%% @equiv receive_response(SSH,ChannelId,close,Timeout)
receive_response(SSH, ChannelId, Timeout) when is_integer(Timeout) ->
receive_response(SSH, ChannelId, close, Timeout).
-%%%-----------------------------------------------------------------
-%%% @spec receive_response(SSH,ChannelId,End,Timeout) ->
-%%% {ok,Data} | {timeout,Data} | {error,Reason}
-%%% SSH = connection()
-%%% ChannelId = integer()
-%%% End = Fun | close | timeout
-%%% Timeout = integer()
-%%% Data = list()
-%%% Reason = term()
-%%%
-%%% @doc Receives expected data from server on the specified
-%%% session channel.
-%%%
-%%% <p>If <code>End == close</code>, data is returned
-%%% to the caller when the channel is closed by the
-%%% server. If a timeout occurs before this happens,
-%%% the function returns <code>{timeout,Data}</code>
-%%% (where <code>Data</code> is the data received so far).
-%%% If <code>End == timeout</code>, a timeout is expected
-%%% and <code>{ok,Data}</code> is returned both in the case
-%%% of a timeout and when the channel is closed. If
-%%% <code>End</code> is a fun, this fun will be
-%%% called with one argument - the data value in a received
-%%% <code>ssh_cm</code> message (see ssh_connection(3)). The
-%%% fun should return <code>true</code> to end the receiving
-%%% operation (and have the so far collected data returned), or
-%%% <code>false</code> to wait for more data from the server.
-%%% (Note that even if a fun is supplied, the function returns
-%%% immediately if the server closes the channel).</p>
receive_response(SSH, ChannelId, End, Timeout) ->
call(SSH, {receive_response,ChannelId,End,Timeout}).
-%%%-----------------------------------------------------------------
-%%% @spec send(SSH,ChannelId,Data) -> ok | {error,Reason}
-%%% @equiv send(SSH,ChannelId,0,Data,DefaultTimeout)
send(SSH, ChannelId, Data) ->
send(SSH, ChannelId, 0, Data, ?DEFAULT_TIMEOUT).
-%%%-----------------------------------------------------------------
-%%% @spec send(SSH,ChannelId,Data,Timeout) -> ok | {error,Reason}
-%%% @equiv send(SSH,ChannelId,0,Data,Timeout)
send(SSH, ChannelId, Data, Timeout) when is_integer(Timeout) ->
send(SSH, ChannelId, 0, Data, Timeout);
-%%%-----------------------------------------------------------------
-%%% @spec send(SSH,ChannelId,Type,Data) -> ok | {error,Reason}
-%%% @equiv send(SSH,ChannelId,Type,Data,DefaultTimeout)
send(SSH, ChannelId, Type, Data) when is_integer(Type) ->
send(SSH, ChannelId, Type, Data, ?DEFAULT_TIMEOUT).
-%%%-----------------------------------------------------------------
-%%% @spec send(SSH,ChannelId,Type,Data,Timeout) -> ok | {error,Reason}
-%%% SSH = connection()
-%%% ChannelId = integer()
-%%% Type = integer()
-%%% Data = list()
-%%% Timeout = integer()
-%%% Reason = term()
-%%%
-%%% @doc Send data to server on specified session channel.
send(SSH, ChannelId, Type, Data, Timeout) ->
call(SSH, {send,ChannelId,Type,Data,Timeout}).
-%%%-----------------------------------------------------------------
-%%% @spec send_and_receive(SSH,ChannelId,Data) ->
-%%% {ok,Data} | {error,Reason}
-%%% @equiv send_and_receive(SSH,ChannelId,Data,close)
send_and_receive(SSH, ChannelId, Data) ->
send_and_receive(SSH, ChannelId, 0, Data, close, ?DEFAULT_TIMEOUT).
-%%%-----------------------------------------------------------------
-%%% @spec send_and_receive(SSH,ChannelId,Data,End) ->
-%%% {ok,Data} | {error,Reason}
-%%% @equiv send_and_receive(SSH,ChannelId,0,Data,End,DefaultTimeout)
send_and_receive(SSH, ChannelId, Data, End) when is_function(End) ->
send_and_receive(SSH, ChannelId, 0, Data, End, ?DEFAULT_TIMEOUT);
-%%%-----------------------------------------------------------------
-%%% @spec send_and_receive(SSH,ChannelId,Data,Timeout) ->
-%%% {ok,Data} | {error,Reason}
-%%% @equiv send_and_receive(SSH,ChannelId,0,Data,close,Timeout)
send_and_receive(SSH, ChannelId, Data, Timeout) when is_integer(Timeout) ->
send_and_receive(SSH, ChannelId, 0, Data, close, Timeout);
-%%%-----------------------------------------------------------------
-%%% @spec send_and_receive(SSH,ChannelId,Type,Data) ->
-%%% {ok,Data} | {error,Reason}
-%%% @equiv send_and_receive(SSH,ChannelId,Type,Data,close,DefaultTimeout)
send_and_receive(SSH, ChannelId, Type, Data) when is_integer(Type) ->
send_and_receive(SSH, ChannelId, Type, Data, close, ?DEFAULT_TIMEOUT).
-%%%-----------------------------------------------------------------
-%%% @spec send_and_receive(SSH,ChannelId,Data,End,Timeout) ->
-%%% {ok,Data} | {error,Reason}
-%%% @equiv send_and_receive(SSH,ChannelId,0,Data,End,Timeout)
send_and_receive(SSH, ChannelId, Data, End, Timeout) when is_integer(Timeout) ->
send_and_receive(SSH, ChannelId, 0, Data, End, Timeout);
-%%%-----------------------------------------------------------------
-%%% @spec send_and_receive(SSH,ChannelId,Type,Data,Timeout) ->
-%%% {ok,Data} | {error,Reason}
-%%% @equiv send_and_receive(SSH,ChannelId,Type,Data,close,Timeout)
send_and_receive(SSH, ChannelId, Type, Data, Timeout) when is_integer(Type) ->
send_and_receive(SSH, ChannelId, Type, Data, close, Timeout);
-%%%-----------------------------------------------------------------
-%%% @spec send_and_receive(SSH,ChannelId,Type,Data,End) ->
-%%% {ok,Data} | {error,Reason}
-%%% @equiv send_and_receive(SSH,ChannelId,Type,Data,End,DefaultTimeout)
send_and_receive(SSH, ChannelId, Type, Data, End) when is_function(End) ->
send_and_receive(SSH, ChannelId, Type, Data, End, ?DEFAULT_TIMEOUT).
-%%%-----------------------------------------------------------------
-%%% @spec send_and_receive(SSH,ChannelId,Type,Data,End,Timeout) ->
-%%% {ok,Data} | {error,Reason}
-%%% SSH = connection()
-%%% ChannelId = integer()
-%%% Type = integer()
-%%% Data = list()
-%%% End = Fun | close | timeout
-%%% Timeout = integer()
-%%% Reason = term()
-%%%
-%%% @doc Send data to server on specified session channel and wait
-%%% to receive the server response.
-%%%
-%%% <p>See <code>receive_response/4</code> for details on the
-%%% <code>End</code> argument.</p>
send_and_receive(SSH, ChannelId, Type, Data, End, Timeout) ->
call(SSH, {send_and_receive,ChannelId,Type,Data,End,Timeout}).
-%%%-----------------------------------------------------------------
-%%% @spec subsystem(SSH,ChannelId,Subsystem) -> Status | {error,Reason}
-%%% @equiv subsystem(SSH,ChannelId,Subsystem,DefaultTimeout)
subsystem(SSH, ChannelId, Subsystem) ->
subsystem(SSH, ChannelId, Subsystem, ?DEFAULT_TIMEOUT).
-%%%-----------------------------------------------------------------
-%%% @spec subsystem(SSH,ChannelId,Subsystem,Timeout) ->
-%%% Status | {error,Reason}
-%%% SSH = connection()
-%%% ChannelId = integer()
-%%% Subsystem = string()
-%%% Timeout = integer()
-%%% Status = success | failure
-%%% Reason = term()
-%%%
-%%% @doc Sends a request to execute a predefined subsystem.
subsystem(SSH, ChannelId, Subsystem, Timeout) ->
call(SSH, {subsystem,ChannelId,Subsystem,Timeout}).
+-spec shell(SSH, ChannelId) -> Result when
+ SSH :: handle() | ct:target_name(),
+ ChannelId :: ssh:ssh_channel_id(),
+ Result :: ok | {error,term()}.
+shell(SSH, ChannelId) ->
+ shell(SSH, ChannelId, ?DEFAULT_TIMEOUT).
+
+-spec shell(SSH, ChannelId, Timeout) -> Result when
+ SSH :: handle() | ct:target_name(),
+ ChannelId :: ssh:ssh_channel_id(),
+ Timeout :: timeout(),
+ Result :: ok | {error,term()}.
+shell(SSH, ChannelId, Timeout) ->
+ call(SSH, {shell,ChannelId,Timeout}).
+
+
%%%-----------------------------------------------------------------
%%%------------------------ SFTP COMMANDS --------------------------
-%%%-----------------------------------------------------------------
-%%% @spec sftp_connect(SSH) -> {ok,Server} | {error,Reason}
-%%% SSH = connection()
-%%% Server = pid()
-%%% Reason = term()
-%%% @doc Starts an SFTP session on an already existing SSH connection.
-%%% <code>Server</code> identifies the new session and must be
-%%% specified whenever SFTP requests are to be sent.
sftp_connect(SSH) ->
call(SSH, sftp_connect).
-%%%-----------------------------------------------------------------
-%%% @spec read_file(SSH, File) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
read_file(SSH, File) ->
call(SSH, {read_file,sftp,File}).
-%%%-----------------------------------------------------------------
-%%% @spec read_file(SSH, Server, File) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
+
read_file(SSH, Server, File) ->
call(SSH, {read_file,Server,File}).
-%%%-----------------------------------------------------------------
-%%% @spec write_file(SSH, File, Iolist) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
write_file(SSH, File, Iolist) ->
call(SSH, {write_file,sftp,File,Iolist}).
-%%%-----------------------------------------------------------------
-%%% @spec write_file(SSH, Server, File, Iolist) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
+
write_file(SSH, Server, File, Iolist) ->
call(SSH, {write_file,Server,File,Iolist}).
-%%%-----------------------------------------------------------------
-%%% @spec list_dir(SSH, Path) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
list_dir(SSH, Path) ->
call(SSH, {list_dir,sftp,Path}).
-%%%-----------------------------------------------------------------
-%%% @spec list_dir(SSH, Server, Path) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
+
list_dir(SSH, Server, Path) ->
call(SSH, {list_dir,Server,Path}).
-%%%-----------------------------------------------------------------
-%%% @spec open(SSH, File, Mode) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
open(SSH, File, Mode) ->
call(SSH, {open,sftp,File,Mode}).
-%%%-----------------------------------------------------------------
-%%% @spec open(SSH, Server, File, Mode) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
+
open(SSH, Server, File, Mode) ->
call(SSH, {open,Server,File,Mode}).
-%%%-----------------------------------------------------------------
-%%% @spec opendir(SSH, Path) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
opendir(SSH, Path) ->
call(SSH, {opendir,sftp,Path}).
-%%%-----------------------------------------------------------------
-%%% @spec opendir(SSH, Server, Path) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
+
opendir(SSH, Server, Path) ->
call(SSH, {opendir,Server,Path}).
-%%%-----------------------------------------------------------------
-%%% @spec close(SSH, Handle) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
close(SSH, Handle) ->
call(SSH, {close,sftp,Handle}).
-%%%-----------------------------------------------------------------
-%%% @spec close(SSH, Server, Handle) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
+
close(SSH, Server, Handle) ->
call(SSH, {close,Server,Handle}).
-%%%-----------------------------------------------------------------
-%%% @spec read(SSH, Handle, Len) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
read(SSH, Handle, Len) ->
call(SSH, {read,sftp,Handle,Len}).
-%%%-----------------------------------------------------------------
-%%% @spec read(SSH, Server, Handle, Len) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
+
read(SSH, Server, Handle, Len) ->
call(SSH, {read,Server,Handle,Len}).
-%%%-----------------------------------------------------------------
-%%% @spec pread(SSH, Handle, Position, Length) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
pread(SSH, Handle, Position, Length) ->
call(SSH, {pread,sftp,Handle,Position,Length}).
-%%%-----------------------------------------------------------------
-%%% @spec pread(SSH, Server, Handle, Position, Length) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
+
pread(SSH, Server, Handle, Position, Length) ->
call(SSH, {pread,Server,Handle,Position,Length}).
-%%%-----------------------------------------------------------------
-%%% @spec aread(SSH, Handle, Len) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
aread(SSH, Handle, Len) ->
call(SSH, {aread,sftp,Handle,Len}).
-%%%-----------------------------------------------------------------
-%%% @spec aread(SSH, Server, Handle, Len) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
+
aread(SSH, Server, Handle, Len) ->
call(SSH, {aread,Server,Handle,Len}).
-%%%-----------------------------------------------------------------
-%%% @spec apread(SSH, Handle, Position, Length) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
apread(SSH, Handle, Position, Length) ->
call(SSH, {apread,sftp,Handle,Position,Length}).
-%%%-----------------------------------------------------------------
-%%% @spec apread(SSH, Server, Handle, Position, Length) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
+
apread(SSH, Server, Handle, Position, Length) ->
call(SSH, {apread,Server,Handle,Position,Length}).
-%%%-----------------------------------------------------------------
-%%% @spec write(SSH, Handle, Data) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
write(SSH, Handle, Data) ->
call(SSH, {write,sftp,Handle,Data}).
-%%%-----------------------------------------------------------------
-%%% @spec write(SSH, Server, Handle, Data) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
+
write(SSH, Server, Handle, Data) ->
call(SSH, {write,Server,Handle,Data}).
-%%%-----------------------------------------------------------------
-%%% @spec pwrite(SSH, Handle, Position, Data) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
pwrite(SSH, Handle, Position, Data) ->
call(SSH, {pwrite,sftp,Handle,Position,Data}).
-%%%-----------------------------------------------------------------
-%%% @spec pwrite(SSH, Server, Handle, Position, Data) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
+
pwrite(SSH, Server, Handle, Position, Data) ->
call(SSH, {pwrite,Server,Handle,Position,Data}).
-%%%-----------------------------------------------------------------
-%%% @spec awrite(SSH, Handle, Data) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
awrite(SSH, Handle, Data) ->
call(SSH, {awrite,sftp,Handle, Data}).
-%%%-----------------------------------------------------------------
-%%% @spec awrite(SSH, Server, Handle, Data) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
+
awrite(SSH, Server, Handle, Data) ->
call(SSH, {awrite,Server,Handle, Data}).
-%%%-----------------------------------------------------------------
-%%% @spec apwrite(SSH, Handle, Position, Data) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
apwrite(SSH, Handle, Position, Data) ->
call(SSH, {apwrite,sftp,Handle,Position,Data}).
-%%%-----------------------------------------------------------------
-%%% @spec apwrite(SSH, Server, Handle, Position, Data) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
+
apwrite(SSH, Server, Handle, Position, Data) ->
call(SSH, {apwrite,Server,Handle,Position,Data}).
-%%%-----------------------------------------------------------------
-%%% @spec position(SSH, Handle, Location) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
position(SSH, Handle, Location) ->
call(SSH, {position,sftp,Handle,Location}).
-%%%-----------------------------------------------------------------
-%%% @spec position(SSH, Server, Handle, Location) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
+
position(SSH, Server, Handle, Location) ->
call(SSH, {position,Server,Handle,Location}).
-%%%-----------------------------------------------------------------
-%%% @spec read_file_info(SSH, Name) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
read_file_info(SSH, Name) ->
call(SSH, {read_file_info,sftp,Name}).
-%%%-----------------------------------------------------------------
-%%% @spec read_file_info(SSH, Server, Name) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
+
read_file_info(SSH, Server, Name) ->
call(SSH, {read_file_info,Server,Name}).
-%%%-----------------------------------------------------------------
-%%% @spec get_file_info(SSH, Handle) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
get_file_info(SSH, Handle) ->
call(SSH, {get_file_info,sftp,Handle}).
-%%%-----------------------------------------------------------------
-%%% @spec get_file_info(SSH, Server, Handle) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
+
get_file_info(SSH, Server, Handle) ->
call(SSH, {get_file_info,Server,Handle}).
-%%%-----------------------------------------------------------------
-%%% @spec read_link_info(SSH, Name) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
read_link_info(SSH, Name) ->
call(SSH, {read_link_info,sftp,Name}).
-%%%-----------------------------------------------------------------
-%%% @spec read_link_info(SSH, Server, Name) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
+
read_link_info(SSH, Server, Name) ->
call(SSH, {read_link_info,Server,Name}).
-%%%-----------------------------------------------------------------
-%%% @spec write_file_info(SSH, Name, Info) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
write_file_info(SSH, Name, Info) ->
call(SSH, {write_file_info,sftp,Name,Info}).
-%%%-----------------------------------------------------------------
-%%% @spec write_file_info(SSH, Server, Name, Info) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
+
write_file_info(SSH, Server, Name, Info) ->
call(SSH, {write_file_info,Server,Name,Info}).
-%%%-----------------------------------------------------------------
-%%% @spec read_link(SSH, Name) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
read_link(SSH, Name) ->
call(SSH, {read_link,sftp,Name}).
-%%%-----------------------------------------------------------------
-%%% @spec read_link(SSH, Server, Name) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
+
read_link(SSH, Server, Name) ->
call(SSH, {read_link,Server,Name}).
-%%%-----------------------------------------------------------------
-%%% @spec make_symlink(SSH, Name, Target) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
make_symlink(SSH, Name, Target) ->
call(SSH, {make_symlink,sftp,Name,Target}).
-%%%-----------------------------------------------------------------
-%%% @spec make_symlink(SSH, Server, Name, Target) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
+
make_symlink(SSH, Server, Name, Target) ->
call(SSH, {make_symlink,Server,Name,Target}).
-%%%-----------------------------------------------------------------
-%%% @spec rename(SSH, OldName, NewName) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
rename(SSH, OldName, NewName) ->
call(SSH, {rename,sftp,OldName,NewName}).
-%%%-----------------------------------------------------------------
-%%% @spec rename(SSH, Server, OldName, NewName) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
+
rename(SSH, Server, OldName, NewName) ->
call(SSH, {rename,Server,OldName,NewName}).
-%%%-----------------------------------------------------------------
-%%% @spec delete(SSH, Name) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
+
delete(SSH, Name) ->
call(SSH, {delete,sftp,Name}).
-%%%-----------------------------------------------------------------
-%%% @spec delete(SSH, Server, Name) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
+
delete(SSH, Server, Name) ->
call(SSH, {delete,Server,Name}).
-%%%-----------------------------------------------------------------
-%%% @spec make_dir(SSH, Name) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
make_dir(SSH, Name) ->
call(SSH, {make_dir,sftp,Name}).
-%%%-----------------------------------------------------------------
-%%% @spec make_dir(SSH, Server, Name) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
+
make_dir(SSH, Server, Name) ->
call(SSH, {make_dir,Server,Name}).
-%%%-----------------------------------------------------------------
-%%% @spec del_dir(SSH, Name) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
del_dir(SSH, Name) ->
call(SSH, {del_dir,sftp,Name}).
-%%%-----------------------------------------------------------------
-%%% @spec del_dir(SSH, Server, Name) -> Result
-%%% SSH = connection()
-%%% Result = ssh_sftp_return() | {error,Reason}
-%%% Reason = term()
-%%% @doc For info and other types, see ssh_sftp(3).
+
del_dir(SSH, Server, Name) ->
call(SSH, {del_dir,Server,Name}).
@@ -934,7 +413,6 @@ del_dir(SSH, Server, Name) ->
%%% Callback functions
%%%=================================================================
-%% @hidden
init(KeyOrName, {ConnType,Addr,Port}, AllOpts) ->
User = proplists:get_value(user, AllOpts),
Password = case proplists:get_value(password, AllOpts) of
@@ -977,13 +455,13 @@ init(KeyOrName, {ConnType,Addr,Port}, AllOpts) ->
SSHRef = element(2, Ok),
try_log(heading(init,KeyOrName),
"Opened ~w connection:\n"
- "Host: ~p (~p)\nUser: ~p\nPassword: ~p\n",
- [ConnType,Addr,Port,User,lists:duplicate(length(Password),$*)]),
+ "Host: ~tp (~p)\nUser: ~tp\nPassword: ~p\n",
+ [ConnType,Addr,Port,User,
+ lists:duplicate(string:length(Password),$*)]),
{ok,SSHRef,#state{ssh_ref=SSHRef, conn_type=ConnType,
target=KeyOrName}}
end.
-%% @hidden
handle_msg(sftp_connect, State) ->
#state{ssh_ref=SSHRef, target=Target} = State,
try_log(heading(sftp_connect,Target), "SSH Ref: ~p", [SSHRef]),
@@ -1015,11 +493,11 @@ handle_msg({exec,Chn,Command,TO}, State) ->
end,
case Chn1 of
{error,_} = ChnError ->
- log(heading(exec,Target), "Opening channel failed: ~p", [ChnError]),
+ log(heading(exec,Target), "Opening channel failed: ~tp", [ChnError]),
{ChnError,State};
_ ->
try_log(heading(exec,Target),
- "SSH Ref: ~p, Chn: ~p, Command: ~p, Timeout: ~p",
+ "SSH Ref: ~p, Chn: ~p, Command: ~tp, Timeout: ~p",
[SSHRef,Chn1,Command,TO]),
case ssh_connection:exec(SSHRef, Chn1, Command, TO) of
success ->
@@ -1042,7 +520,7 @@ handle_msg({send,Chn,Type,Data,TO}, State) ->
#state{ssh_ref=SSHRef, target=Target} = State,
try_log(heading(send,Target),
"SSH Ref: ~p, Chn: ~p, Type: ~p, Timeout: ~p~n"
- "Data: ~p", [SSHRef,Chn,Type,TO,Data]),
+ "Data: ~tp", [SSHRef,Chn,Type,TO,Data]),
Result = ssh_connection:send(SSHRef, Chn, Type, Data, TO),
{Result,State};
@@ -1050,7 +528,7 @@ handle_msg({send_and_receive,Chn,Type,Data,End,TO}, State) ->
#state{ssh_ref=SSHRef, target=Target} = State,
try_log(heading(send_and_receive,Target),
"SSH Ref: ~p, Chn: ~p, Type: ~p, Timeout: ~p~n"
- "Data: ~p", [SSHRef,Chn,Type,TO,Data]),
+ "Data: ~tp", [SSHRef,Chn,Type,TO,Data]),
case ssh_connection:send(SSHRef, Chn, Type, Data, TO) of
ok ->
Result = do_recv_response(SSHRef, Chn, [], End, TO),
@@ -1062,172 +540,177 @@ handle_msg({send_and_receive,Chn,Type,Data,End,TO}, State) ->
handle_msg({subsystem,Chn,Subsystem,TO}, State) ->
#state{ssh_ref=SSHRef, target=Target} = State,
try_log(heading(subsystem,Target),
- "SSH Ref: ~p, Chn: ~p, Subsys: ~p, Timeout: ~p",
+ "SSH Ref: ~p, Chn: ~p, Subsys: ~tp, Timeout: ~p",
[SSHRef,Chn,Subsystem,TO]),
Result = ssh_connection:subsystem(SSHRef, Chn, Subsystem, TO),
{Result,State};
+handle_msg({shell,Chn,TO}, State) ->
+ #state{ssh_ref=SSHRef, target=Target} = State,
+ try_log(heading(shell,Target),
+ "SSH Ref: ~p, Chn: ~p, Timeout: ~p",
+ [SSHRef,Chn,TO]),
+ Result = ssh_connection:shell(SSHRef, Chn),
+ {Result,State};
+
%% --- SFTP Commands ---
handle_msg({read_file,Srv,File}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ "SSH Ref: ~p, Server: ~p~nCmd: ~tp",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:read_file(ref(Srv,SSHRef), File),S};
handle_msg({write_file,Srv,File,Iolist}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ "SSH Ref: ~p, Server: ~p~nCmd: ~tp",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:write_file(ref(Srv,SSHRef), File, Iolist),S};
handle_msg({list_dir,Srv,Path}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ "SSH Ref: ~p, Server: ~p~nCmd: ~tp",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:list_dir(ref(Srv,SSHRef), Path),S};
handle_msg({open,Srv,File,Mode}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ "SSH Ref: ~p, Server: ~p~nCmd: ~tp",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:open(ref(Srv,SSHRef), File, Mode),S};
handle_msg({opendir,Srv,Path}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ "SSH Ref: ~p, Server: ~p~nCmd: ~tp",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:opendir(ref(Srv,SSHRef), Path),S};
handle_msg({close,Srv,Handle}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ "SSH Ref: ~p, Server: ~p~nCmd: ~tp",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:close(ref(Srv,SSHRef), Handle),S};
handle_msg({read,Srv,Handle,Len}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ "SSH Ref: ~p, Server: ~p~nCmd: ~tp",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:read(ref(Srv,SSHRef), Handle, Len),S};
handle_msg({pread,Srv,Handle,Position,Length}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ "SSH Ref: ~p, Server: ~p~nCmd: ~tp",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:pread(ref(Srv,SSHRef),Handle,Position,Length),S};
handle_msg({aread,Srv,Handle,Len}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ "SSH Ref: ~p, Server: ~p~nCmd: ~tp",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:aread(ref(Srv,SSHRef), Handle, Len),S};
handle_msg({apread,Srv,Handle,Position,Length}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ "SSH Ref: ~p, Server: ~p~nCmd: ~tp",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:apread(ref(Srv,SSHRef), Handle, Position, Length),S};
handle_msg({write,Srv,Handle,Data}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ "SSH Ref: ~p, Server: ~p~nCmd: ~tp",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:write(ref(Srv,SSHRef), Handle, Data),S};
handle_msg({pwrite,Srv,Handle,Position,Data}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ "SSH Ref: ~p, Server: ~p~nCmd: ~tp",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:pwrite(ref(Srv,SSHRef), Handle, Position, Data),S};
handle_msg({awrite,Srv,Handle,Data}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ "SSH Ref: ~p, Server: ~p~nCmd: ~tp",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:awrite(ref(Srv,SSHRef), Handle, Data),S};
handle_msg({apwrite,Srv,Handle,Position,Data}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ "SSH Ref: ~p, Server: ~p~nCmd: ~tp",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:apwrite(ref(Srv,SSHRef), Handle, Position, Data),S};
handle_msg({position,Srv,Handle,Location}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ "SSH Ref: ~p, Server: ~p~nCmd: ~tp",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:position(ref(Srv,SSHRef), Handle, Location),S};
handle_msg({read_file_info,Srv,Name}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ "SSH Ref: ~p, Server: ~p~nCmd: ~tp",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:read_file_info(ref(Srv,SSHRef), Name),S};
handle_msg({get_file_info,Srv,Handle}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ "SSH Ref: ~p, Server: ~p~nCmd: ~tp",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:get_file_info(ref(Srv,SSHRef), Handle),S};
handle_msg({read_link_info,Srv,Name}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ "SSH Ref: ~p, Server: ~p~nCmd: ~tp",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:read_link_info(ref(Srv,SSHRef), Name),S};
handle_msg({write_file_info,Srv,Name,Info}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ "SSH Ref: ~p, Server: ~p~nCmd: ~tp",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:write_file_info(ref(Srv,SSHRef), Name, Info),S};
handle_msg({read_link,Srv,Name}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ "SSH Ref: ~p, Server: ~p~nCmd: ~tp",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:read_link(ref(Srv,SSHRef), Name),S};
handle_msg({make_symlink,Srv,Name,Target}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ "SSH Ref: ~p, Server: ~p~nCmd: ~tp",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:make_symlink(ref(Srv,SSHRef), Name, Target),S};
handle_msg({rename,Srv,OldName,NewName}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ "SSH Ref: ~p, Server: ~p~nCmd: ~tp",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:rename(ref(Srv,SSHRef), OldName, NewName),S};
handle_msg({delete,Srv,Name}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ "SSH Ref: ~p, Server: ~p~nCmd: ~tp",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:delete(ref(Srv,SSHRef), Name),S};
handle_msg({make_dir,Srv,Name}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ "SSH Ref: ~p, Server: ~p~nCmd: ~tp",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:make_dir(ref(Srv,SSHRef), Name),S};
handle_msg({del_dir,Srv,Name}=Cmd, S=#state{ssh_ref=SSHRef}) ->
try_log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ "SSH Ref: ~p, Server: ~p~nCmd: ~tp",
[SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:del_dir(ref(Srv,SSHRef), Name),S}.
-%% @hidden
reconnect(_Addr,_State) ->
{error,no_reconnection_of_ssh}.
-%% @hidden
close(SSHRef) ->
disconnect(SSHRef).
-%% @hidden
terminate(SSHRef, State) ->
case State#state.conn_type of
ssh ->
@@ -1244,8 +727,6 @@ terminate(SSHRef, State) ->
%%%=================================================================
%%% Internal functions
-%%%-----------------------------------------------------------------
-%%%
do_recv_response(SSH, Chn, Data, End, Timeout) ->
receive
{ssh_cm, SSH, {open,Chn,RemoteChn,{session}}} ->
@@ -1259,7 +740,7 @@ do_recv_response(SSH, Chn, Data, End, Timeout) ->
{ssh_cm, SSH, {data,Chn,_,NewData}} ->
ssh_connection:adjust_window(SSH, Chn, size(NewData)),
- debug("RECVD~n~p", [binary_to_list(NewData)]),
+ debug("RECVD~n~tp", [binary_to_list(NewData)]),
DataAcc = Data ++ binary_to_list(NewData),
if is_function(End) ->
case End(DataAcc) of
@@ -1312,7 +793,7 @@ do_recv_response(SSH, Chn, Data, End, Timeout) ->
%% {ok,WCh};
Other ->
- debug("UNEXPECTED MESSAGE~n~p ~p~n~p", [SSH,Chn,Other]),
+ debug("UNEXPECTED MESSAGE~n~p ~p~n~tp", [SSH,Chn,Other]),
do_recv_response(SSH, Chn, Data, End, Timeout)
after Timeout ->
@@ -1324,8 +805,6 @@ do_recv_response(SSH, Chn, Data, End, Timeout) ->
end
end.
-%%%-----------------------------------------------------------------
-%%%
get_handle(SSH) when is_pid(SSH) ->
{ok,SSH};
get_handle(SSH) ->
@@ -1338,8 +817,6 @@ get_handle(SSH) ->
Error
end.
-%%%-----------------------------------------------------------------
-%%%
call(SSH, Msg) ->
call(SSH, Msg, infinity).
@@ -1351,29 +828,19 @@ call(SSH, Msg, Timeout) ->
Error
end.
-%%%-----------------------------------------------------------------
-%%%
ref(sftp, SSHRef) -> SSHRef;
ref(Server, _) -> Server.
-%%%-----------------------------------------------------------------
-%%%
mod(Cmd) ->
[Op,_Server|Args] = tuple_to_list(Cmd),
list_to_tuple([Op|Args]).
-%%%-----------------------------------------------------------------
-%%%
heading(Function, Ref) ->
- io_lib:format("ct_ssh:~w ~p",[Function,Ref]).
+ io_lib:format("ct_ssh:~tw ~tp",[Function,Ref]).
-%%%-----------------------------------------------------------------
-%%%
log(Heading, Str, Args) ->
ct_gen_conn:log(Heading, Str, Args).
-%%%-----------------------------------------------------------------
-%%%
try_log(Heading, Str, Args) ->
try_log(Heading, Str, Args, infinity).
@@ -1387,8 +854,6 @@ try_log(Heading, Str, Args, Timeout) ->
ok
end.
-%%%-----------------------------------------------------------------
-%%%
debug(Str) ->
debug(Str, []).
diff --git a/lib/common_test/src/ct_telnet.erl b/lib/common_test/src/ct_telnet.erl
index bff1112ab9..219f58dcf5 100644
--- a/lib/common_test/src/ct_telnet.erl
+++ b/lib/common_test/src/ct_telnet.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2003-2018. 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.
@@ -18,131 +18,6 @@
%% %CopyrightEnd%
%%
-%% @doc Common Test specific layer on top of telnet client `ct_telnet_client.erl'
-%%
-%% <p>Use this module to set up telnet connections, send commands and
-%% perform string matching on the result.
-%% See the `unix_telnet' manual page for information about how to use
-%% `ct_telnet', and configure connections, specifically for unix hosts.</p>
-%% <p>The following default values are defined in `ct_telnet':</p>
-%% <pre>
-%% Connection timeout = 10 sec (time to wait for connection)
-%% Command timeout = 10 sec (time to wait for a command to return)
-%% Max no of reconnection attempts = 3
-%% Reconnection interval = 5 sek (time to wait in between reconnection attempts)
-%% Keep alive = true (will send NOP to the server every 8 sec if connection is idle)
-%% Polling limit = 0 (max number of times to poll to get a remaining string terminated)
-%% Polling interval = 1 sec (sleep time between polls)</pre>
-%% <p>These parameters can be altered by the user with the following
-%% configuration term:</p>
-%% <pre>
-%% {telnet_settings, [{connect_timeout,Millisec},
-%% {command_timeout,Millisec},
-%% {reconnection_attempts,N},
-%% {reconnection_interval,Millisec},
-%% {keep_alive,Bool},
-%% {poll_limit,N},
-%% {poll_interval,Millisec},
-%% {tcp_nodelay,Bool}]}.</pre>
-%% <p><code>Millisec = integer(), N = integer()</code></p>
-%% <p>Enter the <code>telnet_settings</code> term in a configuration
-%% file included in the test and ct_telnet will retrieve the information
-%% automatically. Note that `keep_alive' may be specified per connection if
-%% required. See `unix_telnet' for details.</p>
-%%
-%% == Logging ==
-%%
-%% The default logging behaviour of `ct_telnet' is to print information
-%% to the test case HTML log about performed operations and commands
-%% and their corresponding results. What won't be printed to the HTML log
-%% are text strings sent from the telnet server that are not explicitly
-%% received by means of a `ct_telnet' function such as `expect/3'.
-%% `ct_telnet' may however be configured to use a special purpose event handler,
-%% implemented in `ct_conn_log_h', for logging <b>all</b> telnet traffic.
-%% To use this handler, you need to install a Common Test hook named
-%% `cth_conn_log'. Example (using the test suite info function):
-%%
-%% ```
-%% suite() ->
-%% [{ct_hooks, [{cth_conn_log, [{conn_mod(),hook_options()}]}]}].
-%% '''
-%%
-%% `conn_mod()' is the name of the common_test module implementing
-%% the connection protocol, i.e. `ct_telnet'.
-%%
-%% The `cth_conn_log' hook performs unformatted logging of telnet data to
-%% a separate text file. All telnet communication is captured and printed,
-%% including arbitrary data sent from the server. The link to this text file
-%% can be found on the top of the test case HTML log.
-%%
-%% By default, data for all telnet connections is logged in one common
-%% file (named `default'), which might get messy e.g. if multiple telnet
-%% sessions are running in parallel. It is therefore possible to create a
-%% separate log file for each connection. To configure this, use the hook
-%% option `hosts' and list the names of the servers/connections that will be
-%% used in the suite. Note that the connections must be named for this to work
-%% (see the `open' function below).
-%%
-%% The hook option named `log_type' may be used to change the `cth_conn_log'
-%% behaviour. The default value of this option is `raw', which results in the
-%% behaviour described above. If the value is set to `html', all telnet
-%% communication is printed to the test case HTML log instead.
-%%
-%% All `cth_conn_log' hook options described above can also be specified in
-%% a configuration file with the configuration variable `ct_conn_log'. Example:
-%%
-%% ```
-%% {ct_conn_log, [{ct_telnet,[{log_type,raw},
-%% {hosts,[key_or_name()]}]}]}
-%% '''
-%%
-%% <b>Note</b> that hook options specified in a configuration file
-%% will overwrite any hardcoded hook options in the test suite!
-%%
-%% === Logging example ===
-%%
-%% The following `ct_hooks' statement will cause printing of telnet traffic
-%% to separate logs for the connections named `server1' and `server2'.
-%% Traffic for any other connections will be logged in the default telnet log.
-%%
-%% ```
-%% suite() ->
-%% [{ct_hooks,
-%% [{cth_conn_log, [{ct_telnet,[{hosts,[server1,server2]}]}]}]}].
-%%'''
-%%
-%% As previously explained, the above specification could also be provided
-%% by means of an entry like this in a configuration file:
-%%
-%% ```
-%% {ct_conn_log, [{ct_telnet,[{hosts,[server1,server2]}]}]}.
-%% '''
-%%
-%% in which case the `ct_hooks' statement in the test suite may simply look
-%% like this:
-%%
-%% ```
-%% suite() ->
-%% [{ct_hooks, [{cth_conn_log, []}]}].
-%% '''
-%%
-%% @end
-
-%% @type connection_type() = telnet | ts1 | ts2
-
-%% @type connection() = handle() |
-%% {ct:target_name(),connection_type()} | ct:target_name()
-
-%% @type handle() = ct_gen_conn:handle(). Handle for a
-%% specific telnet connection.
-
-%% @type prompt_regexp() = string(). A regular expression which
-%% matches all possible prompts for a specific type of target. The
-%% regexp must not have any groups i.e. when matching, re:run/3 shall
-%% return a list with one single element.
-%%
-%% @see unix_telnet
-
-module(ct_telnet).
-export([open/1, open/2, open/3, open/4, close/1]).
@@ -186,20 +61,9 @@
reconn_int=?RECONN_TIMEOUT,
tcp_nodelay=false}).
-%%%-----------------------------------------------------------------
-%%% @spec open(Name) -> {ok,Handle} | {error,Reason}
-%%% @equiv open(Name,telnet)
open(Name) ->
open(Name,telnet).
-%%%-----------------------------------------------------------------
-%%% @spec open(Name,ConnType) -> {ok,Handle} | {error,Reason}
-%%% Name = target_name()
-%%% ConnType = ct_telnet:connection_type()
-%%% Handle = ct_telnet:handle()
-%%% Reason = term()
-%%%
-%%% @doc Open a telnet connection to the specified target host.
open(Name,ConnType) ->
case ct_util:get_key_from_name(Name) of
{ok, unix} -> % unix host
@@ -210,48 +74,13 @@ open(Name,ConnType) ->
Error
end.
-%%%-----------------------------------------------------------------
-%%% @spec open(KeyOrName,ConnType,TargetMod) ->
-%%% {ok,Handle} | {error,Reason}
-%%% @equiv open(KeyOrName,ConnType,TargetMod,[])
open(KeyOrName,ConnType,TargetMod) ->
open(KeyOrName,ConnType,TargetMod,KeyOrName).
-%%%-----------------------------------------------------------------
-%%% @spec open(KeyOrName,ConnType,TargetMod,Extra) ->
-%%% {ok,Handle} | {error,Reason}
-%%% KeyOrName = Key | Name
-%%% Key = atom()
-%%% Name = ct:target_name()
-%%% ConnType = connection_type()
-%%% TargetMod = atom()
-%%% Extra = term()
-%%% Handle = handle()
-%%% Reason = term()
-%%%
-%%% @doc Open a telnet connection to the specified target host.
-%%%
-%%% <p>The target data must exist in a configuration file. The connection
-%%% may be associated with either <code>Name</code> and/or the returned
-%%% <code>Handle</code>. To allocate a name for the target,
-%%% use <code>ct:require/2</code> in a test case, or use a
-%%% <code>require</code> statement in the suite info function
-%%% (<code>suite/0</code>), or in a test case info function.
-%%% If you want the connection to be associated with <code>Handle</code> only
-%%% (in case you need to open multiple connections to a host for example),
-%%% simply use <code>Key</code>, the configuration variable name, to
-%%% specify the target. Note that a connection that has no associated target
-%%% name can only be closed with the handle value.</p>
-%%%
-%%% <p><code>TargetMod</code> is a module which exports the functions
-%%% <code>connect(Ip,Port,KeepAlive,Extra)</code> and <code>get_prompt_regexp()</code>
-%%% for the given <code>TargetType</code> (e.g. <code>unix_telnet</code>).</p>
-%%%
-%%% @see ct:require/2
open(KeyOrName,ConnType,TargetMod,Extra) ->
case ct:get_config({KeyOrName,ConnType}) of
undefined ->
- log(undefined,open,"Failed: ~p",[{not_available,KeyOrName}]),
+ log(undefined,open,"Failed: ~tp",[{not_available,KeyOrName}]),
{error,{not_available,KeyOrName,ConnType}};
Addr ->
Addr1 =
@@ -273,7 +102,7 @@ open(KeyOrName,ConnType,TargetMod,Extra) ->
end;
Bool -> Bool
end,
- log(undefined,open,"Connecting to ~p(~p)",
+ log(undefined,open,"Connecting to ~tp(~tp)",
[KeyOrName,Addr1]),
Reconnect =
case ct:get_config({telnet_settings,reconnection_attempts}) of
@@ -287,17 +116,6 @@ open(KeyOrName,ConnType,TargetMod,Extra) ->
{old,true}])
end.
-%%%-----------------------------------------------------------------
-%%% @spec close(Connection) -> ok | {error,Reason}
-%%% Connection = ct_telnet:connection()
-%%% Reason = term()
-%%%
-%%% @doc Close the telnet connection and stop the process managing it.
-%%%
-%%% <p>A connection may be associated with a target name and/or a handle.
-%%% If <code>Connection</code> has no associated target name, it may only
-%%% be closed with the handle value (see the <code>open/4</code>
-%%% function).</p>
close(Connection) ->
case get_handle(Connection) of
{ok,Pid} ->
@@ -315,30 +133,10 @@ close(Connection) ->
%%%=================================================================
%%% Test suite interface
%%%-----------------------------------------------------------------
-%%% @spec cmd(Connection,Cmd) -> {ok,Data} | {error,Reason}
-%%% @equiv cmd(Connection,Cmd,[])
+
cmd(Connection,Cmd) ->
cmd(Connection,Cmd,[]).
-%%%-----------------------------------------------------------------
-%%% @spec cmd(Connection,Cmd,Opts) -> {ok,Data} | {error,Reason}
-%%% Connection = ct_telnet:connection()
-%%% Cmd = string()
-%%% Opts = [Opt]
-%%% Opt = {timeout,timeout()} | {newline,boolean()}
-%%% Data = [string()]
-%%% Reason = term()
-%%% @doc Send a command via telnet and wait for prompt.
-%%%
-%%% <p>This function will by default add a newline to the end of the
-%%% given command. If this is not desired, the option
-%%% `{newline,false}' can be used. This is necessary, for example,
-%%% when sending telnet command sequences (prefixed with the
-%%% Interprete As Command, IAC, character).</p>
-%%%
-%%% <p>The option `timeout' specifies how long the client shall wait for
-%%% prompt. If the time expires, the function returns
-%%% `{error,timeout}'. See the module description for information
-%%% about the default value for the command timeout.</p>
+
cmd(Connection,Cmd,Opts) when is_list(Opts) ->
case check_cmd_opts(Opts) of
ok ->
@@ -363,42 +161,13 @@ check_cmd_opts([]) ->
check_cmd_opts(Opts) ->
check_send_opts(Opts).
-%%%-----------------------------------------------------------------
-%%% @spec cmdf(Connection,CmdFormat,Args) -> {ok,Data} | {error,Reason}
-%%% @equiv cmdf(Connection,CmdFormat,Args,[])
cmdf(Connection,CmdFormat,Args) ->
cmdf(Connection,CmdFormat,Args,[]).
-%%%-----------------------------------------------------------------
-%%% @spec cmdf(Connection,CmdFormat,Args,Opts) -> {ok,Data} | {error,Reason}
-%%% Connection = ct_telnet:connection()
-%%% CmdFormat = string()
-%%% Args = list()
-%%% Opts = [Opt]
-%%% Opt = {timeout,timeout()} | {newline,boolean()}
-%%% Data = [string()]
-%%% Reason = term()
-%%% @doc Send a telnet command and wait for prompt
-%%% (uses a format string and list of arguments to build the command).
-%%%
-%%% <p>See {@link cmd/3} further description.</p>
+
cmdf(Connection,CmdFormat,Args,Opts) when is_list(Args) ->
Cmd = lists:flatten(io_lib:format(CmdFormat,Args)),
cmd(Connection,Cmd,Opts).
-%%%-----------------------------------------------------------------
-%%% @spec get_data(Connection) -> {ok,Data} | {error,Reason}
-%%% Connection = ct_telnet:connection()
-%%% Data = [string()]
-%%% Reason = term()
-%%% @doc Get all data that has been received by the telnet client
-%%% since the last command was sent. Note that only newline terminated
-%%% strings are returned. If the last string received has not yet
-%%% been terminated, the connection may be polled automatically until
-%%% the string is complete. The polling feature is controlled
-%%% by the `poll_limit' and `poll_interval' config values and is
-%%% by default disabled (meaning the function will immediately
-%%% return all complete strings received and save a remaining
-%%% non-terminated string for a later `get_data' call).
get_data(Connection) ->
case get_handle(Connection) of
{ok,Pid} ->
@@ -407,29 +176,9 @@ get_data(Connection) ->
Error
end.
-%%%-----------------------------------------------------------------
-%%% @spec send(Connection,Cmd) -> ok | {error,Reason}
-%%% @equiv send(Connection,Cmd,[])
send(Connection,Cmd) ->
send(Connection,Cmd,[]).
-%%%-----------------------------------------------------------------
-%%% @spec send(Connection,Cmd,Opts) -> ok | {error,Reason}
-%%% Connection = ct_telnet:connection()
-%%% Cmd = string()
-%%% Opts = [Opt]
-%%% Opt = {newline,boolean()}
-%%% Reason = term()
-%%% @doc Send a telnet command and return immediately.
-%%%
-%%% This function will by default add a newline to the end of the
-%%% given command. If this is not desired, the option
-%%% `{newline,false}' can be used. This is necessary, for example,
-%%% when sending telnet command sequences (prefixed with the
-%%% Interprete As Command, IAC, character).
-%%%
-%%% <p>The resulting output from the command can be read with
-%%% <code>get_data/1</code> or <code>expect/2/3</code>.</p>
send(Connection,Cmd,Opts) ->
case check_send_opts(Opts) of
ok ->
@@ -445,158 +194,47 @@ send(Connection,Cmd,Opts) ->
check_send_opts([{newline,Bool}|Opts]) when is_boolean(Bool) ->
check_send_opts(Opts);
+check_send_opts([{newline,String}|Opts]) when is_list(String) ->
+ case lists:all(fun(I) when is_integer(I), I>=0, I=<127 -> true;
+ (_) -> false
+ end, String) of
+ true ->
+ check_send_opts(Opts);
+ false ->
+ {error,{invalid_option,{newline,String}}}
+ end;
check_send_opts([Invalid|_]) ->
{error,{invalid_option,Invalid}};
check_send_opts([]) ->
ok.
-
-%%%-----------------------------------------------------------------
-%%% @spec sendf(Connection,CmdFormat,Args) -> ok | {error,Reason}
-%%% @equiv sendf(Connection,CmdFormat,Args,[])
sendf(Connection,CmdFormat,Args) when is_list(Args) ->
sendf(Connection,CmdFormat,Args,[]).
-%%%-----------------------------------------------------------------
-%%% @spec sendf(Connection,CmdFormat,Args,Opts) -> ok | {error,Reason}
-%%% Connection = ct_telnet:connection()
-%%% CmdFormat = string()
-%%% Args = list()
-%%% Opts = [Opt]
-%%% Opt = {newline,boolean()}
-%%% Reason = term()
-%%% @doc Send a telnet command and return immediately (uses a format
-%%% string and a list of arguments to build the command).
sendf(Connection,CmdFormat,Args,Opts) when is_list(Args) ->
Cmd = lists:flatten(io_lib:format(CmdFormat,Args)),
send(Connection,Cmd,Opts).
-%%%-----------------------------------------------------------------
-%%% @spec expect(Connection,Patterns) -> term()
-%%% @equiv expect(Connections,Patterns,[])
expect(Connection,Patterns) ->
expect(Connection,Patterns,[]).
-%%%-----------------------------------------------------------------
-%%% @spec expect(Connection,Patterns,Opts) -> {ok,Match} |
-%%% {ok,MatchList,HaltReason} |
-%%% {error,Reason}
-%%% Connection = ct_telnet:connection()
-%%% Patterns = Pattern | [Pattern]
-%%% Pattern = string() | {Tag,string()} | prompt | {prompt,Prompt}
-%%% Prompt = string()
-%%% Tag = term()
-%%% Opts = [Opt]
-%%% Opt = {idle_timeout,IdleTimeout} | {total_timeout,TotalTimeout} |
-%%% repeat | {repeat,N} | sequence | {halt,HaltPatterns} |
-%%% ignore_prompt | no_prompt_check | wait_for_prompt |
-%%% {wait_for_prompt,Prompt}
-%%% IdleTimeout = infinity | integer()
-%%% TotalTimeout = infinity | integer()
-%%% N = integer()
-%%% HaltPatterns = Patterns
-%%% MatchList = [Match]
-%%% Match = RxMatch | {Tag,RxMatch} | {prompt,Prompt}
-%%% RxMatch = [string()]
-%%% HaltReason = done | Match
-%%% Reason = timeout | {prompt,Prompt}
-%%%
-%%% @doc Get data from telnet and wait for the expected pattern.
-%%%
-%%% <p><code>Pattern</code> can be a POSIX regular expression. The function
-%%% returns as soon as a pattern has been successfully matched (at least one,
-%%% in the case of multiple patterns).</p>
-%%%
-%%% <p><code>RxMatch</code> is a list of matched strings. It looks
-%%% like this: <code>[FullMatch, SubMatch1, SubMatch2, ...]</code>
-%%% where <code>FullMatch</code> is the string matched by the whole
-%%% regular expression and <code>SubMatchN</code> is the string that
-%%% matched subexpression no <code>N</code>. Subexpressions are
-%%% denoted with '(' ')' in the regular expression</p>
-%%%
-%%% <p>If a <code>Tag</code> is given, the returned <code>Match</code>
-%%% will also include the matched <code>Tag</code>. Else, only
-%%% <code>RxMatch</code> is returned.</p>
-%%%
-%%% <p>The <code>idle_timeout</code> option indicates that the function
-%%% shall return if the telnet client is idle (i.e. if no data is
-%%% received) for more than <code>IdleTimeout</code> milliseconds. Default
-%%% timeout is 10 seconds.</p>
-%%%
-%%% <p>The <code>total_timeout</code> option sets a time limit for
-%%% the complete expect operation. After <code>TotalTimeout</code>
-%%% milliseconds, <code>{error,timeout}</code> is returned. The default
-%%% value is <code>infinity</code> (i.e. no time limit).</p>
-%%%
-%%% <p>The function will return when a prompt is received, even if no
-%%% pattern has yet been matched. In this event,
-%%% <code>{error,{prompt,Prompt}}</code> is returned.
-%%% However, this behaviour may be modified with the
-%%% <code>ignore_prompt</code> or <code>no_prompt_check</code> option, which
-%%% tells <code>expect</code> to return only when a match is found or after a
-%%% timeout.</p>
-%%%
-%%% <p>If the <code>ignore_prompt</code> option is used,
-%%% <code>ct_telnet</code> will ignore any prompt found. This option
-%%% is useful if data sent by the server could include a pattern that
-%%% would match the prompt regexp (as returned by
-%%% <code>TargedMod:get_prompt_regexp/0</code>), but which should not
-%%% cause the function to return.</p>
-%%%
-%%% <p>If the <code>no_prompt_check</code> option is used,
-%%% <code>ct_telnet</code> will not search for a prompt at all. This
-%%% is useful if, for instance, the <code>Pattern</code> itself
-%%% matches the prompt.</p>
-%%%
-%%% <p>The <code>wait_for_prompt</code> option forces <code>ct_telnet</code>
-%%% to wait until the prompt string has been received before returning
-%%% (even if a pattern has already been matched). This is equal to calling:
-%%% <code>expect(Conn, Patterns++[{prompt,Prompt}], [sequence|Opts])</code>.
-%%% Note that <code>idle_timeout</code> and <code>total_timeout</code>
-%%% may abort the operation of waiting for prompt.</p>
-%%%
-%%% <p>The <code>repeat</code> option indicates that the pattern(s)
-%%% shall be matched multiple times. If <code>N</code> is given, the
-%%% pattern(s) will be matched <code>N</code> times, and the function
-%%% will return with <code>HaltReason = done</code>.</p>
-%%%
-%%% <p>The <code>sequence</code> option indicates that all patterns
-%%% shall be matched in a sequence. A match will not be concluded
-%%% untill all patterns are matched.</p>
-%%%
-%%% <p>Both <code>repeat</code> and <code>sequence</code> can be
-%%% interrupted by one or more <code>HaltPatterns</code>. When
-%%% <code>sequence</code> or <code>repeat</code> is used, there will
-%%% always be a <code>MatchList</code> returned, i.e. a list of
-%%% <code>Match</code> instead of only one <code>Match</code>. There
-%%% will also be a <code>HaltReason</code> returned.</p>
-%%%
-%%% <p><underline>Examples:</underline><br/>
-%%% <code>expect(Connection,[{abc,"ABC"},{xyz,"XYZ"}],</code>
-%%% <code>[sequence,{halt,[{nnn,"NNN"}]}]).</code><br/> will try to match
-%%% "ABC" first and then "XYZ", but if "NNN" appears the function will
-%%% return <code>{error,{nnn,["NNN"]}}</code>. If both "ABC" and "XYZ"
-%%% are matched, the function will return
-%%% <code>{ok,[AbcMatch,XyzMatch]}</code>.</p>
-%%%
-%%% <p><code>expect(Connection,[{abc,"ABC"},{xyz,"XYZ"}],</code>
-%%% <code>[{repeat,2},{halt,[{nnn,"NNN"}]}]).</code><br/> will try to match
-%%% "ABC" or "XYZ" twice. If "NNN" appears the function will return
-%%% with <code>HaltReason = {nnn,["NNN"]}</code>.</p>
-%%%
-%%% <p>The <code>repeat</code> and <code>sequence</code> options can be
-%%% combined in order to match a sequence multiple times.</p>
expect(Connection,Patterns,Opts) ->
case get_handle(Connection) of
- {ok,Pid} ->
- call(Pid,{expect,Patterns,Opts});
- Error ->
- Error
+ {ok,Pid} ->
+ case call(Pid,{expect,Patterns,Opts}) of
+ {error,Reason} when element(1,Reason)==bad_pattern ->
+ %% Faulty user input - should fail the test case
+ exit({Reason,{?MODULE,?FUNCTION_NAME,3}});
+ Other ->
+ Other
+ end;
+ Error ->
+ Error
end.
%%%=================================================================
%%% Callback functions
-%% @hidden
+
init(Name,{Ip,Port,Type},{TargetMod,KeepAlive,Extra}) ->
S0 = case ct:get_config(telnet_settings) of
undefined ->
@@ -672,15 +310,14 @@ set_telnet_defaults([{tcp_nodelay,NoDelay}|Ss],S) ->
set_telnet_defaults(Ss,S#state{tcp_nodelay=NoDelay});
set_telnet_defaults([Unknown|Ss],S) ->
force_log(S,error,
- "Bad element in telnet_settings: ~p",[Unknown]),
+ "Bad element in telnet_settings: ~tp",[Unknown]),
set_telnet_defaults(Ss,S);
set_telnet_defaults([],S) ->
S.
-%% @hidden
handle_msg({cmd,Cmd,Opts},State) ->
start_gen_log(heading(cmd,State#state.name)),
- log(State,cmd,"Cmd: ~p",[Cmd]),
+ log(State,cmd,"Cmd: ~tp",[Cmd]),
%% whatever is in the buffer from previous operations
%% will be ignored as we go ahead with this telnet cmd
@@ -715,7 +352,7 @@ handle_msg({cmd,Cmd,Opts},State) ->
case teln_cmd(State#state.teln_pid, Cmd, State#state.prx,
Newline, TO) of
{ok,Data,_PromptType,Rest} ->
- log(State,recv,"Return: ~p",[{ok,Data}]),
+ log(State,recv,"Return: ~tp",[{ok,Data}]),
{{ok,Data},Rest,true};
Error ->
Retry = {retry,{Error,
@@ -723,14 +360,14 @@ handle_msg({cmd,Cmd,Opts},State) ->
State#state.type},
State#state.teln_pid,
{cmd,Cmd,Opts}}},
- log(State,recv,"Return: ~p",[Error]),
+ log(State,recv,"Return: ~tp",[Error]),
{Retry,[],false}
end,
end_gen_log(),
{Return,State#state{buffer=NewBuffer,prompt=Prompt}};
handle_msg({send,Cmd,Opts},State) ->
start_gen_log(heading(send,State#state.name)),
- log(State,send,"Sending: ~p",[Cmd]),
+ log(State,send,"Sending: ~tp",[Cmd]),
debug_cont_gen_log("Throwing Buffer:",[]),
debug_log_lines(State#state.buffer),
@@ -762,12 +399,12 @@ handle_msg(get_data,State) ->
log(State,cmd,"Reading data...",[]),
{ok,Data,Buffer} = teln_get_all_data(State,State#state.buffer,[],[],
State#state.poll_limit),
- log(State,recv,"Return: ~p",[{ok,Data}]),
+ log(State,recv,"Return: ~tp",[{ok,Data}]),
end_gen_log(),
{{ok,Data},State#state{buffer=Buffer}};
handle_msg({expect,Pattern,Opts},State) ->
start_gen_log(heading(expect,State#state.name)),
- log(State,expect,"Expect: ~p\nOpts = ~p\n",[Pattern,Opts]),
+ log(State,expect,"Expect: ~tp\nOpts = ~tp\n",[Pattern,Opts]),
{Return,NewBuffer,Prompt} =
case teln_expect(State#state.name,
State#state.teln_pid,
@@ -779,15 +416,15 @@ handle_msg({expect,Pattern,Opts},State) ->
P = check_if_prompt_was_reached(Data,[]),
{{ok,Data},Rest,P};
{ok,Data,HaltReason,Rest} ->
- force_log(State,expect,"HaltReason: ~p",[HaltReason]),
+ force_log(State,expect,"HaltReason: ~tp",[HaltReason]),
P = check_if_prompt_was_reached(Data,HaltReason),
{{ok,Data,HaltReason},Rest,P};
{error,Reason,Rest} ->
- force_log(State,expect,"Expect failed\n~p",[{error,Reason}]),
+ force_log(State,expect,"Expect failed\n~tp",[{error,Reason}]),
P = check_if_prompt_was_reached([],Reason),
{{error,Reason},Rest,P};
{error,Reason} ->
- force_log(State,expect,"Expect failed\n~p",[{error,Reason}]),
+ force_log(State,expect,"Expect failed\n~tp",[{error,Reason}]),
P = check_if_prompt_was_reached([],Reason),
{{error,Reason},[],P}
end,
@@ -803,7 +440,6 @@ handle_msg({expect,Pattern,Opts},State) ->
{Return1,State#state{buffer=NewBuffer,prompt=Prompt}}.
-%% @hidden
reconnect({Ip,Port,_Type},State) ->
reconnect(Ip,Port,State#state.reconns,State).
reconnect(Ip,Port,N,State=#state{name=Name,
@@ -834,7 +470,6 @@ reconnect(Ip,Port,N,State=#state{name=Name,
end.
-%% @hidden
terminate(TelnPid,State) ->
Result = ct_telnet_client:close(TelnPid),
log(State,close,"Telnet connection for ~w closed.",[TelnPid]),
@@ -896,13 +531,12 @@ check_if_prompt_was_reached(_,_) ->
heading(Action,undefined) ->
io_lib:format("~w ~w",[?MODULE,Action]);
heading(Action,Name) ->
- io_lib:format("~w ~w for ~p",[?MODULE,Action,Name]).
+ io_lib:format("~w ~w for ~tp",[?MODULE,Action,Name]).
force_log(State,Action,String,Args) ->
log(State,Action,String,Args,true).
%%%-----------------------------------------------------------------
-%%% @hidden
log(State,Action,String,Args) when is_record(State, state) ->
log(State,Action,String,Args,false);
log(Name,Action,String,Args) when is_atom(Name) ->
@@ -911,7 +545,6 @@ log(TelnPid,Action,String,Args) when is_pid(TelnPid) ->
log(#state{teln_pid=TelnPid},Action,String,Args,false).
%%%-----------------------------------------------------------------
-%%% @hidden
log(undefined,String,Args) ->
log(#state{},undefined,String,Args,false);
log(Name,String,Args) when is_atom(Name) ->
@@ -920,7 +553,6 @@ log(TelnPid,String,Args) when is_pid(TelnPid) ->
log(#state{teln_pid=TelnPid},undefined,String,Args).
%%%-----------------------------------------------------------------
-%%% @hidden
log(#state{name=Name,teln_pid=TelnPid,host=Host,port=Port},
Action,String,Args,ForcePrint) ->
Name1 = if Name == undefined -> get({ct_telnet_pid2name,TelnPid});
@@ -971,7 +603,6 @@ log(#state{name=Name,teln_pid=TelnPid,host=Host,port=Port},
end.
%%%-----------------------------------------------------------------
-%%% @hidden
start_gen_log(Heading) ->
%% check if output is suppressed
case ct_util:is_silenced(telnet) of
@@ -980,7 +611,6 @@ start_gen_log(Heading) ->
end.
%%%-----------------------------------------------------------------
-%%% @hidden
end_gen_log() ->
%% check if output is suppressed
case ct_util:is_silenced(telnet) of
@@ -988,7 +618,6 @@ end_gen_log() ->
false -> ct_gen_conn:end_log()
end.
-%%% @hidden
%% Debug printouts.
debug_cont_gen_log(Str,Args) ->
Old = put(silent,true),
@@ -1038,8 +667,7 @@ teln_get_all_data(State=#state{teln_pid=Pid,prx=Prx},Data,Acc,LastLine,Polls) ->
found_prompt=false,
prompt_check=true}).
-%% @hidden
-%% @doc Externally the silent_teln_expect function shall only be used
+%% Externally the silent_teln_expect function shall only be used
%% by the TargetModule, i.e. the target specific module which
%% implements connect/2 and get_prompt_regexp/0.
silent_teln_expect(Name,Pid,Data,Pattern,Prx,Opts) ->
@@ -1057,64 +685,72 @@ silent_teln_expect(Name,Pid,Data,Pattern,Prx,Opts) ->
%% 2) Sequence: Several patterns are given, and they are matched in
%% the order they appear in the pattern list.
%% 3a) Repeat (single): 1) is repeated either N times or until a halt
-%% condition is fullfilled.
+%% condition is fulfilled.
%% 3b) Repeat (sequence): 2) is repeated either N times or until a
-%% halt condition is fullfilled.
+%% halt condition is fulfilled.
teln_expect(Name,Pid,Data,Pattern0,Prx,Opts) ->
- HaltPatterns =
+ HaltPatterns0 =
case get_ignore_prompt(Opts) of
true ->
get_haltpatterns(Opts);
false ->
[prompt | get_haltpatterns(Opts)]
end,
-
- PromptCheck = get_prompt_check(Opts),
-
- {WaitForPrompt,Pattern1,Opts1} = wait_for_prompt(Pattern0,Opts),
-
- Seq = get_seq(Opts1),
- Pattern2 = convert_pattern(Pattern1,Seq),
- {IdleTimeout,TotalTimeout} = get_timeouts(Opts1),
-
- EO = #eo{teln_pid=Pid,
- prx=Prx,
- idle_timeout=IdleTimeout,
- total_timeout=TotalTimeout,
- seq=Seq,
- haltpatterns=HaltPatterns,
- prompt_check=PromptCheck},
+ case convert_pattern(HaltPatterns0,false) of
+ {ok,HaltPatterns} ->
+ {WaitForPrompt,Pattern1,Opts1} = wait_for_prompt(Pattern0,Opts),
+ Seq = get_seq(Opts1),
+ case convert_pattern(Pattern1,Seq) of
+ {ok,Pattern2} ->
+ {IdleTimeout,TotalTimeout} = get_timeouts(Opts1),
+ PromptCheck = get_prompt_check(Opts1),
+
+ EO = #eo{teln_pid=Pid,
+ prx=Prx,
+ idle_timeout=IdleTimeout,
+ total_timeout=TotalTimeout,
+ seq=Seq,
+ haltpatterns=HaltPatterns,
+ prompt_check=PromptCheck},
- case get_repeat(Opts1) of
- false ->
- case teln_expect1(Name,Pid,Data,Pattern2,[],EO) of
- {ok,Matched,Rest} when WaitForPrompt ->
- case lists:reverse(Matched) of
- [{prompt,_},Matched1] ->
- {ok,Matched1,Rest};
- [{prompt,_}|Matched1] ->
- {ok,lists:reverse(Matched1),Rest}
- end;
- {ok,Matched,Rest} ->
- {ok,Matched,Rest};
- {halt,Why,Rest} ->
- {error,Why,Rest};
- {error,Reason} ->
- {error,Reason}
- end;
- N ->
- EO1 = EO#eo{repeat=N},
- repeat_expect(Name,Pid,Data,Pattern2,[],EO1)
+ case get_repeat(Opts1) of
+ false ->
+ case teln_expect1(Name,Pid,Data,Pattern2,[],EO) of
+ {ok,Matched,Rest} when WaitForPrompt ->
+ case lists:reverse(Matched) of
+ [{prompt,_},Matched1] ->
+ {ok,Matched1,Rest};
+ [{prompt,_}|Matched1] ->
+ {ok,lists:reverse(Matched1),Rest}
+ end;
+ {ok,Matched,Rest} ->
+ {ok,Matched,Rest};
+ {halt,Why,Rest} ->
+ {error,Why,Rest};
+ {error,Reason} ->
+ {error,Reason}
+ end;
+ N ->
+ EO1 = EO#eo{repeat=N},
+ repeat_expect(Name,Pid,Data,Pattern2,[],EO1)
+ end;
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
end.
-convert_pattern(Pattern,Seq)
- when is_list(Pattern) and not is_integer(hd(Pattern)) ->
- case Seq of
- true -> Pattern;
- false -> rm_dupl(Pattern,[])
- end;
+convert_pattern(Pattern0,Seq)
+ when Pattern0==[] orelse (is_list(Pattern0) and not is_integer(hd(Pattern0))) ->
+ Pattern =
+ case Seq of
+ true -> Pattern0;
+ false -> rm_dupl(Pattern0,[])
+ end,
+ compile_pattern(Pattern,[]);
convert_pattern(Pattern,_Seq) ->
- [Pattern].
+ compile_pattern([Pattern],[]).
rm_dupl([P|Ps],Acc) ->
case lists:member(P,Acc) of
@@ -1126,6 +762,25 @@ rm_dupl([P|Ps],Acc) ->
rm_dupl([],Acc) ->
lists:reverse(Acc).
+compile_pattern([prompt|Patterns],Acc) ->
+ compile_pattern(Patterns,[prompt|Acc]);
+compile_pattern([{prompt,_}=P|Patterns],Acc) ->
+ compile_pattern(Patterns,[P|Acc]);
+compile_pattern([{Tag,Pattern}|Patterns],Acc) ->
+ try re:compile(Pattern,[unicode]) of
+ {ok,MP} -> compile_pattern(Patterns,[{Tag,MP}|Acc]);
+ {error,Error} -> {error,{bad_pattern,{Tag,Pattern},Error}}
+ catch error:badarg -> {error,{bad_pattern,{Tag,Pattern}}}
+ end;
+compile_pattern([Pattern|Patterns],Acc) ->
+ try re:compile(Pattern,[unicode]) of
+ {ok,MP} -> compile_pattern(Patterns,[MP|Acc]);
+ {error,Error} -> {error,{bad_pattern,Pattern,Error}}
+ catch error:badarg -> {error,{bad_pattern,Pattern}}
+ end;
+compile_pattern([],Acc) ->
+ {ok,lists:reverse(Acc)}.
+
get_timeouts(Opts) ->
{case lists:keysearch(idle_timeout,1,Opts) of
{value,{_,T}} ->
@@ -1159,7 +814,7 @@ get_seq(Opts) ->
get_haltpatterns(Opts) ->
case lists:keysearch(halt,1,Opts) of
{value,{halt,HaltPatterns}} ->
- convert_pattern(HaltPatterns,false);
+ HaltPatterns;
false ->
[]
end.
@@ -1203,7 +858,7 @@ wait_for_prompt2(Prompt, Pattern, Opts) ->
{true,Pattern1,Opts1}.
%% Repeat either single or sequence. All match results are accumulated
-%% and returned when a halt condition is fulllfilled.
+%% and returned when a halt condition is fulfilled.
repeat_expect(_Name,_Pid,Rest,_Pattern,Acc,#eo{repeat=0}) ->
{ok,lists:reverse(Acc),done,Rest};
repeat_expect(Name,Pid,Data,Pattern,Acc,EO) ->
@@ -1294,7 +949,7 @@ get_data1(Pid) ->
%% one_expect: split data chunk at prompts
one_expect(Name,Pid,Data,Pattern,EO) when EO#eo.prompt_check==false ->
-% io:format("Raw Data ~p Pattern ~p EO ~p ",[Data,Pattern,EO]),
+% io:format("Raw Data ~tp Pattern ~tp EO ~tp ",[Data,Pattern,EO]),
one_expect1(Name,Pid,Data,Pattern,[],EO#eo{found_prompt=false});
one_expect(Name,Pid,Data,Pattern,EO) ->
case match_prompt(Data,EO#eo.prx) of
@@ -1342,7 +997,7 @@ one_expect1(Name,Pid,Data,Pattern,Rest,EO) ->
%% 2) Sequence.
%% First the whole data chunk is searched for a prompt (to avoid doing
%% a regexp match for the prompt at each line).
-%% If we are searching for anyting else, the datachunk is split into
+%% If we are searching for anything else, the datachunk is split into
%% lines and each line is matched against the first pattern in the list.
%% When a match is found, the match result is accumulated, and we keep
%% searching for the next pattern in the list.
@@ -1472,7 +1127,7 @@ match_line(Name,Pid,Line,[Pattern|Patterns],FoundPrompt,Term,EO,RetTag) ->
end;
match_line(Name,Pid,Line,[],FoundPrompt,Term,EO,match) ->
match_line(Name,Pid,Line,EO#eo.haltpatterns,FoundPrompt,Term,EO,halt);
-%% print any terminated line that can not be matched
+%% print any terminated line that cannot be matched
match_line(Name,Pid,Line,[],_FoundPrompt,true,_EO,halt) ->
log(name_or_pid(Name,Pid)," ~ts",[Line]),
nomatch;
@@ -1530,8 +1185,6 @@ add_tabs([],[$\n|Acc],LastLine) ->
add_tabs([],[],LastLine) ->
{[],lists:reverse(LastLine)}.
-
-%%% @hidden
teln_receive_until_prompt(Pid,Prx,Timeout) ->
Fun = fun() -> teln_receive_until_prompt(Pid,Prx,[],[]) end,
ct_gen_conn:do_within_time(Fun, Timeout).
@@ -1575,7 +1228,7 @@ split_lines([],Line,Lines) ->
match_prompt(Str,Prx) ->
match_prompt(Str,Prx,[]).
match_prompt(Str,Prx,Acc) ->
- case re:run(Str,Prx) of
+ case re:run(Str,Prx,[unicode]) of
nomatch ->
noprompt;
{match,[{Start,Len}]} ->
diff --git a/lib/common_test/src/ct_telnet_client.erl b/lib/common_test/src/ct_telnet_client.erl
index 5df7e279ac..007477c855 100644
--- a/lib/common_test/src/ct_telnet_client.erl
+++ b/lib/common_test/src/ct_telnet_client.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2003-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.
@@ -101,9 +101,11 @@ close(Pid) ->
end.
send_data(Pid, Data) ->
- send_data(Pid, Data, true).
+ send_data(Pid, Data, "\n").
send_data(Pid, Data, true) ->
- send_data(Pid, Data++"\n", false);
+ send_data(Pid, Data, "\n");
+send_data(Pid, Data, Newline) when is_list(Newline) ->
+ send_data(Pid, Data++Newline, false);
send_data(Pid, Data, false) ->
Pid ! {send_data, Data},
ok.
@@ -118,9 +120,10 @@ get_data(Pid) ->
%%%-----------------------------------------------------------------
%%% Internal functions
init(Parent, Server, Port, Timeout, KeepAlive, NoDelay, ConnName) ->
+ ct_util:mark_process(),
case gen_tcp:connect(Server, Port, [list,{packet,0},{nodelay,NoDelay}], Timeout) of
{ok,Sock} ->
- dbg("~p connected to: ~p (port: ~w, keep_alive: ~w)\n",
+ dbg("~tp connected to: ~tp (port: ~w, keep_alive: ~w)\n",
[ConnName,Server,Port,KeepAlive]),
send([?IAC,?DO,?SUPPRESS_GO_AHEAD], Sock, ConnName),
Parent ! {open,self()},
diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl
index 180786273d..2c18caf18f 100644
--- a/lib/common_test/src/ct_testspec.erl
+++ b/lib/common_test/src/ct_testspec.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2018. 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.
@@ -18,10 +18,6 @@
%% %CopyrightEnd%
%%
-%%% @doc Common Test Framework functions handling test specifications.
-%%%
-%%% <p>This module exports functions that are used within CT to
-%%% scan and parse test specifikations.</p>
-module(ct_testspec).
-export([prepare_tests/1, prepare_tests/2,
@@ -344,7 +340,7 @@ create_spec_tree([Spec|Specs],TS,JoinWithNext,Known) ->
create_spec_tree(Specs,TS,JoinWithNext,Known)};
{error,Reason} ->
ReasonStr =
- lists:flatten(io_lib:format("~s",
+ lists:flatten(io_lib:format("~ts",
[file:format_error(Reason)])),
throw({error,{SpecAbsName,ReasonStr}})
end
@@ -537,7 +533,7 @@ replace_names_in_elems([],Modified,_Defs) ->
replace_names_in_string(Term,Defs=[{Name,Replacement=[Ch|_]}|Ds])
when is_integer(Ch) ->
try re:replace(Term,[$'|atom_to_list(Name)]++"'",
- Replacement,[{return,list}]) of
+ Replacement,[{return,list},unicode]) of
Term -> % no match, proceed
replace_names_in_string(Term,Ds);
Term1 ->
@@ -569,7 +565,7 @@ replace_names_in_node1(NodeStr,Defs=[{Name,Replacement}|Ds]) ->
replace_names_in_node1(NodeStr,Ds);
true ->
case re:replace(NodeStr,atom_to_list(Name),
- ReplStr,[{return,list}]) of
+ ReplStr,[{return,list},unicode]) of
NodeStr -> % no match, proceed
replace_names_in_node1(NodeStr,Ds);
NodeStr1 ->
@@ -1101,7 +1097,7 @@ check_term(Term) when is_tuple(Term) ->
true ->
io:format("~nSuspicious term, "
"please check:~n"
- "~p~n", [Term]),
+ "~tp~n", [Term]),
invalid;
false ->
invalid
@@ -1425,7 +1421,12 @@ skip_groups1(Suite,Groups,Cmt,Suites0) ->
GrAndCases1 = GrAndCases0 ++ SkipGroups,
insert_in_order({Suite,GrAndCases1},Suites0,replace);
false ->
- insert_in_order({Suite,SkipGroups},Suites0,replace)
+ case Suites0 of
+ [{all,_}=All|Skips]->
+ [All|Skips++[{Suite,SkipGroups}]];
+ _ ->
+ insert_in_order({Suite,SkipGroups},Suites0,replace)
+ end
end.
skip_cases(Node,Dir,Suite,Cases,Cmt,Tests,false) when is_list(Cases) ->
diff --git a/lib/common_test/src/ct_util.erl b/lib/common_test/src/ct_util.erl
index 4d3a2ae7e3..9f489e9bfb 100644
--- a/lib/common_test/src/ct_util.erl
+++ b/lib/common_test/src/ct_util.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2003-2018. 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.
@@ -18,11 +18,11 @@
%% %CopyrightEnd%
%%
-%%% @doc Common Test Framework Utilities.
+%%% Common Test Framework Utilities.
%%%
-%%% <p>This is a support module for the Common Test Framework. It
+%%% This is a support module for the Common Test Framework. It
%%% implements the process ct_util_server which acts like a data
-%%% holder for suite, configuration and connection data.</p>
+%%% holder for suite, configuration and connection data.
%%%
-module(ct_util).
@@ -65,6 +65,9 @@
-export([warn_duplicates/1]).
+-export([mark_process/0, mark_process/1, is_marked/1, is_marked/2,
+ remaining_test_procs/0]).
+
-export([get_profile_data/0, get_profile_data/1,
get_profile_data/2, open_url/3]).
@@ -80,20 +83,20 @@
%%%-----------------------------------------------------------------
start() ->
start(normal, ".", ?default_verbosity).
-%%% @spec start(Mode) -> Pid | exit(Error)
+%%% -spec start(Mode) -> Pid | exit(Error)
%%% Mode = normal | interactive
%%% Pid = pid()
%%%
-%%% @doc Start start the ct_util_server process
+%%% Start start the ct_util_server process
%%% (tool-internal use only).
%%%
-%%% <p>This function is called from ct_run.erl. It starts and initiates
-%%% the <code>ct_util_server</code></p>
+%%% This function is called from ct_run.erl. It starts and initiates
+%%% the ct_util_server
%%%
-%%% <p>Returns the process identity of the
-%%% <code>ct_util_server</code>.</p>
+%%% Returns the process identity of the
+%%% ct_util_server.
%%%
-%%% @see ct
+%%% See ct.
start(LogDir) when is_list(LogDir) ->
start(normal, LogDir, ?default_verbosity);
start(Mode) ->
@@ -126,6 +129,7 @@ start(Mode, LogDir, Verbosity) ->
do_start(Parent, Mode, LogDir, Verbosity) ->
process_flag(trap_exit,true),
register(ct_util_server,self()),
+ mark_process(),
create_table(?conn_table,#conn.handle),
create_table(?board_table,2),
create_table(?suite_table,#suite_data.key),
@@ -188,7 +192,10 @@ do_start(Parent, Mode, LogDir, Verbosity) ->
ok
end,
- ct_default_gl:start_link(group_leader()),
+ case ct_default_gl:start_link(group_leader()) of
+ {ok, _} -> ok;
+ ignore -> ok
+ end,
{StartTime,TestLogDir} = ct_logs:init(Mode, Verbosity),
@@ -201,22 +208,14 @@ do_start(Parent, Mode, LogDir, Verbosity) ->
ok ->
Parent ! {self(),started};
{fail,CTHReason} ->
- ErrorInfo = if is_atom(CTHReason) ->
- io_lib:format("{~p,~p}",
- [CTHReason,
- erlang:get_stacktrace()]);
- true ->
- CTHReason
- end,
- ct_logs:tc_print('Suite Callback',ErrorInfo,[]),
+ ct_logs:tc_print('Suite Callback',CTHReason,[]),
self() ! {{stop,{self(),{user_error,CTHReason}}},
{Parent,make_ref()}}
catch
- _:CTHReason ->
+ _:CTHReason:StackTrace ->
ErrorInfo = if is_atom(CTHReason) ->
- io_lib:format("{~p,~p}",
- [CTHReason,
- erlang:get_stacktrace()]);
+ io_lib:format("{~tp,~tp}",
+ [CTHReason, StackTrace]);
true ->
CTHReason
end,
@@ -497,7 +496,7 @@ loop(Mode,TestData,StartDir) ->
?MAX_IMPORTANCE,
"CT Error Notification",
"Connection process died: "
- "Pid: ~w, Address: ~p, "
+ "Pid: ~w, Address: ~tp, "
"Callback: ~w\n"
"Reason: ~ts\n\n",
[Pid,A,CB,ErrorHtml]),
@@ -508,7 +507,7 @@ loop(Mode,TestData,StartDir) ->
_ ->
%% Let process crash in case of error, this shouldn't happen!
io:format("\n\nct_util_server got EXIT "
- "from ~w: ~p\n\n", [Pid,Reason]),
+ "from ~w: ~tp\n\n", [Pid,Reason]),
ok = file:set_cwd(StartDir),
exit(Reason)
end
@@ -524,19 +523,19 @@ get_key_from_name(Name)->
ct_config:get_key_from_name(Name).
%%%-----------------------------------------------------------------
-%%% @spec register_connection(TargetName,Address,Callback,Handle) ->
+%%% -spec register_connection(TargetName,Address,Callback,Handle) ->
%%% ok | {error,Reason}
%%% TargetName = ct:target_name()
%%% Address = term()
%%% Callback = atom()
%%% Handle = term
%%%
-%%% @doc Register a new connection (tool-internal use only).
+%%% Register a new connection (tool-internal use only).
%%%
-%%% <p>This function can be called when a new connection is
+%%% This function can be called when a new connection is
%%% established. The connection data is stored in the connection
%%% table, and ct_util will close all registered connections when the
-%%% test is finished by calling <code>Callback:close/1</code>.</p>
+%%% test is finished by calling Callback:close/1.
register_connection(TargetName,Address,Callback,Handle) ->
%% If TargetName is a registered alias for a config
%% variable, use it as reference for the connection,
@@ -557,28 +556,28 @@ register_connection(TargetName,Address,Callback,Handle) ->
ok.
%%%-----------------------------------------------------------------
-%%% @spec unregister_connection(Handle) -> ok
+%%% -spec unregister_connection(Handle) -> ok
%%% Handle = term
%%%
-%%% @doc Unregister a connection (tool-internal use only).
+%%% Unregister a connection (tool-internal use only).
%%%
-%%% <p>This function should be called when a registered connection is
+%%% This function should be called when a registered connection is
%%% closed. It removes the connection data from the connection
-%%% table.</p>
+%%% table.
unregister_connection(Handle) ->
ets:delete(?conn_table,Handle),
ok.
%%%-----------------------------------------------------------------
-%%% @spec does_connection_exist(TargetName,Address,Callback) ->
+%%% -spec does_connection_exist(TargetName,Address,Callback) ->
%%% {ok,Handle} | false
%%% TargetName = ct:target_name()
%%% Address = address
%%% Callback = atom()
%%% Handle = term()
%%%
-%%% @doc Check if a connection already exists.
+%%% Check if a connection already exists.
does_connection_exist(TargetName,Address,Callback) ->
case ct_config:get_key_from_name(TargetName) of
{ok,_Key} ->
@@ -598,7 +597,7 @@ does_connection_exist(TargetName,Address,Callback) ->
end.
%%%-----------------------------------------------------------------
-%%% @spec get_connection(TargetName,Callback) ->
+%%% -spec get_connection(TargetName,Callback) ->
%%% {ok,Connection} | {error,Reason}
%%% TargetName = ct:target_name()
%%% Callback = atom()
@@ -606,8 +605,8 @@ does_connection_exist(TargetName,Address,Callback) ->
%%% Handle = term()
%%% Address = term()
%%%
-%%% @doc Return the connection for <code>Callback</code> on the
-%%% given target (<code>TargetName</code>).
+%%% Return the connection for Callback on the
+%%% given target (TargetName).
get_connection(TargetName,Callback) ->
%% check that TargetName is a registered alias
case ct_config:get_key_from_name(TargetName) of
@@ -628,7 +627,7 @@ get_connection(TargetName,Callback) ->
end.
%%%-----------------------------------------------------------------
-%%% @spec get_connections(ConnPid) ->
+%%% -spec get_connections(ConnPid) ->
%%% {ok,Connections} | {error,Reason}
%%% Connections = [Connection]
%%% Connection = {TargetName,Handle,Callback,Address}
@@ -637,8 +636,8 @@ get_connection(TargetName,Callback) ->
%%% Callback = atom()
%%% Address = term()
%%%
-%%% @doc Get data for all connections associated with a particular
-%%% connection pid (see Callback:init/3).
+%%% Get data for all connections associated with a particular
+%%% connection pid (see Callback:init/3).
get_connections(ConnPid) ->
Conns = ets:tab2list(?conn_table),
lists:flatmap(fun(#conn{targetref=TargetName,
@@ -658,8 +657,7 @@ get_connections(ConnPid) ->
end, Conns).
%%%-----------------------------------------------------------------
-%%% @hidden
-%%% @equiv ct:get_target_name/1
+%%% Equivalent to ct:get_target_name/1
get_target_name(Handle) ->
case ets:select(?conn_table,[{#conn{handle=Handle,targetref='$1',_='_'},
[],
@@ -671,17 +669,14 @@ get_target_name(Handle) ->
end.
%%%-----------------------------------------------------------------
-%%% @spec close_connections() -> ok
+%%% -spec close_connections() -> ok
%%%
-%%% @doc Close all open connections.
+%%% Close all open connections.
close_connections() ->
close_connections(ets:tab2list(?conn_table)),
ok.
%%%-----------------------------------------------------------------
-%%% @spec
-%%%
-%%% @doc
override_silence_all_connections() ->
Protocols = [telnet,ftp,rpc,snmp,ssh],
override_silence_connections(Protocols),
@@ -742,12 +737,12 @@ reset_silent_connections() ->
%%%-----------------------------------------------------------------
-%%% @spec stop(Info) -> ok
+%%% -spec stop(Info) -> ok
%%%
-%%% @doc Stop the ct_util_server and close all existing connections
+%%% Stop the ct_util_server and close all existing connections
%%% (tool-internal use only).
%%%
-%%% @see ct
+%%% See ct.
stop(Info) ->
case whereis(ct_util_server) of
undefined ->
@@ -761,26 +756,25 @@ stop(Info) ->
end.
%%%-----------------------------------------------------------------
-%%% @spec update_last_run_index() -> ok
+%%% -spec update_last_run_index() -> ok
%%%
-%%% @doc Update <code>ct_run.&lt;timestamp&gt;/index.html</code>
+%%% Update ct_run.<timestamp>/index.html
%%% (tool-internal use only).
update_last_run_index() ->
call(update_last_run_index).
%%%-----------------------------------------------------------------
-%%% @spec get_mode() -> Mode
+%%% -spec get_mode() -> Mode
%%% Mode = normal | interactive
%%%
-%%% @doc Return the current mode of the ct_util_server
+%%% Return the current mode of the ct_util_server
%%% (tool-internal use only).
get_mode() ->
call(get_mode).
%%%-----------------------------------------------------------------
-%%% @hidden
-%%% @equiv ct:listenv/1
+%%% Equivalent to ct:listenv/1
listenv(Telnet) ->
case ct_telnet:send(Telnet,"listenv") of
ok ->
@@ -794,33 +788,32 @@ listenv(Telnet) ->
end.
%%%-----------------------------------------------------------------
-%%% @hidden
-%%% @equiv ct:parse_table/1
+%%% Equivalent to ct:parse_table/1
parse_table(Data) ->
{Heading, Rest} = get_headings(Data),
Lines = parse_row(Rest,[],size(Heading)),
{Heading,Lines}.
get_headings(["|" ++ Headings | Rest]) ->
- {remove_space(string:tokens(Headings, "|"),[]), Rest};
+ {remove_space(string:lexemes(Headings, "|"),[]), Rest};
get_headings([_ | Rest]) ->
get_headings(Rest);
get_headings([]) ->
{{},[]}.
parse_row(["|" ++ _ = Row | T], Rows, NumCols) when NumCols > 1 ->
- case string:tokens(Row, "|") of
+ case string:lexemes(Row, "|") of
Values when length(Values) =:= NumCols ->
parse_row(T,[remove_space(Values,[])|Rows], NumCols);
Values when length(Values) < NumCols ->
parse_row([Row ++"\n"++ hd(T) | tl(T)], Rows, NumCols)
end;
-parse_row(["|" ++ _ = Row | T], Rows, 1 = NumCols) ->
- case string:rchr(Row, $|) of
- 1 ->
+parse_row(["|" ++ X = Row | T], Rows, 1 = NumCols) ->
+ case string:find(X, [$|]) of
+ nomatch ->
parse_row([Row ++"\n"++hd(T) | tl(T)], Rows, NumCols);
_Else ->
- parse_row(T, [remove_space(string:tokens(Row,"|"),[])|Rows],
+ parse_row(T, [remove_space(string:lexemes(Row,"|"),[])|Rows],
NumCols)
end;
parse_row([_Skip | T], Rows, NumCols) ->
@@ -829,22 +822,16 @@ parse_row([], Rows, _NumCols) ->
lists:reverse(Rows).
remove_space([Str|Rest],Acc) ->
- remove_space(Rest,[string:strip(string:strip(Str),both,$')|Acc]);
+ remove_space(Rest,[string:trim(string:trim(Str,both,[$\s]),both,[$'])|Acc]);
remove_space([],Acc) ->
list_to_tuple(lists:reverse(Acc)).
%%%-----------------------------------------------------------------
-%%% @spec
-%%%
-%%% @doc
is_test_dir(Dir) ->
- lists:last(string:tokens(filename:basename(Dir), "_")) == "test".
+ lists:last(string:lexemes(filename:basename(Dir), "_")) == "test".
%%%-----------------------------------------------------------------
-%%% @spec
-%%%
-%%% @doc
get_testdir(Dir, all) ->
Abs = abs_name(Dir),
case is_test_dir(Abs) of
@@ -888,9 +875,6 @@ get_testdir(Dir, _) ->
get_testdir(Dir, all).
%%%-----------------------------------------------------------------
-%%% @spec
-%%%
-%%% @doc
get_attached(TCPid) ->
case dbg_iserver:safe_call({get_attpid,TCPid}) of
{ok,AttPid} when is_pid(AttPid) ->
@@ -900,9 +884,6 @@ get_attached(TCPid) ->
end.
%%%-----------------------------------------------------------------
-%%% @spec
-%%%
-%%% @doc
kill_attached(undefined,_AttPid) ->
ok;
kill_attached(_TCPid,undefined) ->
@@ -917,9 +898,6 @@ kill_attached(TCPid,AttPid) ->
%%%-----------------------------------------------------------------
-%%% @spec
-%%%
-%%% @doc
warn_duplicates(Suites) ->
Warn =
fun(Mod) ->
@@ -938,9 +916,67 @@ warn_duplicates(Suites) ->
ok.
%%%-----------------------------------------------------------------
-%%% @spec
-%%%
-%%% @doc
+mark_process() ->
+ mark_process(system).
+
+mark_process(Type) ->
+ put(ct_process_type, Type).
+
+is_marked(Pid) ->
+ is_marked(Pid, system).
+
+is_marked(Pid, Type) ->
+ case process_info(Pid, dictionary) of
+ {dictionary,List} ->
+ Type == proplists:get_value(ct_process_type, List);
+ undefined ->
+ false
+ end.
+
+remaining_test_procs() ->
+ Procs = processes(),
+ {SharedGL,OtherGLs,Procs2} =
+ lists:foldl(
+ fun(Pid, ProcTypes = {Shared,Other,Procs1}) ->
+ case is_marked(Pid, group_leader) of
+ true ->
+ if not is_pid(Shared) ->
+ case test_server_io:get_gl(true) of
+ Pid ->
+ {Pid,Other,
+ lists:delete(Pid,Procs1)};
+ _ ->
+ {Shared,[Pid|Other],Procs1}
+ end;
+ true -> % SharedGL already found
+ {Shared,[Pid|Other],Procs1}
+ end;
+ false ->
+ case is_marked(Pid) of
+ true ->
+ {Shared,Other,lists:delete(Pid,Procs1)};
+ false ->
+ ProcTypes
+ end
+ end
+ end, {undefined,[],Procs}, Procs),
+
+ AllGLs = [SharedGL | OtherGLs],
+ TestProcs =
+ lists:flatmap(fun(Pid) ->
+ case process_info(Pid, group_leader) of
+ {group_leader,GL} ->
+ case lists:member(GL, AllGLs) of
+ true -> [{Pid,GL}];
+ false -> []
+ end;
+ undefined ->
+ []
+ end
+ end, Procs2),
+ {TestProcs, SharedGL, OtherGLs}.
+
+%%%-----------------------------------------------------------------
get_profile_data() ->
get_profile_data(all).
@@ -984,12 +1020,12 @@ get_profile_data(Profile, Key, StartDir) ->
end,
case Result of
{error,enoent} when Profile /= default ->
- io:format(?def_gl, "~nERROR! Missing profile file ~p~n", [File]),
+ io:format(?def_gl, "~nERROR! Missing profile file ~tp~n", [File]),
undefined;
{error,enoent} when Profile == default ->
undefined;
{error,Reason} ->
- io:format(?def_gl,"~nERROR! Error in profile file ~p: ~p~n",
+ io:format(?def_gl,"~nERROR! Error in profile file ~tp: ~tp~n",
[WhichFile,Reason]),
undefined;
{ok,Data} ->
@@ -1000,7 +1036,7 @@ get_profile_data(Profile, Key, StartDir) ->
Data;
_ ->
io:format(?def_gl,
- "~nERROR! Invalid profile data in ~p~n",
+ "~nERROR! Invalid profile data in ~tp~n",
[WhichFile]),
[]
end,
@@ -1084,7 +1120,7 @@ open_url(iexplore, Args, URL) ->
_ = case win32reg:values(R) of
{ok, Paths} ->
Path = proplists:get_value(default, Paths),
- [Cmd | _] = string:tokens(Path, "%"),
+ [Cmd | _] = string:lexemes(Path, "%"),
Cmd1 = Cmd ++ " " ++ Args ++ " " ++ URL,
io:format(?def_gl, "~nOpening ~ts with command:~n ~ts~n", [URL,Cmd1]),
open_port({spawn,Cmd1}, []);
diff --git a/lib/common_test/src/ct_util.hrl b/lib/common_test/src/ct_util.hrl
index 039c8168ec..d5c93d05ba 100644
--- a/lib/common_test/src/ct_util.hrl
+++ b/lib/common_test/src/ct_util.hrl
@@ -62,6 +62,7 @@
merge_tests=true}).
-record(cover, {app=none,
+ local_only=false,
level=details,
excl_mods=[],
incl_mods=[],
diff --git a/lib/common_test/src/ct_webtool.erl b/lib/common_test/src/ct_webtool.erl
index 87af442fd3..32d4255217 100644
--- a/lib/common_test/src/ct_webtool.erl
+++ b/lib/common_test/src/ct_webtool.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2001-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2001-2018. 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.
@@ -120,10 +120,10 @@ debug_app(Mod) ->
out(_,{trace_ts,Pid,call,MFA={M,F,A},{W,_,_},TS},_,S)
when W==webtool;W==mod_esi->
- io:format("~w: (~p)~ncall ~s~n", [TS,Pid,ffunc(MFA)]),
+ io:format("~w: (~p)~ncall ~ts~n", [TS,Pid,ffunc(MFA)]),
[{M,F,length(A)}|S];
out(_,{trace_ts,Pid,return_from,MFA,R,TS},_,[MFA|S]) ->
- io:format("~w: (~p)~nreturned from ~s -> ~p~n", [TS,Pid,ffunc(MFA),R]),
+ io:format("~w: (~p)~nreturned from ~ts -> ~tp~n", [TS,Pid,ffunc(MFA),R]),
S;
out(_,_,_,_) ->
ok.
@@ -171,7 +171,7 @@ script_start([App,Browser]) ->
IExplore=filename:join(win32reg:expand(Val),"iexplore.exe"),
os:cmd("\"" ++ IExplore ++ "\" " ++ Url);
_ when OSType == win32 ->
- io:format("Starting ~w...\n",[Browser]),
+ io:format("Starting ~tw...\n",[Browser]),
os:cmd("\"" ++ atom_to_list(Browser) ++ "\" " ++ Url);
B when B==firefox; B==mozilla ->
io:format("Sending URL to ~w...",[Browser]),
@@ -194,7 +194,7 @@ script_start([App,Browser]) ->
os:cmd(BStr ++ " " ++ Url)
end;
_ ->
- io:format("Starting ~w...\n",[Browser]),
+ io:format("Starting ~tw...\n",[Browser]),
os:cmd(atom_to_list(Browser) ++ " " ++ Url)
end,
ok;
@@ -343,6 +343,7 @@ code_change(_,State,_)->
% Start the gen_server
%----------------------------------------------------------------------
init({Path,Config})->
+ ct_util:mark_process(),
case filelib:is_dir(Path) of
true ->
{ok, Table} = get_tool_files_data(),
@@ -379,7 +380,7 @@ print_url(ConfigData)->
Server=proplists:get_value(server_name,ConfigData,"undefined"),
Port=proplists:get_value(port,ConfigData,"undefined"),
{A,B,C,D}=proplists:get_value(bind_address,ConfigData,"undefined"),
- io:format("WebTool is available at http://~s:~w/~n",[Server,Port]),
+ io:format("WebTool is available at http://~ts:~w/~n",[Server,Port]),
io:format("Or http://~w.~w.~w.~w:~w/~n",[A,B,C,D,Port]).
@@ -770,7 +771,7 @@ fill_out(Nr)->
%Controls whether the user selected a tool to start
%----------------------------------------------------------------------
get_tools(Input)->
- case httpd:parse_query(Input) of
+ case uri_string:dissect_query(Input) of
[]->
no_tools;
Tools->
@@ -859,8 +860,8 @@ handle_app({Name,{start,{func,Start,Stop}}},Data,_Pid,Cmd)->
%%! Here the tool disappears from the webtool interface!!
io:format("\n=======ERROR (webtool, line ~w) =======\n"
"Could not start application \'~p\'\n\n"
- "~w:~w(~s) ->\n"
- "~p\n\n",
+ "~w:~tw(~ts) ->\n"
+ "~tp\n\n",
[?LINE,Name,M,F,format_args(A),Exit]),
ets:delete(Data,Name);
_OK->
@@ -883,16 +884,16 @@ handle_app({Name,{start,{child,ChildSpec}}},Data,Pid,Cmd)->
%%! Here the tool disappears from the webtool interface!!
io:format("\n=======ERROR (webtool, line ~w) =======\n"
"Could not start application \'~p\'\n\n"
- "supervisor:start_child(~p,~p) ->\n"
- "~p\n\n",
+ "supervisor:start_child(~p,~tp) ->\n"
+ "~tp\n\n",
[?LINE,Name,Pid,ChildSpec,{error,Reason}]),
ets:delete(Data,Name);
Error ->
%%! Here the tool disappears from the webtool interface!!
io:format("\n=======ERROR (webtool, line ~w) =======\n"
"Could not start application \'~p\'\n\n"
- "supervisor:start_child(~p,~p) ->\n"
- "~p\n\n",
+ "supervisor:start_child(~p,~tp) ->\n"
+ "~tp\n\n",
[?LINE,Name,Pid,ChildSpec,Error]),
ets:delete(Data,Name)
end;
@@ -924,7 +925,7 @@ handle_app({Name,{start,{app,Real_name}}},Data,_Pid,Cmd)->
io:format("\n=======ERROR (webtool, line ~w) =======\n"
"Could not start application \'~p\'\n\n"
"application:start(~p,~p) ->\n"
- "~p\n\n",
+ "~tp\n\n",
[?LINE,Name,Real_name,temporary,Error]),
ets:delete(Data,Name)
end;
@@ -940,7 +941,7 @@ handle_app({Name,Incorrect},Data,_Pid,Cmd)->
%%! Here the tool disappears from the webtool interface!!
io:format("\n=======ERROR (webtool, line ~w) =======\n"
"Could not ~w application \'~p\'\n\n"
- "Incorrect data: ~p\n\n",
+ "Incorrect data: ~tp\n\n",
[?LINE,Cmd,Name,Incorrect]),
ets:delete(Data,Name).
@@ -1202,12 +1203,12 @@ filter_tool_files(Dir,[File|Rest]) ->
%%%-----------------------------------------------------------------
%%% format functions
ffunc({M,F,A}) when is_list(A) ->
- io_lib:format("~w:~w(~s)\n",[M,F,format_args(A)]);
+ io_lib:format("~w:~tw(~ts)\n",[M,F,format_args(A)]);
ffunc({M,F,A}) when is_integer(A) ->
- io_lib:format("~w:~w/~w\n",[M,F,A]).
+ io_lib:format("~w:~tw/~w\n",[M,F,A]).
format_args([]) ->
"";
format_args(Args) ->
- Str = lists:append(["~p"|lists:duplicate(length(Args)-1,",~p")]),
+ Str = lists:append(["~tp"|lists:duplicate(length(Args)-1,",~tp")]),
io_lib:format(Str,Args).
diff --git a/lib/common_test/src/ct_webtool_sup.erl b/lib/common_test/src/ct_webtool_sup.erl
index c02ec69d04..04fbbf8745 100644
--- a/lib/common_test/src/ct_webtool_sup.erl
+++ b/lib/common_test/src/ct_webtool_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2001-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2001-2018. 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.
@@ -46,6 +46,7 @@ stop(Pid)->
%% {error, Reason}
%%----------------------------------------------------------------------
init(_StartArgs) ->
+ ct_util:mark_process(),
%%Child1 =
%%Child2 ={webcover_backend,{webcover_backend,start_link,[]},permanent,2000,worker,[webcover_backend]},
%%{ok,{{simple_one_for_one,5,10},[Child1]}}.
diff --git a/lib/common_test/src/cth_conn_log.erl b/lib/common_test/src/cth_conn_log.erl
index ef92532969..7b6f03311a 100644
--- a/lib/common_test/src/cth_conn_log.erl
+++ b/lib/common_test/src/cth_conn_log.erl
@@ -24,11 +24,11 @@
%%
%% suite() ->
%% [{ct_hooks, [{cth_conn_log,
-%% [{ct_netconfc:conn_mod(),ct_netconfc:hook_options()}]}]}].
+%% [{conn_mod(),hook_options()}]}]}].
%%
%% or specified in a configuration file:
%%
-%% {ct_conn_log,[{ct_netconfc:conn_mod(),ct_netconfc:hook_options()}]}.
+%% {ct_conn_log,[{conn_mod(),hook_options()}]}.
%%
%% The conn_mod() is the common test module implementing the protocol,
%% e.g. ct_netconfc, ct_telnet, etc. This module must log by calling
@@ -58,28 +58,17 @@
post_end_per_testcase/5]).
%%----------------------------------------------------------------------
-%% Exported types
-%%----------------------------------------------------------------------
--export_type([hook_options/0,
- log_type/0,
- conn_mod/0]).
-
-%%----------------------------------------------------------------------
%% Type declarations
%%----------------------------------------------------------------------
--type hook_options() :: [hook_option()].
-%% Options that can be given to `cth_conn_log' in the `ct_hook' statement.
--type hook_option() :: {log_type,log_type()} |
- {hosts,[ct_gen_conn:key_or_name()]}.
--type log_type() :: raw | pretty | html | silent.
--type conn_mod() :: ct_netconfc | ct_telnet.
+-type hook_options() :: ct:conn_log_options().
+-type log_type() :: ct:conn_log_type().
+-type conn_mod() :: ct:conn_log_mod().
%%----------------------------------------------------------------------
-spec init(Id, HookOpts) -> Result when
Id :: term(),
HookOpts :: hook_options(),
- Result :: {ok,[{conn_mod(),
- {log_type(),[ct_gen_conn:key_or_name()]}}]}.
+ Result :: {ok,[{conn_mod(),{log_type(),[ct:key_or_name()]}}]}.
init(_Id, HookOpts) ->
ConfOpts = ct:get_config(ct_conn_log,[]),
{ok,merge_log_info(ConfOpts,HookOpts)}.
@@ -127,7 +116,7 @@ pre_init_per_testcase(_Suite,TestCase,Config,CthState) ->
"<table borders=1>"
"<b>" ++ ConnModStr ++ " logs:</b>\n" ++
[io_lib:format(
- "<tr><td>~p</td><td><a href=\"~ts\">~ts</a>"
+ "<tr><td>~tp</td><td><a href=\"~ts\">~ts</a>"
"</td></tr>",
[S,ct_logs:uri(L),filename:basename(L)])
|| {S,L} <- Ls] ++
diff --git a/lib/common_test/src/cth_log_redirect.erl b/lib/common_test/src/cth_log_redirect.erl
index 1bc9b10d41..fe869a4373 100644
--- a/lib/common_test/src/cth_log_redirect.erl
+++ b/lib/common_test/src/cth_log_redirect.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2018. 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.
@@ -19,11 +19,9 @@
%%
-module(cth_log_redirect).
-%%% @doc Common Test Framework functions handling test specifications.
+%%% Common Test Framework functions handling test specifications.
%%%
-%%% <p>This module redirects sasl and error logger info to common test log.</p>
-%%% @end
-
+%%% This module redirects sasl and error logger info to common test log.
%% CTH Callbacks
-export([id/1, init/2,
@@ -33,17 +31,19 @@
pre_init_per_testcase/4, post_init_per_testcase/5,
pre_end_per_testcase/4, post_end_per_testcase/5]).
-%% Event handler Callbacks
--export([init/1,
- handle_event/2, handle_call/2, handle_info/2,
- terminate/1, terminate/2, code_change/3]).
+%% Logger handler and gen_server callbacks
+-export([log/2,
+ init/1,
+ handle_cast/2, handle_call/3,
+ terminate/1, terminate/2]).
%% Other
-export([handle_remote_events/1]).
-include("ct.hrl").
+-include("../../kernel/src/logger_internal.hrl").
--behaviour(gen_event).
+-behaviour(gen_server).
-record(eh_state, {log_func,
curr_suite,
@@ -56,7 +56,8 @@ id(_Opts) ->
?MODULE.
init(?MODULE, _Opts) ->
- error_logger:add_report_handler(?MODULE),
+ ct_util:mark_process(),
+ ok = start_log_handler(),
tc_log_async.
pre_init_per_suite(Suite, Config, State) ->
@@ -99,7 +100,7 @@ pre_end_per_testcase(_Suite, _TC, Config, State) ->
post_end_per_testcase(_Suite, _TC, _Config, Result, State) ->
%% Make sure that the event queue is flushed
%% before ending this test case.
- gen_event:call(error_logger, ?MODULE, flush, 300000),
+ gen_server:call(?MODULE, flush, 300000),
{Result, State}.
pre_end_per_group(_Suite, Group, Config, {tc_log, Group}) ->
@@ -113,164 +114,181 @@ post_end_per_group(_Suite, _Group, Config, Return, State) ->
set_curr_func({group,undefined}, Config),
{Return, State}.
-%% Copied and modified from sasl_report_tty_h.erl
-init(_Type) ->
- {ok, #eh_state{log_func = tc_log_async}}.
-
-handle_event({_Type,GL,_Msg}, #eh_state{handle_remote_events = false} = State)
- when node(GL) /= node() ->
- {ok, State};
-handle_event(Event, #eh_state{log_func = LogFunc} = State) ->
- case lists:keyfind(sasl, 1, application:which_applications()) of
- false ->
- sasl_not_started;
- _Else ->
- {ok, ErrLogType} = application:get_env(sasl, errlog_type),
- SReport = sasl_report:format_report(group_leader(), ErrLogType,
- tag_event(Event, local)),
- if is_list(SReport) ->
- SaslHeader = format_header(State),
- case LogFunc of
- tc_log ->
- ct_logs:tc_log(sasl, ?STD_IMPORTANCE,
- SaslHeader, SReport, [], []);
- tc_log_async ->
- ct_logs:tc_log_async(sasl, ?STD_IMPORTANCE,
- SaslHeader, SReport, [])
- end;
- true -> %% Report is an atom if no logging is to be done
- ignore
- end
- end,
- %% note that error_logger (unlike sasl) expects UTC time
- EReport = error_logger_tty_h:write_event(
- tag_event(Event, utc), io_lib),
- if is_list(EReport) ->
- ErrHeader = format_header(State),
- case LogFunc of
- tc_log ->
- ct_logs:tc_log(error_logger, ?STD_IMPORTANCE,
- ErrHeader, EReport, [], []);
- tc_log_async ->
- ct_logs:tc_log_async(error_logger, ?STD_IMPORTANCE,
- ErrHeader, EReport, [])
- end;
- true -> %% Report is an atom if no logging is to be done
- ignore
+start_log_handler() ->
+ case whereis(?MODULE) of
+ undefined ->
+ ChildSpec =
+ #{id=>?MODULE,
+ start=>{gen_server,start_link,[{local,?MODULE},?MODULE,[],[]]},
+ restart=>transient,
+ shutdown=>2000,
+ type=>worker,
+ modules=>[?MODULE]},
+ {ok,_} = supervisor:start_child(logger_sup,ChildSpec),
+ ok;
+ _Pid ->
+ ok
end,
- {ok, State}.
+ ok = logger:add_handler(?MODULE,?MODULE,
+ #{level=>info,
+ formatter=>{?DEFAULT_FORMATTER,
+ ?DEFAULT_FORMAT_CONFIG}}).
+
+init([]) ->
+ {ok, #eh_state{log_func = tc_log_async}}.
-handle_info({'EXIT',User,killed}, State) ->
- case whereis(user) of
- %% init:stop/1/2 has been called, let's finish!
+log(#{msg:={report,Msg},meta:=#{domain:=[otp,sasl]}}=Log,Config) ->
+ case whereis(sasl_sup) of
undefined ->
- remove_handler;
- User ->
- remove_handler;
- _ ->
- {ok,State}
+ ok; % sasl application is not started
+ _Else ->
+ Level =
+ case application:get_env(sasl, errlog_type) of
+ {ok,error} ->
+ error;
+ {ok,_} ->
+ info;
+ undefined ->
+ info
+ end,
+ case Level of
+ error ->
+ case Msg of
+ #{label:={_,progress}} ->
+ ok;
+ _ ->
+ do_log(add_log_category(Log,sasl),Config)
+ end;
+ _ ->
+ do_log(add_log_category(Log,sasl),Config)
+ end
end;
+log(#{meta:=#{domain:=[otp]}}=Log,Config) ->
+ do_log(add_log_category(Log,error_logger),Config);
+log(#{meta:=#{domain:=_}},_) ->
+ ok;
+log(Log,Config) ->
+ do_log(add_log_category(Log,error_logger),Config).
+
+add_log_category(#{meta:=Meta}=Log,Category) ->
+ Log#{meta=>Meta#{?MODULE=>#{category=>Category}}}.
+
+do_log(Log,Config) ->
+ gen_server:call(?MODULE,{log,Log,Config}).
-handle_info(_, State) ->
- {ok,State}.
+handle_cast(_, State) ->
+ {noreply,State}.
-handle_call(flush,State) ->
- {ok, ok, State};
+handle_call({log,#{meta:=#{gl:=GL}},_}, _From,
+ #eh_state{handle_remote_events=false}=State)
+ when node(GL) /= node() ->
+ {reply, ok, State};
+
+handle_call({log,
+ #{meta:=#{?MODULE:=#{category:=Category}}}=Log,
+ #{formatter:={Formatter,FConfig}}},
+ _From,
+ #eh_state{log_func=LogFunc}=State) ->
+ Header = format_header(State),
+ String = Formatter:format(Log,FConfig),
+ case LogFunc of
+ tc_log ->
+ ct_logs:tc_log(Category, ?STD_IMPORTANCE,
+ Header, "~ts", [String], []);
+ tc_log_async ->
+ ct_logs:tc_log_async(sasl, ?STD_IMPORTANCE,
+ Header, "~ts", [String])
+ end,
+ {reply,ok,State};
-handle_call({set_curr_func,{group,Group,Conf},Config},
- State) when is_list(Config) ->
+handle_call(flush,_From,State) ->
+ {reply, ok, State};
+
+handle_call({set_curr_func,{group,Group,Conf},Config},_From,State)
+ when is_list(Config) ->
Parallel = case proplists:get_value(tc_group_properties, Config) of
undefined -> false;
Props -> lists:member(parallel, Props)
end,
- {ok, ok, State#eh_state{curr_group = Group,
- curr_func = Conf,
- parallel_tcs = Parallel}};
-handle_call({set_curr_func,{group,Group,Conf},_SkipOrFail}, State) ->
- {ok, ok, State#eh_state{curr_group = Group,
- curr_func = Conf,
- parallel_tcs = false}};
-handle_call({set_curr_func,{group,undefined},_Config}, State) ->
- {ok, ok, State#eh_state{curr_group = undefined,
- curr_func = undefined,
- parallel_tcs = false}};
-handle_call({set_curr_func,{Suite,Conf},_Config}, State) ->
- {ok, ok, State#eh_state{curr_suite = Suite,
- curr_func = Conf,
- parallel_tcs = false}};
-handle_call({set_curr_func,undefined,_Config}, State) ->
- {ok, ok, State#eh_state{curr_suite = undefined,
- curr_func = undefined,
- parallel_tcs = false}};
-handle_call({set_curr_func,TC,_Config}, State) ->
- {ok, ok, State#eh_state{curr_func = TC}};
-
-handle_call({set_logfunc,NewLogFunc}, State) ->
- {ok, NewLogFunc, State#eh_state{log_func = NewLogFunc}};
-
-handle_call({handle_remote_events,Bool}, State) ->
- {ok, ok, State#eh_state{handle_remote_events = Bool}};
-
-handle_call(_Query, _State) ->
- {error, bad_query}.
+ {reply, ok, State#eh_state{curr_group = Group,
+ curr_func = Conf,
+ parallel_tcs = Parallel}};
+handle_call({set_curr_func,{group,Group,Conf},_SkipOrFail}, _From, State) ->
+ {reply, ok, State#eh_state{curr_group = Group,
+ curr_func = Conf,
+ parallel_tcs = false}};
+handle_call({set_curr_func,{group,undefined},_Config}, _From, State) ->
+ {reply, ok, State#eh_state{curr_group = undefined,
+ curr_func = undefined,
+ parallel_tcs = false}};
+handle_call({set_curr_func,{Suite,Conf},_Config}, _From, State) ->
+ {reply, ok, State#eh_state{curr_suite = Suite,
+ curr_func = Conf,
+ parallel_tcs = false}};
+handle_call({set_curr_func,undefined,_Config}, _From, State) ->
+ {reply, ok, State#eh_state{curr_suite = undefined,
+ curr_func = undefined,
+ parallel_tcs = false}};
+handle_call({set_curr_func,TC,_Config}, _From, State) ->
+ {reply, ok, State#eh_state{curr_func = TC}};
+
+handle_call({set_logfunc,NewLogFunc}, _From, State) ->
+ {reply, NewLogFunc, State#eh_state{log_func = NewLogFunc}};
+
+handle_call({handle_remote_events,Bool}, _From, State) ->
+ {reply, ok, State#eh_state{handle_remote_events = Bool}}.
terminate(_) ->
- error_logger:delete_report_handler(?MODULE),
- [].
+ _ = logger:remove_handler(?MODULE),
+ _ = supervisor:terminate_child(logger_sup,?MODULE),
+ _ = supervisor:delete_child(logger_sup,?MODULE),
+ ok.
terminate(_Arg, _State) ->
ok.
-tag_event(Event, utc) ->
- {calendar:universal_time(), Event};
-tag_event(Event, _) ->
- {calendar:local_time(), Event}.
-
set_curr_func(CurrFunc, Config) ->
- gen_event:call(error_logger, ?MODULE, {set_curr_func, CurrFunc, Config}).
+ gen_server:call(?MODULE, {set_curr_func, CurrFunc, Config}).
set_log_func(Func) ->
- gen_event:call(error_logger, ?MODULE, {set_logfunc, Func}).
+ gen_server:call(?MODULE, {set_logfunc, Func}).
handle_remote_events(Bool) ->
- gen_event:call(error_logger, ?MODULE, {handle_remote_events, Bool}).
+ gen_server:call(?MODULE, {handle_remote_events, Bool}).
%%%-----------------------------------------------------------------
format_header(#eh_state{curr_suite = undefined,
curr_group = undefined,
curr_func = undefined}) ->
- io_lib:format("System report", []);
+ lists:flatten(io_lib:format("System report", []));
format_header(#eh_state{curr_suite = Suite,
curr_group = undefined,
curr_func = undefined}) ->
- io_lib:format("System report during ~w", [Suite]);
+ lists:flatten(io_lib:format("System report during ~w", [Suite]));
format_header(#eh_state{curr_suite = Suite,
curr_group = undefined,
curr_func = TcOrConf}) ->
- io_lib:format("System report during ~w:~w/1",
- [Suite,TcOrConf]);
+ lists:flatten(io_lib:format("System report during ~w:~tw/1",
+ [Suite,TcOrConf]));
format_header(#eh_state{curr_suite = Suite,
curr_group = Group,
curr_func = Conf}) when Conf == init_per_group;
Conf == end_per_group ->
- io_lib:format("System report during ~w:~w/2 for ~w",
- [Suite,Conf,Group]);
+ lists:flatten(io_lib:format("System report during ~w:~w/2 for ~tw",
+ [Suite,Conf,Group]));
format_header(#eh_state{curr_suite = Suite,
curr_group = Group,
parallel_tcs = true}) ->
- io_lib:format("System report during ~w in ~w",
- [Group,Suite]);
+ lists:flatten(io_lib:format("System report during ~tw in ~w",
+ [Group,Suite]));
format_header(#eh_state{curr_suite = Suite,
curr_group = Group,
curr_func = TC}) ->
- io_lib:format("System report during ~w:~w/1 in ~w",
- [Suite,TC,Group]).
-
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
+ lists:flatten(io_lib:format("System report during ~w:~tw/1 in ~tw",
+ [Suite,TC,Group])).
diff --git a/lib/common_test/src/cth_surefire.erl b/lib/common_test/src/cth_surefire.erl
index 0bbb217275..c9b4cb10c6 100644
--- a/lib/common_test/src/cth_surefire.erl
+++ b/lib/common_test/src/cth_surefire.erl
@@ -1,7 +1,7 @@
%%--------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2012-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2012-2018. 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.
@@ -18,10 +18,10 @@
%% %CopyrightEnd%
%%--------------------------------------------------------------------
-%%% @doc Common Test Framework functions handling test specifications.
+%%% Common Test Framework functions handling test specifications.
%%%
-%%% <p>This module creates a junit report of the test run if plugged in
-%%% as a suite_callback.</p>
+%%% This module creates a junit report of the test run if plugged in
+%%% as a suite_callback.
-module(cth_surefire).
@@ -143,7 +143,7 @@ on_tc_fail(_Suite,_TC, Res, State) ->
TC = hd(TCs),
NewTC = TC#testcase{
result =
- {fail,lists:flatten(io_lib:format("~p",[Res]))} },
+ {fail,lists:flatten(io_lib:format("~tp",[Res]))} },
State#state{ test_cases = [NewTC | tl(TCs)]}.
on_tc_skip(Suite,{ConfigFunc,_GrName}, Res, State) ->
@@ -164,7 +164,7 @@ do_tc_skip(Res, State) ->
TC = hd(TCs),
NewTC = TC#testcase{
result =
- {skipped,lists:flatten(io_lib:format("~p",[Res]))} },
+ {skipped,lists:flatten(io_lib:format("~tp",[Res]))} },
State#state{ test_cases = [NewTC | tl(TCs)]}.
init_tc(State, Config) when is_list(Config) == false ->
@@ -184,15 +184,14 @@ end_tc(Name, _Config, _Res, State = #state{ curr_suite = Suite,
Log =
case Log0 of
"" ->
- LowerSuiteName = string:to_lower(atom_to_list(Suite)),
+ LowerSuiteName = string:lowercase(atom_to_list(Suite)),
filename:join(CurrLogDir,LowerSuiteName++"."++Name++".html");
_ ->
Log0
end,
Url = make_url(UrlBase,Log),
ClassName = atom_to_list(Suite),
- PGroup = string:join([ atom_to_list(Group)||
- Group <- lists:reverse(Groups)],"."),
+ PGroup = lists:concat(lists:join(".",lists:reverse(Groups))),
TimeTakes = io_lib:format("~f",[timer:now_diff(?now,TS) / 1000000]),
State#state{ test_cases = [#testcase{ log = Log,
url = Url,
@@ -236,7 +235,7 @@ close_suite(#state{ test_cases = TCs, url_base = UrlBase } = State) ->
terminate(State = #state{ test_cases = [] }) ->
{ok,D} = file:open(State#state.filepath,[write,{encoding,utf8}]),
io:format(D, "<?xml version=\"1.0\" encoding= \"UTF-8\" ?>", []),
- io:format(D, to_xml(State), []),
+ io:format(D, "~ts", [to_xml(State)]),
catch file:sync(D),
catch file:close(D);
terminate(State) ->
@@ -317,9 +316,9 @@ make_url(undefined,_) ->
make_url(_,[]) ->
undefined;
make_url(UrlBase0,Log) ->
- UrlBase = string:strip(UrlBase0,right,$/),
+ UrlBase = string:trim(UrlBase0,trailing,[$/]),
RelativeLog = get_relative_log_url(Log),
- string:join([UrlBase,RelativeLog],"/").
+ lists:flatten(lists:join($/,[UrlBase,RelativeLog])).
get_test_root(Log) ->
LogParts = filename:split(Log),
@@ -329,7 +328,7 @@ get_relative_log_url(Log) ->
LogParts = filename:split(Log),
Start = length(LogParts)-?log_depth,
Length = ?log_depth+1,
- string:join(lists:sublist(LogParts,Start,Length),"/").
+ lists:flatten(lists:join($/,lists:sublist(LogParts,Start,Length))).
count_tcs([#testcase{name=ConfCase}|TCs],Ok,F,S)
when ConfCase=="init_per_suite";
diff --git a/lib/common_test/src/test_server.erl b/lib/common_test/src/test_server.erl
index 857ff27258..756cd4d692 100644
--- a/lib/common_test/src/test_server.erl
+++ b/lib/common_test/src/test_server.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2017. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2018. 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.
@@ -21,7 +21,7 @@
-define(DEFAULT_TIMETRAP_SECS, 60).
%%% TEST_SERVER_CTRL INTERFACE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
--export([run_test_case_apply/1,init_target_info/0]).
+-export([run_test_case_apply/1,init_target_info/0,init_valgrind/0]).
-export([cover_compile/1,cover_analyse/2]).
%%% TEST_SERVER_SUP INTERFACE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -49,6 +49,10 @@
-export([break/1,break/2,break/3,continue/0,continue/1]).
+%%% DEBUGGER INTERFACE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-export([valgrind_new_leaks/0, valgrind_format/2,
+ is_valgrind/0]).
+
%%% PRIVATE EXPORTED %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-export([]).
@@ -69,6 +73,10 @@ init_target_info() ->
username=test_server_sup:get_username(),
cookie=atom_to_list(erlang:get_cookie())}.
+init_valgrind() ->
+ valgrind_new_leaks().
+
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% cover_compile(#cover{app=App,incl=Include,excl=Exclude,cross=Cross}) ->
%% {ok,#cover{mods=AnalyseModules}} | {error,Reason}
@@ -175,7 +183,7 @@ do_cover_compile(Modules) ->
ok.
warn_compile({error,{Reason,Module}}) ->
- io:fwrite("\nWARNING: Could not cover compile ~ts: ~p\n",
+ io:fwrite("\nWARNING: Could not cover compile ~ts: ~tp\n",
[Module,{error,Reason}]).
%% Make sure all modules are loaded and unstick if sticky
@@ -189,7 +197,7 @@ prepare_cover_compile([M|Ms],Sticky) ->
{module,_} ->
prepare_cover_compile([M|Ms],Sticky);
Error ->
- io:fwrite("\nWARNING: Could not load ~w: ~p\n",[M,Error]),
+ io:fwrite("\nWARNING: Could not load ~w: ~tp\n",[M,Error]),
prepare_cover_compile(Ms,Sticky)
end;
{false,_} ->
@@ -358,11 +366,12 @@ stick_all_sticky(Node,Sticky) ->
%% compensate timetraps for runtime delays introduced by e.g. tools like
%% cover.
-run_test_case_apply({Mod,Func,Args,Name,RunInit,TimetrapData}) ->
- case os:getenv("TS_RUN_VALGRIND") of
+run_test_case_apply({CaseNum,Mod,Func,Args,Name,RunInit,TimetrapData}) ->
+ case is_valgrind() of
false ->
ok;
- _ ->
+ true ->
+ valgrind_format("Test case #~w ~w:~w/1", [CaseNum, Mod, Func]),
os:putenv("VALGRIND_LOGFILE_INFIX",atom_to_list(Mod)++"."++
atom_to_list(Func)++"-")
end,
@@ -370,12 +379,13 @@ run_test_case_apply({Mod,Func,Args,Name,RunInit,TimetrapData}) ->
Result = run_test_case_apply(Mod, Func, Args, Name, RunInit,
TimetrapData),
ProcAft = erlang:system_info(process_count),
+ valgrind_new_leaks(),
DetFail = get(test_server_detected_fail),
{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(),
@@ -405,6 +415,7 @@ run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData) ->
St = #st{ref=Ref,pid=Pid,mf={Mod,Func},last_known_loc=unknown,
status=starting,ret_val=[],comment="",timeout=infinity,
config=hd(Args)},
+ ct_util:mark_process(),
run_test_case_msgloop(St).
%% Ugly bug (pre R5A):
@@ -450,13 +461,14 @@ run_test_case_msgloop(#st{ref=Ref,pid=Pid,end_conf_pid=EndConfPid0}=St0) ->
exit(Pid, kill),
%% here's the only place we know Reason, so we save
%% it as a comment, potentially replacing user data
- Error = lists:flatten(io_lib:format("Aborted: ~p",
+ Error = lists:flatten(io_lib:format("Aborted: ~tp",
[Reason])),
- Error1 = lists:flatten([string:strip(S,left) ||
- S <- string:tokens(Error,
- [$\n])]),
- Comment = if length(Error1) > 63 ->
- string:substr(Error1,1,60) ++ "...";
+ Error1 = lists:flatten([string:trim(S,leading,"\s") ||
+ S <- string:lexemes(Error,
+ [$\n])]),
+ ErrorLength = string:length(Error1),
+ Comment = if ErrorLength > 63 ->
+ string:slice(Error1,0,60) ++ "...";
true ->
Error1
end,
@@ -641,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};
@@ -654,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) ->
@@ -756,13 +768,13 @@ print_end_conf_result(Mod,Func,Conf,Cause,Error) ->
Str2Print =
fun(NoHTML) when NoHTML == stdout; NoHTML == major ->
io_lib:format("WARNING! "
- "~w:end_per_testcase(~w, ~tp)"
+ "~w:end_per_testcase(~tw, ~tp)"
" ~s!\n\tReason: ~tp\n",
[Mod,Func,Conf,Cause,Error]);
(minor) ->
ErrorStr = test_server_ctrl:escape_chars(Error),
io_lib:format("WARNING! "
- "~w:end_per_testcase(~w, ~tp)"
+ "~w:end_per_testcase(~tw, ~tp)"
" ~s!\n\tReason: ~ts\n",
[Mod,Func,Conf,Cause,ErrorStr])
end,
@@ -774,6 +786,7 @@ spawn_fw_call(Mod,IPTC={init_per_testcase,Func},CurrConf,Pid,
Why,Loc,SendTo) ->
FwCall =
fun() ->
+ ct_util:mark_process(),
Skip = {skip,{failed,{Mod,init_per_testcase,Why}}},
%% if init_per_testcase fails, the test case
%% should be skipped
@@ -792,7 +805,7 @@ spawn_fw_call(Mod,IPTC={init_per_testcase,Func},CurrConf,Pid,
_ -> died
end,
group_leader() ! {printout,12,
- "ERROR! ~w:init_per_testcase(~w, ~p)"
+ "ERROR! ~w:init_per_testcase(~tw, ~tp)"
" failed!\n\tReason: ~tp\n",
[Mod,Func,CurrConf,Why]},
%% finished, report back
@@ -804,6 +817,7 @@ spawn_fw_call(Mod,EPTC={end_per_testcase,Func},EndConf,Pid,
Why,_Loc,SendTo) ->
FwCall =
fun() ->
+ ct_util:mark_process(),
{RetVal,Report} =
case proplists:get_value(tc_status, EndConf) of
undefined ->
@@ -820,7 +834,7 @@ spawn_fw_call(Mod,EPTC={end_per_testcase,Func},EndConf,Pid,
{timetrap_timeout,TVal} ->
group_leader() !
{printout,12,
- "WARNING! ~w:end_per_testcase(~w, ~p)"
+ "WARNING! ~w:end_per_testcase(~tw, ~tp)"
" failed!\n\tReason: timetrap timeout"
" after ~w ms!\n", [Mod,Func,EndConf,TVal]},
W = "<font color=\"red\">"
@@ -829,46 +843,80 @@ spawn_fw_call(Mod,EPTC={end_per_testcase,Func},EndConf,Pid,
_ ->
group_leader() !
{printout,12,
- "WARNING! ~w:end_per_testcase(~w, ~p)"
+ "WARNING! ~w:end_per_testcase(~tw, ~tp)"
" failed!\n\tReason: ~tp\n",
[Mod,Func,EndConf,Why]},
W = "<font color=\"red\">"
"WARNING: end_per_testcase failed!</font>",
{died,W}
end,
- try do_end_tc_call(Mod,EPTC,{Pid,Report,[EndConf]}, Why) of
- _ -> ok
- catch
- _:FwEndTCErr ->
- exit({fw_notify_done,end_tc,FwEndTCErr})
- end,
- FailLoc = proplists:get_value(tc_fail_loc, EndConf),
+ FailLoc0 = proplists:get_value(tc_fail_loc, EndConf),
+ {RetVal1,FailLoc} =
+ try do_end_tc_call(Mod,EPTC,{Pid,Report,[EndConf]}, Why) of
+ Why ->
+ {RetVal,FailLoc0};
+ {failed,_} = R ->
+ {R,[{Mod,Func}]};
+ R ->
+ {R,FailLoc0}
+ catch
+ _:FwEndTCErr ->
+ exit({fw_notify_done,end_tc,FwEndTCErr})
+ end,
%% finished, report back (if end_per_testcase fails, a warning
%% should be printed as part of the comment)
SendTo ! {self(),fw_notify_done,
- {Time,RetVal,FailLoc,[],Warn}}
+ {Time,RetVal1,FailLoc,[],Warn}}
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() ->
- test_server_sup:framework_call(report, [framework_error,
- {{FwMod,FwFunc},
- FwError}]),
+ ct_util:mark_process(),
+ 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:~w 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);
spawn_fw_call(Mod,Func,CurrConf,Pid,Error,Loc,SendTo) ->
+ ct_util:mark_process(),
{Func1,EndTCFunc} = case Func of
CF when CF == init_per_suite; CF == end_per_suite;
CF == init_per_group; CF == end_per_group ->
@@ -886,17 +934,185 @@ spawn_fw_call(Mod,Func,CurrConf,Pid,Error,Loc,SendTo) ->
FwErrorNotifyErr})
end,
Conf = [{tc_status,{failed,Error}}|CurrConf],
- try do_end_tc_call(Mod,EndTCFunc,{Pid,Error,[Conf]},Error) of
- _ -> ok
- catch
- _:FwEndTCErr ->
- exit({fw_notify_done,end_tc,FwEndTCErr})
- end,
+ {Time,RetVal,Loc1} =
+ try do_end_tc_call(Mod,EndTCFunc,{Pid,Error,[Conf]},Error) of
+ Error ->
+ {died, Error, Loc};
+ {failed,Reason} = NewReturn ->
+ fw_error_notify(Mod,Func1,Conf,Reason),
+ {died, NewReturn, [{Mod,Func}]};
+ NewReturn ->
+ T = case Error of
+ {timetrap_timeout,TT} -> TT;
+ _ -> 0
+ end,
+ {T, NewReturn, Loc}
+ catch
+ _:FwEndTCErr ->
+ exit({fw_notify_done,end_tc,FwEndTCErr})
+ end,
%% finished, report back
- SendTo ! {self(),fw_notify_done,{died,Error,Loc,[],undefined}}
+ SendTo ! {self(),fw_notify_done,{Time,RetVal,Loc1,[],undefined}}
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.
%%
@@ -907,6 +1123,7 @@ start_job_proxy() ->
%% The io_reply_proxy is not the most satisfying solution but it works...
io_reply_proxy(ReplyTo) ->
+ ct_util:mark_process(),
receive
IoReply when is_tuple(IoReply),
element(1, IoReply) == io_reply ->
@@ -916,6 +1133,7 @@ io_reply_proxy(ReplyTo) ->
end.
job_proxy_msgloop() ->
+ ct_util:mark_process(),
receive
%%
@@ -1070,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},
@@ -1322,13 +1543,12 @@ do_init_per_testcase(Mod, Args) ->
{skip,Reason};
exit:{Skip,Reason} when Skip =:= skip; Skip =:= skipped ->
{skip,Reason};
- throw:Other ->
- set_loc(erlang:get_stacktrace()),
+ throw:Other:Stk ->
+ set_loc(Stk),
Line = get_loc(),
print_init_conf_result(Line,"thrown",Other),
{skip,{failed,{Mod,init_per_testcase,Other}}};
- _:Reason0 ->
- Stk = erlang:get_stacktrace(),
+ _:Reason0:Stk ->
Reason = {Reason0,Stk},
set_loc(Stk),
Line = get_loc(),
@@ -1341,7 +1561,7 @@ print_init_conf_result(Line,Cause,Reason) ->
Str2Print =
fun(NoHTML) when NoHTML == stdout; NoHTML == major ->
io_lib:format("ERROR! init_per_testcase ~s!\n"
- "\tLocation: ~p\n\tReason: ~tp\n",
+ "\tLocation: ~tp\n\tReason: ~tp\n",
[Cause,Line,Reason]);
(minor) ->
ReasonStr = test_server_ctrl:escape_chars(Reason),
@@ -1377,20 +1597,19 @@ do_end_per_testcase(Mod,EndFunc,Func,Conf) ->
_ ->
ok
catch
- throw:Other ->
+ throw:Other:Stk ->
Comment0 = case read_comment() of
"" -> "";
Cmt -> Cmt ++ test_server_ctrl:xhtml("<br>",
"<br />")
end,
- set_loc(erlang:get_stacktrace()),
+ set_loc(Stk),
comment(io_lib:format("~ts<font color=\"red\">"
"WARNING: ~w thrown!"
"</font>\n",[Comment0,EndFunc])),
print_end_tc_warning(EndFunc,Other,"thrown",get_loc()),
{failed,{Mod,end_per_testcase,Other}};
- Class:Reason ->
- Stk = erlang:get_stacktrace(),
+ Class:Reason:Stk ->
set_loc(Stk),
Why = case Class of
exit -> {'EXIT',Reason};
@@ -1413,7 +1632,7 @@ print_end_tc_warning(EndFunc,Reason,Cause,Loc) ->
Str2Print =
fun(NoHTML) when NoHTML == stdout; NoHTML == major ->
io_lib:format("WARNING: ~w ~s!\n"
- "Reason: ~tp\nLine: ~p\n",
+ "Reason: ~tp\nLine: ~tp\n",
[EndFunc,Cause,Reason,Loc]);
(minor) ->
ReasonStr = test_server_ctrl:escape_chars(Reason),
@@ -1515,7 +1734,7 @@ lookup_config(Key,Config) ->
{value,{Key,Val}} ->
Val;
_ ->
- io:format("Could not find element ~p in Config.~n",[Key]),
+ io:format("Could not find element ~tp in Config.~n",[Key]),
undefined
end.
@@ -1532,8 +1751,7 @@ ts_tc(M, F, A) ->
throw:{skipped, Reason} -> {skip, Reason};
exit:{skip, Reason} -> {skip, Reason};
exit:{skipped, Reason} -> {skip, Reason};
- Type:Reason ->
- Stk = erlang:get_stacktrace(),
+ Type:Reason:Stk ->
set_loc(Stk),
case Type of
throw ->
@@ -1600,7 +1818,7 @@ format(Detail, Format, Args) ->
Str =
case catch io_lib:format(Format,Args) of
{'EXIT',_} ->
- io_lib:format("illegal format; ~p with args ~p.\n",
+ io_lib:format("illegal format; ~tp with args ~tp.\n",
[Format,Args]);
Valid -> Valid
end,
@@ -1722,8 +1940,8 @@ fail(Reason) ->
try
exit({suite_failed,Reason})
catch
- Class:R ->
- case erlang:get_stacktrace() of
+ Class:R:Stacktrace ->
+ case Stacktrace of
[{?MODULE,fail,1,_}|Stk] -> ok;
Stk -> ok
end,
@@ -1732,7 +1950,7 @@ fail(Reason) ->
cast_to_list(X) when is_list(X) -> X;
cast_to_list(X) when is_atom(X) -> atom_to_list(X);
-cast_to_list(X) -> lists:flatten(io_lib:format("~p", [X])).
+cast_to_list(X) -> lists:flatten(io_lib:format("~tp", [X])).
@@ -1745,8 +1963,8 @@ fail() ->
try
exit(suite_failed)
catch
- Class:R ->
- case erlang:get_stacktrace() of
+ Class:R:Stacktrace ->
+ case Stacktrace of
[{?MODULE,fail,0,_}|Stk] -> ok;
Stk -> ok
end,
@@ -1793,6 +2011,7 @@ break(CBM, TestCase, Comment) ->
spawn_break_process(Pid, PName) ->
spawn(fun() ->
register(PName, self()),
+ ct_util:mark_process(),
receive
continue -> continue(Pid);
cancel -> ok
@@ -1827,7 +2046,8 @@ timetrap_scale_factor() ->
{ 2, fun() -> has_lock_checking() end},
{ 3, fun() -> has_superfluous_schedulers() end},
{ 6, fun() -> is_debug() end},
- {10, fun() -> is_cover() end}
+ {10, fun() -> is_cover() end},
+ {10, fun() -> is_valgrind() end}
]).
timetrap_scale_factor(Scales) ->
@@ -1903,7 +2123,7 @@ ensure_timetrap(Config) ->
Garbage ->
erase(test_server_default_timetrap),
format("=== WARNING: garbage in "
- "test_server_default_timetrap: ~p~n",
+ "test_server_default_timetrap: ~tp~n",
[Garbage])
end,
DTmo = case lists:keysearch(default_timeout,1,Config) of
@@ -1932,7 +2152,7 @@ cancel_default_timetrap(true) ->
Garbage ->
erase(test_server_default_timetrap),
format("=== WARNING: garbage in "
- "test_server_default_timetrap: ~p~n",
+ "test_server_default_timetrap: ~tp~n",
[Garbage]),
error
end.
@@ -1941,7 +2161,7 @@ time_ms({hours,N}, _, _) -> hours(N);
time_ms({minutes,N}, _, _) -> minutes(N);
time_ms({seconds,N}, _, _) -> seconds(N);
time_ms({Other,_N}, _, _) ->
- format("=== ERROR: Invalid time specification: ~p. "
+ format("=== ERROR: Invalid time specification: ~tp. "
"Should be seconds, minutes, or hours.~n", [Other]),
exit({invalid_time_format,Other});
time_ms(Ms, _, _) when is_integer(Ms) -> Ms;
@@ -1989,6 +2209,7 @@ time_ms_apply(Func, TCPid, MultAndScale) ->
user_timetrap_supervisor(Func, Spawner, TCPid, GL, T0, MultAndScale) ->
process_flag(trap_exit, true),
+ ct_util:mark_process(),
Spawner ! {self(),infinity},
MonRef = monitor(process, TCPid),
UserTTSup = self(),
@@ -2022,15 +2243,15 @@ call_user_timetrap(Func, Sup) when is_function(Func) ->
try Func() of
Result ->
Sup ! {self(),Result}
- catch _:Error ->
- exit({Error,erlang:get_stacktrace()})
+ catch _:Error:Stk ->
+ exit({Error,Stk})
end;
call_user_timetrap({M,F,A}, Sup) ->
try apply(M,F,A) of
Result ->
Sup ! {self(),Result}
- catch _:Error ->
- exit({Error,erlang:get_stacktrace()})
+ catch _:Error:Stk ->
+ exit({Error,Stk})
end.
save_user_timetrap(TCPid, UserTTSup, StartTime) ->
@@ -2559,6 +2780,7 @@ run_on_shielded_node(Fun, CArgs) when is_function(Fun), is_list(CArgs) ->
-spec start_job_proxy_fun(_, _) -> fun(() -> no_return()).
start_job_proxy_fun(Master, Fun) ->
fun () ->
+ ct_util:mark_process(),
_ = start_job_proxy(),
receive
Ref ->
@@ -2686,9 +2908,9 @@ is_cover() ->
is_debug() ->
case catch erlang:system_info(debug_compiled) of
{'EXIT', _} ->
- case string:str(erlang:system_info(system_version), "debug") of
- Int when is_integer(Int), Int > 0 -> true;
- _ -> false
+ case string:find(erlang:system_info(system_version), "debug") of
+ nomatch -> false;
+ _ -> true
end;
Res ->
Res
@@ -2724,11 +2946,46 @@ has_superfluous_schedulers() ->
%% We might want to do more tests on a commercial platform, for instance
%% ensuring that all applications have documentation).
is_commercial() ->
- case string:str(erlang:system_info(system_version), "source") of
- Int when is_integer(Int), Int > 0 -> false;
- _ -> true
+ case string:find(erlang:system_info(system_version), "source") of
+ nomatch -> true;
+ _ -> false
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% is_valgrind() -> boolean()
+%%
+%% Returns true if valgrind is running, else false
+is_valgrind() ->
+ case catch erlang:system_info({valgrind, running}) of
+ {'EXIT', _} -> false;
+ Res -> Res
end.
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% DEBUGGER INTERFACE %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% valgrind_new_leaks() -> ok
+%%
+%% Checks for new memory leaks if Valgrind is active.
+valgrind_new_leaks() ->
+ catch erlang:system_info({valgrind, memory}),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% valgrind_format(Format, Args) -> ok
+%% Format = string()
+%% Args = lists()
+%%
+%% Outputs the formatted string to Valgrind's logfile,if Valgrind is active.
+valgrind_format(Format, Args) ->
+ (catch erlang:system_info({valgrind, io_lib:format(Format, Args)})),
+ ok.
+
+
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Apply given function and reply to caller or proxy.
diff --git a/lib/common_test/src/test_server_ctrl.erl b/lib/common_test/src/test_server_ctrl.erl
index 064e375cd5..003d08d70d 100644
--- a/lib/common_test/src/test_server_ctrl.erl
+++ b/lib/common_test/src/test_server_ctrl.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2018. 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.
@@ -89,6 +89,7 @@
-define(logdir_ext, ".logs").
-define(data_dir_suffix, "_data/").
-define(suitelog_name, "suite.log").
+-define(suitelog_latest_name, "suite.log.latest").
-define(coverlog_name, "cover.html").
-define(raw_coverlog_name, "cover.log").
-define(cross_coverlog_name, "cross_cover.html").
@@ -232,7 +233,7 @@ parse_cmd_line(['SPEC',Spec|Cmds], SpecList, Names, Param, Trc, Cov, TCCB) ->
parse_cmd_line(Cmds, TermList++SpecList, [Name|Names], Param,
Trc, Cov, TCCB);
{error,Reason} ->
- io:format("Can't open ~w: ~p\n",[Spec, file:format_error(Reason)]),
+ io:format("Can't open ~tw: ~tp\n",[Spec, file:format_error(Reason)]),
parse_cmd_line(Cmds, SpecList, Names, Param, Trc, Cov, TCCB)
end;
parse_cmd_line(['NAME',Name|Cmds], SpecList, Names, Param, Trc, Cov, TCCB) ->
@@ -261,7 +262,7 @@ parse_cmd_line(['COVER',App,CF,Analyse|Cmds], SpecList, Names, Param, Trc, _Cov,
parse_cmd_line(['TESTCASE_CALLBACK',Mod,Func|Cmds], SpecList, Names, Param, Trc, Cov, _) ->
parse_cmd_line(Cmds, SpecList, Names, Param, Trc, Cov, {Mod,Func});
parse_cmd_line([Obj|_Cmds], _SpecList, _Names, _Param, _Trc, _Cov, _TCCB) ->
- io:format("~w: Bad argument: ~w\n", [?MODULE,Obj]),
+ io:format("~w: Bad argument: ~tw\n", [?MODULE,Obj]),
io:format(" Use the `ts' module to start tests.\n", []),
io:format(" (If you ARE using `ts', there is a bug in `ts'.)\n", []),
halt(1);
@@ -280,7 +281,7 @@ parse_cmd_line([], SpecList, Names, Param, Trc, Cov, TCCB) ->
cast_to_list(X) when is_list(X) -> X;
cast_to_list(X) when is_atom(X) -> atom_to_list(X);
-cast_to_list(X) -> lists:flatten(io_lib:format("~w", [X])).
+cast_to_list(X) -> lists:flatten(io_lib:format("~tw", [X])).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% START INTERFACE
@@ -878,7 +879,7 @@ handle_call({testcase_callback,ModFunc}, _From, State) ->
ok;
false ->
io:format(user,
- "WARNING! Callback function ~w:~w/4 undefined.~n~n",
+ "WARNING! Callback function ~w:~tw/4 undefined.~n~n",
[Mod,Func])
end;
_ ->
@@ -1016,7 +1017,7 @@ handle_info({'EXIT',Pid,Reason}, State) ->
killed ->
io:format("Suite ~ts was killed\n", [Name]);
_Other ->
- io:format("Suite ~ts was killed with reason ~p\n",
+ io:format("Suite ~ts was killed with reason ~tp\n",
[Name,Reason])
end,
State2 = State#state{jobs=NewJobs},
@@ -1126,6 +1127,7 @@ init_tester(Mod, Func, Args, Dir, Name, {_,_,MinLev}=Levels,
RejectIoReqs, CreatePrivDir, TCCallback, ExtraTools) ->
process_flag(trap_exit, true),
_ = test_server_io:start_link(),
+ put(app, common_test),
put(test_server_name, Name),
put(test_server_dir, Dir),
put(test_server_total_time, 0),
@@ -1150,6 +1152,12 @@ init_tester(Mod, Func, Args, Dir, Name, {_,_,MinLev}=Levels,
end,
%% before first print, read and set logging options
+ FWLogDir =
+ case test_server_sup:framework_call(get_log_dir, [], []) of
+ {ok,FwDir} -> FwDir;
+ _ -> filename:dirname(Dir)
+ end,
+ put(test_server_framework_logdir, FWLogDir),
LogOpts = test_server_sup:framework_call(get_logopts, [], []),
put(test_server_logopts, LogOpts),
@@ -1168,10 +1176,10 @@ init_tester(Mod, Func, Args, Dir, Name, {_,_,MinLev}=Levels,
{'EXIT',test_suites_done} ->
ok;
{'EXIT',_Pid,Reason} ->
- print(1, "EXIT, reason ~p", [Reason]);
+ print(1, "EXIT, reason ~tp", [Reason]);
{'EXIT',Reason} ->
report_severe_error(Reason),
- print(1, "EXIT, reason ~p", [Reason])
+ print(1, "EXIT, reason ~tp", [Reason])
end,
Time = TimeMy/1000000,
SuccessStr =
@@ -1263,7 +1271,7 @@ do_spec(SpecName, TimetrapSpec) when is_list(SpecName) ->
{ok,TermList} ->
do_spec_list(TermList,TimetrapSpec);
{error,Reason} ->
- io:format("Can't open ~ts: ~p\n", [SpecName,Reason]),
+ io:format("Can't open ~ts: ~tp\n", [SpecName,Reason]),
{error,{cant_open_spec,Reason}}
end.
@@ -1346,7 +1354,7 @@ do_spec_terms([{require_nodenames,NumNames}|Terms], TopCases, SkipList, Config)
do_spec_terms(Terms, TopCases, SkipList,
update_config(Config, {nodenames,NodeNames}));
do_spec_terms([Other|Terms], TopCases, SkipList, Config) ->
- io:format("** WARNING: Spec file contains unknown directive ~p\n",
+ io:format("** WARNING: Spec file contains unknown directive ~tp\n",
[Other]),
do_spec_terms(Terms, TopCases, SkipList, Config).
@@ -1385,7 +1393,7 @@ temp_nodename([Chr|Base], Acc) ->
%%
%% Counts the test cases that are about to run and returns that number.
%% If there's a conf group in TestSpec with a repeat property, the total number
-%% of cases can not be calculated and NoOfCases = unknown.
+%% of cases cannot be calculated and NoOfCases = unknown.
count_test_cases(TopCases, SkipCases) when is_list(TopCases) ->
case collect_all_cases(TopCases, SkipCases) of
{error,_Why} = Error ->
@@ -1435,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) ->
@@ -1464,13 +1474,14 @@ get_suites([], Mods) ->
lists:reverse(Mods).
add_mod(Mod, Mods) ->
- case string:rstr(atom_to_list(Mod), "_SUITE") of
- 0 -> false;
- _ -> % test suite
+ case lists:reverse(atom_to_list(Mod)) of
+ "ETIUS_" ++ _ -> % test suite
case lists:member(Mod, Mods) of
true -> false;
false -> true
- end
+ end;
+ _ ->
+ false
end.
@@ -1503,7 +1514,7 @@ do_test_cases(TopCases, SkipCases,
FwMod = get_fw_mod(?MODULE),
case collect_all_cases(TopCases, SkipCases) of
{error,Why} ->
- print(1, "Error starting: ~p", [Why]),
+ print(1, "Error starting: ~tp", [Why]),
exit(test_suites_done);
TestSpec0 ->
N = case remove_conf(TestSpec0) of
@@ -1549,7 +1560,7 @@ do_test_cases(TopCases, SkipCases,
Html1}
end,
- print(html, Header),
+ print(html, "~ts", [Header]),
print(html, xhtml("<p>", "<h4>")),
print_timestamp(html, "Test started at "),
@@ -1596,10 +1607,10 @@ do_test_cases(TopCases, SkipCases,
[?suitelog_name,CoverLog,?unexpected_io_log]),
print(html,
"<p>~ts</p>\n" ++
- xhtml(["<table bgcolor=\"white\" border=\"3\" cellpadding=\"5\">\n",
- "<thead>\n"],
- ["<table id=\"",?sortable_table_name,"\">\n",
- "<thead>\n"]) ++
+ xhtml("<table bgcolor=\"white\" border=\"3\" cellpadding=\"5\">\n" ++
+ "<thead>\n",
+ "<table id=\"" ++ ?sortable_table_name ++ "\">\n" ++
+ "<thead>\n") ++
"<tr><th>Num</th><th>Module</th><th>Group</th>" ++
"<th>Case</th><th>Log</th><th>Time</th><th>Result</th>" ++
"<th>Comment</th></tr>\n</thead>\n<tbody>\n",
@@ -1711,6 +1722,12 @@ start_log_file() ->
test_server_io:set_fd(html, Html),
test_server_io:set_fd(unexpected_io, Unexpected),
+ %% we must assume the redirection file (to the latest suite index) can
+ %% be stored on the level above the log directory of the current test
+ TopDir = filename:dirname(get(test_server_framework_logdir)),
+ RedirectLink = filename:join(TopDir, ?suitelog_latest_name ++ ?html_ext),
+ make_html_link(RedirectLink, HtmlName, redirect),
+
make_html_link(filename:absname(?last_test ++ ?html_ext),
HtmlName, filename:basename(Dir)),
LinkName = filename:join(Dir, ?last_link),
@@ -1739,11 +1756,18 @@ make_html_link(LinkName, Target, Explanation) ->
false ->
"file:" ++ uri_encode(Target)
end,
- H = [html_header(Explanation),
- "<h1>Last test</h1>\n"
- "<a href=\"",Href,"\">",Explanation,"</a>\n"
- "</body>\n</html>\n"],
+ H = if Explanation == redirect ->
+ Meta = ["<meta http-equiv=\"refresh\" "
+ "content=\"0; url=", Href, "\" />\n"],
+ [html_header("redirect", Meta), "</html>\n"];
+ true ->
+ [html_header(Explanation),
+ "<h1>Last test</h1>\n"
+ "<a href=\"",Href,"\">",Explanation,"</a>\n"
+ "</body>\n</html>\n"]
+ end,
ok = write_html_file(LinkName, H).
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% start_minor_log_file(Mod, Func, ParallelTC) -> AbsName
@@ -1762,18 +1786,14 @@ make_html_link(LinkName, Target, Explanation) ->
start_minor_log_file(Mod, Func, ParallelTC) ->
MFA = {Mod,Func,1},
LogDir = get(test_server_log_dir_base),
- Name0 = lists:flatten(io_lib:format("~w.~w~ts", [Mod,Func,?html_ext])),
- Name = downcase(Name0),
+ Name = minor_log_file_name(Mod,Func),
AbsName = filename:join(LogDir, Name),
case (ParallelTC orelse (element(1,file:read_file_info(AbsName))==ok)) of
false -> %% normal case, unique name
start_minor_log_file1(Mod, Func, LogDir, AbsName, MFA);
true -> %% special case, duplicate names
Tag = test_server_sup:unique_name(),
- Name1_0 =
- lists:flatten(io_lib:format("~w.~w.~ts~ts", [Mod,Func,Tag,
- ?html_ext])),
- Name1 = downcase(Name1_0),
+ Name1 = minor_log_file_name(Mod,Func,[$.|Tag]),
AbsName1 = filename:join(LogDir, Name1),
start_minor_log_file1(Mod, Func, LogDir, AbsName1, MFA)
end.
@@ -1784,7 +1804,7 @@ start_minor_log_file1(Mod, Func, LogDir, AbsName, MFA) ->
put(test_server_minor_fd, Fd),
test_server_gl:set_minor_fd(group_leader(), Fd, MFA),
- TestDescr = io_lib:format("Test ~w:~w result", [Mod,Func]),
+ TestDescr = io_lib:format("Test ~w:~tw result", [Mod,Func]),
{Header,Footer} =
case test_server_sup:framework_call(get_html_wrapper,
[TestDescr,false,
@@ -1825,13 +1845,13 @@ start_minor_log_file1(Mod, Func, LogDir, AbsName, MFA) ->
lists:member(no_src, get(test_server_logopts))} of
{true,false} ->
print(Lev, ["$tc_html",
- Info ++ "<a href=\"~ts#~ts\">~w:~w/~w</a> "
+ Info ++ "<a href=\"~ts#~ts\">~w:~tw/~w</a> "
"(click for source code)\n"],
[uri_encode(SrcListing),
uri_encode(atom_to_list(Func)++"-1",utf8),
Mod,Func,Arity]);
_ ->
- print(Lev, ["$tc_html",Info ++ "~w:~w/~w\n"], [Mod,Func,Arity])
+ print(Lev, ["$tc_html",Info ++ "~w:~tw/~w\n"], [Mod,Func,Arity])
end
end,
@@ -1845,6 +1865,19 @@ stop_minor_log_file() ->
ok = file:close(Fd),
put(test_server_minor_fd, undefined).
+minor_log_file_name(Mod,Func) ->
+ minor_log_file_name(Mod,Func,"").
+minor_log_file_name(Mod,Func,Tag) ->
+ Name =
+ downcase(
+ lists:flatten(
+ io_lib:format("~w.~tw~s~s", [Mod,Func,Tag,?html_ext]))),
+ Ok = file:native_name_encoding()==utf8
+ orelse io_lib:printable_latin1_list(Name),
+ if Ok -> Name;
+ true -> exit({error,unicode_name_on_latin1_file_system})
+ end.
+
downcase(S) -> downcase(S, []).
downcase([Uc|Rest], Result) when $A =< Uc, Uc =< $Z ->
downcase(Rest, [Uc-$A+$a|Result]);
@@ -2030,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} =
@@ -2107,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]
@@ -2154,6 +2195,7 @@ do_add_end_per_suite_and_skip(LastMod, LastRef, Mod, FwMod) ->
%% Runs the specified tests, then displays/logs the summary.
run_test_cases(TestSpec, Config, TimetrapData) ->
+ test_server:init_valgrind(),
case lists:member(no_src, get(test_server_logopts)) of
true ->
ok;
@@ -2269,7 +2311,7 @@ run_test_cases(TestSpec, Config, TimetrapData) ->
%% test_server_io:print_buffered/1 to print the data. To help with this,
%% two variables in the process dictionary are used:
%% 'test_server_common_io_handler' and 'test_server_queued_io'. The values
-%% are set to as follwing:
+%% are set to as following:
%%
%% Value Meaning
%% ----- -------
@@ -2601,7 +2643,7 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0,
NumStr ->
%% Ex: "123 456 789" or "123,456,789" -> {123,456,789}
list_to_tuple([list_to_integer(NS) ||
- NS <- string:tokens(NumStr, [$ ,$:,$,])])
+ NS <- string:lexemes(NumStr, [$ ,$:,$,])])
end,
{shuffle_cases(Ref, Cs0, UseSeed),{shuffle,UseSeed}}
end;
@@ -2736,7 +2778,7 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0,
TimetrapData, Mode, Status2);
Bad ->
print(minor,
- "~n*** ~w returned bad elements in Config: ~p.~n",
+ "~n*** ~tw returned bad elements in Config: ~tp.~n",
[Func,Bad]),
Reason = {failed,{Mod,init_per_suite,bad_return}},
Cases2 = skip_cases_upto(Ref, Cases, Reason, conf, CurrMode,
@@ -2752,9 +2794,9 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0,
stop_minor_log_file(),
run_test_cases_loop(Cases, [NewCfg|Config], TimetrapData, Mode, Status2);
{_,{framework_error,{FwMod,FwFunc},Reason},_} ->
- print(minor, "~n*** ~w failed in ~w. Reason: ~p~n",
+ print(minor, "~n*** ~w failed in ~tw. Reason: ~tp~n",
[FwMod,FwFunc,Reason]),
- print(1, "~w failed in ~w. Reason: ~p~n", [FwMod,FwFunc,Reason]),
+ print(1, "~w failed in ~tw. Reason: ~tp~n", [FwMod,FwFunc,Reason]),
exit(framework_error);
{_,Fail,_} when element(1,Fail) == 'EXIT';
element(1,Fail) == timetrap_timeout;
@@ -2763,7 +2805,7 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0,
{Cases2,Config1,Status3} =
if StartConf ->
ReportAbortRepeat(failed),
- print(minor, "~n*** ~w failed.~n"
+ print(minor, "~n*** ~tw failed.~n"
" Skipping all cases.", [Func]),
Reason = {failed,{Mod,Func,Fail}},
{skip_cases_upto(Ref, Cases, Reason, conf, CurrMode,
@@ -2786,7 +2828,7 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0,
{Cases2,Config1,Status3} =
if StartConf ->
ReportAbortRepeat(auto_skipped),
- print(minor, "~n*** ~w auto skipped.~n"
+ print(minor, "~n*** ~tw auto skipped.~n"
" Skipping all cases.", [Func]),
{skip_cases_upto(Ref, Cases, SkipReason, conf, CurrMode,
auto_skip_case),
@@ -2803,7 +2845,7 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0,
{_,{Skip,Reason},_} when StartConf and ((Skip==skip) or (Skip==skipped)) ->
ReportAbortRepeat(skipped),
- print(minor, "~n*** ~w skipped.~n"
+ print(minor, "~n*** ~tw skipped.~n"
" Skipping all cases.", [Func]),
set_io_buffering(IOHandler),
stop_minor_log_file(),
@@ -2813,7 +2855,7 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0,
delete_status(Ref, Status2));
{_,{skip_and_save,Reason,_SavedConfig},_} when StartConf ->
ReportAbortRepeat(skipped),
- print(minor, "~n*** ~w skipped.~n"
+ print(minor, "~n*** ~tw skipped.~n"
" Skipping all cases.", [Func]),
set_io_buffering(IOHandler),
stop_minor_log_file(),
@@ -2878,7 +2920,7 @@ run_test_cases_loop([{make,Ref,{Mod,Func,Args}}|Cases0], Config, TimetrapData,
Mode, Status) ->
case run_test_case(Ref, 0, Mod, Func, Args, skip_init, TimetrapData) of
{_,Why={'EXIT',_},_} ->
- print(minor, "~n*** ~w failed.~n"
+ print(minor, "~n*** ~tw failed.~n"
" Skipping all cases.", [Func]),
Reason = {failed,{Mod,Func,Why}},
Cases = skip_cases_upto(Ref, Cases0, Reason, conf, Mode,
@@ -2894,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
@@ -2906,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 ->
@@ -2916,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
@@ -2932,9 +3005,9 @@ run_test_cases_loop([{Mod,Func,Args}|Cases], Config, TimetrapData, Mode, Status)
RunInit, TimetrapData, Mode) of
%% callback to framework module failed, exit immediately
{_,{framework_error,{FwMod,FwFunc},Reason},_} ->
- print(minor, "~n*** ~w failed in ~w. Reason: ~p~n",
+ print(minor, "~n*** ~w failed in ~tw. Reason: ~tp~n",
[FwMod,FwFunc,Reason]),
- print(1, "~w failed in ~w. Reason: ~p~n", [FwMod,FwFunc,Reason]),
+ print(1, "~w failed in ~tw. Reason: ~tp~n", [FwMod,FwFunc,Reason]),
stop_minor_log_file(),
exit(framework_error);
%% sequential execution of test case finished
@@ -2943,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*** ~w failed.~n"
+ 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
@@ -2981,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
@@ -3105,8 +3185,8 @@ print_conf_time(ConfTime) ->
print_props([]) ->
ok;
print_props(Props) ->
- print(major, "=group_props ~p", [Props]),
- print(minor, "Group properties: ~p~n", [Props]).
+ print(major, "=group_props ~tp", [Props]),
+ print(minor, "Group properties: ~tp~n", [Props]).
%% repeat N times: {repeat,N}
%% repeat N times or until all successful: {repeat_until_all_ok,N}
@@ -3253,13 +3333,13 @@ skip_case1(Type, CaseNum, Mod, Func, Comment, Mode) ->
ResultCol = if Type == auto -> ?auto_skip_color;
Type == user -> ?user_skip_color
end,
- print(major, "~n=case ~w:~w", [Mod,Func]),
+ print(major, "~n=case ~w:~tw", [Mod,Func]),
GroupName = case get_name(Mode) of
undefined ->
"";
GrName ->
GrName1 = cast_to_list(GrName),
- print(major, "=group_props ~p", [[{name,GrName1}]]),
+ print(major, "=group_props ~tp", [[{name,GrName1}]]),
GrName1
end,
print(major, "=started ~s", [lists:flatten(timestamp_get(""))]),
@@ -3270,11 +3350,12 @@ skip_case1(Type, CaseNum, Mod, Func, Comment, Mode) ->
print(major, "=result skipped: ~ts", [Comment1])
end,
if CaseNum == 0 ->
- print(2,"*** Skipping ~w ***", [{Mod,Func}]);
+ print(2,"*** Skipping ~tw ***", [{Mod,Func}]);
true ->
- print(2,"*** Skipping test case #~w ~w ***", [CaseNum,{Mod,Func}])
+ print(2,"*** Skipping test case #~w ~tw ***", [CaseNum,{Mod,Func}])
end,
- TR = xhtml("<tr valign=\"top\">", ["<tr class=\"",odd_or_even(),"\">"]),
+ TR = xhtml("<tr valign=\"top\">",
+ "<tr class=\"" ++ odd_or_even() ++ "\">"),
GroupName = case get_name(Mode) of
undefined -> "";
Name -> cast_to_list(Name)
@@ -3283,7 +3364,7 @@ skip_case1(Type, CaseNum, Mod, Func, Comment, Mode) ->
TR ++ "<td>" ++ Col0 ++ "~ts" ++ Col1 ++ "</td>"
"<td>" ++ Col0 ++ "~w" ++ Col1 ++ "</td>"
"<td>" ++ Col0 ++ "~ts" ++ Col1 ++ "</td>"
- "<td>" ++ Col0 ++ "~w" ++ Col1 ++ "</td>"
+ "<td>" ++ Col0 ++ "~tw" ++ Col1 ++ "</td>"
"<td>" ++ Col0 ++ "< >" ++ Col1 ++ "</td>"
"<td>" ++ Col0 ++ "0.000s" ++ Col1 ++ "</td>"
"<td><font color=\"~ts\">SKIPPED</font></td>"
@@ -3419,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]).
@@ -3504,7 +3595,7 @@ wait_and_resend(Ref, [{_,CurrPid,CaseNum,Mod,Func}|Ps] = Cases, Ok,Skip,Fail) ->
{'EXIT',CurrPid,Reason} when Reason /= normal ->
%% unexpected termination of test case process
{value,{_,_,CaseNum,Mod,Func}} = lists:keysearch(CurrPid, 2, Cases),
- print(1, "Error! Process for test case #~w (~w:~w) died! Reason: ~p",
+ print(1, "Error! Process for test case #~w (~w:~tw) died! Reason: ~tp",
[CaseNum, Mod, Func, Reason]),
exit({unexpected_termination,{CaseNum,Mod,Func},{CurrPid,Reason}})
end;
@@ -3643,7 +3734,7 @@ handle_io_and_exits(Main, CurrPid, CaseNum, Mod, Func, Cases) ->
{'EXIT',TCPid,Reason} when Reason /= normal ->
test_server_io:print_buffered(CurrPid),
{value,{_,_,Num,M,F}} = lists:keysearch(TCPid, 2, Cases),
- print(1, "Error! Process for test case #~w (~w:~w) died! Reason: ~p",
+ print(1, "Error! Process for test case #~w (~w:~tw) died! Reason: ~tp",
[Num, M, F, Reason]),
exit({unexpected_termination,{Num,M,F},{TCPid,Reason}})
end.
@@ -3694,6 +3785,7 @@ run_test_case(Ref, Num, Mod, Func, Args, RunInit, TimetrapData, Mode) ->
spawn_link(
fun() ->
process_flag(trap_exit, true),
+ ct_util:mark_process(),
_ = [put(Key, Val) || {Key,Val} <- Dictionary],
set_io_buffering({tc,Main}),
run_test_case1(Ref, Num, Mod, Func, Args, RunInit,
@@ -3716,7 +3808,7 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit,
end,
TSDir = get(test_server_dir),
- print(major, "=case ~w:~w", [Mod, Func]),
+ print(major, "=case ~w:~tw", [Mod, Func]),
MinorName = start_minor_log_file(Mod, Func, self() /= Main),
MinorBase = filename:basename(MinorName),
print(major, "=logfile ~ts", [filename:basename(MinorName)]),
@@ -3763,8 +3855,8 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit,
end,
print(minor,
- escape_chars(io_lib:format("Config value:\n\n ~tp\n", [Args2Print])),
- []),
+ "~ts",
+ [escape_chars(io_lib:format("Config value:\n\n ~tp\n", [Args2Print]))]),
print(minor, "Current directory is ~tp\n", [Cwd]),
GrNameStr = case GrName of
@@ -3773,12 +3865,12 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit,
end,
print(major, "=started ~s", [lists:flatten(timestamp_get(""))]),
{{Col0,Col1},Style} = get_font_style((RunInit==run_init), Mode),
- TR = xhtml("<tr valign=\"top\">", ["<tr class=\"",odd_or_even(),"\">"]),
+ TR = xhtml("<tr valign=\"top\">", "<tr class=\"" ++ odd_or_even() ++ "\">"),
EncMinorBase = uri_encode(MinorBase),
print(html, TR ++ "<td>" ++ Col0 ++ "~ts" ++ Col1 ++ "</td>"
"<td>" ++ Col0 ++ "~w" ++ Col1 ++ "</td>"
"<td>" ++ Col0 ++ "~ts" ++ Col1 ++ "</td>"
- "<td><a href=\"~ts\">~w</a></td>"
+ "<td><a href=\"~ts\">~tw</a></td>"
"<td><a href=\"~ts#top\">&lt;</a> <a href=\"~ts#end\">&gt;</a></td>",
[num2str(Num),fw_name(Mod),GrNameStr,EncMinorBase,Func,
EncMinorBase,EncMinorBase]),
@@ -3787,7 +3879,7 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit,
%% run the test case
{Result,DetectedFail,ProcsBefore,ProcsAfter} =
- run_test_case_apply(Mod, Func, [UpdatedArgs], GrName,
+ run_test_case_apply(Num, Mod, Func, [UpdatedArgs], GrName,
RunInit, TimetrapData),
{Time,RetVal,Loc,Opts,Comment} =
case Result of
@@ -3798,7 +3890,7 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit,
print(minor, "<a name=\"end\"></a>", [], internal_raw),
print(minor, "\n", [], internal_raw),
print_timestamp(minor, "Ended at "),
- print(major, "=ended ~s", [lists:flatten(timestamp_get(""))]),
+ print(major, "=ended ~s", [timestamp_get("")]),
do_unless_parallel(Main, fun() -> file:set_cwd(filename:dirname(TSDir)) end),
@@ -3808,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,
@@ -3894,13 +3990,13 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit,
{'EXIT',_} = Exit ->
print(minor,
"WARNING: There might be slavenodes left in the"
- " system. I tried to kill them, but I failed: ~p\n",
+ " system. I tried to kill them, but I failed: ~tp\n",
[Exit]);
[] -> ok;
List ->
print(minor, "WARNING: ~w slave nodes in system after test"++
"case. Tried to killed them.~n"++
- " Names:~p",
+ " Names:~tp",
[length(List),List])
end;
false ->
@@ -3960,7 +4056,7 @@ progress(skip, CaseNum, Mod, Func, GrName, Loc, Reason, Time,
if_auto_skip(Reason,
fun() -> {?auto_skip_color,auto_skip,auto_skipped} end,
fun() -> {?user_skip_color,skip,skipped} end),
- print(major, "=result ~w: ~p", [ReportTag,Reason1]),
+ print(major, "=result ~w: ~tp", [ReportTag,Reason1]),
print(1, "*** SKIPPED ~ts ***",
[get_info_str(Mod,Func, CaseNum, get(test_server_cases))]),
test_server_sup:framework_call(report, [tc_done,{Mod,{Func,GrName},
@@ -3969,11 +4065,12 @@ progress(skip, CaseNum, Mod, Func, GrName, Loc, Reason, Time,
true -> "~w"
end, [Time]),
ReasonStr = escape_chars(reason_to_string(Reason1)),
- ReasonStr1 = lists:flatten([string:strip(S,left) ||
- S <- string:tokens(ReasonStr,[$\n])]),
+ ReasonStr1 = lists:flatten([string:trim(S,leading,"\s") ||
+ S <- string:lexemes(ReasonStr,[$\n])]),
+ ReasonLength = string:length(ReasonStr1),
ReasonStr2 =
- if length(ReasonStr1) > 80 ->
- string:substr(ReasonStr1, 1, 77) ++ "...";
+ if ReasonLength > 80 ->
+ string:slice(ReasonStr1, 0, 77) ++ "...";
true ->
ReasonStr1
end,
@@ -3993,7 +4090,7 @@ progress(skip, CaseNum, Mod, Func, GrName, Loc, Reason, Time,
progress(failed, CaseNum, Mod, Func, GrName, Loc, timetrap_timeout, T,
Comment0, {St0,St1}) ->
- print(major, "=result failed: timeout, ~p", [Loc]),
+ print(major, "=result failed: timeout, ~tp", [Loc]),
print(1, "*** FAILED ~ts ***",
[get_info_str(Mod,Func, CaseNum, get(test_server_cases))]),
test_server_sup:framework_call(report,
@@ -4019,7 +4116,7 @@ progress(failed, CaseNum, Mod, Func, GrName, Loc, timetrap_timeout, T,
progress(failed, CaseNum, Mod, Func, GrName, Loc, {testcase_aborted,Reason}, _T,
Comment0, {St0,St1}) ->
- print(major, "=result failed: testcase_aborted, ~p", [Loc]),
+ print(major, "=result failed: testcase_aborted, ~tp", [Loc]),
print(1, "*** FAILED ~ts ***",
[get_info_str(Mod,Func, CaseNum, get(test_server_cases))]),
test_server_sup:framework_call(report,
@@ -4041,14 +4138,14 @@ progress(failed, CaseNum, Mod, Func, GrName, Loc, {testcase_aborted,Reason}, _T,
FormatLoc = test_server_sup:format_loc(Loc),
print(minor, "=== Location: ~ts", [FormatLoc]),
print(minor,
- escape_chars(io_lib:format("=== Reason: {testcase_aborted,~p}",
- [Reason])),
- []),
+ "~ts",
+ [escape_chars(io_lib:format("=== Reason: {testcase_aborted,~tp}",
+ [Reason]))]),
failed;
progress(failed, CaseNum, Mod, Func, GrName, unknown, Reason, Time,
Comment0, {St0,St1}) ->
- print(major, "=result failed: ~p, ~w", [Reason,unknown_location]),
+ print(major, "=result failed: ~tp, ~w", [Reason,unknown_location]),
print(1, "*** FAILED ~ts ***",
[get_info_str(Mod,Func, CaseNum, get(test_server_cases))]),
test_server_sup:framework_call(report, [tc_done,{Mod,{Func,GrName},
@@ -4056,12 +4153,13 @@ progress(failed, CaseNum, Mod, Func, GrName, unknown, Reason, Time,
TimeStr = io_lib:format(if is_float(Time) -> "~.3fs";
true -> "~w"
end, [Time]),
- ErrorReason = escape_chars(lists:flatten(io_lib:format("~p", [Reason]))),
- ErrorReason1 = lists:flatten([string:strip(S,left) ||
- S <- string:tokens(ErrorReason,[$\n])]),
+ ErrorReason = escape_chars(lists:flatten(io_lib:format("~tp", [Reason]))),
+ ErrorReason1 = lists:flatten([string:trim(S,leading,"\s") ||
+ S <- string:lexemes(ErrorReason,[$\n])]),
+ ErrorReasonLength = string:length(ErrorReason1),
ErrorReason2 =
- if length(ErrorReason1) > 63 ->
- string:substr(ErrorReason1, 1, 60) ++ "...";
+ if ErrorReasonLength > 63 ->
+ string:slice(ErrorReason1, 0, 60) ++ "...";
true ->
ErrorReason1
end,
@@ -4080,8 +4178,8 @@ progress(failed, CaseNum, Mod, Func, GrName, unknown, Reason, Time,
print(minor, "=== Location: ~w", [unknown]),
{FStr,FormattedReason} = format_exception(Reason),
print(minor,
- escape_chars(io_lib:format("=== Reason: " ++ FStr, [FormattedReason])),
- []),
+ "~ts",
+ [escape_chars(io_lib:format("=== Reason: " ++ FStr, [FormattedReason]))]),
failed;
progress(failed, CaseNum, Mod, Func, GrName, Loc, Reason, Time,
@@ -4093,7 +4191,7 @@ progress(failed, CaseNum, Mod, Func, GrName, Loc, Reason, Time,
end;
true -> {Loc,Loc}
end,
- print(major, "=result failed: ~p, ~p", [Reason,LocMaj]),
+ print(major, "=result failed: ~tp, ~tp", [Reason,LocMaj]),
print(1, "*** FAILED ~ts ***",
[get_info_str(Mod,Func, CaseNum, get(test_server_cases))]),
test_server_sup:framework_call(report, [tc_done,{Mod,{Func,GrName},
@@ -4115,8 +4213,9 @@ progress(failed, CaseNum, Mod, Func, GrName, Loc, Reason, Time,
FormatLoc = test_server_sup:format_loc(LocMin),
print(minor, "=== Location: ~ts", [FormatLoc]),
{FStr,FormattedReason} = format_exception(Reason),
- print(minor, "=== Reason: " ++
- escape_chars(io_lib:format(FStr, [FormattedReason])), []),
+ print(minor, "~ts",
+ ["=== Reason: " ++
+ escape_chars(io_lib:format(FStr, [FormattedReason]))]),
failed;
progress(ok, _CaseNum, Mod, Func, GrName, _Loc, RetVal, Time,
@@ -4149,8 +4248,8 @@ progress(ok, _CaseNum, Mod, Func, GrName, _Loc, RetVal, Time,
"~ts</tr>\n",
[TimeStr,Comment]),
print(minor,
- escape_chars(io_lib:format("=== Returned value: ~tp", [RetVal])),
- []),
+ "~ts",
+ [escape_chars(io_lib:format("=== Returned value: ~tp", [RetVal]))]),
ok.
%%--------------------------------------------------------------------
@@ -4236,7 +4335,7 @@ update_skip_counters(Pat, {US,AS}) ->
Result.
get_info_str(Mod,Func, 0, _Cases) ->
- io_lib:format("~w", [{Mod,Func}]);
+ io_lib:format("~tw", [{Mod,Func}]);
get_info_str(_Mod,_Func, CaseNum, unknown) ->
"test case " ++ integer_to_list(CaseNum);
get_info_str(_Mod,_Func, CaseNum, Cases) ->
@@ -4251,11 +4350,11 @@ print_if_known(Known, {SK,AK}, {SU,AU}) ->
to_string(Term) when is_list(Term) ->
case (catch io_lib:format("~ts", [Term])) of
- {'EXIT',_} -> lists:flatten(io_lib:format("~p", [Term]));
+ {'EXIT',_} -> lists:flatten(io_lib:format("~tp", [Term]));
String -> lists:flatten(String)
end;
to_string(Term) ->
- lists:flatten(io_lib:format("~p", [Term])).
+ lists:flatten(io_lib:format("~tp", [Term])).
get_last_loc(Loc) when is_tuple(Loc) ->
Loc;
@@ -4327,14 +4426,14 @@ format_exception(Reason={_Error,Stack}) when is_list(Stack) ->
undefined ->
case application:get_env(test_server, format_exception) of
{ok,false} ->
- {"~p",Reason};
+ {"~tp",Reason};
_ ->
do_format_exception(Reason)
end;
FW ->
case application:get_env(FW, format_exception) of
{ok,false} ->
- {"~p",Reason};
+ {"~tp",Reason};
_ ->
do_format_exception(Reason)
end
@@ -4345,19 +4444,19 @@ format_exception(Error) ->
do_format_exception(Reason={Error,Stack}) ->
StackFun = fun(_, _, _) -> false end,
PF = fun(Term, I) ->
- io_lib:format("~." ++ integer_to_list(I) ++ "p", [Term])
+ io_lib:format("~." ++ integer_to_list(I) ++ "tp", [Term])
end,
- case catch lib:format_exception(1, error, Error, Stack, StackFun, PF) of
- {'EXIT',_} ->
- {"~p",Reason};
+ case catch erl_error:format_exception(1, error, Error, Stack, StackFun, PF, utf8) of
+ {'EXIT',_R} ->
+ {"~tp",Reason};
Formatted ->
- Formatted1 = re:replace(Formatted, "exception error: ", "", [{return,list}]),
+ Formatted1 = re:replace(Formatted, "exception error: ", "", [{return,list},unicode]),
{"~ts",lists:flatten(Formatted1)}
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% run_test_case_apply(Mod, Func, Args, Name, RunInit,
+%% run_test_case_apply(CaseNum, Mod, Func, Args, Name, RunInit,
%% TimetrapData) ->
%% {{Time,RetVal,Loc,Opts,Comment},DetectedFail,ProcessesBefore,ProcessesAfter} |
%% {{died,Reason,unknown,Comment},DetectedFail,ProcessesBefore,ProcessesAfter}
@@ -4371,9 +4470,9 @@ do_format_exception(Reason={Error,Stack}) ->
%% ProcessesBefore = ProcessesAfter = integer()
%%
-run_test_case_apply(Mod, Func, Args, Name, RunInit,
+run_test_case_apply(CaseNum, Mod, Func, Args, Name, RunInit,
TimetrapData) ->
- test_server:run_test_case_apply({Mod,Func,Args,Name,RunInit,
+ test_server:run_test_case_apply({CaseNum,Mod,Func,Args,Name,RunInit,
TimetrapData}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -4457,7 +4556,7 @@ format(Detail, Format, Args) ->
Str =
case catch io_lib:format(Format, Args) of
{'EXIT',_} ->
- io_lib:format("illegal format; ~p with args ~p.\n",
+ io_lib:format("illegal format; ~tp with args ~tp.\n",
[Format,Args]);
Valid -> Valid
end,
@@ -4507,7 +4606,7 @@ timestamp_get(Leader) ->
timestamp_get_internal(Leader, Format) ->
{YY,MM,DD,H,M,S} = time_get(),
- io_lib:format(Format, [Leader,YY,MM,DD,H,M,S]).
+ lists:flatten(io_lib:format(Format, [Leader,YY,MM,DD,H,M,S])).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% time_get() -> {YY,MM,DD,H,M,S}
@@ -4763,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,_,_} ->
@@ -4853,7 +4960,7 @@ collect_files(Dir, Pattern, St, Mode) ->
Wc = filename:join([Dir1,Pattern++"{.erl,"++code:objfile_extension()++"}"]),
case catch filelib:wildcard(Wc) of
{'EXIT', Reason} ->
- io:format("Could not collect files: ~p~n", [Reason]),
+ io:format("Could not collect files: ~tp~n", [Reason]),
{error,{collect_fail,Dir,Pattern}};
Files ->
%% convert to module names and remove duplicates
@@ -4871,7 +4978,7 @@ collect_files(Dir, Pattern, St, Mode) ->
fullname_to_mod(Path) when is_list(Path) ->
%% If this is called with a binary, then we are probably in +fnu
%% mode and have found a beam file with name encoded as latin1. We
- %% will let this crash since it can not work to load such a module
+ %% will let this crash since it cannot work to load such a module
%% anyway. It should be removed or renamed!
list_to_atom(filename:rootname(filename:basename(Path))).
@@ -4897,13 +5004,13 @@ check_deny_req({Req,Val}, DenyList) ->
%%io:format("ValCheck ~p=~p in ~p\n", [Req,Val,DenyList]),
case lists:keysearch(Req, 1, DenyList) of
{value,{_Req,DenyVal}} when Val >= DenyVal ->
- {denied,io_lib:format("Requirement ~p=~p", [Req,Val])};
+ {denied,io_lib:format("Requirement ~tp=~tp", [Req,Val])};
_ ->
check_deny_req(Req, DenyList)
end;
check_deny_req(Req, DenyList) ->
case lists:member(Req, DenyList) of
- true -> {denied,io_lib:format("Requirement ~p", [Req])};
+ true -> {denied,io_lib:format("Requirement ~tp", [Req])};
false -> granted
end.
@@ -5004,7 +5111,7 @@ get_target_info() ->
start_node(Name, Type, Options) ->
T = 10 * ?ACCEPT_TIMEOUT * test_server:timetrap_scale_factor(),
- format(minor, "Attempt to start ~w node ~p with options ~p",
+ format(minor, "Attempt to start ~w node ~tp with options ~tp",
[Type, Name, Options]),
case controller_call({start_node,Name,Type,Options}, T) of
{{ok,Nodename}, Host, Cmd, Info, Warning} ->
@@ -5026,16 +5133,16 @@ start_node(Name, Type, Options) ->
{fail,{Ret, Host, Cmd}} ->
format(minor,
"Failed to start node ~tp on ~tp with command: ~ts~n"
- "Reason: ~p",
+ "Reason: ~tp",
[Name, Host, Cmd, Ret]),
{fail,Ret};
{Ret, undefined, undefined} ->
- format(minor, "Failed to start node ~tp: ~p", [Name,Ret]),
+ format(minor, "Failed to start node ~tp: ~tp", [Name,Ret]),
Ret;
{Ret, Host, Cmd} ->
format(minor,
"Failed to start node ~tp on ~tp with command: ~ts~n"
- "Reason: ~p",
+ "Reason: ~tp",
[Name, Host, Cmd, Ret]),
Ret
end.
@@ -5132,10 +5239,10 @@ display_info([Pid|T], R, M) ->
Other
end,
Reds = fetch(reductions, Info),
- LM = length(fetch(messages, Info)),
+ LM = fetch(message_queue_len, Info),
pformat(io_lib:format("~w", [Pid]),
- io_lib:format("~w", [Call]),
- io_lib:format("~w", [Curr]), Reds, LM),
+ io_lib:format("~tw", [Call]),
+ io_lib:format("~tw", [Curr]), Reds, LM),
display_info(T, R+Reds, M + LM)
end;
display_info([], R, M) ->
@@ -5249,11 +5356,11 @@ read_cover_file(CoverFile) ->
case check_cover_file(List, [], [], []) of
{ok,Exclude,Include,Cross} -> {Exclude,Include,Cross};
error ->
- io:fwrite("Faulty format of CoverFile ~p\n", [CoverFile]),
+ io:fwrite("Faulty format of CoverFile ~tp\n", [CoverFile]),
{[],[],[]}
end;
{error,Reason} ->
- io:fwrite("Can't read CoverFile ~ts\nReason: ~p\n",
+ io:fwrite("Can't read CoverFile ~ts\nReason: ~tp\n",
[CoverFile,Reason]),
{[],[],[]}
end.
@@ -5521,8 +5628,8 @@ write_coverlog_header(CoverLog) ->
case catch io:put_chars(CoverLog,html_header("Coverage results")) of
{'EXIT',Reason} ->
io:format("\n\nERROR: Could not write normal heading in coverlog.\n"
- "CoverLog: ~w\n"
- "Reason: ~p\n",
+ "CoverLog: ~tw\n"
+ "Reason: ~tp\n",
[CoverLog,Reason]),
io:format(CoverLog,"<html><body>\n", []);
_ ->
@@ -5645,6 +5752,13 @@ html_header(Title) ->
"<body bgcolor=\"white\" text=\"black\" "
"link=\"blue\" vlink=\"purple\" alink=\"red\">\n"].
+html_header(Title, Meta) ->
+ ["<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n"
+ "<!-- autogenerated by '", atom_to_list(?MODULE), "'. -->\n"
+ "<html>\n"
+ "<head>\n"
+ "<title>", Title, "</title>\n"] ++ Meta ++ ["</head>\n"].
+
open_html_file(File) ->
open_utf8_file(File).
@@ -5697,7 +5811,7 @@ uri_encode_comp([Char|Chars],Encoding) ->
Reserved = sets:is_element(Char, reserved()),
case (Char>127 andalso Encoding==latin1) orelse Reserved of
true ->
- [ $% | http_util:integer_to_hexlist(Char)] ++
+ [ $% | integer_to_list(Char, 16)] ++
uri_encode_comp(Chars,Encoding);
false ->
[Char | uri_encode_comp(Chars,Encoding)]
@@ -5719,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_gl.erl b/lib/common_test/src/test_server_gl.erl
index 4845b86dd3..24dd5cd54c 100644
--- a/lib/common_test/src/test_server_gl.erl
+++ b/lib/common_test/src/test_server_gl.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2012-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2012-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.
@@ -132,6 +132,7 @@ set_props(GL, PropList) ->
%%% Internal functions.
init([TSIO]) ->
+ ct_util:mark_process(group_leader),
EscChars = case application:get_env(test_server, esc_chars) of
{ok,ECBool} -> ECBool;
_ -> true
@@ -173,8 +174,8 @@ handle_info({'DOWN',Ref,process,_,Reason}=D, #st{minor_monitor=Ref}=St) ->
case Reason of
normal -> ok;
_ ->
- Data = io_lib:format("=== WARNING === TC: ~w\n"
- "Got down from minor Fd ~w: ~w\n\n",
+ Data = io_lib:format("=== WARNING === TC: ~tw\n"
+ "Got down from minor Fd ~w: ~tw\n\n",
[St#st.tc,St#st.minor,D]),
test_server_io:print_unexpected(Data)
end,
@@ -319,7 +320,7 @@ output(Level, Str, Sender, From, St) when is_atom(Level) ->
output_to_file(Level, dress_output(Str, Sender, St), From, St).
output_to_file(minor, Data0, From, #st{tc={M,F,A},minor=none}) ->
- Data = [io_lib:format("=== ~w:~w/~w\n", [M,F,A]),Data0],
+ Data = [io_lib:format("=== ~w:~tw/~w\n", [M,F,A]),Data0],
test_server_io:print(From, unexpected_io, Data),
ok;
output_to_file(minor, Data, From, #st{tc=TC,minor=Fd}) ->
@@ -328,10 +329,10 @@ output_to_file(minor, Data, From, #st{tc=TC,minor=Fd}) ->
catch
Type:Reason ->
Data1 =
- [io_lib:format("=== ERROR === TC: ~w\n"
+ [io_lib:format("=== ERROR === TC: ~tw\n"
"Failed to write to minor Fd: ~w\n"
"Type: ~w\n"
- "Reason: ~w\n",
+ "Reason: ~tw\n",
[TC,Fd,Type,Reason]),
Data,"\n"],
test_server_io:print(From, unexpected_io, Data1)
diff --git a/lib/common_test/src/test_server_io.erl b/lib/common_test/src/test_server_io.erl
index fdabf17b08..ef31521950 100644
--- a/lib/common_test/src/test_server_io.erl
+++ b/lib/common_test/src/test_server_io.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2012-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2012-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.
@@ -184,6 +184,7 @@ reset_state() ->
init([]) ->
process_flag(trap_exit, true),
+ ct_util:mark_process(),
Empty = gb_trees:empty(),
{ok,Shared} = test_server_gl:start_link(self()),
{ok,#st{fds=Empty,shared_gl=Shared,gls=gb_sets:empty(),
@@ -262,7 +263,7 @@ handle_call(reset_state, From, #st{phase=stopping,pending_ops=Ops}=St) ->
{Result,NewSt1}
end,
{noreply,St#st{pending_ops=[{From,Op}|Ops]}};
-handle_call(reset_state, _From, #st{fds=Fds,tags=Tags,gls=Gls,
+handle_call(reset_state, _From, #st{fds=Fds,tags=Tags,shared_gl=Shared0,gls=Gls,
offline_buffer=OfflineBuff}) ->
%% close open log files
lists:foreach(fun(Tag) ->
@@ -273,6 +274,7 @@ handle_call(reset_state, _From, #st{fds=Fds,tags=Tags,gls=Gls,
file:close(Fd)
end
end, Tags),
+ test_server_gl:stop(Shared0),
GlList = gb_sets:to_list(Gls),
_ = [test_server_gl:stop(GL) || GL <- GlList],
timer:sleep(100),
@@ -320,7 +322,7 @@ handle_call(finish, From, St) ->
handle_info({'EXIT',Pid,normal}, #st{gls=Gls0,stopping=From}=St) ->
Gls = gb_sets:delete_any(Pid, Gls0),
- case gb_sets:is_empty(Gls) andalso stopping =/= undefined of
+ case gb_sets:is_empty(Gls) andalso From =/= undefined of
true ->
%% No more group leaders left.
gen_server:reply(From, ok),
@@ -329,6 +331,9 @@ handle_info({'EXIT',Pid,normal}, #st{gls=Gls0,stopping=From}=St) ->
%% Wait for more group leaders to finish.
{noreply,St#st{gls=Gls,phase=stopping}}
end;
+handle_info({'EXIT',Pid,killed}, #st{gls=Gls0}=St) ->
+ %% forced termination of group leader
+ {noreply,St#st{gls=gb_sets:delete_any(Pid, Gls0)}};
handle_info({'EXIT',_Pid,Reason}, _St) ->
exit(Reason);
handle_info(stop_group_leaders, #st{gls=Gls}=St) ->
@@ -359,7 +364,7 @@ handle_info(kill_group_leaders, #st{gls=Gls,stopping=From,
end, St#st{phase=idle,pending_ops=[]}, Ops),
{noreply,St1};
handle_info(Other, St) ->
- io:format("Ignoring: ~p\n", [Other]),
+ io:format("Ignoring: ~tp\n", [Other]),
{noreply,St}.
terminate(_, _) ->
@@ -395,7 +400,7 @@ do_output(Tag, Str, Phase, #st{fds=Fds}=St) ->
none when Phase /= started ->
buffer;
none ->
- S = io_lib:format("\n*** ERROR: ~w, line ~w: No known '~p' log file\n",
+ S = io_lib:format("\n*** ERROR: ~w, line ~w: No known '~tp' log file\n",
[?MODULE,?LINE,Tag]),
do_output(stdout, [S,Str], Phase, St);
{value,Fd} ->
@@ -407,7 +412,7 @@ do_output(Tag, Str, Phase, #st{fds=Fds}=St) ->
end
catch _:Error ->
S = io_lib:format("\n*** ERROR: ~w, line ~w: Error writing to "
- "log file '~p': ~p\n",
+ "log file '~tp': ~tp\n",
[?MODULE,?LINE,Tag,Error]),
do_output(stdout, [S,Str], Phase, St)
end
diff --git a/lib/common_test/src/test_server_node.erl b/lib/common_test/src/test_server_node.erl
index 0b406c54cc..f77d5a4966 100644
--- a/lib/common_test/src/test_server_node.erl
+++ b/lib/common_test/src/test_server_node.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2018. 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.
@@ -18,11 +18,11 @@
%% %CopyrightEnd%
%%
-module(test_server_node).
--compile(r12).
+-compile(r20).
%%%
%%% The same compiled code for this module must be possible to load
-%%% in R12B and later.
+%%% in R16B and later.
%%%
%% Test Controller interface
@@ -237,23 +237,23 @@ print_trc(Out,{trace_ts,P,call,{M,F,A},C,Ts},N) ->
io:format(Out,
"~w: ~s~n"
"Process : ~w~n"
- "Call : ~w:~w/~w~n"
- "Arguments : ~p~n"
- "Caller : ~w~n~n",
+ "Call : ~w:~tw/~w~n"
+ "Arguments : ~tp~n"
+ "Caller : ~tw~n~n",
[N,ts(Ts),P,M,F,length(A),A,C]);
print_trc(Out,{trace_ts,P,call,{M,F,A},Ts},N) ->
io:format(Out,
"~w: ~s~n"
"Process : ~w~n"
- "Call : ~w:~w/~w~n"
- "Arguments : ~p~n~n",
+ "Call : ~w:~tw/~w~n"
+ "Arguments : ~tp~n~n",
[N,ts(Ts),P,M,F,length(A),A]);
print_trc(Out,{trace_ts,P,return_from,{M,F,A},R,Ts},N) ->
io:format(Out,
"~w: ~s~n"
"Process : ~w~n"
- "Return from : ~w:~w/~w~n"
- "Return value : ~p~n~n",
+ "Return from : ~w:~tw/~w~n"
+ "Return value : ~tp~n~n",
[N,ts(Ts),P,M,F,A,R]);
print_trc(Out,{drop,X},N) ->
io:format(Out,
@@ -263,7 +263,7 @@ print_trc(Out,Trace,N) ->
Ts = element(size(Trace),Trace),
io:format(Out,
"~w: ~s~n"
- "Trace : ~p~n~n",
+ "Trace : ~tp~n~n",
[N,ts(Ts),Trace]).
ts({_, _, Micro} = Now) ->
{{Y,M,D},{H,Min,S}} = calendar:now_to_local_time(Now),
@@ -315,9 +315,11 @@ start_node_peer(SlaveName, OptList, From, TI) ->
Prog0 = start_node_get_option_value(erl, OptList, default),
Prog = quote_progname(pick_erl_program(Prog0)),
Args =
- case string:str(SuppliedArgs,"-setcookie") of
- 0 -> "-setcookie " ++ TI#target_info.cookie ++ " " ++ SuppliedArgs;
- _ -> SuppliedArgs
+ case string:find(SuppliedArgs,"-setcookie") of
+ nomatch ->
+ "-setcookie " ++ TI#target_info.cookie ++ " " ++ SuppliedArgs;
+ _ ->
+ SuppliedArgs
end,
Cmd = lists:concat([Prog,
" -detached ",
@@ -580,7 +582,7 @@ kill_node(SI) ->
cast_to_list(X) when is_list(X) -> X;
cast_to_list(X) when is_atom(X) -> atom_to_list(X);
-cast_to_list(X) -> lists:flatten(io_lib:format("~w", [X])).
+cast_to_list(X) -> lists:flatten(io_lib:format("~tw", [X])).
%%% L contains elements of the forms
@@ -589,18 +591,27 @@ cast_to_list(X) -> lists:flatten(io_lib:format("~w", [X])).
%%% this
%%%
pick_erl_program(default) ->
- cast_to_list(lib:progname());
+ ct:get_progname();
pick_erl_program(L) ->
P = random_element(L),
case P of
{prog, S} ->
S;
{release, S} ->
+ clear_erl_aflags(),
find_release(S);
this ->
- cast_to_list(lib:progname())
+ ct:get_progname()
end.
+clear_erl_aflags() ->
+ %% When starting a node with a previous release, options in
+ %% ERL_AFLAGS could prevent the node from starting. For example,
+ %% if ERL_AFLAGS is set to "-emu_type lcnt", the node will only
+ %% start if the previous release happens to also have a lock
+ %% counter emulator installed (unlikely).
+ os:unsetenv("ERL_AFLAGS").
+
%% This is an attempt to distinguish between spaces in the program
%% path and spaces that separate arguments. The program is quoted to
%% allow spaces in the path.
@@ -609,10 +620,10 @@ pick_erl_program(L) ->
%% ({prog,String}) or if the -program switch to beam is used and
%% includes arguments (typically done by cerl in OTP test environment
%% in order to ensure that slave/peer nodes are started with the same
-%% emulator and flags as the test node. The return from lib:progname()
-%% could then typically be '/<full_path_to>/cerl -gcov').
+%% emulator and flags as the test node. The return from ct:get_progname()
+%% could then typically be "/<full_path_to>/cerl -gcov").
quote_progname(Progname) ->
- do_quote_progname(string:tokens(Progname," ")).
+ do_quote_progname(string:lexemes(Progname," ")).
do_quote_progname([Prog]) ->
"\""++Prog++"\"";
@@ -654,9 +665,19 @@ find_release({unix,linux}, Rel) ->
find_release(_, _) -> none.
find_rel_linux(Rel) ->
- case suse_release() of
- none -> [];
- SuseRel -> find_rel_suse(Rel, SuseRel)
+ try
+ case ubuntu_release() of
+ none -> none;
+ [UbuntuRel |_] -> throw(find_rel_ubuntu(Rel, UbuntuRel))
+ end,
+ case suse_release() of
+ none -> none;
+ SuseRel -> throw(find_rel_suse(Rel, SuseRel))
+ end,
+ []
+ catch
+ throw:Result ->
+ Result
end.
find_rel_suse(Rel, SuseRel) ->
@@ -692,7 +713,7 @@ find_rel_suse_2(Rel, RootWc) ->
case file:list_dir(RelDir) of
{ok,Dirs} ->
case lists:filter(fun(Dir) ->
- case re:run(Dir, Pat) of
+ case re:run(Dir, Pat, [unicode]) of
nomatch -> false;
_ -> true
end
@@ -733,6 +754,93 @@ suse_release(Fd) ->
end
end.
+find_rel_ubuntu(_Rel, UbuntuRel) when is_integer(UbuntuRel), UbuntuRel < 16 ->
+ [];
+find_rel_ubuntu(Rel, UbuntuRel) when is_integer(UbuntuRel) ->
+ Root = "/usr/local/otp/releases/ubuntu",
+ lists:foldl(fun (ChkUbuntuRel, Acc) ->
+ find_rel_ubuntu_aux1(Rel, Root++integer_to_list(ChkUbuntuRel))
+ ++ Acc
+ end,
+ [],
+ lists:seq(16, UbuntuRel)).
+
+find_rel_ubuntu_aux1(Rel, RootWc) ->
+ case erlang:system_info(wordsize) of
+ 4 ->
+ find_rel_ubuntu_aux2(Rel, RootWc++"_32");
+ 8 ->
+ find_rel_ubuntu_aux2(Rel, RootWc++"_64") ++
+ find_rel_ubuntu_aux2(Rel, RootWc++"_32")
+ end.
+
+find_rel_ubuntu_aux2(Rel, RootWc) ->
+ RelDir = filename:dirname(RootWc),
+ Pat = filename:basename(RootWc ++ "_" ++ Rel) ++ ".*",
+ case file:list_dir(RelDir) of
+ {ok,Dirs} ->
+ case lists:filter(fun(Dir) ->
+ case re:run(Dir, Pat, [unicode]) of
+ nomatch -> false;
+ _ -> true
+ end
+ end, Dirs) of
+ [] ->
+ [];
+ [R|_] ->
+ [filename:join([RelDir,R,"bin","erl"])]
+ end;
+ _ ->
+ []
+ end.
+
+ubuntu_release() ->
+ case file:open("/etc/lsb-release", [read]) of
+ {ok,Fd} ->
+ try
+ ubuntu_release(Fd, undefined, undefined)
+ after
+ file:close(Fd)
+ end;
+ {error,_} -> none
+ end.
+
+ubuntu_release(_Fd, DistrId, Rel) when DistrId /= undefined,
+ Rel /= undefined ->
+ Ubuntu = case DistrId of
+ "Ubuntu" -> true;
+ "ubuntu" -> true;
+ _ -> false
+ end,
+ case Ubuntu of
+ false -> none;
+ true -> Rel
+ end;
+ubuntu_release(Fd, DistroId, Rel) ->
+ case io:get_line(Fd, '') of
+ eof ->
+ none;
+ Line when is_list(Line) ->
+ case re:run(Line, "^DISTRIB_ID=(\\w+)$",
+ [{capture,all_but_first,list}]) of
+ {match,[NewDistroId]} ->
+ ubuntu_release(Fd, NewDistroId, Rel);
+ nomatch ->
+ case re:run(Line, "^DISTRIB_RELEASE=(\\d+(?:\\.\\d+)*)$",
+ [{capture,all_but_first,list}]) of
+ {match,[RelList]} ->
+ NewRel = lists:map(fun (N) ->
+ list_to_integer(N)
+ end,
+ string:lexemes(RelList, ".")),
+ ubuntu_release(Fd, DistroId, NewRel);
+ nomatch ->
+ ubuntu_release(Fd, DistroId, Rel)
+ end
+ end
+ end.
+
+
unpack(Bin) ->
{One,Term} = split_binary(Bin, 1),
case binary_to_list(One) of
@@ -747,6 +855,7 @@ unpack(Bin) ->
id(I) -> I.
print_data(Port) ->
+ ct_util:mark_process(),
receive
{Port, {data, Bytes}} ->
io:put_chars(Bytes),
diff --git a/lib/common_test/src/test_server_sup.erl b/lib/common_test/src/test_server_sup.erl
index 6922e01fcc..ab8066a88d 100644
--- a/lib/common_test/src/test_server_sup.erl
+++ b/lib/common_test/src/test_server_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1998-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1998-2018. 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.
@@ -56,6 +56,7 @@ timetrap(Timeout0, Scale, Pid) ->
timetrap(Timeout0, ReportTVal, Scale, Pid) ->
process_flag(priority, max),
+ ct_util:mark_process(),
Timeout = if not Scale -> Timeout0;
true -> test_server:timetrap_scale_factor() * Timeout0
end,
@@ -83,7 +84,7 @@ kill_the_process(Pid, Timeout0, TruncTO, ReportTVal) ->
"Testcase process ~w not "
"responding to timetrap "
"timeout:~n"
- " ~p.~n"
+ " ~tp.~n"
"Killing testcase...~n",
[Pid, Trap]),
exit(Pid, kill)
@@ -144,11 +145,11 @@ call_crash(Time,Crash,M,F,A) ->
{'EXIT',Pid,_Reason} when Crash==any ->
ok;
{'EXIT',Reason} ->
- test_server:format(12, "Wrong crash reason. Wanted ~p, got ~p.",
+ test_server:format(12, "Wrong crash reason. Wanted ~tp, got ~tp.",
[Crash, Reason]),
exit({wrong_crash_reason,Reason});
{'EXIT',Pid,Reason} ->
- test_server:format(12, "Wrong crash reason. Wanted ~p, got ~p.",
+ test_server:format(12, "Wrong crash reason. Wanted ~tp, got ~tp.",
[Crash, Reason]),
exit({wrong_crash_reason,Reason});
{'EXIT',OtherPid,Reason} when OldTrapExit == false ->
@@ -334,11 +335,11 @@ do_appup_tests(_, _Application, Up, Down, Modules) ->
ok ->
test_server:format(minor, "OK~n");
Error ->
- test_server:format(minor, "ERROR ~p~n", [Error]),
+ test_server:format(minor, "ERROR ~tp~n", [Error]),
test_server:fail(Error)
end;
Error ->
- test_server:format(minor, "ERROR ~p~n", [Error]),
+ test_server:format(minor, "ERROR ~tp~n", [Error]),
test_server:fail(Error)
end.
@@ -346,7 +347,7 @@ check_appup_clauses_plausible([], _Direction, _Modules) ->
ok;
check_appup_clauses_plausible([{Re, Instrs} | Rest], Direction, Modules)
when is_binary(Re) ->
- case re:compile(Re) of
+ case re:compile(Re,[unicode]) of
{ok, _} ->
case check_appup_instructions(Instrs, Direction, Modules) of
ok ->
@@ -557,7 +558,7 @@ check_dict(Dict, Reason) ->
[] ->
1; % All ok.
List ->
- io:format("** ~ts (~ts) ->~n~p~n",[Reason, Dict, List]),
+ io:format("** ~ts (~ts) ->~n~tp~n",[Reason, Dict, List]),
0
end.
@@ -566,7 +567,7 @@ check_dict_tolerant(Dict, Reason, Mode) ->
[] ->
1; % All ok.
List ->
- io:format("** ~ts (~ts) ->~n~p~n",[Reason, Dict, List]),
+ io:format("** ~ts (~ts) ->~n~tp~n",[Reason, Dict, List]),
case Mode of
pedantic ->
0;
@@ -646,7 +647,7 @@ append_files_to_logfile([File|Files]) ->
%% fail, but in that case it will throw an exception so that
%% we will be aware of the problem.
io:format(Fd, "Unable to write the crash dump "
- "to this file: ~p~n", [file:format_error(Error)])
+ "to this file: ~tp~n", [file:format_error(Error)])
end;
_Error ->
io:format(Fd, "Failed to read: ~ts\n", [File])
@@ -769,18 +770,19 @@ 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,
+ ct_util:mark_process(),
try apply(Mod,Func,Args) of
Result ->
Result
catch
exit:Why ->
EH(Why);
- error:Why ->
- EH({Why,erlang:get_stacktrace()});
+ error:Why:Stacktrace ->
+ EH({Why,Stacktrace});
throw:Why ->
EH(Why)
end;
@@ -802,9 +804,9 @@ format_loc([{Mod,Func,Line}|Rest]) ->
format_loc([{Mod,LineOrFunc}]) ->
format_loc({Mod,LineOrFunc});
format_loc({Mod,Func}) when is_atom(Func) ->
- io_lib:format("{~w,~w}",[Mod,Func]);
+ io_lib:format("{~w,~tw}",[Mod,Func]);
format_loc(Loc) ->
- io_lib:format("~p",[Loc]).
+ io_lib:format("~tp",[Loc]).
format_loc1([{Mod,Func,Line}]) ->
[" ",format_loc1({Mod,Func,Line}),"]"];
@@ -824,12 +826,12 @@ format_loc1({Mod,Func,Line}) ->
true ->
Line
end,
- io_lib:format("{~w,~w,<a href=\"~ts~ts#~s\">~w</a>}",
+ io_lib:format("{~w,~tw,<a href=\"~ts~ts#~ts\">~tw</a>}",
[Mod,Func,
test_server_ctrl:uri_encode(downcase(ModStr)),
?src_listing_ext,Link,Line]);
_ ->
- io_lib:format("{~w,~w,~w}",[Mod,Func,Line])
+ io_lib:format("{~w,~tw,~tw}",[Mod,Func,Line])
end.
downcase(S) -> downcase(S, []).
@@ -850,6 +852,7 @@ util_start() ->
undefined ->
spawn_link(fun() ->
register(?MODULE, self()),
+ put(app, common_test),
util_loop(#util_state{starter=Starter})
end),
ok;
diff --git a/lib/common_test/src/unix_telnet.erl b/lib/common_test/src/unix_telnet.erl
index 4897ddb2f8..7a210237a8 100644
--- a/lib/common_test/src/unix_telnet.erl
+++ b/lib/common_test/src/unix_telnet.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2018. 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.
@@ -18,43 +18,8 @@
%% %CopyrightEnd%
%%
-%%% @doc Callback module for ct_telnet, for connecting to a telnet
-%%% server on a unix host.
-%%%
-%%% <p>It requires the following entry in the config file:</p>
-%%% <pre>
-%%% {unix,[{telnet,HostNameOrIpAddress},
-%%% {port,PortNum}, % optional
-%%% {username,UserName},
-%%% {password,Password},
-%%% {keep_alive,Bool}, % optional
-%%% {tcp_nodely,Bool}]} % optional</pre>
-%%%
-%%% <p>To communicate via telnet to the host specified by
-%%% <code>HostNameOrIpAddress</code>, use the interface functions in
-%%% <code>ct_telnet</code>, e.g. <code>open(Name), cmd(Name,Cmd), ...</code>.</p>
-%%%
-%%% <p><code>Name</code> is the name you allocated to the unix host in
-%%% your <code>require</code> statement. E.g.</p>
-%%% <pre> suite() -> [{require,Name,{unix,[telnet]}}].</pre>
-%%% <p>or</p>
-%%% <pre> ct:require(Name,{unix,[telnet]}).</pre>
-%%%
-%%% <p>The "keep alive" activity (i.e. that Common Test sends NOP to the server
-%%% every 10 seconds if the connection is idle) may be enabled or disabled for one
-%%% particular connection as described here. It may be disabled for all connections
-%%% using <c>telnet_settings</c> (see <c>ct_telnet</c>).</p>
-%%%
-%%% <p>Note that the <code>{port,PortNum}</code> tuple is optional and if
-%%% omitted, default telnet port 23 will be used. Also the <c>keep_alive</c> tuple
-%%% is optional, and the value defauls to true (enabled).</p>
-%%%
-%%% @see ct
-%%% @see ct_telnet
-module(unix_telnet).
--compile(export_all).
-
%% Callbacks for ct_telnet.erl
-export([connect/7,get_prompt_regexp/0]).
-import(ct_telnet,[start_gen_log/1,log/4,end_gen_log/0]).
@@ -63,36 +28,9 @@
-define(password,"Password: ").
-define(prx,"login: |Password: |\\\$ |> ").
-%%%-----------------------------------------------------------------
-%%% @spec get_prompt_regexp() -> PromptRegexp
-%%% PromptRegexp = ct_telnet:prompt_regexp()
-%%%
-%%% @doc Callback for ct_telnet.erl.
-%%%
-%%% <p>Return a suitable regexp string that will match common
-%%% prompts for users on unix hosts.</p>
get_prompt_regexp() ->
?prx.
-
-%%%-----------------------------------------------------------------
-%%% @spec connect(ConnName,Ip,Port,Timeout,KeepAlive,Extra) ->
-%%% {ok,Handle} | {error,Reason}
-%%% ConnName = ct:target_name()
-%%% Ip = string() | {integer(),integer(),integer(),integer()}
-%%% Port = integer()
-%%% Timeout = integer()
-%%% KeepAlive = bool()
-%%% TCPNoDelay = bool()
-%%% Extra = ct:target_name() | {Username,Password}
-%%% Username = string()
-%%% Password = string()
-%%% Handle = ct_telnet:handle()
-%%% Reason = term()
-%%%
-%%% @doc Callback for ct_telnet.erl.
-%%%
-%%% <p>Setup telnet connection to a unix host.</p>
connect(ConnName,Ip,Port,Timeout,KeepAlive,TCPNoDelay,Extra) ->
case Extra of
{Username,Password} ->
@@ -123,7 +61,8 @@ connect1(Name,Ip,Port,Timeout,KeepAlive,TCPNoDelay,Username,Password) ->
prompt,?prx,[]) of
{ok,{prompt,?password},_} ->
ok = ct_telnet_client:send_data(Pid,Password),
- Stars = lists:duplicate(length(Password),$*),
+ Stars =
+ lists:duplicate(string:length(Password),$*),
log(Name,send,"Password: ~s",[Stars]),
% ok = ct_telnet_client:send_data(Pid,""),
case ct_telnet:silent_teln_expect(Name,Pid,[],
@@ -134,25 +73,25 @@ connect1(Name,Ip,Port,Timeout,KeepAlive,TCPNoDelay,Username,Password) ->
Prompt=/=?password ->
{ok,Pid};
Error ->
- log(Name,recv,"Password failed\n~p\n",
+ log(Name,recv,"Password failed\n~tp\n",
[Error]),
{error,Error}
end;
Error ->
- log(Name,recv,"Login to ~p:~p failed\n~p\n",[Ip,Port,Error]),
+ log(Name,recv,"Login to ~p:~p failed\n~tp\n",[Ip,Port,Error]),
{error,Error}
end;
{ok,[{prompt,_OtherPrompt1},{prompt,_OtherPrompt2}],_} ->
{ok,Pid};
Error ->
log(Name,conn_error,
- "Did not get expected prompt from ~p:~p\n~p\n",
+ "Did not get expected prompt from ~p:~p\n~tp\n",
[Ip,Port,Error]),
{error,Error}
end;
Error ->
log(Name,conn_error,
- "Could not open telnet connection to ~p:~p\n~p\n",
+ "Could not open telnet connection to ~p:~p\n~tp\n",
[Ip,Port,Error]),
Error
end,
diff --git a/lib/common_test/src/vts.erl b/lib/common_test/src/vts.erl
index f1c5051164..38e549d2d6 100644
--- a/lib/common_test/src/vts.erl
+++ b/lib/common_test/src/vts.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2003-2018. 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.
@@ -157,6 +157,7 @@ test_info(_VtsPid,Type,Data) ->
init(Parent) ->
register(?MODULE,self()),
process_flag(trap_exit,true),
+ ct_util:mark_process(),
Parent ! {self(),started},
{ok,Cwd} = file:get_cwd(),
InitState = #state{start_dir=Cwd},
@@ -250,7 +251,7 @@ loop(State) ->
{'EXIT',Pid,Reason} ->
case State#state.test_runner of
Pid ->
- io:format("Test run error: ~p\n",[Reason]),
+ io:format("Test run error: ~tp\n",[Reason]),
loop(State);
_ ->
loop(State)
@@ -284,6 +285,7 @@ run_test1(State=#state{tests=Tests,current_log_dir=LogDir,
logopts=LogOpts}) ->
Self=self(),
RunTest = fun() ->
+ ct_util:mark_process(),
case ct_run:do_run(Tests,[],LogDir,LogOpts) of
{error,_Reason} ->
aborted();
@@ -551,7 +553,7 @@ case_select(Dir,Suite,Case,N) ->
true = code:add_pathz(Dir),
case catch apply(Suite,all,[]) of
{'EXIT',Reason} ->
- io:format("\n~p\n",[Reason]),
+ io:format("\n~tp\n",[Reason]),
red(["COULD NOT READ TESTCASES!!",br(),
"See erlang shell for info"]);
{skip,_Reason} ->
@@ -917,7 +919,7 @@ get_input_data(Input,Key)->
end.
parse(Input) ->
- httpd:parse_query(Input).
+ uri_string:dissect_query(Input).
vts_integer_to_list(X) when is_atom(X) ->
atom_to_list(X);