diff options
| -rw-r--r-- | lib/common_test/doc/src/ct_master_chapter.xml | 49 | ||||
| -rw-r--r-- | lib/common_test/doc/src/run_test_chapter.xml | 16 | ||||
| -rwxr-xr-x | lib/common_test/src/ct_config_xml.erl | 3 | ||||
| -rw-r--r-- | lib/common_test/src/ct_master.erl | 113 | ||||
| -rw-r--r-- | lib/common_test/src/ct_slave.erl | 1 | ||||
| -rw-r--r-- | lib/common_test/src/ct_testspec.erl | 155 | ||||
| -rw-r--r-- | lib/common_test/src/ct_util.hrl | 2 | ||||
| -rw-r--r-- | 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 @@        <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),  | 
