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