From 1327bd8956b2d0f6b5a9201ce9ecf361f94733aa Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Mon, 19 Apr 2010 17:28:07 +0200 Subject: Move 'node_start' and 'eval' terms into new 'init' term node_start+eval -> init(node_start, eval) Also include some documentation updates. --- lib/common_test/doc/src/ct_master_chapter.xml | 49 +++++--- lib/common_test/doc/src/run_test_chapter.xml | 16 +-- lib/common_test/src/ct_config_xml.erl | 3 - lib/common_test/src/ct_master.erl | 113 ++++++++++++++----- lib/common_test/src/ct_slave.erl | 1 - lib/common_test/src/ct_testspec.erl | 155 ++++++++++---------------- lib/common_test/src/ct_util.hrl | 2 +- lib/common_test/test/ct_master_SUITE.erl | 7 +- 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 @@

ct_master:abort() (stop all) or ct_master:abort(Nodes)

For detailed information about the ct_master API, please see the - manual page for this module.

+ manual page for this module.

Test Specifications @@ -197,32 +197,49 @@
Automatic startup of the test target nodes -

Is is possible to start nodes acting like a test targets automatically - using a new term in the test specification, node_start:

+

Is is possible to perform initial actions on test target nodes + automatically using a new term in the test specification, init.

+

Currently, two sub-terms are supported, node_start and eval.

      {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, []}}}.
     

This test specification declares that node1@host1 is to be started using user's callback my_slave_callback with no parameters, and nodes node1@host2 and node2@host2 will be started with the default callback module ct_slave, - using the given user name and password to log into remote host host2.

-

Default ct_slave callback module delivered with the Common Test has the following features: + using the given user name and password to log into remote host host2. + Also, there will be function module:function/0 evaluated on the + node1@host3, and result of this call will be printed to the log.

+

Default ct_slave callback module + delivered with the Common Test has the following features: - Starting the Erlang nodes or local or remote hosts (ssh is used for remote ones); - Ability to start Erlang emulator with the additional flags (any flags supported by erl are supported); - Supervision of the node being start using internal callback functions. Used to prevent hanging of started nodes. Configurable; - Monitoring of the master node by the slaves. Slave node may be stopped in case of master node termination. Configurable; - Execution of the user's functions after slave node is started. Functions can be given as a list of {Module, Function, Arguments} tuples. + Starting the Erlang nodes or local or remote hosts + (ssh is used for remote ones); + + Ability to start Erlang emulator with the additional flags + (any flags supported by erl are supported); + + Supervision of the node being start using internal callback + functions. Used to prevent hanging of started nodes. Configurable; + + Monitoring of the master node by the slaves. Slave node may be + stopped in case of master node termination. Configurable; + + Execution of the user's functions after slave node is started. + Functions can be given as a list of {Module, Function, Arguments} tuples. +

-

If any eval term is specified for a node which has node_start 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 startup_functions list - (see ct_slave chapter in the Reference Manual).

+

Note that it is possible to specify eval term for the node as well + as startup_functions in the node_start options list. In this + case first node will be started, then the startup_functions are + executed, and finally functions specified with eval will be called. +

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}.

Test terms:

@@ -461,15 +461,9 @@
       Lastly, all suites for systems t3 are to be completely skipped and this
 	should be explicitly noted in the log files.
     
-    

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 eval 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. +

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 Automatic startup of the test target nodes chapter for details.

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), -- cgit v1.2.3