1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2006-2018. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20
21-module(ct_testspec).
22
23-export([prepare_tests/1, prepare_tests/2,
24	 collect_tests_from_list/2, collect_tests_from_list/3,
25	 collect_tests_from_file/2, collect_tests_from_file/3,
26         get_tests/1]).
27
28-export([testspec_rec2list/1, testspec_rec2list/2]).
29
30-include("ct_util.hrl").
31-define(testspec_fields, record_info(fields, testspec)).
32
33%%%------------------------------------------------------------------
34%%% NOTE:
35%%% Multiple testspecs may be used as input with the result that
36%%% the data is merged. It's in this case up to the user to ensure
37%%% there are no clashes in any "global" variables, such as logdir.
38%%%-------------------------------------------------------------------
39
40%%%-------------------------------------------------------------------
41%%% prepare_tests/2 compiles the testspec data into a list of tests
42%%% to be run and a list of tests to be skipped, either for one
43%%% particular node or for all nodes.
44%%%-------------------------------------------------------------------
45
46%%%-------------------------------------------------------------------
47%%% Version 1 - extract and return all tests and skips for Node
48%%%             (incl all_nodes)
49%%%-------------------------------------------------------------------
50prepare_tests(TestSpec,Node) when is_record(TestSpec,testspec),
51				  is_atom(Node) ->
52    case lists:keysearch(Node,1,prepare_tests(TestSpec)) of
53	{value,{Node,Run,Skip}} ->
54	    {Run,Skip};
55	false ->
56	    {[],[]}
57    end.
58
59%%%-------------------------------------------------------------------
60%%% Version 2 - create and return a list of {Node,Run,Skip} tuples,
61%%% one for each node specified in the test specification.
62%%% The tuples in the Run list will have the form {Dir,Suites,Cases}
63%%% and the tuples in the Skip list will have the form
64%%% {Dir,Suites,Comment} or {Dir,Suite,Cases,Comment}.
65%%%-------------------------------------------------------------------
66prepare_tests(TestSpec) when is_record(TestSpec,testspec) ->
67    Tests = TestSpec#testspec.tests,
68    %% Sort Tests into "flat" Run and Skip lists (not sorted per node).
69    {Run,Skip} = get_run_and_skip(Tests,[],[]),
70
71    %% Create initial list of {Node,{Run,Skip}} tuples
72    NodeList = lists:map(fun(N) -> {N,{[],[]}} end, list_nodes(TestSpec)),
73
74    %% Get all Run tests sorted per node basis.
75    NodeList1 = run_per_node(Run,NodeList,
76			     TestSpec#testspec.merge_tests),
77
78    %% Get all Skip entries sorted per node basis.
79    NodeList2 = skip_per_node(Skip,NodeList1),
80
81    %% Change representation.
82    Result=
83	lists:map(fun({Node,{Run1,Skip1}}) ->
84			  Run2 = lists:map(fun({D,{Ss,Cs}}) ->
85						   {D,Ss,Cs}
86					   end, Run1),
87			  Skip2 = lists:map(fun({D,{Ss,Cmt}}) ->
88						    {D,Ss,Cmt};
89					       ({D,{S,Cs,Cmt}}) ->
90						    {D,S,Cs,Cmt}
91					    end, Skip1),
92			  {Node,Run2,Skip2}
93		  end, NodeList2),
94    Result.
95
96%% run_per_node/2 takes the Run list as input and returns a list
97%% of {Node,RunPerNode,[]} tuples where the tests have been sorted
98%% on a per node basis.
99run_per_node([{{Node,Dir},Test}|Ts],Result,MergeTests) ->
100    {value,{Node,{Run,Skip}}} = lists:keysearch(Node,1,Result),
101    Run1 = case MergeTests of
102	       false ->
103		   append({Dir, Test}, Run);
104	       true ->
105		   merge_tests(Dir,Test,Run)
106	   end,
107    run_per_node(Ts,insert_in_order({Node,{Run1,Skip}},Result,replace),
108		 MergeTests);
109run_per_node([],Result,_) ->
110    Result.
111
112merge_tests(Dir,Test={all,_},TestDirs) ->
113    %% overwrite all previous entries for Dir
114    TestDirs1 = lists:filter(fun({D,_}) when D==Dir ->
115				     false;
116				(_) ->
117				     true
118			     end,TestDirs),
119    insert_in_order({Dir,Test},TestDirs1);
120merge_tests(Dir,Test={Suite,all},TestDirs) ->
121    TestDirs1 = lists:filter(fun({D,{S,_}}) when D==Dir,S==Suite ->
122				     false;
123				(_) ->
124				     true
125			     end,TestDirs),
126    TestDirs1++[{Dir,Test}];
127merge_tests(Dir,Test,TestDirs) ->
128    merge_suites(Dir,Test,TestDirs).
129
130merge_suites(Dir,{Suite,Cases},[{Dir,{Suite,Cases0}}|Dirs]) ->
131    Cases1 = insert_in_order(Cases,Cases0),
132    [{Dir,{Suite,Cases1}}|Dirs];
133merge_suites(Dir,Test,[Other|Dirs]) ->
134    [Other|merge_suites(Dir,Test,Dirs)];
135merge_suites(Dir,Test,[]) ->
136    [{Dir,Test}].
137
138%% skip_per_node/2 takes the Skip list as input and returns a list
139%% of {Node,RunPerNode,SkipPerNode} tuples where the skips have been
140%% sorted on a per node basis.
141skip_per_node([{{Node,Dir},Test}|Ts],Result) ->
142    {value,{Node,{Run,Skip}}} = lists:keysearch(Node,1,Result),
143    Skip1 = [{Dir,Test}|Skip],
144    skip_per_node(Ts,insert_in_order({Node,{Run,Skip1}},Result,replace));
145skip_per_node([],Result) ->
146    Result.
147
148%% get_run_and_skip/3 takes a list of test terms as input and sorts
149%% them into a list of Run tests and a list of Skip entries. The
150%% elements all have the form
151%%
152%%   {{Node,Dir},TestData}
153%%
154%% TestData has the form:
155%%
156%%   Run entry:  {Suite,Cases}
157%%
158%%   Skip entry: {Suites,Comment} or {Suite,Cases,Comment}
159%%
160get_run_and_skip([{{Node,Dir},Suites}|Tests],Run,Skip) ->
161    TestDir = ct_util:get_testdir(Dir,catch element(1,hd(Suites))),
162    case lists:keysearch(all,1,Suites) of
163	{value,_} ->				% all Suites in Dir
164	    Skipped = get_skipped_suites(Node,TestDir,Suites),
165	    %% note: this adds an 'all' test even if only skip is specified,
166	    %% probably a good thing cause it gets logged as skipped then
167	    get_run_and_skip(Tests,
168			     [[{{Node,TestDir},{all,all}}]|Run],
169			     [Skipped|Skip]);
170	false ->
171	    {R,S} = prepare_suites(Node,TestDir,Suites,[],[]),
172	    get_run_and_skip(Tests,[R|Run],[S|Skip])
173    end;
174get_run_and_skip([],Run,Skip) ->
175    {lists:flatten(lists:reverse(Run)),
176     lists:flatten(lists:reverse(Skip))}.
177
178prepare_suites(Node,Dir,[{Suite,Cases}|Suites],Run,Skip) ->
179    case lists:member(all,Cases) of
180	true ->					% all Cases in Suite
181	    Skipped = get_skipped_cases(Node,Dir,Suite,Cases),
182	    %% note: this adds an 'all' test even if only skip is specified
183	    prepare_suites(Node,Dir,Suites,
184			   [[{{Node,Dir},{Suite,all}}]|Run],
185			   [Skipped|Skip]);
186	false ->
187	    {Run1,Skip1} = prepare_cases(Node,Dir,Suite,Cases,Run,Skip),
188	    prepare_suites(Node,Dir,Suites,Run1,Skip1)
189    end;
190prepare_suites(_Node,_Dir,[],Run,Skip) ->
191    {lists:flatten(lists:reverse(Run)),
192     lists:flatten(lists:reverse(Skip))}.
193
194prepare_cases(Node,Dir,Suite,Cases,Run,Skip) ->
195    case get_skipped_cases(Node,Dir,Suite,Cases) of
196	[SkipAll={{Node,Dir},{Suite,_Cmt}}] ->	      % all cases to be skipped
197	    case lists:any(fun({{N,D},{S,all}}) when N == Node,
198						     D == Dir,
199						     S == Suite ->
200				   true;
201			      ({{N,D},{S,Cs}}) when N == Node,
202						    D == Dir,
203						    S == Suite ->
204				   lists:member(all,Cs);
205			      (_) -> false
206			   end, lists:flatten(Run)) of
207		true ->
208		    {Run,[SkipAll|Skip]};
209		false ->
210		    %% note: this adds an 'all' test even if
211		    %% only skip is specified
212		    {[{{Node,Dir},{Suite,all}}|Run],[SkipAll|Skip]}
213	    end;
214	Skipped ->
215	    %% note: this adds a test even if only skip is specified
216	    PrepC = lists:foldr(fun({{G,Cs},{skip,_Cmt}}, Acc) when
217					  is_atom(G) ->
218					case lists:keymember(G, 1, Cases) of
219					    true ->
220						Acc;
221					    false ->
222						[{skipped,G,Cs}|Acc]
223					end;
224				   ({C,{skip,_Cmt}},Acc) ->
225					case lists:member(C,Cases) of
226					    true ->
227						Acc;
228					    false ->
229						[{skipped,C}|Acc]
230					end;
231				   (C,Acc) -> [C|Acc]
232				end, [], Cases),
233	    {[{{Node,Dir},{Suite,PrepC}}|Run],[Skipped|Skip]}
234    end.
235
236get_skipped_suites(Node,Dir,Suites) ->
237    lists:flatten(get_skipped_suites1(Node,Dir,Suites)).
238
239get_skipped_suites1(Node,Dir,[{Suite,Cases}|Suites]) ->
240    SkippedCases = get_skipped_cases(Node,Dir,Suite,Cases),
241    [SkippedCases|get_skipped_suites1(Node,Dir,Suites)];
242get_skipped_suites1(_,_,[]) ->
243    [].
244
245get_skipped_cases(Node,Dir,Suite,Cases) ->
246    case lists:keysearch(all,1,Cases) of
247	{value,{all,{skip,Cmt}}} ->
248	    [{{Node,Dir},{Suite,Cmt}}];
249	_ ->
250	    get_skipped_cases1(Node,Dir,Suite,Cases)
251    end.
252
253get_skipped_cases1(Node,Dir,Suite,[{Case,{skip,Cmt}}|Cs]) ->
254    [{{Node,Dir},{Suite,Case,Cmt}}|get_skipped_cases1(Node,Dir,Suite,Cs)];
255get_skipped_cases1(Node,Dir,Suite,[_Case|Cs]) ->
256    get_skipped_cases1(Node,Dir,Suite,Cs);
257get_skipped_cases1(_,_,_,[]) ->
258    [].
259
260%%% collect_tests_from_file reads a testspec file and returns a record
261%%% containing the data found.
262collect_tests_from_file(Specs,Relaxed) ->
263    collect_tests_from_file(Specs,[node()],Relaxed).
264
265collect_tests_from_file(Specs,Nodes,Relaxed) when is_list(Nodes) ->
266    NodeRefs = lists:map(fun(N) -> {undefined,N} end, Nodes),
267    %% [Spec1,Spec2,...] means create one testpec record per Spec file
268    %% [[Spec1,Spec2,...]] means merge all specs into one testspec record
269    {Join,Specs1} = if is_list(hd(hd(Specs))) -> {true,hd(Specs)};
270		    true -> {false,Specs}
271		 end,
272    Specs2 = [filename:absname(S) || S <- Specs1],
273    TS0 = #testspec{nodes=NodeRefs},
274
275    try create_testspecs(Specs2,TS0,Relaxed,Join) of
276	{{[],_},SeparateTestSpecs} ->
277	    filter_and_convert(SeparateTestSpecs);
278	{{_,#testspec{tests=[]}},SeparateTestSpecs} ->
279	    filter_and_convert(SeparateTestSpecs);
280	{Joined,SeparateTestSpecs} ->
281	    [filter_and_convert(Joined) |
282	     filter_and_convert(SeparateTestSpecs)]
283    catch
284	_:Error={error,_} ->
285	    Error;
286	_:Error ->
287	    {error,Error}
288    end.
289
290filter_and_convert(Joined) when is_tuple(Joined) ->
291    hd(filter_and_convert([Joined]));
292filter_and_convert([{_,#testspec{tests=[]}}|TSs]) ->
293    filter_and_convert(TSs);
294filter_and_convert([{[{SpecFile,MergeTests}|SMs],TestSpec}|TSs]) ->
295    #testspec{config = CfgFiles} = TestSpec,
296    TestSpec1 = TestSpec#testspec{config = delete_dups(CfgFiles),
297				  merge_tests = MergeTests},
298    %% set the merge_tests value for the testspec to the value
299    %% of the first test spec in the set
300    [{[SpecFile | [SF || {SF,_} <- SMs]], TestSpec1} | filter_and_convert(TSs)];
301filter_and_convert([]) ->
302    [].
303
304delete_dups(Elems) ->
305    delete_dups1(lists:reverse(Elems),[]).
306
307delete_dups1([E|Es],Keep) ->
308    case lists:member(E,Es) of
309	true ->
310	    delete_dups1(Es,Keep);
311	false ->
312	    delete_dups1(Es,[E|Keep])
313    end;
314delete_dups1([],Keep) ->
315    Keep.
316
317create_testspecs(Specs,TestSpec,Relaxed,Join) ->
318    %% SpecsTree = {SpecAbsName, TermsInSpec,
319    %%              IncludedJoinTree, IncludedSeparateTree,
320    %%              JoinSpecWithRest, RestSpecsTree}
321    SpecsTree = create_spec_tree(Specs,TestSpec,Join,[]),
322    create_specs(SpecsTree,TestSpec,TestSpec,Relaxed).
323
324create_spec_tree([Spec|Specs],TS,JoinWithNext,Known) ->
325    SpecDir = filename:dirname(filename:absname(Spec)),
326    TS1 = TS#testspec{spec_dir=SpecDir},
327    SpecAbsName = get_absfile(Spec,TS1),
328    case lists:member(SpecAbsName,Known) of
329	true ->
330	    throw({error,{cyclic_reference,SpecAbsName}});
331	false ->
332	    case file:consult(SpecAbsName) of
333		{ok,Terms} ->
334		    Terms1 = replace_names(Terms),
335		    {InclJoin,InclSep} = get_included_specs(Terms1,TS1),
336		    {SpecAbsName,Terms1,
337		     create_spec_tree(InclJoin,TS,true,[SpecAbsName|Known]),
338		     create_spec_tree(InclSep,TS,false,[SpecAbsName|Known]),
339		     JoinWithNext,
340		     create_spec_tree(Specs,TS,JoinWithNext,Known)};
341		{error,Reason} ->
342		    ReasonStr =
343			lists:flatten(io_lib:format("~ts",
344						    [file:format_error(Reason)])),
345		    throw({error,{SpecAbsName,ReasonStr}})
346	    end
347    end;
348create_spec_tree([],_TS,_JoinWithNext,_Known) ->
349    [].
350
351create_specs({Spec,Terms,InclJoin,InclSep,JoinWithNext,NextSpec},
352	     TestSpec,TestSpec0,Relaxed) ->
353    SpecDir = filename:dirname(filename:absname(Spec)),
354    TestSpec1 = create_spec(Terms,TestSpec#testspec{spec_dir=SpecDir},
355			    JoinWithNext,Relaxed),
356
357    {{JoinSpecs1,JoinTS1},Separate1} = create_specs(InclJoin,TestSpec1,
358						    TestSpec0,Relaxed),
359
360    {{JoinSpecs2,JoinTS2},Separate2} =
361	case JoinWithNext of
362	    true ->
363		create_specs(NextSpec,JoinTS1,
364			     TestSpec0,Relaxed);
365	    false ->
366		{{[],JoinTS1},[]}
367	end,
368    {SepJoinSpecs,Separate3} = create_specs(InclSep,TestSpec0,
369					    TestSpec0,Relaxed),
370    {SepJoinSpecs1,Separate4} =
371	case JoinWithNext of
372	    true ->
373		{{[],TestSpec},[]};
374	    false ->
375		create_specs(NextSpec,TestSpec0,
376			     TestSpec0,Relaxed)
377	end,
378
379    SpecInfo = {Spec,TestSpec1#testspec.merge_tests},
380    AllSeparate =
381	[TSData || TSData = {Ss,_TS} <- Separate3++Separate1++
382		                        [SepJoinSpecs]++Separate2++
383		                        [SepJoinSpecs1]++Separate4,
384		   Ss /= []],
385    case {JoinWithNext,JoinSpecs1} of
386	{true,_} ->
387	    {{[SpecInfo|(JoinSpecs1++JoinSpecs2)],JoinTS2},
388	     AllSeparate};
389	{false,[]} ->
390	    {{[],TestSpec},
391	     [{[SpecInfo],TestSpec1}|AllSeparate]};
392	{false,_} ->
393	    {{[SpecInfo|(JoinSpecs1++JoinSpecs2)],JoinTS2},
394	     AllSeparate}
395    end;
396create_specs([],TestSpec,_,_Relaxed) ->
397    {{[],TestSpec},[]}.
398
399create_spec(Terms,TestSpec,JoinedByPrev,Relaxed) ->
400    %% it's the "includer" that decides the value of merge_tests
401    Terms1 = if not JoinedByPrev ->
402		     [{set_merge_tests,true}|Terms];
403		true ->
404		     [{set_merge_tests,false}|Terms]
405	     end,
406    TS = #testspec{tests=Tests, logdir=LogDirs} =
407	collect_tests({false,Terms1},TestSpec,Relaxed),
408    LogDirs1 = lists:delete(".",LogDirs) ++ ["."],
409    TS#testspec{tests=lists:flatten(Tests),
410		logdir=LogDirs1}.
411
412collect_tests_from_list(Terms,Relaxed) ->
413    collect_tests_from_list(Terms,[node()],Relaxed).
414
415collect_tests_from_list(Terms,Nodes,Relaxed) when is_list(Nodes) ->
416    {ok,Cwd} = file:get_cwd(),
417    NodeRefs = lists:map(fun(N) -> {undefined,N} end, Nodes),
418    case catch collect_tests({true,Terms},#testspec{nodes=NodeRefs,
419						    spec_dir=Cwd},
420			     Relaxed) of
421	E = {error,_} ->
422	    E;
423	TS ->
424	    #testspec{tests=Tests, logdir=LogDirs} = TS,
425	    LogDirs1 = lists:delete(".",LogDirs) ++ ["."],
426	    TS#testspec{tests=lists:flatten(Tests), logdir=LogDirs1}
427    end.
428
429collect_tests({Replace,Terms},TestSpec=#testspec{alias=As,nodes=Ns},Relaxed) ->
430    put(relaxed,Relaxed),
431    Terms1 = if Replace -> replace_names(Terms);
432		true    -> Terms
433	     end,
434    {MergeTestsDef,Terms2} =
435	case proplists:get_value(set_merge_tests,Terms1,true) of
436	    false ->
437		%% disable merge_tests
438		{TestSpec#testspec.merge_tests,
439		 proplists:delete(merge_tests,Terms1)};
440	    true ->
441		{true,Terms1}
442	end,
443    %% reverse nodes and aliases initially to get the order of them right
444    %% in case this spec is being joined with a previous one
445    TestSpec1 = get_global(Terms2,TestSpec#testspec{alias = lists:reverse(As),
446						    nodes = lists:reverse(Ns),
447						    merge_tests = MergeTestsDef}),
448    TestSpec2 = get_all_nodes(Terms2,TestSpec1),
449    {Terms3, TestSpec3} = filter_init_terms(Terms2, [], TestSpec2),
450
451    add_tests(Terms3,TestSpec3).
452
453%% replace names (atoms) in the testspec matching those in 'define' terms by
454%% searching recursively through tuples and lists
455replace_names(Terms) ->
456    Defs =
457	lists:flatmap(fun(Def={define,Name,_Replacement}) ->
458			      %% check that name follows convention
459			      if not is_atom(Name) ->
460				      throw({illegal_name_in_testspec,Name});
461				 true ->
462				      [First|_] = atom_to_list(Name),
463				      if ((First == $?) or (First == $$)
464					  or (First == $_)
465					  or ((First >= $A)
466					      and (First =< $Z))) ->
467					      [Def];
468					 true ->
469					      throw({illegal_name_in_testspec,
470						     Name})
471				      end
472			      end;
473			 (_) -> []
474		      end, Terms),
475    DefProps = replace_names_in_defs(Defs,[]),
476    replace_names(Terms,[],DefProps).
477
478replace_names_in_defs([Def|Left],ModDefs) ->
479    [{define,Name,Replacement}] = replace_names([Def],[],ModDefs),
480    replace_names_in_defs(Left,[{Name,Replacement}|ModDefs]);
481replace_names_in_defs([],ModDefs) ->
482    ModDefs.
483
484replace_names([Term|Ts],Modified,Defs) when is_tuple(Term) ->
485    [TypeTag|Data] = tuple_to_list(Term),
486    Term1 = list_to_tuple([TypeTag|replace_names_in_elems(Data,[],Defs)]),
487    replace_names(Ts,[Term1|Modified],Defs);
488replace_names([Term|Ts],Modified,Defs) when is_atom(Term) ->
489    case proplists:get_value(Term,Defs) of
490	undefined ->
491	    replace_names(Ts,[Term|Modified],Defs);
492	Replacement ->
493	    replace_names(Ts,[Replacement|Modified],Defs)
494    end;
495replace_names([Term=[Ch|_]|Ts],Modified,Defs) when is_integer(Ch) ->
496    %% Term *could* be a string, attempt to search through it
497    Term1 = replace_names_in_string(Term,Defs),
498    replace_names(Ts,[Term1|Modified],Defs);
499replace_names([Term|Ts],Modified,Defs) ->
500    replace_names(Ts,[Term|Modified],Defs);
501replace_names([],Modified,_Defs) ->
502    lists:reverse(Modified).
503
504replace_names_in_elems([Elem|Es],Modified,Defs) when is_tuple(Elem) ->
505    Elem1 = list_to_tuple(replace_names_in_elems(tuple_to_list(Elem),[],Defs)),
506    replace_names_in_elems(Es,[Elem1|Modified],Defs);
507replace_names_in_elems([Elem|Es],Modified,Defs) when is_atom(Elem) ->
508    case proplists:get_value(Elem,Defs) of
509	undefined ->
510	    %% if Term is a node name, check it for replacements as well
511	    Elem1 = replace_names_in_node(Elem,Defs),
512	    replace_names_in_elems(Es,[Elem1|Modified],Defs);
513	Replacement ->
514	    replace_names_in_elems(Es,[Replacement|Modified],Defs)
515    end;
516replace_names_in_elems([Elem=[Ch|_]|Es],Modified,Defs) when is_integer(Ch) ->
517    %% Term *could* be a string, attempt to search through it
518    case replace_names_in_string(Elem,Defs) of
519	Elem ->
520	    List = replace_names_in_elems(Elem,[],Defs),
521	    replace_names_in_elems(Es,[List|Modified],Defs);
522	Elem1 ->
523	    replace_names_in_elems(Es,[Elem1|Modified],Defs)
524    end;
525replace_names_in_elems([Elem|Es],Modified,Defs) when is_list(Elem) ->
526    List = replace_names_in_elems(Elem,[],Defs),
527    replace_names_in_elems(Es,[List|Modified],Defs);
528replace_names_in_elems([Elem|Es],Modified,Defs) ->
529    replace_names_in_elems(Es,[Elem|Modified],Defs);
530replace_names_in_elems([],Modified,_Defs) ->
531    lists:reverse(Modified).
532
533replace_names_in_string(Term,Defs=[{Name,Replacement=[Ch|_]}|Ds])
534  when is_integer(Ch) ->
535    try re:replace(Term,[$'|atom_to_list(Name)]++"'",
536		   Replacement,[{return,list},unicode]) of
537	Term ->					% no match, proceed
538	    replace_names_in_string(Term,Ds);
539	Term1 ->
540	    replace_names_in_string(Term1,Defs)
541    catch
542	_:_ -> Term			% Term is not a string
543    end;
544replace_names_in_string(Term,[_|Ds]) ->
545    replace_names_in_string(Term,Ds);
546replace_names_in_string(Term,[]) ->
547    Term.
548
549replace_names_in_node(Node,Defs) ->
550    String = atom_to_list(Node),
551    case lists:member($@,String) of
552	true ->
553	    list_to_atom(replace_names_in_node1(String,Defs));
554	false ->
555	    Node
556    end.
557
558replace_names_in_node1(NodeStr,Defs=[{Name,Replacement}|Ds]) ->
559    ReplStr = case Replacement of
560		  [Ch|_] when is_integer(Ch) -> Replacement;
561		  _ when is_atom(Replacement) -> atom_to_list(Replacement);
562		  _ -> false
563	      end,
564    if ReplStr == false ->
565	    replace_names_in_node1(NodeStr,Ds);
566       true ->
567	    case re:replace(NodeStr,atom_to_list(Name),
568			    ReplStr,[{return,list},unicode]) of
569		NodeStr ->			% no match, proceed
570		    replace_names_in_node1(NodeStr,Ds);
571		NodeStr1 ->
572		    replace_names_in_node1(NodeStr1,Defs)
573	    end
574    end;
575replace_names_in_node1(NodeStr,[]) ->
576    NodeStr.
577
578%% look for other specification files, either to join with the
579%% current spec, or execute as separate test runs
580get_included_specs(Terms,TestSpec) ->
581    get_included_specs(Terms,TestSpec,[],[]).
582
583get_included_specs([{specs,How,SpecOrSpecs}|Ts],TestSpec,Join,Sep) ->
584    Specs = case SpecOrSpecs of
585		[File|_] when is_list(File) ->
586		    [get_absfile(Spec,TestSpec) || Spec <- SpecOrSpecs];
587		[Ch|_] when is_integer(Ch) ->
588		    [get_absfile(SpecOrSpecs,TestSpec)]
589	    end,
590    if How == join ->
591	    get_included_specs(Ts,TestSpec,Join++Specs,Sep);
592       true ->
593	    get_included_specs(Ts,TestSpec,Join,Sep++Specs)
594    end;
595get_included_specs([_|Ts],TestSpec,Join,Sep) ->
596    get_included_specs(Ts,TestSpec,Join,Sep);
597get_included_specs([],_,Join,Sep) ->
598    {Join,Sep}.
599
600%% global terms that will be used for analysing all other terms in the spec
601get_global([{merge_tests,Bool}|Ts],Spec) ->
602    get_global(Ts,Spec#testspec{merge_tests=Bool});
603
604%% the 'define' term replaces the 'alias' and 'node' terms, but we need to keep
605%% the latter two for backwards compatibility...
606get_global([{alias,Ref,Dir}|Ts],Spec=#testspec{alias=Refs}) ->
607    get_global(Ts,Spec#testspec{alias=[{Ref,get_absdir(Dir,Spec)}|Refs]});
608get_global([{node,Ref,Node}|Ts],Spec=#testspec{nodes=Refs}) ->
609    get_global(Ts,Spec#testspec{nodes=[{Ref,Node} |
610				       lists:keydelete(Node,2,Refs)]});
611
612get_global([_|Ts],Spec) ->
613    get_global(Ts,Spec);
614get_global([],Spec=#testspec{nodes=Ns, alias=As}) ->
615    Spec#testspec{nodes=lists:reverse(Ns), alias=lists:reverse(As)}.
616
617get_absfile(Callback,FullName,#testspec{spec_dir=SpecDir}) ->
618    % we need to temporary switch to new cwd here, because
619    % otherwise config files cannot be found
620    {ok, OldWd} = file:get_cwd(),
621    ok = file:set_cwd(SpecDir),
622    R =  Callback:check_parameter(FullName),
623    ok = file:set_cwd(OldWd),
624    case R of
625	{ok, {file, FullName}}->
626	    File = filename:basename(FullName),
627	    Dir = get_absname(filename:dirname(FullName),SpecDir),
628	    filename:join(Dir,File);
629	{ok, {config, FullName}}->
630	    FullName;
631	{error, {nofile, FullName}}->
632	    FullName;
633	{error, {wrong_config, FullName}}->
634	    FullName
635    end.
636
637get_absfile(FullName,#testspec{spec_dir=SpecDir}) ->
638    File = filename:basename(FullName),
639    Dir = get_absname(filename:dirname(FullName),SpecDir),
640    filename:join(Dir,File).
641
642get_absdir(Dir,#testspec{spec_dir=SpecDir}) ->
643    get_absname(Dir,SpecDir).
644
645get_absname(Dir,SpecDir) ->
646    AbsName = filename:absname(Dir,SpecDir),
647    shorten_path(AbsName,SpecDir).
648
649shorten_path(Path,SpecDir) ->
650    case shorten_split_path(filename:split(Path),[]) of
651	[] ->
652	    [Root|_] = filename:split(SpecDir),
653	    Root;
654	Short ->
655	    filename:join(Short)
656    end.
657
658shorten_split_path([".."|Path],SoFar) ->
659    shorten_split_path(Path,tl(SoFar));
660shorten_split_path(["."|Path],SoFar) ->
661    shorten_split_path(Path,SoFar);
662shorten_split_path([Dir|Path],SoFar) ->
663    shorten_split_path(Path,[Dir|SoFar]);
664shorten_split_path([],SoFar) ->
665    lists:reverse(SoFar).
666
667%% go through all tests and register all nodes found
668get_all_nodes([{suites,Nodes,_,_}|Ts],Spec) when is_list(Nodes) ->
669    get_all_nodes(Ts,save_nodes(Nodes,Spec));
670get_all_nodes([{suites,Node,_,_}|Ts],Spec) ->
671    get_all_nodes(Ts,save_nodes([Node],Spec));
672get_all_nodes([{groups,[Char|_],_,_,_}|Ts],Spec) when is_integer(Char) ->
673    get_all_nodes(Ts,Spec);
674get_all_nodes([{groups,Nodes,_,_,_}|Ts],Spec) when is_list(Nodes) ->
675    get_all_nodes(Ts,save_nodes(Nodes,Spec));
676get_all_nodes([{groups,Nodes,_,_,_,_}|Ts],Spec) when is_list(Nodes) ->
677    get_all_nodes(Ts,save_nodes(Nodes,Spec));
678get_all_nodes([{groups,_,_,_,{cases,_}}|Ts],Spec) ->
679    get_all_nodes(Ts,Spec);
680get_all_nodes([{groups,Node,_,_,_}|Ts],Spec) ->
681    get_all_nodes(Ts,save_nodes([Node],Spec));
682get_all_nodes([{groups,Node,_,_,_,_}|Ts],Spec) ->
683    get_all_nodes(Ts,save_nodes([Node],Spec));
684get_all_nodes([{cases,Nodes,_,_,_}|Ts],Spec) when is_list(Nodes) ->
685    get_all_nodes(Ts,save_nodes(Nodes,Spec));
686get_all_nodes([{cases,Node,_,_,_}|Ts],Spec) ->
687    get_all_nodes(Ts,save_nodes([Node],Spec));
688get_all_nodes([{skip_suites,Nodes,_,_,_}|Ts],Spec) when is_list(Nodes) ->
689    get_all_nodes(Ts,save_nodes(Nodes,Spec));
690get_all_nodes([{skip_suites,Node,_,_,_}|Ts],Spec) ->
691    get_all_nodes(Ts,save_nodes([Node],Spec));
692get_all_nodes([{skip_groups,[Char|_],_,_,_,_}|Ts],Spec) when is_integer(Char) ->
693    get_all_nodes(Ts,Spec);
694get_all_nodes([{skip_groups,Nodes,_,_,_,_}|Ts],Spec) when is_list(Nodes) ->
695    get_all_nodes(Ts,save_nodes(Nodes,Spec));
696get_all_nodes([{skip_groups,Node,_,_,_,_}|Ts],Spec) ->
697    get_all_nodes(Ts,save_nodes([Node],Spec));
698get_all_nodes([{skip_groups,Nodes,_,_,_,_,_}|Ts],Spec) when is_list(Nodes) ->
699    get_all_nodes(Ts,save_nodes(Nodes,Spec));
700get_all_nodes([{skip_groups,Node,_,_,_,_,_}|Ts],Spec) ->
701    get_all_nodes(Ts,save_nodes([Node],Spec));
702get_all_nodes([{skip_cases,Nodes,_,_,_,_}|Ts],Spec) when is_list(Nodes) ->
703    get_all_nodes(Ts,save_nodes(Nodes,Spec));
704get_all_nodes([{skip_cases,Node,_,_,_,_}|Ts],Spec) ->
705    get_all_nodes(Ts,save_nodes([Node],Spec));
706get_all_nodes([_Other|Ts],Spec) ->
707    get_all_nodes(Ts,Spec);
708get_all_nodes([],Spec) ->
709    Spec.
710
711filter_init_terms([{init,InitOptions}|Ts],NewTerms,Spec) ->
712    filter_init_terms([{init,list_nodes(Spec),InitOptions}|Ts],
713		      NewTerms,Spec);
714filter_init_terms([{init,all_nodes,InitOptions}|Ts],NewTerms,Spec) ->
715    filter_init_terms([{init,list_nodes(Spec),InitOptions}|Ts],
716		      NewTerms,Spec);
717filter_init_terms([{init,NodeRef,InitOptions}|Ts],
718		  NewTerms,Spec) when is_atom(NodeRef) ->
719    filter_init_terms([{init,[NodeRef],InitOptions}|Ts],NewTerms,Spec);
720filter_init_terms([{init,NodeRefs,InitOption}|Ts],
721		  NewTerms,Spec) when is_tuple(InitOption) ->
722    filter_init_terms([{init,NodeRefs,[InitOption]}|Ts],NewTerms,Spec);
723filter_init_terms([{init,[NodeRef|NodeRefs],InitOptions}|Ts],
724		  NewTerms,Spec=#testspec{init=InitData}) ->
725    NodeStartOptions =
726	case lists:keyfind(node_start,1,InitOptions) of
727	    {node_start,NSOptions}->
728		case lists:keyfind(callback_module,1,NSOptions) of
729		    {callback_module,_Callback}->
730			NSOptions;
731		    false->
732			[{callback_module,ct_slave}|NSOptions]
733		end;
734	    false->
735		[]
736	end,
737    EvalTerms = case lists:keyfind(eval,1,InitOptions) of
738		    {eval,MFA} when is_tuple(MFA) ->
739			[MFA];
740		    {eval,MFAs} when is_list(MFAs) ->
741			MFAs;
742		    false->
743			[]
744		end,
745    Node = ref2node(NodeRef,Spec#testspec.nodes),
746    InitData2 = add_option({node_start,NodeStartOptions},Node,InitData,true),
747    InitData3 = add_option({eval,EvalTerms},Node,InitData2,false),
748    filter_init_terms([{init,NodeRefs,InitOptions}|Ts],
749		      NewTerms,Spec#testspec{init=InitData3});
750filter_init_terms([{init,[],_}|Ts],NewTerms,Spec) ->
751    filter_init_terms(Ts,NewTerms,Spec);
752filter_init_terms([Term|Ts],NewTerms,Spec) ->
753    filter_init_terms(Ts,[Term|NewTerms],Spec);
754filter_init_terms([],NewTerms,Spec) ->
755    {lists:reverse(NewTerms),Spec}.
756
757add_option({Key,Value},Node,List,WarnIfExists) when is_list(Value) ->
758    OldOptions = case lists:keyfind(Node,1,List) of
759	{Node,Options}->
760	    Options;
761	false->
762	    []
763    end,
764    NewOption = case lists:keyfind(Key,1,OldOptions) of
765	{Key,OldOption} when WarnIfExists,OldOption/=[]->
766	    io:format("There is an option ~w=~w already "
767		      "defined for node ~w, skipping new ~w~n",
768		[Key,OldOption,Node,Value]),
769	    OldOption;
770	{Key,OldOption}->
771	    OldOption ++ Value;
772	false->
773	    Value
774    end,
775    lists:keystore(Node,1,List,
776	{Node,lists:keystore(Key,1,OldOptions,{Key,NewOption})});
777add_option({Key,Value},Node,List,WarnIfExists) ->
778    add_option({Key,[Value]},Node,List,WarnIfExists).
779
780save_nodes(Nodes,Spec=#testspec{nodes=NodeRefs}) ->
781    NodeRefs1 =
782	lists:foldr(fun(all_nodes,NR) ->
783			    NR;
784		       (Node,NR) ->
785			    case lists:keymember(Node,1,NR) of
786				true ->
787				    NR;
788				false ->
789				    case lists:keymember(Node,2,NR) of
790					true ->
791					    NR;
792					false ->
793					    [{undefined,Node}|NR]
794				    end
795			    end
796		    end,NodeRefs,Nodes),
797    Spec#testspec{nodes=NodeRefs1}.
798
799list_nodes(#testspec{nodes=NodeRefs}) ->
800    lists:map(fun({_Ref,Node}) -> Node end, NodeRefs).
801
802
803%%%-----------------------------------------------------------------
804%%% Parse the given test specs and return the complete set of specs
805%%% and tests to run/skip.
806%%% [Spec1,Spec2,...] means create separate tests per spec
807%%% [[Spec1,Spec2,...]] means merge all specs into one
808-spec get_tests(Specs) -> {ok,[{Specs,Tests}]} | {error,Reason} when
809      Specs :: [string()] | [[string()]],
810      Tests :: {Node,Run,Skip},
811      Node :: atom(),
812      Run :: {Dir,Suites,Cases},
813      Skip :: {Dir,Suites,Comment} | {Dir,Suites,Cases,Comment},
814      Dir :: string(),
815      Suites :: atom | [atom()] | all,
816      Cases :: atom | [atom()] | all,
817      Comment :: string(),
818      Reason :: term().
819
820get_tests(Specs) ->
821    case collect_tests_from_file(Specs,true) of
822        Tests when is_list(Tests) ->
823            {ok,[{S,prepare_tests(R)} || {S,R} <- Tests]};
824        Error ->
825            Error
826    end.
827
828%%     -----------------------------------------------------
829%%   /                                                       \
830%%  |  When adding test/config terms, remember to update      |
831%%  |  valid_terms/0 also!                                    |
832%%   \                                                       /
833%%     -----------------------------------------------------
834
835%% --- suites ---
836add_tests([{suites,all_nodes,Dir,Ss}|Ts],Spec) ->
837    add_tests([{suites,list_nodes(Spec),Dir,Ss}|Ts],Spec);
838add_tests([{suites,Dir,Ss}|Ts],Spec) ->
839    add_tests([{suites,all_nodes,Dir,Ss}|Ts],Spec);
840add_tests([{suites,Nodes,Dir,Ss}|Ts],Spec) when is_list(Nodes) ->
841    Ts1 = per_node(Nodes,suites,[Dir,Ss],Ts,Spec#testspec.nodes),
842    add_tests(Ts1,Spec);
843add_tests([{suites,Node,Dir,Ss}|Ts],Spec) ->
844    Tests = Spec#testspec.tests,
845    Tests1 = insert_suites(ref2node(Node,Spec#testspec.nodes),
846			   ref2dir(Dir,Spec),
847			   Ss,Tests, Spec#testspec.merge_tests),
848    add_tests(Ts,Spec#testspec{tests=Tests1});
849
850%% --- groups ---
851%% Later make it possible to specify group execution properties
852%% that will override thse in the suite. Also make it possible
853%% create dynamic groups in specification, i.e. to group test cases
854%% by means of groups defined only in the test specification.
855add_tests([{groups,all_nodes,Dir,Suite,Gs}|Ts],Spec) ->
856    add_tests([{groups,list_nodes(Spec),Dir,Suite,Gs}|Ts],Spec);
857add_tests([{groups,all_nodes,Dir,Suite,Gs,{cases,TCs}}|Ts],Spec) ->
858    add_tests([{groups,list_nodes(Spec),Dir,Suite,Gs,{cases,TCs}}|Ts],Spec);
859add_tests([{groups,Dir,Suite,Gs}|Ts],Spec) ->
860    add_tests([{groups,all_nodes,Dir,Suite,Gs}|Ts],Spec);
861add_tests([{groups,Dir,Suite,Gs,{cases,TCs}}|Ts],Spec) ->
862    add_tests([{groups,all_nodes,Dir,Suite,Gs,{cases,TCs}}|Ts],Spec);
863add_tests([{groups,Nodes,Dir,Suite,Gs}|Ts],Spec) when is_list(Nodes) ->
864    Ts1 = per_node(Nodes,groups,[Dir,Suite,Gs],Ts,Spec#testspec.nodes),
865    add_tests(Ts1,Spec);
866add_tests([{groups,Nodes,Dir,Suite,Gs,{cases,TCs}}|Ts],
867	  Spec) when is_list(Nodes) ->
868    Ts1 = per_node(Nodes,groups,[Dir,Suite,Gs,{cases,TCs}],Ts,
869		   Spec#testspec.nodes),
870    add_tests(Ts1,Spec);
871add_tests([{groups,Node,Dir,Suite,Gs}|Ts],Spec) ->
872    Tests = Spec#testspec.tests,
873    Tests1 = insert_groups(ref2node(Node,Spec#testspec.nodes),
874			   ref2dir(Dir,Spec),
875			   Suite,Gs,all,Tests,
876			   Spec#testspec.merge_tests),
877    add_tests(Ts,Spec#testspec{tests=Tests1});
878add_tests([{groups,Node,Dir,Suite,Gs,{cases,TCs}}|Ts],Spec) ->
879    Tests = Spec#testspec.tests,
880    Tests1 = insert_groups(ref2node(Node,Spec#testspec.nodes),
881			   ref2dir(Dir,Spec),
882			   Suite,Gs,TCs,Tests,
883			   Spec#testspec.merge_tests),
884    add_tests(Ts,Spec#testspec{tests=Tests1});
885
886%% --- cases ---
887add_tests([{cases,all_nodes,Dir,Suite,Cs}|Ts],Spec) ->
888    add_tests([{cases,list_nodes(Spec),Dir,Suite,Cs}|Ts],Spec);
889add_tests([{cases,Dir,Suite,Cs}|Ts],Spec) ->
890    add_tests([{cases,all_nodes,Dir,Suite,Cs}|Ts],Spec);
891add_tests([{cases,Nodes,Dir,Suite,Cs}|Ts],Spec) when is_list(Nodes) ->
892    Ts1 = per_node(Nodes,cases,[Dir,Suite,Cs],Ts,Spec#testspec.nodes),
893    add_tests(Ts1,Spec);
894add_tests([{cases,Node,Dir,Suite,Cs}|Ts],Spec) ->
895    Tests = Spec#testspec.tests,
896    Tests1 = insert_cases(ref2node(Node,Spec#testspec.nodes),
897			  ref2dir(Dir,Spec),
898			  Suite,Cs,Tests,
899			  Spec#testspec.merge_tests),
900    add_tests(Ts,Spec#testspec{tests=Tests1});
901
902%% --- skip_suites ---
903add_tests([{skip_suites,all_nodes,Dir,Ss,Cmt}|Ts],Spec) ->
904    add_tests([{skip_suites,list_nodes(Spec),Dir,Ss,Cmt}|Ts],Spec);
905add_tests([{skip_suites,Dir,Ss,Cmt}|Ts],Spec) ->
906    add_tests([{skip_suites,all_nodes,Dir,Ss,Cmt}|Ts],Spec);
907add_tests([{skip_suites,Nodes,Dir,Ss,Cmt}|Ts],Spec) when is_list(Nodes) ->
908    Ts1 = per_node(Nodes,skip_suites,[Dir,Ss,Cmt],Ts,Spec#testspec.nodes),
909    add_tests(Ts1,Spec);
910add_tests([{skip_suites,Node,Dir,Ss,Cmt}|Ts],Spec) ->
911    Tests = Spec#testspec.tests,
912    Tests1 = skip_suites(ref2node(Node,Spec#testspec.nodes),
913			 ref2dir(Dir,Spec),
914			 Ss,Cmt,Tests,
915			 Spec#testspec.merge_tests),
916    add_tests(Ts,Spec#testspec{tests=Tests1});
917
918%% --- skip_groups ---
919add_tests([{skip_groups,all_nodes,Dir,Suite,Gs,Cmt}|Ts],Spec) ->
920    add_tests([{skip_groups,list_nodes(Spec),Dir,Suite,Gs,Cmt}|Ts],Spec);
921add_tests([{skip_groups,all_nodes,Dir,Suite,Gs,{cases,TCs},Cmt}|Ts],Spec) ->
922    add_tests([{skip_groups,list_nodes(Spec),Dir,Suite,Gs,{cases,TCs},Cmt}|Ts],
923	      Spec);
924add_tests([{skip_groups,Dir,Suite,Gs,Cmt}|Ts],Spec) ->
925    add_tests([{skip_groups,all_nodes,Dir,Suite,Gs,Cmt}|Ts],Spec);
926add_tests([{skip_groups,Dir,Suite,Gs,{cases,TCs},Cmt}|Ts],Spec) ->
927    add_tests([{skip_groups,all_nodes,Dir,Suite,Gs,{cases,TCs},Cmt}|Ts],Spec);
928add_tests([{skip_groups,Nodes,Dir,Suite,Gs,Cmt}|Ts],Spec) when is_list(Nodes) ->
929    Ts1 = per_node(Nodes,skip_groups,[Dir,Suite,Gs,Cmt],Ts,Spec#testspec.nodes),
930    add_tests(Ts1,Spec);
931add_tests([{skip_groups,Nodes,Dir,Suite,Gs,{cases,TCs},Cmt}|Ts],
932	  Spec) when is_list(Nodes) ->
933    Ts1 = per_node(Nodes,skip_groups,[Dir,Suite,Gs,{cases,TCs},Cmt],Ts,
934		   Spec#testspec.nodes),
935    add_tests(Ts1,Spec);
936add_tests([{skip_groups,Node,Dir,Suite,Gs,Cmt}|Ts],Spec) ->
937    Tests = Spec#testspec.tests,
938    Tests1 = skip_groups(ref2node(Node,Spec#testspec.nodes),
939			 ref2dir(Dir,Spec),
940			 Suite,Gs,all,Cmt,Tests,
941			 Spec#testspec.merge_tests),
942    add_tests(Ts,Spec#testspec{tests=Tests1});
943add_tests([{skip_groups,Node,Dir,Suite,Gs,{cases,TCs},Cmt}|Ts],Spec) ->
944    Tests = Spec#testspec.tests,
945    Tests1 = skip_groups(ref2node(Node,Spec#testspec.nodes),
946			 ref2dir(Dir,Spec),
947			 Suite,Gs,TCs,Cmt,Tests,
948			 Spec#testspec.merge_tests),
949    add_tests(Ts,Spec#testspec{tests=Tests1});
950
951%% --- skip_cases ---
952add_tests([{skip_cases,all_nodes,Dir,Suite,Cs,Cmt}|Ts],Spec) ->
953    add_tests([{skip_cases,list_nodes(Spec),Dir,Suite,Cs,Cmt}|Ts],Spec);
954add_tests([{skip_cases,Dir,Suite,Cs,Cmt}|Ts],Spec) ->
955    add_tests([{skip_cases,all_nodes,Dir,Suite,Cs,Cmt}|Ts],Spec);
956add_tests([{skip_cases,Nodes,Dir,Suite,Cs,Cmt}|Ts],Spec) when is_list(Nodes) ->
957    Ts1 = per_node(Nodes,skip_cases,[Dir,Suite,Cs,Cmt],Ts,Spec#testspec.nodes),
958    add_tests(Ts1,Spec);
959add_tests([{skip_cases,Node,Dir,Suite,Cs,Cmt}|Ts],Spec) ->
960    Tests = Spec#testspec.tests,
961    Tests1 = skip_cases(ref2node(Node,Spec#testspec.nodes),
962			ref2dir(Dir,Spec),
963			Suite,Cs,Cmt,Tests,Spec#testspec.merge_tests),
964    add_tests(Ts,Spec#testspec{tests=Tests1});
965
966%% --- various configuration terms ---
967add_tests([{config,Nodes,CfgDir,Files}|Ts],Spec) when is_list(Nodes);
968						      Nodes == all_nodes ->
969    add_tests([{config,Nodes,{CfgDir,Files}}|Ts],Spec);
970add_tests([{config,Node,CfgDir,FileOrFiles}|Ts],Spec) ->
971    add_tests([{config,Node,{CfgDir,FileOrFiles}}|Ts],Spec);
972add_tests([{config,CfgDir=[Ch|_],Files}|Ts],Spec) when is_integer(Ch) ->
973    add_tests([{config,all_nodes,{CfgDir,Files}}|Ts],Spec);
974
975add_tests([{event_handler,Nodes,Hs,Args}|Ts],Spec) when is_list(Nodes);
976							Nodes == all_nodes ->
977    add_tests([{event_handler,Nodes,{Hs,Args}}|Ts],Spec);
978add_tests([{event_handler,Node,HOrHs,Args}|Ts],Spec) ->
979    add_tests([{event_handler,Node,{HOrHs,Args}}|Ts],Spec);
980
981add_tests([{enable_builtin_hooks,Bool}|Ts],Spec) ->
982    add_tests(Ts, Spec#testspec{enable_builtin_hooks = Bool});
983
984add_tests([{release_shell,Bool}|Ts],Spec) ->
985    add_tests(Ts, Spec#testspec{release_shell = Bool});
986
987%% --- handled/errors ---
988add_tests([{set_merge_tests,_}|Ts],Spec) ->	% internal
989    add_tests(Ts,Spec);
990
991add_tests([{define,_,_}|Ts],Spec) ->		% handled
992    add_tests(Ts,Spec);
993
994add_tests([{alias,_,_}|Ts],Spec) ->		% handled
995    add_tests(Ts,Spec);
996
997add_tests([{node,_,_}|Ts],Spec) ->		% handled
998    add_tests(Ts,Spec);
999
1000add_tests([{merge_tests,_} | Ts], Spec) ->      % handled
1001    add_tests(Ts,Spec);
1002
1003add_tests([{specs,_,_} | Ts], Spec) ->          % handled
1004    add_tests(Ts,Spec);
1005
1006%%     --------------------------------------------------
1007%%   /                                                    \
1008%%  |  General add_tests/2 clauses below will work for     |
1009%%  |  most test spec configuration terms                  |
1010%%   \                                                    /
1011%%     --------------------------------------------------
1012
1013%% create one test entry per known node and reinsert
1014add_tests([Term={Tag,all_nodes,Data}|Ts],Spec) ->
1015    case check_term(Term) of
1016	valid ->
1017	    Tests = [{Tag,Node,Data} ||	Node <- list_nodes(Spec),
1018					should_be_added(Tag,Node,Data,Spec)],
1019	    add_tests(Tests++Ts,Spec);
1020	invalid ->				% ignore term
1021	    Unknown = Spec#testspec.unknown,
1022	    add_tests(Ts,Spec#testspec{unknown=Unknown++[Term]})
1023    end;
1024%% create one test entry per node in Nodes and reinsert
1025add_tests([{Tag,[],Data}|Ts],Spec) ->
1026    add_tests([{Tag,all_nodes,Data}|Ts],Spec);
1027add_tests([{Tag,String=[Ch|_],Data}|Ts],Spec) when is_integer(Ch) ->
1028    add_tests([{Tag,all_nodes,{String,Data}}|Ts],Spec);
1029add_tests([{Tag,NodesOrOther,Data}|Ts],Spec) when is_list(NodesOrOther) ->
1030    case lists:all(fun(Test) -> is_node(Test,Spec#testspec.nodes)
1031		   end, NodesOrOther) of
1032	true ->
1033	    Ts1 = per_node(NodesOrOther,Tag,[Data],Ts,Spec#testspec.nodes),
1034	    add_tests(Ts1,Spec);
1035	false ->
1036	    add_tests([{Tag,all_nodes,{NodesOrOther,Data}}|Ts],Spec)
1037    end;
1038%% update data for testspec term of type Tag
1039add_tests([Term={Tag,NodeOrOther,Data}|Ts],Spec) ->
1040    case is_node(NodeOrOther,Spec#testspec.nodes) of
1041	true ->
1042	    case check_term(Term) of
1043		valid ->
1044		    Node = ref2node(NodeOrOther,Spec#testspec.nodes),
1045		    NodeIxData =
1046			update_recorded(Tag,Node,Spec) ++
1047			handle_data(Tag,Node,Data,Spec),
1048		    add_tests(Ts,mod_field(Spec,Tag,NodeIxData));
1049		invalid ->			% ignore term
1050		    Unknown = Spec#testspec.unknown,
1051		    add_tests(Ts,Spec#testspec{unknown=Unknown++[Term]})
1052	    end;
1053	false ->
1054	    add_tests([{Tag,all_nodes,{NodeOrOther,Data}}|Ts],Spec)
1055    end;
1056%% this test should be added for all known nodes
1057add_tests([Term={Tag,Data}|Ts],Spec) ->
1058    case check_term(Term) of
1059	valid ->
1060	    add_tests([{Tag,all_nodes,Data}|Ts],Spec);
1061	invalid ->
1062	    Unknown = Spec#testspec.unknown,
1063	    add_tests(Ts,Spec#testspec{unknown=Unknown++[Term]})
1064    end;
1065%% some other data than a tuple
1066add_tests([Other|Ts],Spec) ->
1067    case get(relaxed) of
1068	true ->
1069	    Unknown = Spec#testspec.unknown,
1070	    add_tests(Ts,Spec#testspec{unknown=Unknown++[Other]});
1071	false ->
1072	    throw({error,{undefined_term_in_spec,Other}})
1073    end;
1074
1075add_tests([],Spec) ->				% done
1076    Spec.
1077
1078%% check if it's a CT term that has bad format or if the user seems to
1079%% have added something of his/her own, which we'll let pass if relaxed
1080%% mode is enabled.
1081check_term(Term) when is_tuple(Term) ->
1082    Size = size(Term),
1083    [Name|_] = tuple_to_list(Term),
1084    Valid = valid_terms(),
1085    case lists:member({Name,Size},Valid) of
1086	true ->
1087	    valid;
1088	false ->
1089	    case lists:keymember(Name,1,Valid) of
1090		true ->					% halt
1091		    throw({error,{bad_term_in_spec,Term}});
1092		false ->				% ignore
1093		    case get(relaxed) of
1094			true ->
1095			    %% warn if name resembles a CT term
1096			    case resembles_ct_term(Name,size(Term)) of
1097				true ->
1098				    io:format("~nSuspicious term, "
1099					      "please check:~n"
1100					      "~tp~n", [Term]),
1101				    invalid;
1102				false ->
1103				    invalid
1104			    end;
1105			false ->
1106			    throw({error,{undefined_term_in_spec,Term}})
1107		    end
1108	    end
1109    end.
1110
1111%% specific data handling before saving in testspec record, e.g.
1112%% converting relative paths to absolute for directories and files
1113%% (introduce a clause *only* if the data value needs processing)
1114handle_data(logdir,Node,Dir,Spec) ->
1115    [{Node,ref2dir(Dir,Spec)}];
1116handle_data(cover,Node,File,Spec) ->
1117    [{Node,get_absfile(File,Spec)}];
1118handle_data(cover_stop,Node,Stop,_Spec) ->
1119    [{Node,Stop}];
1120handle_data(include,Node,Dirs=[D|_],Spec) when is_list(D) ->
1121    [{Node,ref2dir(Dir,Spec)} || Dir <- Dirs];
1122handle_data(include,Node,Dir=[Ch|_],Spec) when is_integer(Ch) ->
1123    handle_data(include,Node,[Dir],Spec);
1124handle_data(config,Node,File=[Ch|_],Spec) when is_integer(Ch) ->
1125    handle_data(config,Node,[File],Spec);
1126handle_data(config,Node,{CfgDir,File=[Ch|_]},Spec) when is_integer(Ch) ->
1127    handle_data(config,Node,{CfgDir,[File]},Spec);
1128handle_data(config,Node,Files=[F|_],Spec) when is_list(F) ->
1129    [{Node,get_absfile(File,Spec)} || File <- Files];
1130handle_data(config,Node,{CfgDir,Files=[F|_]},Spec) when is_list(F) ->
1131    [{Node,filename:join(ref2dir(CfgDir,Spec),File)} || File <- Files];
1132handle_data(userconfig,Node,CBs,Spec) when is_list(CBs) ->
1133    [{Node,{Callback,get_absfile(Callback,Config,Spec)}} ||
1134	{Callback,Config} <- CBs];
1135handle_data(userconfig,Node,CB,Spec) when is_tuple(CB) ->
1136    handle_data(userconfig,Node,[CB],Spec);
1137handle_data(event_handler,Node,H,Spec) when is_atom(H) ->
1138    handle_data(event_handler,Node,{[H],[]},Spec);
1139handle_data(event_handler,Node,{H,Args},Spec) when is_atom(H) ->
1140    handle_data(event_handler,Node,{[H],Args},Spec);
1141handle_data(event_handler,Node,Hs,_Spec) when is_list(Hs) ->
1142    [{Node,EvH,[]} || EvH <- Hs];
1143handle_data(event_handler,Node,{Hs,Args},_Spec) when is_list(Hs) ->
1144    [{Node,EvH,Args} || EvH <- Hs];
1145handle_data(ct_hooks,Node,Hooks,_Spec) when is_list(Hooks) ->
1146    [{Node,Hook} || Hook <- Hooks ];
1147handle_data(ct_hooks,Node,Hook,_Spec) ->
1148    [{Node,Hook}];
1149handle_data(stylesheet,Node,CSSFile,Spec) ->
1150    [{Node,get_absfile(CSSFile,Spec)}];
1151handle_data(verbosity,Node,VLvls,_Spec) when is_integer(VLvls) ->
1152    [{Node,[{'$unspecified',VLvls}]}];
1153handle_data(verbosity,Node,VLvls,_Spec) when is_list(VLvls) ->
1154    VLvls1 = lists:map(fun(VLvl = {_Cat,_Lvl}) -> VLvl;
1155			  (Lvl) -> {'$unspecified',Lvl} end, VLvls),
1156    [{Node,VLvls1}];
1157handle_data(multiply_timetraps,Node,Mult,_Spec) when is_integer(Mult) ->
1158    [{Node,Mult}];
1159handle_data(scale_timetraps,Node,Scale,_Spec) when Scale == true;
1160                                                   Scale == false ->
1161    [{Node,Scale}];
1162handle_data(silent_connections,Node,all,_Spec) ->
1163    [{Node,[all]}];
1164handle_data(silent_connections,Node,Conn,_Spec) when is_atom(Conn) ->
1165    [{Node,[Conn]}];
1166handle_data(silent_connections,Node,Conns,_Spec) ->
1167    [{Node,Conns}];
1168handle_data(_Tag,Node,Data,_Spec) ->
1169    [{Node,Data}].
1170
1171%% check if duplicates should be saved or not
1172should_be_added(Tag,Node,_Data,Spec) ->
1173    if
1174	%% list terms *without* possible duplicates here
1175	Tag == logdir;       Tag == logopts;
1176	Tag == basic_html;   Tag == esc_chars;
1177	Tag == label;        Tag == auto_compile;
1178	Tag == abort_if_missing_suites;
1179	Tag == stylesheet;   Tag == verbosity;
1180        Tag == multiply_timetraps;
1181        Tag == scale_timetraps;
1182	Tag == silent_connections ->
1183	    lists:keymember(ref2node(Node,Spec#testspec.nodes),1,
1184			    read_field(Spec,Tag)) == false;
1185	%% for terms *with* possible duplicates
1186	true ->
1187	    true
1188    end.
1189
1190%% check if previous elements for Node should be deleted
1191update_recorded(Tag,Node,Spec) ->
1192    if Tag == config; Tag == userconfig; Tag == event_handler;
1193       Tag == ct_hooks; Tag == include ->
1194	    read_field(Spec,Tag);
1195       true ->
1196	    %% delete previous value for Tag
1197	    lists:keydelete(Node,1,read_field(Spec,Tag))
1198    end.
1199
1200%% create one test term per node
1201per_node(Nodes,Tag,Data,Tests,Refs) ->
1202    Separated = per_node(Nodes,Tag,Data,Refs),
1203    Separated ++ Tests.
1204per_node([N|Ns],Tag,Data,Refs) ->
1205    [list_to_tuple([Tag,ref2node(N,Refs)|Data])|per_node(Ns,Tag,Data,Refs)];
1206per_node([],_,_,_) ->
1207    [].
1208
1209%% Change the testspec record "back" to a list of tuples
1210testspec_rec2list(Rec) ->
1211    {Terms,_} = lists:mapfoldl(fun(unknown, Pos) ->
1212				       {element(Pos, Rec),Pos+1};
1213				  (F, Pos) ->
1214				       {{F,element(Pos, Rec)},Pos+1}
1215			       end,2,?testspec_fields),
1216    lists:flatten(Terms).
1217
1218%% Extract one or more values from a testspec record and
1219%% return the result as a list of tuples
1220testspec_rec2list(Field, Rec) when is_atom(Field) ->
1221    [Term] = testspec_rec2list([Field], Rec),
1222    Term;
1223testspec_rec2list(Fields, Rec) ->
1224    Terms = testspec_rec2list(Rec),
1225    [{Field,proplists:get_value(Field, Terms)} || Field <- Fields].
1226
1227%% read the value for FieldName in record Rec#testspec
1228read_field(Rec, FieldName) ->
1229    catch lists:foldl(fun(F, Pos) when F == FieldName ->
1230			      throw(element(Pos, Rec));
1231			 (_,Pos)  ->
1232			      Pos+1
1233		      end,2,?testspec_fields).
1234
1235%% modify the value for FieldName in record Rec#testspec
1236mod_field(Rec, FieldName, NewVal) ->
1237    [_testspec|RecList] = tuple_to_list(Rec),
1238    RecList1 =
1239	(catch lists:foldl(fun(F, {Prev,[_OldVal|Rest]}) when F == FieldName ->
1240				   throw(lists:reverse(Prev) ++ [NewVal|Rest]);
1241			      (_,{Prev,[Field|Rest]})  ->
1242				   {[Field|Prev],Rest}
1243			   end,{[],RecList},?testspec_fields)),
1244    list_to_tuple([testspec|RecList1]).
1245
1246%% Representation:
1247%% {{Node,Dir},[{Suite1,[GrOrCase11,GrOrCase12,...]},
1248%%              {Suite2,[GrOrCase21,GrOrCase22,...]},...]}
1249%% {{Node,Dir},[{Suite1,{skip,Cmt}},
1250%%              {Suite2,[{GrOrCase21,{skip,Cmt}},GrOrCase22,...]},...]}
1251%% GrOrCase = {GroupSpec,[Case1,Case2,...]} | Case
1252%% GroupSpec = {GroupName,OverrideProps} |
1253%%             {GroupName,OverrideProps,SubGroupSpec}
1254%% OverrideProps = Props | default
1255%% SubGroupSpec = GroupSpec | []
1256
1257insert_suites(Node,Dir,[S|Ss],Tests, MergeTests) ->
1258    Tests1 = insert_cases(Node,Dir,S,all,Tests,MergeTests),
1259    insert_suites(Node,Dir,Ss,Tests1,MergeTests);
1260insert_suites(_Node,_Dir,[],Tests,_MergeTests) ->
1261    Tests;
1262insert_suites(Node,Dir,S,Tests,MergeTests) ->
1263    insert_suites(Node,Dir,[S],Tests,MergeTests).
1264
1265insert_groups(Node,Dir,Suite,Group,Cases,Tests,MergeTests)
1266  when is_atom(Group); is_tuple(Group) ->
1267    insert_groups(Node,Dir,Suite,[Group],Cases,Tests,MergeTests);
1268insert_groups(Node,Dir,Suite,Groups,Cases,Tests,false) when
1269      ((Cases == all) or is_list(Cases)) and is_list(Groups) ->
1270    Groups1 = [if is_list(Gr) ->		% preserve group path
1271		       {[Gr],Cases};
1272		  true ->
1273		       {Gr,Cases} end || Gr <- Groups],
1274    append({{Node,Dir},[{Suite,Groups1}]},Tests);
1275insert_groups(Node,Dir,Suite,Groups,Cases,Tests,true) when
1276      ((Cases == all) or is_list(Cases)) and is_list(Groups) ->
1277    Groups1 = [if is_list(Gr) ->		% preserve group path
1278		       {[Gr],Cases};
1279		  true ->
1280		       {Gr,Cases} end || Gr <- Groups],
1281    {Tests1,Done} =
1282	lists:foldr(fun(All={{N,D},[{all,_}]},{Replaced,_}) when N == Node,
1283								 D == Dir ->
1284			    {[All|Replaced],true};
1285		       ({{N,D},Suites0},{Replaced,_}) when N == Node,
1286							   D == Dir ->
1287			    Suites1 = insert_groups1(Suite,Groups1,Suites0),
1288			    {[{{N,D},Suites1}|Replaced],true};
1289		       (T,{Replaced,Match}) ->
1290			    {[T|Replaced],Match}
1291		    end, {[],false}, Tests),
1292    if not Done ->
1293	    Tests ++ [{{Node,Dir},[{Suite,Groups1}]}];
1294       true ->
1295	    Tests1
1296    end;
1297insert_groups(Node,Dir,Suite,Groups,Case,Tests, MergeTests)
1298  when is_atom(Case) ->
1299    Cases = if Case == all -> all; true -> [Case] end,
1300    insert_groups(Node,Dir,Suite,Groups,Cases,Tests, MergeTests).
1301
1302insert_groups1(_Suite,_Groups,all) ->
1303    all;
1304insert_groups1(Suite,Groups,Suites0) ->
1305    case lists:keysearch(Suite,1,Suites0) of
1306	{value,{Suite,all}} ->
1307	    Suites0;
1308	{value,{Suite,GrAndCases0}} ->
1309	    GrAndCases = insert_groups2(Groups,GrAndCases0),
1310	    insert_in_order({Suite,GrAndCases},Suites0,replace);
1311	false ->
1312	    insert_in_order({Suite,Groups},Suites0)
1313    end.
1314
1315insert_groups2(_Groups,all) ->
1316    all;
1317insert_groups2([Group={Gr,Cases}|Groups],GrAndCases) ->
1318    case lists:keysearch(Gr,1,GrAndCases) of
1319	{value,{Gr,all}} ->
1320	    GrAndCases;
1321	{value,{Gr,Cases0}} ->
1322	    Cases1 = insert_in_order(Cases,Cases0),
1323	    insert_groups2(Groups,insert_in_order({Gr,Cases1},GrAndCases));
1324	false ->
1325	    insert_groups2(Groups,insert_in_order(Group,GrAndCases))
1326    end;
1327insert_groups2([],GrAndCases) ->
1328    GrAndCases.
1329
1330insert_cases(Node,Dir,Suite,Cases,Tests,false) when is_list(Cases) ->
1331    append({{Node,Dir},[{Suite,Cases}]},Tests);
1332insert_cases(Node,Dir,Suite,Cases,Tests,true) when is_list(Cases) ->
1333    {Tests1,Done} =
1334	lists:foldr(fun(All={{N,D},[{all,_}]},{Merged,_}) when N == Node,
1335							       D == Dir ->
1336			    {[All|Merged],true};
1337		       ({{N,D},Suites0},{Merged,_}) when N == Node,
1338							   D == Dir ->
1339			    Suites1 = insert_cases1(Suite,Cases,Suites0),
1340			    {[{{N,D},Suites1}|Merged],true};
1341		       (T,{Merged,Match}) ->
1342			    {[T|Merged],Match}
1343		    end, {[],false}, Tests),
1344    if Tests == [] ->
1345	    %% initial case with length(Cases) > 1, we need to do this
1346	    %% to merge possible duplicate cases in Cases
1347	    [{{Node,Dir},insert_cases1(Suite,Cases,[{Suite,[]}])}];
1348	not Done ->
1349	    %% no merging done, simply add these cases to Tests
1350	    Tests ++ [{{Node,Dir},[{Suite,Cases}]}];
1351       true ->
1352	    Tests1
1353    end;
1354insert_cases(Node,Dir,Suite,Case,Tests,MergeTests) when is_atom(Case) ->
1355    insert_cases(Node,Dir,Suite,[Case],Tests,MergeTests).
1356
1357insert_cases1(_Suite,_Cases,all) ->
1358    all;
1359insert_cases1(Suite,Cases,Suites0) ->
1360    case lists:keysearch(Suite,1,Suites0) of
1361	{value,{Suite,all}} ->
1362	    Suites0;
1363	{value,{Suite,Cases0}} ->
1364	    Cases1 = insert_in_order(Cases,Cases0),
1365	    insert_in_order({Suite,Cases1},Suites0,replace);
1366	false ->
1367	    insert_in_order({Suite,Cases},Suites0)
1368    end.
1369
1370skip_suites(Node,Dir,[S|Ss],Cmt,Tests,MergeTests) ->
1371    Tests1 = skip_cases(Node,Dir,S,all,Cmt,Tests,MergeTests),
1372    skip_suites(Node,Dir,Ss,Cmt,Tests1,MergeTests);
1373skip_suites(_Node,_Dir,[],_Cmt,Tests,_MergeTests) ->
1374    Tests;
1375skip_suites(Node,Dir,S,Cmt,Tests,MergeTests) ->
1376    skip_suites(Node,Dir,[S],Cmt,Tests,MergeTests).
1377
1378skip_groups(Node,Dir,Suite,Group,all,Cmt,Tests,MergeTests)
1379  when is_atom(Group) ->
1380    skip_groups(Node,Dir,Suite,[Group],all,Cmt,Tests,MergeTests);
1381skip_groups(Node,Dir,Suite,Group,Cases,Cmt,Tests,MergeTests)
1382  when is_atom(Group) ->
1383    skip_groups(Node,Dir,Suite,[Group],Cases,Cmt,Tests,MergeTests);
1384skip_groups(Node,Dir,Suite,Groups,Case,Cmt,Tests,MergeTests)
1385  when is_atom(Case),Case =/= all ->
1386    skip_groups(Node,Dir,Suite,Groups,[Case],Cmt,Tests,MergeTests);
1387skip_groups(Node,Dir,Suite,Groups,Cases,Cmt,Tests,false) when
1388      ((Cases == all) or is_list(Cases)) and is_list(Groups) ->
1389    Suites1 = skip_groups1(Suite,[{Gr,Cases} || Gr <- Groups],Cmt,[]),
1390    append({{Node,Dir},Suites1},Tests);
1391skip_groups(Node,Dir,Suite,Groups,Cases,Cmt,Tests,true) when
1392      ((Cases == all) or is_list(Cases)) and is_list(Groups) ->
1393    {Tests1,Done} =
1394	lists:foldr(fun({{N,D},Suites0},{Merged,_}) when N == Node,
1395							   D == Dir ->
1396			    Suites1 = skip_groups1(Suite,
1397						   [{Gr,Cases} || Gr <- Groups],
1398						   Cmt,Suites0),
1399			    {[{{N,D},Suites1}|Merged],true};
1400		       (T,{Merged,Match}) ->
1401			    {[T|Merged],Match}
1402		    end, {[],false}, Tests),
1403    if not Done ->
1404	    Tests ++ [{{Node,Dir},skip_groups1(Suite,
1405					      [{Gr,Cases} || Gr <- Groups],
1406					      Cmt,[])}];
1407       true ->
1408	    Tests1
1409    end;
1410skip_groups(Node,Dir,Suite,Groups,Case,Cmt,Tests,MergeTests)
1411  when is_atom(Case) ->
1412    Cases = if Case == all -> all; true -> [Case] end,
1413    skip_groups(Node,Dir,Suite,Groups,Cases,Cmt,Tests,MergeTests).
1414
1415skip_groups1(Suite,Groups,Cmt,Suites0) ->
1416    SkipGroups = lists:map(fun(Group) ->
1417				   {Group,{skip,Cmt}}
1418			   end,Groups),
1419    case lists:keysearch(Suite,1,Suites0) of
1420	{value,{Suite,GrAndCases0}} ->
1421	    GrAndCases1 = GrAndCases0 ++ SkipGroups,
1422	    insert_in_order({Suite,GrAndCases1},Suites0,replace);
1423	false ->
1424	    case Suites0 of
1425		[{all,_}=All|Skips]->
1426		    [All|Skips++[{Suite,SkipGroups}]];
1427                _ ->
1428                    insert_in_order({Suite,SkipGroups},Suites0,replace)
1429            end
1430    end.
1431
1432skip_cases(Node,Dir,Suite,Cases,Cmt,Tests,false) when is_list(Cases) ->
1433    Suites1 = skip_cases1(Suite,Cases,Cmt,[]),
1434    append({{Node,Dir},Suites1},Tests);
1435skip_cases(Node,Dir,Suite,Cases,Cmt,Tests,true) when is_list(Cases) ->
1436    {Tests1,Done} =
1437	lists:foldr(fun({{N,D},Suites0},{Merged,_}) when N == Node,
1438							 D == Dir ->
1439			    Suites1 = skip_cases1(Suite,Cases,Cmt,Suites0),
1440			    {[{{N,D},Suites1}|Merged],true};
1441		       (T,{Merged,Match}) ->
1442			    {[T|Merged],Match}
1443		    end, {[],false}, Tests),
1444    if not Done ->
1445	    Tests ++ [{{Node,Dir},skip_cases1(Suite,Cases,Cmt,[])}];
1446       true ->
1447	    Tests1
1448    end;
1449skip_cases(Node,Dir,Suite,Case,Cmt,Tests,MergeTests) when is_atom(Case) ->
1450    skip_cases(Node,Dir,Suite,[Case],Cmt,Tests,MergeTests).
1451
1452skip_cases1(Suite,Cases,Cmt,Suites0) ->
1453    SkipCases = lists:map(fun(C) ->
1454				  {C,{skip,Cmt}}
1455			  end,Cases),
1456    case lists:keysearch(Suite,1,Suites0) of
1457	{value,{Suite,Cases0}} ->
1458	    Cases1 = Cases0 ++ SkipCases,
1459	    insert_in_order({Suite,Cases1},Suites0,replace);
1460	false ->
1461	    case Suites0 of
1462		[{all,_}=All|Skips]->
1463		    [All|Skips++[{Suite,SkipCases}]];
1464		_ ->
1465		    insert_in_order({Suite,SkipCases},Suites0,replace)
1466	    end
1467    end.
1468
1469append(Elem, List) ->
1470    List ++ [Elem].
1471
1472insert_in_order(Elems,Dest) ->
1473        insert_in_order1(Elems,Dest,false).
1474
1475insert_in_order(Elems,Dest,replace) ->
1476    insert_in_order1(Elems,Dest,true).
1477
1478insert_in_order1([_E|Es],all,Replace) ->
1479    insert_in_order1(Es,all,Replace);
1480
1481insert_in_order1([E|Es],List,Replace) ->
1482    List1 = insert_elem(E,List,[],Replace),
1483    insert_in_order1(Es,List1,Replace);
1484insert_in_order1([],List,_Replace) ->
1485    List;
1486insert_in_order1(E,List,Replace) ->
1487    insert_elem(E,List,[],Replace).
1488
1489
1490insert_elem({Key,_}=E,[{Key,_}|Rest],SoFar,true) ->
1491    lists:reverse([E|SoFar]) ++ Rest;
1492insert_elem({E,_},[E|Rest],SoFar,true) ->
1493    lists:reverse([E|SoFar]) ++ Rest;
1494insert_elem(E,[E|Rest],SoFar,true) ->
1495    lists:reverse([E|SoFar]) ++ Rest;
1496
1497insert_elem({all,_}=E,_,SoFar,_Replace) ->
1498    lists:reverse([E|SoFar]);
1499insert_elem(_E,[all|_],SoFar,_Replace) ->
1500    lists:reverse(SoFar);
1501insert_elem(_E,[{all,_}],SoFar,_Replace) ->
1502    lists:reverse(SoFar);
1503insert_elem({Key,_}=E,[{Key,[]}|Rest],SoFar,_Replace) ->
1504    lists:reverse([E|SoFar]) ++ Rest;
1505insert_elem(E,[E1|Rest],SoFar,Replace) ->
1506    insert_elem(E,Rest,[E1|SoFar],Replace);
1507insert_elem(E,[],SoFar,_Replace) ->
1508    lists:reverse([E|SoFar]).
1509
1510ref2node(all_nodes,_Refs) ->
1511    all_nodes;
1512ref2node(master,_Refs) ->
1513    master;
1514ref2node(RefOrNode,Refs) ->
1515    case lists:member($@,atom_to_list(RefOrNode)) of
1516	false ->				% a ref
1517	    case lists:keysearch(RefOrNode,1,Refs) of
1518		{value,{RefOrNode,Node}} ->
1519		    Node;
1520		false ->
1521		    throw({error,{noderef_missing,RefOrNode}})
1522	    end;
1523	true ->					% a node
1524	    RefOrNode
1525    end.
1526
1527ref2dir(Ref,Spec) ->
1528    ref2dir(Ref,Spec#testspec.alias,Spec).
1529
1530ref2dir(Ref,Refs,Spec) when is_atom(Ref) ->
1531    case lists:keysearch(Ref,1,Refs) of
1532	{value,{Ref,Dir}} ->
1533	    get_absdir(Dir,Spec);
1534	false ->
1535	    throw({error,{alias_missing,Ref}})
1536    end;
1537ref2dir(Dir,_,Spec) when is_list(Dir) ->
1538    get_absdir(Dir,Spec);
1539ref2dir(What,_,_) ->
1540    throw({error,{invalid_directory_name,What}}).
1541
1542is_node(What,Nodes) when is_atom(What) ->
1543    is_node([What],Nodes);
1544is_node([master|_],_Nodes) ->
1545    true;
1546is_node(What={N,H},Nodes) when is_atom(N), is_atom(H) ->
1547    is_node([What],Nodes);
1548is_node([What|_],Nodes) ->
1549    case lists:keymember(What,1,Nodes) or
1550	 lists:keymember(What,2,Nodes) of
1551	true ->
1552	    true;
1553	false ->
1554	    false
1555    end;
1556is_node([],_) ->
1557    false.
1558
1559valid_terms() ->
1560    [
1561     {set_merge_tests,2},
1562     {define,3},
1563     {specs,3},
1564     {node,3},
1565     {cover,2},
1566     {cover,3},
1567     {cover_stop,2},
1568     {cover_stop,3},
1569     {config,2},
1570     {config,3},
1571     {config,4},
1572     {userconfig,2},
1573     {userconfig,3},
1574     {alias,3},
1575     {merge_tests,2},
1576     {logdir,2},
1577     {logdir,3},
1578     {logopts,2},
1579     {logopts,3},
1580     {basic_html,2},
1581     {basic_html,3},
1582     {esc_chars,2},
1583     {esc_chars,3},
1584     {verbosity,2},
1585     {verbosity,3},
1586     {silent_connections,2},
1587     {silent_connections,3},
1588     {label,2},
1589     {label,3},
1590     {event_handler,2},
1591     {event_handler,3},
1592     {event_handler,4},
1593     {ct_hooks,2},
1594     {ct_hooks,3},
1595     {enable_builtin_hooks,2},
1596     {release_shell,2},
1597     {multiply_timetraps,2},
1598     {multiply_timetraps,3},
1599     {scale_timetraps,2},
1600     {scale_timetraps,3},
1601     {include,2},
1602     {include,3},
1603     {auto_compile,2},
1604     {auto_compile,3},
1605     {abort_if_missing_suites,2},
1606     {abort_if_missing_suites,3},
1607     {stylesheet,2},
1608     {stylesheet,3},
1609     {suites,3},
1610     {suites,4},
1611     {groups,4},
1612     {groups,5},
1613     {groups,6},
1614     {cases,4},
1615     {cases,5},
1616     {skip_suites,4},
1617     {skip_suites,5},
1618     {skip_groups,5},
1619     {skip_groups,6},
1620     {skip_groups,7},
1621     {skip_cases,5},
1622     {skip_cases,6},
1623     {create_priv_dir,2},
1624     {create_priv_dir,3}
1625    ].
1626
1627%% this function "guesses" if the user has misspelled a term name
1628resembles_ct_term(Name,Size) when is_atom(Name) ->
1629    resembles_ct_term2(atom_to_list(Name),Size);
1630resembles_ct_term(_Name,_) ->
1631    false.
1632
1633resembles_ct_term2(Name,Size) when length(Name) > 3 ->
1634    CTTerms = [{atom_to_list(Tag),Sz} || {Tag,Sz} <- valid_terms()],
1635    compare_names(Name,Size,CTTerms);
1636resembles_ct_term2(_,_) ->
1637    false.
1638
1639compare_names(Name,Size,[{Term,Sz}|Ts]) ->
1640    if abs(Size-Sz) > 0 ->
1641	    compare_names(Name,Size,Ts);
1642       true ->
1643	    Diff = abs(length(Name)-length(Term)),
1644	    if Diff > 1 ->
1645		    compare_names(Name,Size,Ts);
1646	       true ->
1647		    Common = common_letters(Name,Term,0),
1648		    Bad = abs(length(Name)-Common),
1649		    if Bad > 2 ->
1650			    compare_names(Name,Size,Ts);
1651		       true ->
1652			    true
1653		    end
1654	    end
1655    end;
1656compare_names(_,_,[]) ->
1657    false.
1658
1659common_letters(_,[],Count) ->
1660    Count;
1661common_letters([L|Ls],Term,Count) ->
1662    case lists:member(L,Term) of
1663	true ->
1664	    Term1 = lists:delete(L,Term),
1665	    common_letters(Ls,Term1,Count+1);
1666	false ->
1667	    common_letters(Ls,Term,Count)
1668    end;
1669common_letters([],_,Count) ->
1670    Count.
1671