aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/common_test/doc/src/ct_master_chapter.xml49
-rw-r--r--lib/common_test/doc/src/run_test_chapter.xml16
-rwxr-xr-xlib/common_test/src/ct_config_xml.erl3
-rw-r--r--lib/common_test/src/ct_master.erl113
-rw-r--r--lib/common_test/src/ct_slave.erl1
-rw-r--r--lib/common_test/src/ct_testspec.erl155
-rw-r--r--lib/common_test/src/ct_util.hrl2
-rw-r--r--lib/common_test/test/ct_master_SUITE.erl7
8 files changed, 184 insertions, 162 deletions
diff --git a/lib/common_test/doc/src/ct_master_chapter.xml b/lib/common_test/doc/src/ct_master_chapter.xml
index 1622b5b29c..4ab891edee 100644
--- a/lib/common_test/doc/src/ct_master_chapter.xml
+++ b/lib/common_test/doc/src/ct_master_chapter.xml
@@ -102,7 +102,7 @@
<p><c>ct_master:abort()</c> (stop all) or <c>ct_master:abort(Nodes)</c></p>
<p>For detailed information about the <c>ct_master</c> API, please see the
- manual page for this module.</p>
+ <seealso marker="ct_master">manual page</seealso> for this module.</p>
</section>
<section>
<title>Test Specifications</title>
@@ -197,32 +197,49 @@
<section>
<title>Automatic startup of the test target nodes</title>
<marker id="ct_slave"></marker>
- <p>Is is possible to start nodes acting like a test targets automatically
- using a new term in the test specification, <c>node_start</c>:</p>
+ <p>Is is possible to perform initial actions on test target nodes
+ automatically using a new term in the test specification, <c>init</c>.</p>
+ <p>Currently, two sub-terms are supported, <c>node_start</c> and <c>eval</c>.</p>
<pre>
{node, node1, node1@host1}.
{node, node2, node1@host2}.
{node, node3, node2@host2}.
- {node_start, node1, [{callback_module, my_slave_callback}]}.
- {node_start, [node2, node3], [{username, "ct_user"}, {password, "ct_password"}]}.
+ {node, node4, node1@host3}.
+ {init, node1, [{node_start, [{callback_module, my_slave_callback}]}]}.
+ {init, [node2, node3], {node_start, [{username, "ct_user"}, {password, "ct_password"}]}}.
+ {init, node4, {eval, {module, function, []}}}.
</pre>
<p>This test specification declares that <c>node1@host1</c> is to be started using
user's callback <c>my_slave_callback</c> with no parameters, and nodes <c>node1@host2</c> and
<c>node2@host2</c> will be started with the default callback module <c>ct_slave</c>,
- using the given user name and password to log into remote host <c>host2</c>.</p>
- <p>Default <seealso marker="ct_slave">ct_slave</seealso> callback module delivered with the Common Test has the following features:
+ using the given user name and password to log into remote host <c>host2</c>.
+ Also, there will be function module:function/0 evaluated on the
+ <c>node1@host3</c>, and result of this call will be printed to the log.</p>
+ <p>Default <seealso marker="ct_slave">ct_slave</seealso> callback module
+ delivered with the Common Test has the following features:
<list>
- <item>Starting the Erlang nodes or local or remote hosts (ssh is used for remote ones);</item>
- <item>Ability to start Erlang emulator with the additional flags (any flags supported by <c>erl</c> are supported);</item>
- <item>Supervision of the node being start using internal callback functions. Used to prevent hanging of started nodes. Configurable;</item>
- <item>Monitoring of the master node by the slaves. Slave node may be stopped in case of master node termination. Configurable;</item>
- <item>Execution of the user's functions after slave node is started. Functions can be given as a list of {Module, Function, Arguments} tuples.</item>
+ <item>Starting the Erlang nodes or local or remote hosts
+ (ssh is used for remote ones);
+ </item>
+ <item>Ability to start Erlang emulator with the additional flags
+ (any flags supported by <c>erl</c> are supported);
+ </item>
+ <item>Supervision of the node being start using internal callback
+ functions. Used to prevent hanging of started nodes. Configurable;
+ </item>
+ <item>Monitoring of the master node by the slaves. Slave node may be
+ stopped in case of master node termination. Configurable;
+ </item>
+ <item>Execution of the user's functions after slave node is started.
+ Functions can be given as a list of {Module, Function, Arguments} tuples.
+ </item>
</list>
</p>
- <p>If any <c>eval</c> term is specified for a node which has <c>node_start</c> term
- defined for it, the evaluation of the functions will be deferred until the node is started.
- The functions to be evaluated are simply added to the end of the <c>startup_functions</c> list
- (see <seealso marker="ct_slave">ct_slave</seealso> chapter in the Reference Manual).</p>
+ <p>Note that it is possible to specify <c>eval</c> term for the node as well
+ as <c>startup_functions</c> in the <c>node_start</c> options list. In this
+ case first node will be started, then the <c>startup_functions</c> are
+ executed, and finally functions specified with <c>eval</c> will be called.
+ </p>
</section>
</chapter>
diff --git a/lib/common_test/doc/src/run_test_chapter.xml b/lib/common_test/doc/src/run_test_chapter.xml
index f5dc477c6f..44b4077ec4 100644
--- a/lib/common_test/doc/src/run_test_chapter.xml
+++ b/lib/common_test/doc/src/run_test_chapter.xml
@@ -384,8 +384,8 @@
{event_handler, EventHandlers, InitArgs}.
{event_handler, NodeRefs, EventHandlers, InitArgs}.
- {eval, [{Module, Function, Arguments}]}.
- {eval, [NodeAlias], [{Module, Function, Arguments}]}.
+ {init, Options}.
+ {init, [NodeAlias], Options}.
</pre>
<p>Test terms:</p>
<pre>
@@ -461,15 +461,9 @@
<item>Lastly, all suites for systems t3 are to be completely skipped and this
should be explicitly noted in the log files.</item>
</list>
- <p>It is possible to evaluate any function(s) during processing of the test
- specification. This feature may be used e.g. to add some directories to
- the code path, initialise some ETS table etc. New <c>eval</c> terms can be
- used for this purpose.
- If only a list of functions is given, then all functions from the list
- will be consequently applied on the current node. If at least one node
- alias is given, then the functions will be called remotely on all nodes
- specified. Please note that in case when node is defined to be started by
- the CT Master, the evaluation will be deferred.
+ <p>It is possible to specify initialization options for nodes defined in the
+ test specification. Currently, there are options to start the node and/or to
+ evaluate any function on the node.
See the <seealso marker="ct_master_chapter#ct_slave">Automatic startup of
the test target nodes</seealso> chapter for details.</p>
<p>It is possible for the user to provide a test specification that
diff --git a/lib/common_test/src/ct_config_xml.erl b/lib/common_test/src/ct_config_xml.erl
index 111d1426c9..4ced80aeac 100755
--- a/lib/common_test/src/ct_config_xml.erl
+++ b/lib/common_test/src/ct_config_xml.erl
@@ -24,9 +24,6 @@
-module(ct_config_xml).
-export([read_config/1, check_parameter/1]).
-% DEBUG ONLY
--export([list_to_term/1]).
-
% read config file
read_config(ConfigFile) ->
case catch do_read_xml_config(ConfigFile) of
diff --git a/lib/common_test/src/ct_master.erl b/lib/common_test/src/ct_master.erl
index ecf516bd98..6cabe4c7a2 100644
--- a/lib/common_test/src/ct_master.erl
+++ b/lib/common_test/src/ct_master.erl
@@ -101,12 +101,12 @@ run([TS|TestSpecs],AllowUserTerms,InclNodes,ExclNodes) when is_list(TS),
TSRec=#testspec{logdir=AllLogDirs,
config=StdCfgFiles,
userconfig=UserCfgFiles,
- node_start=AllNSOpts,
+ init=AllInitOpts,
event_handler=AllEvHs} ->
AllCfgFiles = {StdCfgFiles, UserCfgFiles},
RunSkipPerNode = ct_testspec:prepare_tests(TSRec),
RunSkipPerNode2 = exclude_nodes(ExclNodes,RunSkipPerNode),
- run_all(RunSkipPerNode2,AllLogDirs,AllCfgFiles,AllEvHs,[],[],AllNSOpts,TS1)
+ run_all(RunSkipPerNode2,AllLogDirs,AllCfgFiles,AllEvHs,[],[],AllInitOpts,TS1)
end,
[{TS,Result} | run(TestSpecs,AllowUserTerms,InclNodes,ExclNodes)];
run([],_,_,_) ->
@@ -162,12 +162,12 @@ run_on_node([TS|TestSpecs],AllowUserTerms,Node) when is_list(TS),is_atom(Node) -
{error,Reason};
TSRec=#testspec{logdir=AllLogDirs,
config=StdCfgFiles,
- node_start=AllNSOpts,
+ init=AllInitOpts,
userconfig=UserCfgFiles,
event_handler=AllEvHs} ->
AllCfgFiles = {StdCfgFiles, UserCfgFiles},
{Run,Skip} = ct_testspec:prepare_tests(TSRec,Node),
- run_all([{Node,Run,Skip}],AllLogDirs,AllCfgFiles,AllEvHs,[],[],AllNSOpts,TS1)
+ run_all([{Node,Run,Skip}],AllLogDirs,AllCfgFiles,AllEvHs,[],[],AllInitOpts,TS1)
end,
[{TS,Result} | run_on_node(TestSpecs,AllowUserTerms,Node)];
run_on_node([],_,_) ->
@@ -189,7 +189,7 @@ run_on_node(TestSpecs,Node) ->
run_all([{Node,Run,Skip}|Rest],AllLogDirs,
{AllStdCfgFiles, AllUserCfgFiles}=AllCfgFiles,
- AllEvHs,NodeOpts,LogDirs,NSOptions,Specs) ->
+ AllEvHs,NodeOpts,LogDirs,InitOptions,Specs) ->
LogDir =
lists:foldl(fun({N,Dir},_Found) when N == Node ->
Dir;
@@ -221,15 +221,15 @@ run_all([{Node,Run,Skip}|Rest],AllLogDirs,
{logdir,LogDir},
{config,StdCfgFiles},
{event_handler,EvHs}] ++ UserCfgFiles},
- run_all(Rest,AllLogDirs,AllCfgFiles,AllEvHs,[NO|NodeOpts],[LogDir|LogDirs],NSOptions,Specs);
-run_all([],AllLogDirs,_,AllEvHs,NodeOpts,LogDirs,NSOptions,Specs) ->
+ run_all(Rest,AllLogDirs,AllCfgFiles,AllEvHs,[NO|NodeOpts],[LogDir|LogDirs],InitOptions,Specs);
+run_all([],AllLogDirs,_,AllEvHs,NodeOpts,LogDirs,InitOptions,Specs) ->
Handlers = [{H,A} || {Master,H,A} <- AllEvHs, Master == master],
MasterLogDir = case lists:keysearch(master,1,AllLogDirs) of
{value,{_,Dir}} -> Dir;
false -> "."
end,
log(tty,"Master Logdir","~s",[MasterLogDir]),
- start_master(lists:reverse(NodeOpts),Handlers,MasterLogDir,LogDirs,NSOptions,Specs),
+ start_master(lists:reverse(NodeOpts),Handlers,MasterLogDir,LogDirs,InitOptions,Specs),
ok.
@@ -269,15 +269,15 @@ progress() ->
start_master(NodeOptsList) ->
start_master(NodeOptsList,[],".",[],[],[]).
-start_master(NodeOptsList,EvHandlers,MasterLogDir,LogDirs,NSOptions,Specs) ->
+start_master(NodeOptsList,EvHandlers,MasterLogDir,LogDirs,InitOptions,Specs) ->
Master = spawn_link(?MODULE,init_master,[self(),NodeOptsList,EvHandlers,
- MasterLogDir,LogDirs,NSOptions,Specs]),
+ MasterLogDir,LogDirs,InitOptions,Specs]),
receive
{Master,Result} -> Result
end.
%%% @hidden
-init_master(Parent,NodeOptsList,EvHandlers,MasterLogDir,LogDirs,NSOptions,Specs) ->
+init_master(Parent,NodeOptsList,EvHandlers,MasterLogDir,LogDirs,InitOptions,Specs) ->
case whereis(ct_master) of
undefined ->
register(ct_master,self()),
@@ -330,11 +330,10 @@ init_master(Parent,NodeOptsList,EvHandlers,MasterLogDir,LogDirs,NSOptions,Specs)
Pid when is_pid(Pid) ->
ok
end,
- init_master1(Parent,NodeOptsList,NSOptions,LogDirs).
+ init_master1(Parent,NodeOptsList,InitOptions,LogDirs).
-init_master1(Parent,NodeOptsList,NSOptions,LogDirs) ->
- start_nodes(NSOptions),
- {Inaccessible,NodeOptsList1} = ping_nodes(NodeOptsList,[],[]),
+init_master1(Parent,NodeOptsList,InitOptions,LogDirs) ->
+ {Inaccessible,NodeOptsList1,InitOptions1} = init_nodes(NodeOptsList,InitOptions),
case Inaccessible of
[] ->
init_master2(Parent,NodeOptsList,LogDirs);
@@ -348,7 +347,7 @@ init_master1(Parent,NodeOptsList,NSOptions,LogDirs) ->
"Proceeding without: ~p",[Inaccessible]),
init_master2(Parent,NodeOptsList1,LogDirs);
"r\n" ->
- init_master1(Parent,NodeOptsList,NSOptions,LogDirs);
+ init_master1(Parent,NodeOptsList,InitOptions1,LogDirs);
_ ->
log(html,"Aborting Tests","",[]),
ct_master_event:stop(),
@@ -559,6 +558,9 @@ get_pid(Node,NodeCtrlPids) ->
undefined
end.
+ping_nodes(NodeOptions)->
+ ping_nodes(NodeOptions, [], []).
+
ping_nodes([NO={Node,_Opts}|NOs],Inaccessible,NodeOpts) ->
case net_adm:ping(Node) of
pong ->
@@ -702,21 +704,72 @@ reply(Result,To) ->
To ! {self(),Result},
ok.
-start_nodes([])->
- ok;
-start_nodes([{NodeName, Options}|Rest])->
- [NodeS,HostS]=string:tokens(atom_to_list(NodeName), "@"),
- Node=list_to_atom(NodeS),
- Host=list_to_atom(HostS),
- {value, {callback_module, Callback}, Options2}=
- lists:keytake(callback_module, 1, Options),
- case Callback:start(Host, Node, Options2) of
- {ok, NodeName} ->
- io:format("Node ~p started successfully with callback ~p~n", [NodeName,Callback]);
- {error, Reason, _NodeName} ->
- io:format("Failed to start node ~p with callback ~p! Reason: ~p~n", [NodeName, Callback, Reason])
+init_nodes(NodeOptions, InitOptions)->
+ ping_nodes(NodeOptions),
+ start_nodes(InitOptions),
+ eval_on_nodes(InitOptions),
+ {Inaccessible, NodeOptions1}=ping_nodes(NodeOptions),
+ InitOptions1 = filter_accessible(InitOptions, Inaccessible),
+ {Inaccessible, NodeOptions1, InitOptions1}.
+
+% only nodes which are inaccessible now, should be initiated later
+filter_accessible(InitOptions, Inaccessible)->
+ [{Node,Option}||{Node,Option}<-InitOptions, lists:member(Node, Inaccessible)].
+
+start_nodes(InitOptions)->
+ lists:foreach(fun({NodeName, Options})->
+ [NodeS,HostS]=string:tokens(atom_to_list(NodeName), "@"),
+ Node=list_to_atom(NodeS),
+ Host=list_to_atom(HostS),
+ HasNodeStart = lists:keymember(node_start, 1, Options),
+ IsAlive = lists:member(NodeName, nodes()),
+ case {HasNodeStart, IsAlive} of
+ {false, false}->
+ io:format("WARNING: Node ~p is not alive but has no node_start option~n", [NodeName]);
+ {false, true}->
+ io:format("Node ~p is alive~n", [NodeName]);
+ {true, false}->
+ {node_start, NodeStart} = lists:keyfind(node_start, 1, Options),
+ {value, {callback_module, Callback}, NodeStart2}=
+ lists:keytake(callback_module, 1, NodeStart),
+ case Callback:start(Host, Node, NodeStart2) of
+ {ok, NodeName} ->
+ io:format("Node ~p started successfully with callback ~p~n", [NodeName,Callback]);
+ {error, Reason, _NodeName} ->
+ io:format("Failed to start node ~p with callback ~p! Reason: ~p~n", [NodeName, Callback, Reason])
+ end;
+ {true, true}->
+ io:format("WARNING: Node ~p is alive but has node_start option~n", [NodeName])
+ end
+ end,
+ InitOptions).
+
+eval_on_nodes(InitOptions)->
+ lists:foreach(fun({NodeName, Options})->
+ HasEval = lists:keymember(eval, 1, Options),
+ IsAlive = lists:member(NodeName, nodes()),
+ case {HasEval, IsAlive} of
+ {false,_}->
+ ok;
+ {true,false}->
+ io:format("WARNING: Node ~p is not alive but has eval option ~n", [NodeName]);
+ {true,true}->
+ {eval, MFAs} = lists:keyfind(eval, 1, Options),
+ evaluate(NodeName, MFAs)
+ end
end,
- start_nodes(Rest).
+ InitOptions).
+
+evaluate(Node, [{M,F,A}|MFAs])->
+ case rpc:call(Node, M, F, A) of
+ {badrpc,Reason}->
+ io:format("WARNING: Failed to call ~p:~p/~p on node ~p due to ~p~n", [M,F,length(A),Node,Reason]);
+ Result->
+ io:format("Called ~p:~p/~p on node ~p, result: ~p~n", [M,F,length(A),Node,Result])
+ end,
+ evaluate(Node, MFAs);
+evaluate(_Node, [])->
+ ok.
%cast(Msg) ->
% cast(whereis(ct_master),Msg).
diff --git a/lib/common_test/src/ct_slave.erl b/lib/common_test/src/ct_slave.erl
index 476815895c..22cb17e55d 100644
--- a/lib/common_test/src/ct_slave.erl
+++ b/lib/common_test/src/ct_slave.erl
@@ -359,7 +359,6 @@ get_cmd(Node, Flags)->
spawn_local_node(Node, Options)->
ErlFlags = Options#options.erl_flags,
Cmd = get_cmd(Node, ErlFlags),
- %io:format("Running cmd: ~p~n", [Cmd]),
open_port({spawn, Cmd}, [stream]).
% start crypto and ssh if not yet started
diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl
index db3efb16e8..4e34c1513e 100644
--- a/lib/common_test/src/ct_testspec.erl
+++ b/lib/common_test/src/ct_testspec.erl
@@ -270,52 +270,8 @@ collect_tests(Terms,TestSpec,Relaxed) ->
put(relaxed,Relaxed),
TestSpec1 = get_global(Terms,TestSpec),
TestSpec2 = get_all_nodes(Terms,TestSpec1),
- % filter out node_start options and save them into the specification
- {Terms2, TestSpec3} = filter_nodestart_specs(Terms, [], TestSpec2),
- % only save the 'global' evals and evals for nodes which have no node_start
- {Terms3, TestSpec4} = filter_evals(Terms2, [], TestSpec3),
- % after evaluation, only valid terms exist in the specification list
- Terms4 = case catch evaluate(Terms3, [], TestSpec4) of
- {error,{Node,{M,F,A},Reason}} ->
- io:format("Error! Common Test failed to evaluate ~w:~w/~w on ~w. "
- "Reason: ~p~n~n", [M,F,A,Node,Reason]),
- Terms3;
- NewTerms -> NewTerms
- end,
- add_tests(Terms4,TestSpec4).
-
-evaluate([{eval,Node,[{_,_,_}|_]=Mfas}|Ts],NewTerms,Spec)->
- EvalTerms = lists:map(fun(Mfa)->
- {eval, Node, Mfa}
- end,
- Mfas),
- evaluate([EvalTerms|Ts], NewTerms, Spec);
-evaluate([{eval,[{_,_,_}|_]=Mfas}|Ts],NewTerms,Spec)->
- EvalTerms = lists:map(fun(Mfa)->
- {eval, Mfa}
- end,
- Mfas),
- evaluate([EvalTerms|Ts], NewTerms, Spec);
-evaluate([{eval,Node,{M,F,Args}}|Ts],NewTerms,Spec) ->
- case rpc:call(Node,M,F,Args) of
- {badrpc,Reason} ->
- throw({error,{Node,{M,F,length(Args)},Reason}});
- _ ->
- ok
- end,
- evaluate(Ts,NewTerms,Spec);
-evaluate([{eval,{M,F,Args}}|Ts],NewTerms,Spec) ->
- case catch apply(M,F,Args) of
- {'EXIT',Reason} ->
- throw({error,{node(),{M,F,length(Args)},Reason}});
- _ ->
- ok
- end,
- evaluate(Ts,NewTerms,Spec);
-evaluate([Term|Ts], NewTerms, Spec)->
- evaluate(Ts, [Term|NewTerms], Spec);
-evaluate([], NewTerms, _Spec) ->
- NewTerms.
+ {Terms2, TestSpec3} = filter_init_terms(Terms, [], TestSpec2),
+ add_tests(Terms2,TestSpec3).
get_global([{alias,Ref,Dir}|Ts],Spec=#testspec{alias=Refs}) ->
get_global(Ts,Spec#testspec{alias=[{Ref,get_absdir(Dir,Spec)}|Refs]});
@@ -392,64 +348,67 @@ get_all_nodes([_|Ts],Spec) ->
get_all_nodes([],Spec) ->
Spec.
-filter_nodestart_specs([{node_start, Options}|Ts], NewTerms, Spec) ->
- filter_nodestart_specs([{node_start, list_nodes(Spec), Options}|Ts], NewTerms, Spec);
-filter_nodestart_specs([{node_start, NodeRef, Options}|Ts], NewTerms, Spec) when is_atom(NodeRef) ->
- filter_nodestart_specs([{node_start, [NodeRef], Options}|Ts], NewTerms, Spec);
-filter_nodestart_specs([{node_start, NodeRefs, Options}|Ts], NewTerms, Spec=#testspec{node_start=NodeStart})->
- Options2 = case lists:keyfind(callback_module, 1, Options) of
+filter_init_terms([{init, InitOptions}|Ts], NewTerms, Spec)->
+ filter_init_terms([{init, list_nodes(Spec), InitOptions}|Ts], NewTerms, Spec);
+filter_init_terms([{init, NodeRef, InitOptions}|Ts], NewTerms, Spec)
+ when is_atom(NodeRef)->
+ filter_init_terms([{init, [NodeRef], InitOptions}|Ts], NewTerms, Spec);
+filter_init_terms([{init, NodeRefs, InitOption}|Ts], NewTerms, Spec) when is_tuple(InitOption) ->
+ filter_init_terms([{init, NodeRefs, [InitOption]}|Ts], NewTerms, Spec);
+filter_init_terms([{init, [NodeRef|NodeRefs], InitOptions}|Ts], NewTerms, Spec=#testspec{init=InitData})->
+ NodeStartOptions = case lists:keyfind(node_start, 1, InitOptions) of
+ {node_start, NSOptions}->
+ case lists:keyfind(callback_module, 1, NSOptions) of
+ {callback_module, _Callback}->
+ NSOptions;
+ false->
+ [{callback_module, ct_slave}|NSOptions]
+ end;
false->
- [{callback_module, ct_slave}|Options];
- {callback_module, _Callback}->
- Options
+ []
end,
- NSSAdder = fun(NodeRef, NodeStartAcc)->
- Node=ref2node(NodeRef,Spec#testspec.nodes),
- case lists:keyfind(Node, 1, NodeStartAcc) of
- false->
- [{Node, Options2}|NodeStartAcc];
- {Node, OtherOptions}->
- io:format("~nWarning: There are other options defined for node ~p:"
- "~n~w, skipping ~n~w...~n", [Node, OtherOptions, Options2]),
- NodeStartAcc
- end
+ EvalTerms = case lists:keyfind(eval, 1, InitOptions) of
+ {eval, MFA} when is_tuple(MFA)->
+ [MFA];
+ {eval, MFAs} when is_list(MFAs)->
+ MFAs;
+ false->
+ []
end,
- NodeStart2 = lists:foldl(NSSAdder, NodeStart, NodeRefs),
- filter_nodestart_specs(Ts, NewTerms, Spec#testspec{node_start=NodeStart2});
-filter_nodestart_specs([Term|Ts], NewTerms, Spec)->
- filter_nodestart_specs(Ts, [Term|NewTerms], Spec);
-filter_nodestart_specs([], NewTerms, Spec) ->
+ Node = ref2node(NodeRef,Spec#testspec.nodes),
+ InitData2 = add_option({node_start, NodeStartOptions}, Node, InitData, true),
+ InitData3 = add_option({eval, EvalTerms}, Node, InitData2, false),
+ filter_init_terms([{init, NodeRefs, InitOptions}|Ts], NewTerms, Spec#testspec{init=InitData3});
+filter_init_terms([{init, [], _}|Ts], NewTerms, Spec)->
+ filter_init_terms(Ts, NewTerms, Spec);
+filter_init_terms([Term|Ts], NewTerms, Spec)->
+ filter_init_terms(Ts, [Term|NewTerms], Spec);
+filter_init_terms([], NewTerms, Spec)->
{NewTerms, Spec}.
-filter_evals([{eval,NodeRefs,Mfa}|Ts], NewTerms, Spec) when is_list(NodeRefs)->
- EvalTerms = lists:map(fun(NodeRef)->
- {eval, NodeRef, Mfa}
- end,
- NodeRefs),
- filter_evals(EvalTerms++Ts, NewTerms, Spec);
-filter_evals([{eval,NodeRef,{_,_,_}=Mfa}|Ts],NewTerms,Spec)->
- filter_evals([{eval,NodeRef,[Mfa]}|Ts],NewTerms,Spec);
-filter_evals([{eval,NodeRef,[{_,_,_}|_]=Mfas}=EvalTerm|Ts],
- NewTerms,Spec=#testspec{node_start=NodeStart})->
- Node=ref2node(NodeRef,Spec#testspec.nodes),
- case lists:keyfind(Node, 1, NodeStart) of
- false->
- filter_evals(Ts, [EvalTerm|NewTerms], Spec);
+add_option([], _, List, _)->
+ List;
+add_option({Key, Value}, Node, List, WarnIfExists) when is_list(Value)->
+ OldOptions = case lists:keyfind(Node, 1, List) of
{Node, Options}->
- Options2 = case lists:keyfind(startup_functions, 1, Options) of
- false->
- [{startup_functions, Mfas}|Options];
- {startup_functions, StartupFunctions}->
- lists:keyreplace(startup_functions, 1, Options,
- {startup_functions, StartupFunctions ++ Mfas})
- end,
- NodeStart2 = lists:keyreplace(Node, 1, NodeStart, {Node, Options2}),
- filter_evals(Ts, NewTerms, Spec#testspec{node_start=NodeStart2})
- end;
-filter_evals([Term|Ts], NewTerms, Spec)->
- filter_evals(Ts, [Term|NewTerms], Spec);
-filter_evals([], NewTerms, Spec)->
- {NewTerms, Spec}.
+ Options;
+ false->
+ []
+ end,
+ NewOption = case lists:keyfind(Key, 1, OldOptions) of
+ {Key, OldOption} when WarnIfExists, OldOption/=[]->
+ io:format("There is an option ~w=~w already defined for node ~p, skipping new ~w~n",
+ [Key, OldOption, Node, Value]),
+ OldOption;
+ {Key, OldOption}->
+ OldOption ++ Value;
+ false->
+ Value
+ end,
+ lists:keystore(Node, 1, List,
+ {Node, lists:keystore(Key, 1, OldOptions, {Key, NewOption})});
+add_option({Key, Value}, Node, List, WarnIfExists)->
+ add_option({Key, [Value]}, Node, List, WarnIfExists).
save_nodes(Nodes,Spec=#testspec{nodes=NodeRefs}) ->
NodeRefs1 =
diff --git a/lib/common_test/src/ct_util.hrl b/lib/common_test/src/ct_util.hrl
index 7c8fd67e1d..7ddb7d8c2b 100644
--- a/lib/common_test/src/ct_util.hrl
+++ b/lib/common_test/src/ct_util.hrl
@@ -29,7 +29,7 @@
-record(testspec, {spec_dir,
nodes=[],
- node_start=[],
+ init=[],
logdir=["."],
cover=[],
config=[],
diff --git a/lib/common_test/test/ct_master_SUITE.erl b/lib/common_test/test/ct_master_SUITE.erl
index 0f0d13f683..2d4a29cde1 100644
--- a/lib/common_test/test/ct_master_SUITE.erl
+++ b/lib/common_test/test/ct_master_SUITE.erl
@@ -99,8 +99,11 @@ make_spec(DataDir, FileName, NodeNames, Suites, Config)->
NodeNames),
NS = lists:map(fun(NodeName)->
- {node_start, NodeName, [{startup_functions, [{io, format, ["hello, world~n"]}]},
- {monitor_master, true}]}
+ {init, NodeName, [
+ {node_start, [{startup_functions, []}, {monitor_master, false}]},
+ {eval, {erlang, nodes, []}}
+ ]
+ }
end,
NodeNames),