1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2004-2020. 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_run). 22 23%% Script interface 24-export([script_start/0,script_usage/0]). 25 26%% User interface 27-export([install/1,install/2,run/1,run/2,run/3,run_test/1, 28 run_testspec/1,step/3,step/4,refresh_logs/1]). 29 30%% Misc internal API functions 31-export([variables_file_name/1,script_start1/2,run_test2/1, run_make/3]). 32 33-include("ct.hrl"). 34-include("ct_event.hrl"). 35-include("ct_util.hrl"). 36 37-define(abs(Name), filename:absname(Name)). 38-define(testdir(Name, Suite), ct_util:get_testdir(Name, Suite)). 39 40-define(EXIT_STATUS_TEST_SUCCESSFUL, 0). 41-define(EXIT_STATUS_TEST_CASE_FAILED, 1). 42-define(EXIT_STATUS_TEST_RUN_FAILED, 2). 43 44-define(default_verbosity, [{default,?MAX_VERBOSITY}, 45 {'$unspecified',?MAX_VERBOSITY}]). 46 47-record(opts, {label, 48 profile, 49 shell, 50 cover, 51 cover_stop, 52 coverspec, 53 step, 54 logdir, 55 logopts = [], 56 basic_html, 57 esc_chars = true, 58 verbosity = [], 59 config = [], 60 event_handlers = [], 61 ct_hooks = [], 62 enable_builtin_hooks, 63 include = [], 64 auto_compile, 65 abort_if_missing_suites, 66 silent_connections = [], 67 stylesheet, 68 multiply_timetraps, 69 scale_timetraps, 70 create_priv_dir, 71 testspec_files = [], 72 current_testspec, 73 tests, 74 starter}). 75 76script_start() -> 77 process_flag(trap_exit, true), 78 Init = init:get_arguments(), 79 CtArgs = lists:takewhile(fun({ct_erl_args,_}) -> false; 80 (_) -> true end, Init), 81 82 %% convert relative dirs added with pa or pz (pre erl_args on 83 %% the ct_run command line) to absolute so that app modules 84 %% can be found even after CT changes CWD to logdir 85 rel_to_abs(CtArgs), 86 87 Args = 88 case application:get_env(common_test, run_test_start_opts) of 89 {ok,EnvStartOpts} -> 90 FlagFilter = fun(Flags) -> 91 lists:filter(fun({root,_}) -> false; 92 ({progname,_}) -> false; 93 ({home,_}) -> false; 94 ({noshell,_}) -> false; 95 ({noinput,_}) -> false; 96 (_) -> true 97 end, Flags) 98 end, 99 %% used for purpose of testing the run_test interface 100 io:format(user, "~n-------------------- START ARGS " 101 "--------------------~n", []), 102 io:format(user, "--- Init args:~n~tp~n", [FlagFilter(Init)]), 103 io:format(user, "--- CT args:~n~tp~n", [FlagFilter(CtArgs)]), 104 EnvArgs = opts2args(EnvStartOpts), 105 io:format(user, "--- Env opts -> args:~n~tp~n =>~n~tp~n", 106 [EnvStartOpts,EnvArgs]), 107 Merged = merge_arguments(CtArgs ++ EnvArgs), 108 io:format(user, "--- Merged args:~n~tp~n", [FlagFilter(Merged)]), 109 io:format(user, "-----------------------------------" 110 "-----------------~n~n", []), 111 Merged; 112 _ -> 113 merge_arguments(CtArgs) 114 end, 115 case proplists:get_value(help, Args) of 116 undefined -> script_start(Args); 117 _ -> script_usage() 118 end. 119 120script_start(Args) -> 121 Tracing = start_trace(Args), 122 case ct_repeat:loop_test(script, Args) of 123 false -> 124 {ok,Cwd} = file:get_cwd(), 125 CTVsn = 126 case filename:basename(code:lib_dir(common_test)) of 127 CTBase when is_list(CTBase) -> 128 case string:lexemes(CTBase, "-") of 129 ["common_test",Vsn] -> " v"++Vsn; 130 _ -> "" 131 end 132 end, 133 io:format("~nCommon Test~s starting (cwd is ~ts)~n~n", 134 [CTVsn,Cwd]), 135 Self = self(), 136 Pid = spawn_link(fun() -> script_start1(Self, Args) end), 137 receive 138 {'EXIT',Pid,Reason} -> 139 case Reason of 140 {user_error,What} -> 141 io:format("\nTest run failed!\nReason: ~tp\n\n\n", 142 [What]), 143 finish(Tracing, ?EXIT_STATUS_TEST_RUN_FAILED, Args); 144 _ -> 145 io:format("Test run crashed! " 146 "This could be an internal error " 147 "- please report!\n\n" 148 "~tp\n\n\n", [Reason]), 149 finish(Tracing, ?EXIT_STATUS_TEST_RUN_FAILED, Args) 150 end; 151 {Pid,{error,Reason}} -> 152 io:format("\nTest run failed! Reason:\n~tp\n\n\n",[Reason]), 153 finish(Tracing, ?EXIT_STATUS_TEST_RUN_FAILED, Args); 154 {Pid,Result} -> 155 io:nl(), 156 finish(Tracing, analyze_test_result(Result, Args), Args) 157 end; 158 {error,_LoopReason} -> 159 finish(Tracing, ?EXIT_STATUS_TEST_RUN_FAILED, Args); 160 Result -> 161 io:nl(), 162 finish(Tracing, analyze_test_result(Result, Args), Args) 163 end. 164 165%% analyze the result of one test run, or many (in case of looped test) 166analyze_test_result(ok, _) -> 167 ?EXIT_STATUS_TEST_SUCCESSFUL; 168analyze_test_result({error,_Reason}, _) -> 169 ?EXIT_STATUS_TEST_RUN_FAILED; 170analyze_test_result({_Ok,Failed,{_UserSkipped,AutoSkipped}}, Args) -> 171 if Failed > 0 -> 172 ?EXIT_STATUS_TEST_CASE_FAILED; 173 true -> 174 case AutoSkipped of 175 0 -> 176 ?EXIT_STATUS_TEST_SUCCESSFUL; 177 _ -> 178 case get_start_opt(exit_status, 179 fun([ExitOpt]) -> ExitOpt end, 180 Args) of 181 undefined -> 182 ?EXIT_STATUS_TEST_CASE_FAILED; 183 "ignore_config" -> 184 ?EXIT_STATUS_TEST_SUCCESSFUL 185 end 186 end 187 end; 188analyze_test_result([Result|Rs], Args) -> 189 case analyze_test_result(Result, Args) of 190 ?EXIT_STATUS_TEST_SUCCESSFUL -> 191 analyze_test_result(Rs, Args); 192 Other -> 193 Other 194 end; 195analyze_test_result([], _) -> 196 ?EXIT_STATUS_TEST_SUCCESSFUL; 197analyze_test_result(interactive_mode, _) -> 198 interactive_mode; 199analyze_test_result(Unknown, _) -> 200 io:format("\nTest run failed! Reason:\n~tp\n\n\n",[Unknown]), 201 ?EXIT_STATUS_TEST_RUN_FAILED. 202 203finish(Tracing, ExitStatus, Args) -> 204 stop_trace(Tracing), 205 timer:sleep(1000), 206 if ExitStatus == interactive_mode -> 207 interactive_mode; 208 true -> 209 %% it's possible to tell CT to finish execution with a call 210 %% to a different function than the normal halt/1 BIF 211 %% (meant to be used mainly for reading the CT exit status) 212 case get_start_opt(halt_with, 213 fun([HaltMod,HaltFunc]) -> 214 {list_to_atom(HaltMod), 215 list_to_atom(HaltFunc)} end, 216 Args) of 217 undefined -> 218 halt(ExitStatus); 219 {M,F} -> 220 apply(M, F, [ExitStatus]) 221 end 222 end. 223 224script_start1(Parent, Args) -> 225 %% tag this process 226 ct_util:mark_process(), 227 %% read general start flags 228 Label = get_start_opt(label, fun([Lbl]) -> Lbl end, Args), 229 Profile = get_start_opt(profile, fun([Prof]) -> Prof end, Args), 230 Shell = get_start_opt(shell, true, Args), 231 Cover = get_start_opt(cover, fun([CoverFile]) -> ?abs(CoverFile) end, Args), 232 CoverStop = get_start_opt(cover_stop, 233 fun([CS]) -> list_to_atom(CS) end, Args), 234 LogDir = get_start_opt(logdir, fun([LogD]) -> LogD end, Args), 235 LogOpts = get_start_opt(logopts, 236 fun(Os) -> [list_to_atom(O) || O <- Os] end, 237 [], Args), 238 Verbosity = verbosity_args2opts(Args), 239 MultTT = get_start_opt(multiply_timetraps, 240 fun([MT]) -> list_to_number(MT) end, Args), 241 ScaleTT = get_start_opt(scale_timetraps, 242 fun([CT]) -> list_to_atom(CT); 243 ([]) -> true 244 end, Args), 245 CreatePrivDir = get_start_opt(create_priv_dir, 246 fun([PD]) -> list_to_atom(PD); 247 ([]) -> auto_per_tc 248 end, Args), 249 EvHandlers = event_handler_args2opts(Args), 250 CTHooks = ct_hooks_args2opts(Args), 251 EnableBuiltinHooks = get_start_opt(enable_builtin_hooks, 252 fun([CT]) -> list_to_atom(CT); 253 ([]) -> undefined 254 end, undefined, Args), 255 256 %% check flags and set corresponding application env variables 257 258 %% ct_decrypt_key | ct_decrypt_file 259 case proplists:get_value(ct_decrypt_key, Args) of 260 [DecryptKey] -> 261 application:set_env(common_test, decrypt, {key,DecryptKey}); 262 undefined -> 263 case proplists:get_value(ct_decrypt_file, Args) of 264 [DecryptFile] -> 265 application:set_env(common_test, decrypt, 266 {file,?abs(DecryptFile)}); 267 undefined -> 268 application:unset_env(common_test, decrypt) 269 end 270 end, 271 %% no_auto_compile + include 272 {AutoCompile,IncludeDirs} = 273 case proplists:get_value(no_auto_compile, Args) of 274 undefined -> 275 application:set_env(common_test, auto_compile, true), 276 InclDirs = 277 case proplists:get_value(include, Args) of 278 Incls when is_list(hd(Incls)) -> 279 [filename:absname(IDir) || IDir <- Incls]; 280 Incl when is_list(Incl) -> 281 [filename:absname(Incl)]; 282 undefined -> 283 [] 284 end, 285 case os:getenv("CT_INCLUDE_PATH") of 286 false -> 287 application:set_env(common_test, include, InclDirs), 288 {undefined,InclDirs}; 289 CtInclPath -> 290 AllInclDirs = 291 string:lexemes(CtInclPath,[$:,$ ,$,]) ++ InclDirs, 292 application:set_env(common_test, include, AllInclDirs), 293 {undefined,AllInclDirs} 294 end; 295 _ -> 296 application:set_env(common_test, auto_compile, false), 297 {false,[]} 298 end, 299 300 %% abort test run if some suites can't be compiled 301 AbortIfMissing = get_start_opt(abort_if_missing_suites, 302 fun([]) -> true; 303 ([Bool]) -> list_to_atom(Bool) 304 end, false, Args), 305 %% silent connections 306 SilentConns = 307 get_start_opt(silent_connections, 308 fun(["all"]) -> [all]; 309 (Conns) -> [list_to_atom(Conn) || Conn <- Conns] 310 end, [], Args), 311 %% stylesheet 312 Stylesheet = get_start_opt(stylesheet, 313 fun([SS]) -> ?abs(SS) end, Args), 314 %% basic_html - used by ct_logs 315 BasicHtml = case proplists:get_value(basic_html, Args) of 316 undefined -> 317 application:set_env(common_test, basic_html, false), 318 undefined; 319 _ -> 320 application:set_env(common_test, basic_html, true), 321 true 322 end, 323 %% esc_chars - used by ct_logs 324 EscChars = case proplists:get_value(no_esc_chars, Args) of 325 undefined -> 326 application:set_env(common_test, esc_chars, true), 327 undefined; 328 _ -> 329 application:set_env(common_test, esc_chars, false), 330 false 331 end, 332 %% disable_log_cache - used by ct_logs 333 case proplists:get_value(disable_log_cache, Args) of 334 undefined -> 335 application:set_env(common_test, disable_log_cache, false); 336 _ -> 337 application:set_env(common_test, disable_log_cache, true) 338 end, 339 %% log_cleanup - used by ct_logs 340 KeepLogs = get_start_opt(keep_logs, 341 fun ct_logs:parse_keep_logs/1, 342 all, 343 Args), 344 application:set_env(common_test, keep_logs, KeepLogs), 345 346 Opts = #opts{label = Label, profile = Profile, 347 shell = Shell, 348 cover = Cover, cover_stop = CoverStop, 349 logdir = LogDir, logopts = LogOpts, 350 basic_html = BasicHtml, 351 esc_chars = EscChars, 352 verbosity = Verbosity, 353 event_handlers = EvHandlers, 354 ct_hooks = CTHooks, 355 enable_builtin_hooks = EnableBuiltinHooks, 356 auto_compile = AutoCompile, 357 abort_if_missing_suites = AbortIfMissing, 358 include = IncludeDirs, 359 silent_connections = SilentConns, 360 stylesheet = Stylesheet, 361 multiply_timetraps = MultTT, 362 scale_timetraps = ScaleTT, 363 create_priv_dir = CreatePrivDir, 364 starter = script}, 365 366 %% check if log files should be refreshed or go on to run tests... 367 Result = run_or_refresh(Opts, Args), 368 369 %% send final results to starting process waiting in script_start/0 370 Parent ! {self(), Result}. 371 372run_or_refresh(Opts = #opts{logdir = LogDir}, Args) -> 373 case proplists:get_value(refresh_logs, Args) of 374 undefined -> 375 script_start2(Opts, Args); 376 Refresh -> 377 LogDir1 = case Refresh of 378 [] -> which(logdir,LogDir); 379 [RefreshDir] -> ?abs(RefreshDir) 380 end, 381 {ok,Cwd} = file:get_cwd(), 382 ok = file:set_cwd(LogDir1), 383 %% give the shell time to print version etc 384 timer:sleep(500), 385 io:nl(), 386 case catch ct_logs:make_all_runs_index(refresh) of 387 {'EXIT',ARReason} -> 388 ok = file:set_cwd(Cwd), 389 {error,{all_runs_index,ARReason}}; 390 _ -> 391 case catch ct_logs:make_all_suites_index(refresh) of 392 {'EXIT',ASReason} -> 393 ok = file:set_cwd(Cwd), 394 {error,{all_suites_index,ASReason}}; 395 _ -> 396 ok = file:set_cwd(Cwd), 397 io:format("Logs in ~ts refreshed!~n~n", 398 [LogDir1]), 399 timer:sleep(500), % time to flush io before quitting 400 ok 401 end 402 end 403 end. 404 405script_start2(Opts = #opts{shell = undefined}, Args) -> 406 case proplists:get_value(spec, Args) of 407 Specs when Specs =/= [], Specs =/= undefined -> 408 Specs1 = get_start_opt(join_specs, [Specs], Specs, Args), 409 %% using testspec as input for test 410 Relaxed = get_start_opt(allow_user_terms, true, false, Args), 411 try ct_testspec:collect_tests_from_file(Specs1, Relaxed) of 412 TestSpecData -> 413 execute_all_specs(TestSpecData, Opts, Args, []) 414 catch 415 throw:{error,Reason}:StackTrace -> 416 {error,{invalid_testspec,{Reason,StackTrace}}}; 417 _:Reason:StackTrace -> 418 {error,{invalid_testspec,{Reason,StackTrace}}} 419 end; 420 [] -> 421 {error,no_testspec_specified}; 422 _ -> % no testspec used 423 %% read config/userconfig from start flags 424 InitConfig = ct_config:prepare_config_list(Args), 425 TheLogDir = which(logdir, Opts#opts.logdir), 426 case check_and_install_configfiles(InitConfig, 427 TheLogDir, 428 Opts) of 429 ok -> % go on read tests from start flags 430 script_start3(Opts#opts{config=InitConfig, 431 logdir=TheLogDir}, Args); 432 Error -> 433 Error 434 end 435 end; 436 437script_start2(Opts, Args) -> 438 %% read config/userconfig from start flags 439 InitConfig = ct_config:prepare_config_list(Args), 440 LogDir = which(logdir, Opts#opts.logdir), 441 case check_and_install_configfiles(InitConfig, LogDir, Opts) of 442 ok -> % go on read tests from start flags 443 script_start3(Opts#opts{config=InitConfig, 444 logdir=LogDir}, Args); 445 Error -> 446 Error 447 end. 448 449execute_all_specs([], _, _, Result) -> 450 Result1 = lists:reverse(Result), 451 case lists:keysearch('EXIT', 1, Result1) of 452 {value,{_,_,ExitReason}} -> 453 exit(ExitReason); 454 false -> 455 case lists:keysearch(error, 1, Result1) of 456 {value,Error} -> 457 Error; 458 false -> 459 lists:foldl(fun({Ok,Fail,{UserSkip,AutoSkip}}, 460 {Ok1,Fail1,{UserSkip1,AutoSkip1}}) -> 461 {Ok1+Ok,Fail1+Fail, 462 {UserSkip1+UserSkip, 463 AutoSkip1+AutoSkip}} 464 end, {0,0,{0,0}}, Result1) 465 end 466 end; 467 468execute_all_specs([{Specs,TS} | TSs], Opts, Args, Result) -> 469 CombinedOpts = combine_test_opts(TS, Specs, Opts), 470 try execute_one_spec(TS, CombinedOpts, Args) of 471 ExecResult -> 472 execute_all_specs(TSs, Opts, Args, [ExecResult|Result]) 473 catch 474 _ : ExitReason -> 475 execute_all_specs(TSs, Opts, Args, 476 [{'EXIT',self(),ExitReason}|Result]) 477 end. 478 479execute_one_spec(TS, Opts, Args) -> 480 %% read config/userconfig from start flags 481 InitConfig = ct_config:prepare_config_list(Args), 482 TheLogDir = which(logdir, Opts#opts.logdir), 483 %% merge config from start flags with config from testspec 484 AllConfig = merge_vals([InitConfig, Opts#opts.config]), 485 case check_and_install_configfiles(AllConfig, TheLogDir, Opts) of 486 ok -> % read tests from spec 487 {Run,Skip} = ct_testspec:prepare_tests(TS, node()), 488 Result = do_run(Run, Skip, Opts#opts{config=AllConfig, 489 logdir=TheLogDir, 490 current_testspec=TS}, Args), 491 ct_util:delete_testdata(testspec), 492 Result; 493 Error -> 494 Error 495 end. 496 497combine_test_opts(TS, Specs, Opts) -> 498 TSOpts = get_data_for_node(TS, node()), 499 500 Label = choose_val(Opts#opts.label, 501 TSOpts#opts.label), 502 503 Profile = choose_val(Opts#opts.profile, 504 TSOpts#opts.profile), 505 506 LogDir = choose_val(Opts#opts.logdir, 507 TSOpts#opts.logdir), 508 509 AllLogOpts = merge_vals([Opts#opts.logopts, 510 TSOpts#opts.logopts]), 511 AllVerbosity = 512 merge_keyvals([Opts#opts.verbosity, 513 TSOpts#opts.verbosity]), 514 AllSilentConns = 515 merge_vals([Opts#opts.silent_connections, 516 TSOpts#opts.silent_connections]), 517 Cover = 518 choose_val(Opts#opts.cover, 519 TSOpts#opts.cover), 520 CoverStop = 521 choose_val(Opts#opts.cover_stop, 522 TSOpts#opts.cover_stop), 523 MultTT = 524 choose_val(Opts#opts.multiply_timetraps, 525 TSOpts#opts.multiply_timetraps), 526 ScaleTT = 527 choose_val(Opts#opts.scale_timetraps, 528 TSOpts#opts.scale_timetraps), 529 530 CreatePrivDir = 531 choose_val(Opts#opts.create_priv_dir, 532 TSOpts#opts.create_priv_dir), 533 534 AllEvHs = 535 merge_vals([Opts#opts.event_handlers, 536 TSOpts#opts.event_handlers]), 537 538 AllCTHooks = merge_vals( 539 [Opts#opts.ct_hooks, 540 TSOpts#opts.ct_hooks]), 541 542 EnableBuiltinHooks = 543 choose_val( 544 Opts#opts.enable_builtin_hooks, 545 TSOpts#opts.enable_builtin_hooks), 546 547 Stylesheet = 548 choose_val(Opts#opts.stylesheet, 549 TSOpts#opts.stylesheet), 550 551 AllInclude = merge_vals([Opts#opts.include, 552 TSOpts#opts.include]), 553 application:set_env(common_test, include, AllInclude), 554 555 AutoCompile = 556 case choose_val(Opts#opts.auto_compile, 557 TSOpts#opts.auto_compile) of 558 undefined -> 559 true; 560 ACBool -> 561 application:set_env(common_test, 562 auto_compile, 563 ACBool), 564 ACBool 565 end, 566 567 AbortIfMissing = choose_val(Opts#opts.abort_if_missing_suites, 568 TSOpts#opts.abort_if_missing_suites), 569 570 BasicHtml = 571 case choose_val(Opts#opts.basic_html, 572 TSOpts#opts.basic_html) of 573 undefined -> 574 false; 575 BHBool -> 576 application:set_env(common_test, basic_html, 577 BHBool), 578 BHBool 579 end, 580 581 EscChars = 582 case choose_val(Opts#opts.esc_chars, 583 TSOpts#opts.esc_chars) of 584 undefined -> 585 true; 586 ECBool -> 587 application:set_env(common_test, esc_chars, 588 ECBool), 589 ECBool 590 end, 591 592 Opts#opts{label = Label, 593 profile = Profile, 594 testspec_files = Specs, 595 cover = Cover, 596 cover_stop = CoverStop, 597 logdir = which(logdir, LogDir), 598 logopts = AllLogOpts, 599 basic_html = BasicHtml, 600 esc_chars = EscChars, 601 verbosity = AllVerbosity, 602 silent_connections = AllSilentConns, 603 config = TSOpts#opts.config, 604 event_handlers = AllEvHs, 605 ct_hooks = AllCTHooks, 606 enable_builtin_hooks = EnableBuiltinHooks, 607 stylesheet = Stylesheet, 608 auto_compile = AutoCompile, 609 abort_if_missing_suites = AbortIfMissing, 610 include = AllInclude, 611 multiply_timetraps = MultTT, 612 scale_timetraps = ScaleTT, 613 create_priv_dir = CreatePrivDir}. 614 615check_and_install_configfiles( 616 Configs, LogDir, #opts{ 617 event_handlers = EvHandlers, 618 ct_hooks = CTHooks, 619 enable_builtin_hooks = EnableBuiltinHooks} ) -> 620 case ct_config:check_config_files(Configs) of 621 false -> 622 install([{config,Configs}, 623 {event_handler,EvHandlers}, 624 {ct_hooks,CTHooks}, 625 {enable_builtin_hooks,EnableBuiltinHooks}], LogDir); 626 {value,{error,{nofile,File}}} -> 627 {error,{cant_read_config_file,File}}; 628 {value,{error,{wrong_config,Message}}}-> 629 {error,{wrong_config,Message}}; 630 {value,{error,{callback,Info}}} -> 631 {error,{cant_load_callback_module,Info}} 632 end. 633 634script_start3(Opts, Args) -> 635 Opts1 = get_start_opt(step, 636 fun(Step) -> 637 Opts#opts{step = Step, 638 cover = undefined} 639 end, Opts, Args), 640 case {proplists:get_value(dir, Args), 641 proplists:get_value(suite, Args), 642 groups_and_cases(proplists:get_value(group, Args), 643 proplists:get_value(testcase, Args))} of 644 %% flag specified without data 645 {_,_,Error={error,_}} -> 646 Error; 647 {_,[],_} -> 648 {error,no_suite_specified}; 649 {[],_,_} -> 650 {error,no_dir_specified}; 651 652 {Dirs,undefined,[]} when is_list(Dirs) -> 653 script_start4(Opts#opts{tests = tests(Dirs)}, Args); 654 655 {undefined,Suites,[]} when is_list(Suites) -> 656 Ts = tests([suite_to_test(S) || S <- Suites]), 657 script_start4(Opts1#opts{tests = Ts}, Args); 658 659 {undefined,Suite,GsAndCs} when is_list(Suite) -> 660 case [suite_to_test(S) || S <- Suite] of 661 DirMods = [_] -> 662 Ts = tests(DirMods, GsAndCs), 663 script_start4(Opts1#opts{tests = Ts}, Args); 664 [_,_|_] -> 665 {error,multiple_suites_and_cases}; 666 _ -> 667 {error,incorrect_start_options} 668 end; 669 670 {[_,_|_],Suite,[]} when is_list(Suite) -> 671 {error,multiple_dirs_and_suites}; 672 673 {[Dir],Suite,GsAndCs} when is_list(Dir), is_list(Suite) -> 674 case [suite_to_test(Dir,S) || S <- Suite] of 675 DirMods when GsAndCs == [] -> 676 Ts = tests(DirMods), 677 script_start4(Opts1#opts{tests = Ts}, Args); 678 DirMods = [_] when GsAndCs /= [] -> 679 Ts = tests(DirMods, GsAndCs), 680 script_start4(Opts1#opts{tests = Ts}, Args); 681 [_,_|_] when GsAndCs /= [] -> 682 {error,multiple_suites_and_cases}; 683 _ -> 684 {error,incorrect_start_options} 685 end; 686 687 {undefined,undefined,GsAndCs} when GsAndCs /= [] -> 688 {error,incorrect_start_options}; 689 690 {undefined,undefined,_} -> 691 if Opts#opts.shell -> 692 script_start4(Opts#opts{tests = []}, Args); 693 true -> 694 %% no start options, use default "-dir ./" 695 {ok,Dir} = file:get_cwd(), 696 io:format("ct_run -dir ~ts~n~n", [Dir]), 697 script_start4(Opts#opts{tests = tests([Dir])}, Args) 698 end 699 end. 700 701script_start4(#opts{label = Label, profile = Profile, 702 shell = true, config = Config, 703 event_handlers = EvHandlers, 704 ct_hooks = CTHooks, 705 logopts = LogOpts, 706 verbosity = Verbosity, 707 enable_builtin_hooks = EnableBuiltinHooks, 708 logdir = LogDir, testspec_files = Specs}, _Args) -> 709 710 %% label - used by ct_logs 711 application:set_env(common_test, test_label, Label), 712 713 %% profile - used in ct_util 714 application:set_env(common_test, profile, Profile), 715 716 if Config == [] -> 717 ok; 718 true -> 719 io:format("\nInstalling: ~tp\n\n", [Config]) 720 end, 721 case install([{config,Config},{event_handler,EvHandlers}, 722 {ct_hooks, CTHooks}, 723 {enable_builtin_hooks,EnableBuiltinHooks}]) of 724 ok -> 725 _ = ct_util:start(interactive, LogDir, 726 add_verbosity_defaults(Verbosity)), 727 ct_util:set_testdata({logopts, LogOpts}), 728 log_ts_names(Specs), 729 io:nl(), 730 interactive_mode; 731 Error -> 732 Error 733 end; 734script_start4(Opts = #opts{tests = Tests}, Args) -> 735 do_run(Tests, [], Opts, Args). 736 737script_usage() -> 738 io:format("\nUsage:\n\n"), 739 io:format("Run tests from command line:\n\n" 740 "\tct_run -dir TestDir1 TestDir2 .. TestDirN |" 741 "\n\t [-dir TestDir] -suite Suite1 Suite2 .. SuiteN" 742 "\n\t [-group Group1 Group2 .. GroupN] [-case Case1 Case2 .. CaseN]" 743 "\n\t [-step [config | keep_inactive]]" 744 "\n\t [-config ConfigFile1 ConfigFile2 .. ConfigFileN]" 745 "\n\t [-userconfig CallbackModule ConfigFile1 .. ConfigFileN]" 746 "\n\t [-decrypt_key Key] | [-decrypt_file KeyFile]" 747 "\n\t [-logdir LogDir]" 748 "\n\t [-logopts LogOpt1 LogOpt2 .. LogOptN]" 749 "\n\t [-verbosity GenVLvl | [CategoryVLvl1 .. CategoryVLvlN]]" 750 "\n\t [-silent_connections [ConnType1 ConnType2 .. ConnTypeN]]" 751 "\n\t [-stylesheet CSSFile]" 752 "\n\t [-cover CoverCfgFile]" 753 "\n\t [-cover_stop Bool]" 754 "\n\t [-event_handler EvHandler1 EvHandler2 .. EvHandlerN]" 755 "\n\t [-ct_hooks CTHook1 CTHook2 .. CTHookN]" 756 "\n\t [-include InclDir1 InclDir2 .. InclDirN]" 757 "\n\t [-no_auto_compile]" 758 "\n\t [-abort_if_missing_suites]" 759 "\n\t [-multiply_timetraps N]" 760 "\n\t [-scale_timetraps]" 761 "\n\t [-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]" 762 "\n\t [-basic_html]" 763 "\n\t [-no_esc_chars]" 764 "\n\t [-repeat N] |" 765 "\n\t [-duration HHMMSS [-force_stop [skip_rest]]] |" 766 "\n\t [-until [YYMoMoDD]HHMMSS [-force_stop [skip_rest]]]" 767 "\n\t [-exit_status ignore_config]" 768 "\n\t [-help]\n\n"), 769 io:format("Run tests using test specification:\n\n" 770 "\tct_run -spec TestSpec1 TestSpec2 .. TestSpecN" 771 "\n\t [-config ConfigFile1 ConfigFile2 .. ConfigFileN]" 772 "\n\t [-decrypt_key Key] | [-decrypt_file KeyFile]" 773 "\n\t [-logdir LogDir]" 774 "\n\t [-logopts LogOpt1 LogOpt2 .. LogOptN]" 775 "\n\t [-verbosity GenVLvl | [CategoryVLvl1 .. CategoryVLvlN]]" 776 "\n\t [-allow_user_terms]" 777 "\n\t [-join_specs]" 778 "\n\t [-silent_connections [ConnType1 ConnType2 .. ConnTypeN]]" 779 "\n\t [-stylesheet CSSFile]" 780 "\n\t [-cover CoverCfgFile]" 781 "\n\t [-cover_stop Bool]" 782 "\n\t [-event_handler EvHandler1 EvHandler2 .. EvHandlerN]" 783 "\n\t [-ct_hooks CTHook1 CTHook2 .. CTHookN]" 784 "\n\t [-include InclDir1 InclDir2 .. InclDirN]" 785 "\n\t [-no_auto_compile]" 786 "\n\t [-abort_if_missing_suites]" 787 "\n\t [-multiply_timetraps N]" 788 "\n\t [-scale_timetraps]" 789 "\n\t [-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]" 790 "\n\t [-basic_html]" 791 "\n\t [-no_esc_chars]" 792 "\n\t [-repeat N] |" 793 "\n\t [-duration HHMMSS [-force_stop [skip_rest]]] |" 794 "\n\t [-until [YYMoMoDD]HHMMSS [-force_stop [skip_rest]]]\n\n"), 795 io:format("Refresh the HTML index files:\n\n" 796 "\tct_run -refresh_logs [LogDir]" 797 " [-logdir LogDir] " 798 " [-basic_html]\n\n"), 799 io:format("Run CT in interactive mode:\n\n" 800 "\tct_run -shell" 801 "\n\t [-config ConfigFile1 ConfigFile2 .. ConfigFileN]" 802 "\n\t [-decrypt_key Key] | [-decrypt_file KeyFile]\n\n"), 803 io:format("Run tests in web based GUI:\n\n" 804 "\n\t [-config ConfigFile1 ConfigFile2 .. ConfigFileN]" 805 "\n\t [-decrypt_key Key] | [-decrypt_file KeyFile]" 806 "\n\t [-dir TestDir1 TestDir2 .. TestDirN] |" 807 "\n\t [-suite Suite [-case Case]]" 808 "\n\t [-logopts LogOpt1 LogOpt2 .. LogOptN]" 809 "\n\t [-verbosity GenVLvl | [CategoryVLvl1 .. CategoryVLvlN]]" 810 "\n\t [-include InclDir1 InclDir2 .. InclDirN]" 811 "\n\t [-no_auto_compile]" 812 "\n\t [-abort_if_missing_suites]" 813 "\n\t [-multiply_timetraps N]" 814 "\n\t [-scale_timetraps]" 815 "\n\t [-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]" 816 "\n\t [-basic_html]" 817 "\n\t [-no_esc_chars]\n\n"). 818 819install(Opts) -> 820 install(Opts, "."). 821 822install(Opts, LogDir) -> 823 824 ConfOpts = ct_config:add_default_callback(Opts), 825 826 case application:get_env(common_test, decrypt) of 827 {ok,_} -> 828 ok; 829 _ -> 830 case lists:keysearch(decrypt, 1, Opts) of 831 {value,{_,KeyOrFile}} -> 832 application:set_env(common_test, decrypt, KeyOrFile); 833 false -> 834 application:unset_env(common_test, decrypt) 835 end 836 end, 837 case whereis(ct_util_server) of 838 undefined -> 839 VarFile = variables_file_name(LogDir), 840 case file:open(VarFile, [write, {encoding,utf8}]) of 841 {ok,Fd} -> 842 _ = [io:format(Fd, "~tp.\n", [Opt]) || Opt <- ConfOpts], 843 ok = file:close(Fd); 844 {error,Reason} -> 845 io:format("CT failed to install configuration data. Please " 846 "verify that the log directory exists and that " 847 "write permission is set.\n\n", []), 848 {error,{VarFile,Reason}} 849 end; 850 _ -> 851 io:format("It is not possible to install CT while running " 852 "in interactive mode.\n" 853 "To exit this mode, run ct:stop_interactive().\n" 854 "To enter the interactive mode again, " 855 "run ct:start_interactive()\n\n", []), 856 {error,interactive_mode} 857 end. 858 859variables_file_name(Dir) -> 860 filename:join(Dir, "variables-"++atom_to_list(node())). 861 862run_test(StartOpt) when is_tuple(StartOpt) -> 863 run_test([StartOpt]); 864 865run_test(StartOpts) when is_list(StartOpts) -> 866 CTPid = spawn(run_test1_fun(StartOpts)), 867 Ref = monitor(process, CTPid), 868 receive 869 {'DOWN',Ref,process,CTPid,{user_error,Error}} -> 870 {error,Error}; 871 {'DOWN',Ref,process,CTPid,Other} -> 872 Other 873 end. 874 875-spec run_test1_fun(_) -> fun(() -> no_return()). 876 877run_test1_fun(StartOpts) -> 878 fun() -> 879 ct_util:mark_process(), 880 run_test1(StartOpts) 881 end. 882 883run_test1(StartOpts) when is_list(StartOpts) -> 884 case proplists:get_value(refresh_logs, StartOpts) of 885 undefined -> 886 Tracing = start_trace(StartOpts), 887 {ok,Cwd} = file:get_cwd(), 888 io:format("~nCommon Test starting (cwd is ~ts)~n~n", [Cwd]), 889 Res = 890 case ct_repeat:loop_test(func, StartOpts) of 891 false -> 892 case catch run_test2(StartOpts) of 893 {'EXIT',Reason} -> 894 ok = file:set_cwd(Cwd), 895 {error,Reason}; 896 Result -> 897 Result 898 end; 899 Result -> 900 Result 901 end, 902 stop_trace(Tracing), 903 exit(Res); 904 RefreshDir -> 905 %% log_cleanup - used by ct_logs 906 KeepLogs = get_start_opt(keep_logs, 907 fun ct_logs:parse_keep_logs/1, 908 all, 909 StartOpts), 910 application:set_env(common_test, keep_logs, KeepLogs), 911 ok = refresh_logs(?abs(RefreshDir)), 912 exit(done) 913 end. 914 915run_test2(StartOpts) -> 916 %% label 917 Label = get_start_opt(label, fun(Lbl) when is_list(Lbl) -> Lbl; 918 (Lbl) when is_atom(Lbl) -> atom_to_list(Lbl) 919 end, StartOpts), 920 %% profile 921 Profile = get_start_opt(profile, fun(Prof) when is_list(Prof) -> 922 Prof; 923 (Prof) when is_atom(Prof) -> 924 atom_to_list(Prof) 925 end, StartOpts), 926 %% logdir 927 LogDir = get_start_opt(logdir, fun(LD) when is_list(LD) -> LD end, 928 StartOpts), 929 %% logopts 930 LogOpts = get_start_opt(logopts, value, [], StartOpts), 931 932 %% verbosity 933 Verbosity = 934 get_start_opt(verbosity, 935 fun(VLvls) when is_list(VLvls) -> 936 lists:map(fun(VLvl = {_Cat,_Lvl}) -> 937 VLvl; 938 (Lvl) -> 939 {'$unspecified',Lvl} 940 end, VLvls); 941 (VLvl) when is_integer(VLvl) -> 942 [{'$unspecified',VLvl}] 943 end, [], StartOpts), 944 945 %% config & userconfig 946 CfgFiles = ct_config:get_config_file_list(StartOpts), 947 948 %% event handlers 949 EvHandlers = 950 case proplists:get_value(event_handler, StartOpts) of 951 undefined -> 952 []; 953 H when is_atom(H) -> 954 [{H,[]}]; 955 H -> 956 Hs = 957 if is_tuple(H) -> [H]; 958 is_list(H) -> H; 959 true -> [] 960 end, 961 lists:flatten( 962 lists:map(fun(EH) when is_atom(EH) -> 963 {EH,[]}; 964 ({HL,Args}) when is_list(HL) -> 965 [{EH,Args} || EH <- HL]; 966 ({EH,Args}) when is_atom(EH) -> 967 {EH,Args}; 968 (_) -> 969 [] 970 end, Hs)) 971 end, 972 973 %% CT Hooks 974 CTHooks = get_start_opt(ct_hooks, value, [], StartOpts), 975 EnableBuiltinHooks = get_start_opt(enable_builtin_hooks, 976 fun(EBH) when EBH == true; 977 EBH == false -> 978 EBH 979 end, undefined, StartOpts), 980 981 %% silent connections 982 SilentConns = get_start_opt(silent_connections, 983 fun(all) -> [all]; 984 (Conns) -> Conns 985 end, [], StartOpts), 986 %% stylesheet 987 Stylesheet = get_start_opt(stylesheet, 988 fun(SS) -> ?abs(SS) end, 989 StartOpts), 990 %% code coverage 991 Cover = get_start_opt(cover, 992 fun(CoverFile) -> ?abs(CoverFile) end, StartOpts), 993 CoverStop = get_start_opt(cover_stop, value, StartOpts), 994 995 %% timetrap manipulation 996 MultiplyTT = get_start_opt(multiply_timetraps, value, StartOpts), 997 ScaleTT = get_start_opt(scale_timetraps, value, StartOpts), 998 999 %% create unique priv dir names 1000 CreatePrivDir = get_start_opt(create_priv_dir, value, StartOpts), 1001 1002 %% auto compile & include files 1003 {AutoCompile,Include} = 1004 case proplists:get_value(auto_compile, StartOpts) of 1005 undefined -> 1006 application:set_env(common_test, auto_compile, true), 1007 InclDirs = 1008 case proplists:get_value(include, StartOpts) of 1009 undefined -> 1010 []; 1011 Incls when is_list(hd(Incls)) -> 1012 [filename:absname(IDir) || IDir <- Incls]; 1013 Incl when is_list(Incl) -> 1014 [filename:absname(Incl)] 1015 end, 1016 case os:getenv("CT_INCLUDE_PATH") of 1017 false -> 1018 application:set_env(common_test, include, InclDirs), 1019 {undefined,InclDirs}; 1020 CtInclPath -> 1021 InclDirs1 = string:lexemes(CtInclPath, [$:,$ ,$,]), 1022 AllInclDirs = InclDirs1++InclDirs, 1023 application:set_env(common_test, include, AllInclDirs), 1024 {undefined,AllInclDirs} 1025 end; 1026 ACBool -> 1027 application:set_env(common_test, auto_compile, ACBool), 1028 {ACBool,[]} 1029 end, 1030 1031 %% abort test run if some suites can't be compiled 1032 AbortIfMissing = get_start_opt(abort_if_missing_suites, value, false, 1033 StartOpts), 1034 1035 %% decrypt config file 1036 case proplists:get_value(decrypt, StartOpts) of 1037 undefined -> 1038 application:unset_env(common_test, decrypt); 1039 Key={key,_} -> 1040 application:set_env(common_test, decrypt, Key); 1041 {file,KeyFile} -> 1042 application:set_env(common_test, decrypt, {file,?abs(KeyFile)}) 1043 end, 1044 1045 %% basic html - used by ct_logs 1046 BasicHtml = 1047 case proplists:get_value(basic_html, StartOpts) of 1048 undefined -> 1049 application:set_env(common_test, basic_html, false), 1050 undefined; 1051 BasicHtmlBool -> 1052 application:set_env(common_test, basic_html, BasicHtmlBool), 1053 BasicHtmlBool 1054 end, 1055 %% esc_chars - used by ct_logs 1056 EscChars = 1057 case proplists:get_value(esc_chars, StartOpts) of 1058 undefined -> 1059 application:set_env(common_test, esc_chars, true), 1060 undefined; 1061 EscCharsBool -> 1062 application:set_env(common_test, esc_chars, EscCharsBool), 1063 EscCharsBool 1064 end, 1065 %% disable_log_cache - used by ct_logs 1066 case proplists:get_value(disable_log_cache, StartOpts) of 1067 undefined -> 1068 application:set_env(common_test, disable_log_cache, false); 1069 DisableCacheBool -> 1070 application:set_env(common_test, disable_log_cache, DisableCacheBool) 1071 end, 1072 %% log_cleanup - used by ct_logs 1073 KeepLogs = get_start_opt(keep_logs, 1074 fun ct_logs:parse_keep_logs/1, 1075 all, 1076 StartOpts), 1077 application:set_env(common_test, keep_logs, KeepLogs), 1078 1079 %% stepped execution 1080 Step = get_start_opt(step, value, StartOpts), 1081 1082 Opts = #opts{label = Label, profile = Profile, 1083 cover = Cover, cover_stop = CoverStop, 1084 step = Step, logdir = LogDir, 1085 logopts = LogOpts, basic_html = BasicHtml, 1086 esc_chars = EscChars, 1087 config = CfgFiles, 1088 verbosity = Verbosity, 1089 event_handlers = EvHandlers, 1090 ct_hooks = CTHooks, 1091 enable_builtin_hooks = EnableBuiltinHooks, 1092 auto_compile = AutoCompile, 1093 abort_if_missing_suites = AbortIfMissing, 1094 include = Include, 1095 silent_connections = SilentConns, 1096 stylesheet = Stylesheet, 1097 multiply_timetraps = MultiplyTT, 1098 scale_timetraps = ScaleTT, 1099 create_priv_dir = CreatePrivDir, 1100 starter = ct}, 1101 1102 %% test specification 1103 case proplists:get_value(spec, StartOpts) of 1104 undefined -> 1105 case lists:keysearch(prepared_tests, 1, StartOpts) of 1106 {value,{_,{Run,Skip},Specs}} -> % use prepared tests 1107 run_prepared(Run, Skip, Opts#opts{testspec_files = Specs}, 1108 StartOpts); 1109 false -> 1110 run_dir(Opts, StartOpts) 1111 end; 1112 Specs -> 1113 Relaxed = get_start_opt(allow_user_terms, value, false, StartOpts), 1114 %% using testspec(s) as input for test 1115 run_spec_file(Relaxed, Opts#opts{testspec_files = Specs}, StartOpts) 1116 end. 1117 1118run_spec_file(Relaxed, 1119 Opts = #opts{testspec_files = Specs}, 1120 StartOpts) -> 1121 Specs1 = case Specs of 1122 [X|_] when is_integer(X) -> [Specs]; 1123 _ -> Specs 1124 end, 1125 AbsSpecs = lists:map(fun(SF) -> ?abs(SF) end, Specs1), 1126 AbsSpecs1 = get_start_opt(join_specs, [AbsSpecs], AbsSpecs, StartOpts), 1127 try ct_testspec:collect_tests_from_file(AbsSpecs1, Relaxed) of 1128 TestSpecData -> 1129 run_all_specs(TestSpecData, Opts, StartOpts, []) 1130 catch 1131 throw:{error,CTReason}:StackTrace -> 1132 exit({error,{invalid_testspec,{CTReason,StackTrace}}}); 1133 _:CTReason:StackTrace -> 1134 exit({error,{invalid_testspec,{CTReason,StackTrace}}}) 1135 end. 1136 1137run_all_specs([], _, _, TotResult) -> 1138 TotResult1 = lists:reverse(TotResult), 1139 case lists:keysearch('EXIT', 1, TotResult1) of 1140 {value,{_,_,ExitReason}} -> 1141 exit(ExitReason); 1142 false -> 1143 case lists:keysearch(error, 1, TotResult1) of 1144 {value,Error} -> 1145 Error; 1146 false -> 1147 lists:foldl(fun({Ok,Fail,{UserSkip,AutoSkip}}, 1148 {Ok1,Fail1,{UserSkip1,AutoSkip1}}) -> 1149 {Ok1+Ok,Fail1+Fail, 1150 {UserSkip1+UserSkip, 1151 AutoSkip1+AutoSkip}}; 1152 (Pid, Acc) when is_pid(Pid) -> Acc 1153 end, {0,0,{0,0}}, TotResult1) 1154 end 1155 end; 1156 1157run_all_specs([{Specs,TS} | TSs], Opts, StartOpts, TotResult) -> 1158 Combined = #opts{config = TSConfig} = combine_test_opts(TS, Specs, Opts), 1159 AllConfig = merge_vals([Opts#opts.config, TSConfig]), 1160 try run_one_spec(TS, 1161 Combined#opts{config = AllConfig, 1162 current_testspec=TS}, 1163 StartOpts) of 1164 Result -> 1165 run_all_specs(TSs, Opts, StartOpts, [Result | TotResult]) 1166 catch 1167 _ : Reason -> 1168 run_all_specs(TSs, Opts, StartOpts, [{error,Reason} | TotResult]) 1169 end. 1170 1171run_one_spec(TS, CombinedOpts, StartOpts) -> 1172 #opts{logdir = Logdir, config = Config} = CombinedOpts, 1173 case check_and_install_configfiles(Config, Logdir, CombinedOpts) of 1174 ok -> 1175 {Run,Skip} = ct_testspec:prepare_tests(TS, node()), 1176 reformat_result(catch do_run(Run, Skip, CombinedOpts, StartOpts)); 1177 Error -> 1178 Error 1179 end. 1180 1181run_prepared(Run, Skip, Opts = #opts{logdir = LogDir, 1182 config = CfgFiles}, 1183 StartOpts) -> 1184 LogDir1 = which(logdir, LogDir), 1185 case check_and_install_configfiles(CfgFiles, LogDir1, Opts) of 1186 ok -> 1187 reformat_result(catch do_run(Run, Skip, Opts#opts{logdir = LogDir1}, 1188 StartOpts)); 1189 {error,_Reason} = Error -> 1190 exit(Error) 1191 end. 1192 1193check_config_file(Callback, File)-> 1194 case code:is_loaded(Callback) of 1195 false -> 1196 case code:load_file(Callback) of 1197 {module,_} -> ok; 1198 {error,Why} -> exit({error,{cant_load_callback_module,Why}}) 1199 end; 1200 _ -> 1201 ok 1202 end, 1203 case Callback:check_parameter(File) of 1204 {ok,{file,File}}-> 1205 ?abs(File); 1206 {ok,{config,_}}-> 1207 File; 1208 {error,{wrong_config,Message}}-> 1209 exit({error,{wrong_config,{Callback,Message}}}); 1210 {error,{nofile,File}}-> 1211 exit({error,{no_such_file,?abs(File)}}) 1212 end. 1213 1214run_dir(Opts = #opts{logdir = LogDir, 1215 config = CfgFiles, 1216 event_handlers = EvHandlers, 1217 ct_hooks = CTHook, 1218 enable_builtin_hooks = EnableBuiltinHooks}, 1219 StartOpts) -> 1220 LogDir1 = which(logdir, LogDir), 1221 Opts1 = Opts#opts{logdir = LogDir1}, 1222 AbsCfgFiles = 1223 lists:map(fun({Callback,FileList})-> 1224 case code:is_loaded(Callback) of 1225 {file,_Path}-> 1226 ok; 1227 false -> 1228 case code:load_file(Callback) of 1229 {module,Callback}-> 1230 ok; 1231 {error,_}-> 1232 exit({error,{no_such_module, 1233 Callback}}) 1234 end 1235 end, 1236 {Callback, 1237 lists:map(fun(File)-> 1238 check_config_file(Callback, File) 1239 end, FileList)} 1240 end, CfgFiles), 1241 case install([{config,AbsCfgFiles}, 1242 {event_handler,EvHandlers}, 1243 {ct_hooks, CTHook}, 1244 {enable_builtin_hooks,EnableBuiltinHooks}], LogDir1) of 1245 ok -> ok; 1246 {error,_IReason} = IError -> exit(IError) 1247 end, 1248 case {proplists:get_value(dir, StartOpts), 1249 proplists:get_value(suite, StartOpts), 1250 groups_and_cases(proplists:get_value(group, StartOpts), 1251 proplists:get_value(testcase, StartOpts))} of 1252 %% flag specified without data 1253 {_,_,Error={error,_}} -> 1254 Error; 1255 {_,[],_} -> 1256 {error,no_suite_specified}; 1257 {[],_,_} -> 1258 {error,no_dir_specified}; 1259 1260 {Dirs=[Hd|_],undefined,[]} when is_list(Dirs), not is_integer(Hd) -> 1261 Dirs1 = [if is_atom(D) -> atom_to_list(D); 1262 true -> D end || D <- Dirs], 1263 reformat_result(catch do_run(tests(Dirs1), [], Opts1, StartOpts)); 1264 1265 {Dir=[Hd|_],undefined,[]} when is_list(Dir) and is_integer(Hd) -> 1266 reformat_result(catch do_run(tests(Dir), [], Opts1, StartOpts)); 1267 1268 {Dir,undefined,[]} when is_atom(Dir) and (Dir /= undefined) -> 1269 reformat_result(catch do_run(tests(atom_to_list(Dir)), 1270 [], Opts1, StartOpts)); 1271 1272 {undefined,Suites=[Hd|_],[]} when not is_integer(Hd) -> 1273 Suites1 = [suite_to_test(S) || S <- Suites], 1274 reformat_result(catch do_run(tests(Suites1), [], Opts1, StartOpts)); 1275 1276 {undefined,Suite,[]} when is_atom(Suite) and 1277 (Suite /= undefined) -> 1278 {Dir,Mod} = suite_to_test(Suite), 1279 reformat_result(catch do_run(tests(Dir, Mod), [], Opts1, StartOpts)); 1280 1281 {undefined,Suite,GsAndCs} when is_atom(Suite) and 1282 (Suite /= undefined) -> 1283 {Dir,Mod} = suite_to_test(Suite), 1284 reformat_result(catch do_run(tests(Dir, Mod, GsAndCs), 1285 [], Opts1, StartOpts)); 1286 1287 {undefined,[Hd,_|_],_GsAndCs} when not is_integer(Hd) -> 1288 exit({error,multiple_suites_and_cases}); 1289 1290 {undefined,Suite=[Hd|Tl],GsAndCs} when is_integer(Hd) ; 1291 (is_list(Hd) and (Tl == [])) ; 1292 (is_atom(Hd) and (Tl == [])) -> 1293 {Dir,Mod} = suite_to_test(Suite), 1294 reformat_result(catch do_run(tests(Dir, Mod, GsAndCs), 1295 [], Opts1, StartOpts)); 1296 1297 {[Hd,_|_],_Suites,[]} when is_list(Hd) ; not is_integer(Hd) -> 1298 exit({error,multiple_dirs_and_suites}); 1299 1300 {undefined,undefined,GsAndCs} when GsAndCs /= [] -> 1301 exit({error,incorrect_start_options}); 1302 1303 {Dir,Suite,GsAndCs} when is_integer(hd(Dir)) ; 1304 (is_atom(Dir) and (Dir /= undefined)) ; 1305 ((length(Dir) == 1) and is_atom(hd(Dir))) ; 1306 ((length(Dir) == 1) and is_list(hd(Dir))) -> 1307 Dir1 = if is_atom(Dir) -> atom_to_list(Dir); 1308 true -> Dir end, 1309 if Suite == undefined -> 1310 exit({error,incorrect_start_options}); 1311 1312 is_integer(hd(Suite)) ; 1313 (is_atom(Suite) and (Suite /= undefined)) ; 1314 ((length(Suite) == 1) and is_atom(hd(Suite))) ; 1315 ((length(Suite) == 1) and is_list(hd(Suite))) -> 1316 {Dir2,Mod} = suite_to_test(Dir1, Suite), 1317 case GsAndCs of 1318 [] -> 1319 reformat_result(catch do_run(tests(Dir2, Mod), 1320 [], Opts1, StartOpts)); 1321 _ -> 1322 reformat_result(catch do_run(tests(Dir2, Mod, 1323 GsAndCs), 1324 [], Opts1, StartOpts)) 1325 end; 1326 1327 is_list(Suite) -> % multiple suites 1328 case [suite_to_test(Dir1, S) || S <- Suite] of 1329 [_,_|_] when GsAndCs /= [] -> 1330 exit({error,multiple_suites_and_cases}); 1331 [{Dir2,Mod}] when GsAndCs /= [] -> 1332 reformat_result(catch do_run(tests(Dir2, Mod, 1333 GsAndCs), 1334 [], Opts1, StartOpts)); 1335 DirMods -> 1336 reformat_result(catch do_run(tests(DirMods), 1337 [], Opts1, StartOpts)) 1338 end 1339 end; 1340 1341 {undefined,undefined,[]} -> 1342 {ok,Dir} = file:get_cwd(), 1343 %% No start options, use default {dir,CWD} 1344 reformat_result(catch do_run(tests(Dir), [], Opts1, StartOpts)); 1345 1346 {Dir,Suite,GsAndCs} -> 1347 exit({error,{incorrect_start_options,{Dir,Suite,GsAndCs}}}) 1348 end. 1349 1350run_testspec(TestSpec) -> 1351 CTPid = spawn(run_testspec1_fun(TestSpec)), 1352 Ref = monitor(process, CTPid), 1353 receive 1354 {'DOWN',Ref,process,CTPid,{user_error,Error}} -> 1355 Error; 1356 {'DOWN',Ref,process,CTPid,Other} -> 1357 Other 1358 end. 1359 1360-spec run_testspec1_fun(_) -> fun(() -> no_return()). 1361 1362run_testspec1_fun(TestSpec) -> 1363 fun() -> 1364 ct_util:mark_process(), 1365 run_testspec1(TestSpec) 1366 end. 1367 1368run_testspec1(TestSpec) -> 1369 {ok,Cwd} = file:get_cwd(), 1370 io:format("~nCommon Test starting (cwd is ~ts)~n~n", [Cwd]), 1371 case catch run_testspec2(TestSpec) of 1372 {'EXIT',Reason} -> 1373 ok = file:set_cwd(Cwd), 1374 exit({error,Reason}); 1375 Result -> 1376 exit(Result) 1377 end. 1378 1379run_testspec2(File) when is_list(File), is_integer(hd(File)) -> 1380 case file:read_file_info(File) of 1381 {ok,_} -> 1382 exit("Bad argument, " 1383 "use ct:run_test([{spec," ++ File ++ "}])"); 1384 _ -> 1385 exit("Bad argument, list of tuples expected, " 1386 "use ct:run_test/1 for test specification files") 1387 end; 1388 1389run_testspec2(TestSpec) -> 1390 case catch ct_testspec:collect_tests_from_list(TestSpec, false) of 1391 {E,CTReason} when E == error ; E == 'EXIT' -> 1392 exit({error,CTReason}); 1393 TS -> 1394 Opts = get_data_for_node(TS, node()), 1395 1396 AllInclude = 1397 case os:getenv("CT_INCLUDE_PATH") of 1398 false -> 1399 Opts#opts.include; 1400 CtInclPath -> 1401 EnvInclude = string:lexemes(CtInclPath, [$:,$ ,$,]), 1402 EnvInclude++Opts#opts.include 1403 end, 1404 application:set_env(common_test, include, AllInclude), 1405 1406 LogDir1 = which(logdir,Opts#opts.logdir), 1407 case check_and_install_configfiles( 1408 Opts#opts.config, LogDir1, Opts) of 1409 ok -> 1410 Opts1 = Opts#opts{testspec_files = [], 1411 logdir = LogDir1, 1412 include = AllInclude}, 1413 {Run,Skip} = ct_testspec:prepare_tests(TS, node()), 1414 reformat_result(catch do_run(Run, Skip, Opts1, [])); 1415 {error,_GCFReason} = GCFError -> 1416 exit(GCFError) 1417 end 1418 end. 1419 1420get_data_for_node(#testspec{label = Labels, 1421 profile = Profiles, 1422 logdir = LogDirs, 1423 logopts = LogOptsList, 1424 basic_html = BHs, 1425 esc_chars = EscChs, 1426 stylesheet = SSs, 1427 verbosity = VLvls, 1428 silent_connections = SilentConnsList, 1429 cover = CoverFs, 1430 cover_stop = CoverStops, 1431 config = Cfgs, 1432 userconfig = UsrCfgs, 1433 event_handler = EvHs, 1434 ct_hooks = CTHooks, 1435 enable_builtin_hooks = EnableBuiltinHooks, 1436 auto_compile = ACs, 1437 abort_if_missing_suites = AiMSs, 1438 include = Incl, 1439 multiply_timetraps = MTs, 1440 scale_timetraps = STs, 1441 create_priv_dir = PDs}, Node) -> 1442 Label = proplists:get_value(Node, Labels), 1443 Profile = proplists:get_value(Node, Profiles), 1444 LogDir = case proplists:get_value(Node, LogDirs) of 1445 undefined -> "."; 1446 Dir -> Dir 1447 end, 1448 LogOpts = case proplists:get_value(Node, LogOptsList) of 1449 undefined -> []; 1450 LOs -> LOs 1451 end, 1452 BasicHtml = proplists:get_value(Node, BHs), 1453 EscChars = proplists:get_value(Node, EscChs), 1454 Stylesheet = proplists:get_value(Node, SSs), 1455 Verbosity = case proplists:get_value(Node, VLvls) of 1456 undefined -> []; 1457 Lvls -> Lvls 1458 end, 1459 SilentConns = case proplists:get_value(Node, SilentConnsList) of 1460 undefined -> []; 1461 SCs -> SCs 1462 end, 1463 Cover = proplists:get_value(Node, CoverFs), 1464 CoverStop = proplists:get_value(Node, CoverStops), 1465 MT = proplists:get_value(Node, MTs), 1466 ST = proplists:get_value(Node, STs), 1467 CreatePrivDir = proplists:get_value(Node, PDs), 1468 ConfigFiles = [{?ct_config_txt,F} || {N,F} <- Cfgs, N==Node] ++ 1469 [CBF || {N,CBF} <- UsrCfgs, N==Node], 1470 EvHandlers = [{H,A} || {N,H,A} <- EvHs, N==Node], 1471 FiltCTHooks = [Hook || {N,Hook} <- CTHooks, N==Node], 1472 AutoCompile = proplists:get_value(Node, ACs), 1473 AbortIfMissing = proplists:get_value(Node, AiMSs), 1474 Include = [I || {N,I} <- Incl, N==Node], 1475 #opts{label = Label, 1476 profile = Profile, 1477 logdir = LogDir, 1478 logopts = LogOpts, 1479 basic_html = BasicHtml, 1480 esc_chars = EscChars, 1481 stylesheet = Stylesheet, 1482 verbosity = Verbosity, 1483 silent_connections = SilentConns, 1484 cover = Cover, 1485 cover_stop = CoverStop, 1486 config = ConfigFiles, 1487 event_handlers = EvHandlers, 1488 ct_hooks = FiltCTHooks, 1489 enable_builtin_hooks = EnableBuiltinHooks, 1490 auto_compile = AutoCompile, 1491 abort_if_missing_suites = AbortIfMissing, 1492 include = Include, 1493 multiply_timetraps = MT, 1494 scale_timetraps = ST, 1495 create_priv_dir = CreatePrivDir}. 1496 1497refresh_logs(LogDir) -> 1498 {ok,Cwd} = file:get_cwd(), 1499 case file:set_cwd(LogDir) of 1500 E = {error,_Reason} -> 1501 E; 1502 _ -> 1503 case catch ct_logs:make_all_suites_index(refresh) of 1504 {'EXIT',ASReason} -> 1505 ok = file:set_cwd(Cwd), 1506 {error,{all_suites_index,ASReason}}; 1507 _ -> 1508 case catch ct_logs:make_all_runs_index(refresh) of 1509 {'EXIT',ARReason} -> 1510 ok = file:set_cwd(Cwd), 1511 {error,{all_runs_index,ARReason}}; 1512 _ -> 1513 ok = file:set_cwd(Cwd), 1514 io:format("Logs in ~ts refreshed!~n",[LogDir]), 1515 ok 1516 end 1517 end 1518 end. 1519 1520which(logdir, undefined) -> 1521 "."; 1522which(logdir, Dir) -> 1523 Dir. 1524 1525choose_val(undefined, V1) -> 1526 V1; 1527choose_val(V0, _V1) -> 1528 V0. 1529 1530merge_vals(Vs) -> 1531 lists:append(Vs). 1532 1533merge_keyvals(Vs) -> 1534 make_unique(lists:append(Vs)). 1535 1536make_unique([Elem={Key,_} | Elems]) -> 1537 [Elem | make_unique(proplists:delete(Key, Elems))]; 1538make_unique([]) -> 1539 []. 1540 1541listify([C|_]=Str) when is_integer(C) -> [Str]; 1542listify(L) when is_list(L) -> L; 1543listify(E) -> [E]. 1544 1545delistify([E]) -> E; 1546delistify(E) -> E. 1547 1548 1549run(TestDir, Suite, Cases) -> 1550 case install([]) of 1551 ok -> 1552 reformat_result(catch do_run(tests(TestDir, Suite, Cases), [])); 1553 Error -> 1554 Error 1555 end. 1556 1557run(TestDir, Suite) when is_list(TestDir), is_integer(hd(TestDir)) -> 1558 case install([]) of 1559 ok -> 1560 reformat_result(catch do_run(tests(TestDir, Suite), [])); 1561 Error -> 1562 Error 1563 end. 1564 1565run(TestDirs) -> 1566 case install([]) of 1567 ok -> 1568 reformat_result(catch do_run(tests(TestDirs), [])); 1569 Error -> 1570 Error 1571 end. 1572 1573reformat_result({'EXIT',{user_error,Reason}}) -> 1574 {error,Reason}; 1575reformat_result({user_error,Reason}) -> 1576 {error,Reason}; 1577reformat_result(Result) -> 1578 Result. 1579 1580suite_to_test(Suite) when is_atom(Suite) -> 1581 suite_to_test(atom_to_list(Suite)); 1582 1583suite_to_test(Suite) when is_list(Suite) -> 1584 {filename:dirname(Suite), 1585 list_to_atom(filename:rootname(filename:basename(Suite)))}. 1586 1587suite_to_test(Dir, Suite) when is_atom(Suite) -> 1588 suite_to_test(Dir, atom_to_list(Suite)); 1589 1590suite_to_test(Dir, Suite) when is_list(Suite) -> 1591 case filename:dirname(Suite) of 1592 "." -> 1593 {Dir,list_to_atom(filename:rootname(Suite))}; 1594 DirName -> % ignore Dir 1595 File = filename:basename(Suite), 1596 {DirName,list_to_atom(filename:rootname(File))} 1597 end. 1598 1599groups_and_cases(Gs, Cs) when ((Gs == undefined) or (Gs == [])) and 1600 ((Cs == undefined) or (Cs == [])) -> 1601 []; 1602groups_and_cases(Gs, Cs) when Gs == undefined ; Gs == [] -> 1603 if (Cs == all) or (Cs == [all]) or (Cs == ["all"]) -> all; 1604 true -> [ensure_atom(C) || C <- listify(Cs)] 1605 end; 1606groups_and_cases(GOrGs, Cs) when (is_atom(GOrGs) orelse 1607 (is_list(GOrGs) andalso 1608 (is_atom(hd(GOrGs)) orelse 1609 (is_list(hd(GOrGs)) andalso 1610 is_atom(hd(hd(GOrGs))))))) -> 1611 if (Cs == undefined) or (Cs == []) or 1612 (Cs == all) or (Cs == [all]) or (Cs == ["all"]) -> 1613 [{GOrGs,all}]; 1614 true -> 1615 [{GOrGs,[ensure_atom(C) || C <- listify(Cs)]}] 1616 end; 1617groups_and_cases(Gs, Cs) when is_integer(hd(hd(Gs))) -> 1618 %% if list of strings, this comes from 'ct_run -group G1 G2 ...' and 1619 %% we need to parse the strings 1620 Gs1 = 1621 if (Gs == [all]) or (Gs == ["all"]) -> 1622 all; 1623 true -> 1624 lists:map(fun(G) -> 1625 {ok,Ts,_} = erl_scan:string(G++"."), 1626 {ok,Term} = erl_parse:parse_term(Ts), 1627 Term 1628 end, Gs) 1629 end, 1630 groups_and_cases(Gs1, Cs); 1631groups_and_cases(Gs, Cs) -> 1632 {error,{incorrect_group_or_case_option,Gs,Cs}}. 1633 1634tests(TestDir, Suites, []) when is_list(TestDir), is_integer(hd(TestDir)) -> 1635 [{?testdir(TestDir,Suites),ensure_atom(Suites),all}]; 1636tests(TestDir, Suite, Cases) when is_list(TestDir), is_integer(hd(TestDir)) -> 1637 [{?testdir(TestDir,Suite),ensure_atom(Suite),Cases}]; 1638tests([TestDir], Suite, Cases) when is_list(TestDir), is_integer(hd(TestDir)) -> 1639 [{?testdir(TestDir,Suite),ensure_atom(Suite),Cases}]. 1640tests([{Dir,Suite}],Cases) -> 1641 [{?testdir(Dir,Suite),ensure_atom(Suite),Cases}]; 1642tests(TestDir, Suite) when is_list(TestDir), is_integer(hd(TestDir)) -> 1643 tests(TestDir, ensure_atom(Suite), all); 1644tests([TestDir], Suite) when is_list(TestDir), is_integer(hd(TestDir)) -> 1645 tests(TestDir, ensure_atom(Suite), all). 1646tests(DirSuites) when is_list(DirSuites), is_tuple(hd(DirSuites)) -> 1647 [{?testdir(Dir,Suite),ensure_atom(Suite),all} || {Dir,Suite} <- DirSuites]; 1648tests(TestDir) when is_list(TestDir), is_integer(hd(TestDir)) -> 1649 tests([TestDir]); 1650tests(TestDirs) when is_list(TestDirs), is_list(hd(TestDirs)) -> 1651 [{?testdir(TestDir,all),all,all} || TestDir <- TestDirs]. 1652 1653do_run(Tests, Misc) when is_list(Misc) -> 1654 do_run(Tests, Misc, ".", []). 1655 1656do_run(Tests, Misc, LogDir, LogOpts) when is_list(Misc), 1657 is_list(LogDir), 1658 is_list(LogOpts) -> 1659 Opts = 1660 case proplists:get_value(step, Misc) of 1661 undefined -> 1662 #opts{}; 1663 StepOpts -> 1664 #opts{step = StepOpts} 1665 end, 1666 do_run(Tests, [], Opts#opts{logdir = LogDir}, []); 1667 1668do_run(Tests, Skip, Opts, Args) when is_record(Opts, opts) -> 1669 #opts{label = Label, profile = Profile, 1670 verbosity = VLvls} = Opts, 1671 %% label - used by ct_logs 1672 TestLabel = 1673 if Label == undefined -> undefined; 1674 is_atom(Label) -> atom_to_list(Label); 1675 is_list(Label) -> Label; 1676 true -> undefined 1677 end, 1678 application:set_env(common_test, test_label, TestLabel), 1679 1680 %% profile - used in ct_util 1681 TestProfile = 1682 if Profile == undefined -> undefined; 1683 is_atom(Profile) -> atom_to_list(Profile); 1684 is_list(Profile) -> Profile; 1685 true -> undefined 1686 end, 1687 application:set_env(common_test, profile, TestProfile), 1688 1689 case code:which(test_server) of 1690 non_existing -> 1691 {error,no_path_to_test_server}; 1692 _ -> 1693 %% This env variable is used by test_server to determine 1694 %% which framework it runs under. 1695 case os:getenv("TEST_SERVER_FRAMEWORK") of 1696 false -> 1697 os:putenv("TEST_SERVER_FRAMEWORK", "ct_framework"), 1698 os:putenv("TEST_SERVER_FRAMEWORK_NAME", "common_test"); 1699 "ct_framework" -> 1700 ok; 1701 Other -> 1702 erlang:display( 1703 list_to_atom( 1704 "Note: TEST_SERVER_FRAMEWORK = " ++ Other)) 1705 end, 1706 Verbosity = add_verbosity_defaults(VLvls), 1707 case ct_util:start(Opts#opts.logdir, Verbosity) of 1708 {error,interactive_mode} -> 1709 io:format("CT is started in interactive mode. " 1710 "To exit this mode, " 1711 "run ct:stop_interactive().\n" 1712 "To enter the interactive mode again, " 1713 "run ct:start_interactive()\n\n",[]), 1714 {error,interactive_mode}; 1715 _Pid -> 1716 ct_util:set_testdata({starter,Opts#opts.starter}), 1717 compile_and_run(Tests, Skip, 1718 Opts#opts{verbosity=Verbosity}, Args) 1719 end 1720 end. 1721 1722compile_and_run(Tests, Skip, Opts, Args) -> 1723 %% save stylesheet info 1724 ct_util:set_testdata({stylesheet,Opts#opts.stylesheet}), 1725 %% save logopts 1726 ct_util:set_testdata({logopts,Opts#opts.logopts}), 1727 %% save info about current testspec (testspec record or undefined) 1728 ct_util:set_testdata({testspec,Opts#opts.current_testspec}), 1729 1730 %% enable silent connections 1731 case Opts#opts.silent_connections of 1732 [] -> 1733 ok; 1734 Conns -> 1735 case lists:member(all, Conns) of 1736 true -> 1737 Conns1 = ct_util:override_silence_all_connections(), 1738 ct_logs:log("Silent connections", "~tp", [Conns1]); 1739 false -> 1740 ct_util:override_silence_connections(Conns), 1741 ct_logs:log("Silent connections", "~tp", [Conns]) 1742 end 1743 end, 1744 log_ts_names(Opts#opts.testspec_files), 1745 TestSuites = suite_tuples(Tests), 1746 1747 {_TestSuites1,SuiteMakeErrors,AllMakeErrors} = 1748 case application:get_env(common_test, auto_compile) of 1749 {ok,false} -> 1750 {TestSuites1,SuitesNotFound} = 1751 verify_suites(TestSuites), 1752 {TestSuites1,SuitesNotFound,SuitesNotFound}; 1753 _ -> 1754 {SuiteErrs,HelpErrs} = auto_compile(TestSuites), 1755 {TestSuites,SuiteErrs,SuiteErrs++HelpErrs} 1756 end, 1757 1758 case continue(AllMakeErrors, Opts#opts.abort_if_missing_suites) of 1759 true -> 1760 SavedErrors = save_make_errors(SuiteMakeErrors), 1761 ct_repeat:log_loop_info(Args), 1762 1763 try final_tests(Tests,Skip,SavedErrors) of 1764 {Tests1,Skip1} -> 1765 ReleaseSh = proplists:get_value(release_shell, Args), 1766 ct_util:set_testdata({release_shell,ReleaseSh}), 1767 TestResult = 1768 possibly_spawn(ReleaseSh == true, Tests1, Skip1, Opts), 1769 case TestResult of 1770 {Ok,Errors,Skipped} -> 1771 NoOfMakeErrors = 1772 lists:foldl(fun({_,BadMods}, X) -> 1773 X + length(BadMods) 1774 end, 0, SuiteMakeErrors), 1775 {Ok,Errors+NoOfMakeErrors,Skipped}; 1776 ErrorResult -> 1777 ErrorResult 1778 end 1779 catch 1780 _:BadFormat -> 1781 {error,BadFormat} 1782 end; 1783 false -> 1784 io:nl(), 1785 ct_util:stop(clean), 1786 BadMods = 1787 lists:foldr( 1788 fun({{_,_},Ms}, Acc) -> 1789 Ms ++ lists:foldl( 1790 fun(M, Acc1) -> 1791 lists:delete(M, Acc1) 1792 end, Acc, Ms) 1793 end, [], AllMakeErrors), 1794 {error,{make_failed,BadMods}} 1795 end. 1796 1797%% keep the shell as the top controlling process 1798possibly_spawn(false, Tests, Skip, Opts) -> 1799 TestResult = (catch do_run_test(Tests, Skip, Opts)), 1800 case TestResult of 1801 {EType,_} = Error when EType == user_error; 1802 EType == error -> 1803 ct_util:stop(clean), 1804 exit(Error); 1805 _ -> 1806 ct_util:stop(normal), 1807 TestResult 1808 end; 1809 1810%% we must return control to the shell now, so we spawn 1811%% a test supervisor process to keep an eye on the test run 1812possibly_spawn(true, Tests, Skip, Opts) -> 1813 CTUtilSrv = whereis(ct_util_server), 1814 Supervisor = 1815 fun() -> 1816 ct_util:mark_process(), 1817 process_flag(trap_exit, true), 1818 link(CTUtilSrv), 1819 TestRun = 1820 fun() -> 1821 ct_util:mark_process(), 1822 TestResult = (catch do_run_test(Tests, Skip, Opts)), 1823 case TestResult of 1824 {EType,_} = Error when EType == user_error; 1825 EType == error -> 1826 ct_util:stop(clean), 1827 exit(Error); 1828 _ -> 1829 ct_util:stop(normal), 1830 exit({ok,TestResult}) 1831 end 1832 end, 1833 TestRunPid = spawn_link(TestRun), 1834 receive 1835 {'EXIT',TestRunPid,{ok,TestResult}} -> 1836 io:format(user, "~nCommon Test returned ~tp~n~n", 1837 [TestResult]); 1838 {'EXIT',TestRunPid,Error} -> 1839 exit(Error) 1840 end 1841 end, 1842 unlink(CTUtilSrv), 1843 SupPid = spawn(Supervisor), 1844 io:format(user, "~nTest control handed over to process ~w~n~n", 1845 [SupPid]), 1846 SupPid. 1847 1848%% attempt to compile the modules specified in TestSuites 1849auto_compile(TestSuites) -> 1850 io:format("~nCommon Test: Running make in test directories...~n"), 1851 UserInclude = 1852 case application:get_env(common_test, include) of 1853 {ok,UserInclDirs} when length(UserInclDirs) > 0 -> 1854 io:format("Including the following directories:~n"), 1855 [begin io:format("~tp~n",[UserInclDir]), {i,UserInclDir} end || 1856 UserInclDir <- UserInclDirs]; 1857 _ -> 1858 [] 1859 end, 1860 SuiteMakeErrors = 1861 lists:flatmap(fun({TestDir,Suite} = TS) -> 1862 case run_make(suites, TestDir, 1863 Suite, UserInclude, 1864 [nowarn_export_all]) of 1865 {error,{make_failed,Bad}} -> 1866 [{TS,Bad}]; 1867 {error,_} -> 1868 [{TS,[filename:join(TestDir, 1869 "*_SUITE")]}]; 1870 _ -> 1871 [] 1872 end 1873 end, TestSuites), 1874 1875 %% try to compile other modules than SUITEs in the test directories 1876 {_,HelpMakeErrors} = 1877 lists:foldl( 1878 fun({Dir,Suite}, {Done,Failed}) -> 1879 case lists:member(Dir, Done) of 1880 false -> 1881 Failed1 = 1882 case run_make(helpmods, Dir, Suite, UserInclude, []) of 1883 {error,{make_failed,BadMods}} -> 1884 [{{Dir,all},BadMods}|Failed]; 1885 {error,_} -> 1886 [{{Dir,all},[Dir]}|Failed]; 1887 _ -> 1888 Failed 1889 end, 1890 {[Dir|Done],Failed1}; 1891 true -> % already visited 1892 {Done,Failed} 1893 end 1894 end, {[],[]}, TestSuites), 1895 {SuiteMakeErrors,lists:reverse(HelpMakeErrors)}. 1896 1897%% verify that specified test suites exist (if auto compile is disabled) 1898verify_suites(TestSuites) -> 1899 io:nl(), 1900 Verify = 1901 fun({Dir,Suite}=DS,{Found,NotFound}) -> 1902 case locate_test_dir(Dir, Suite) of 1903 {ok,TestDir} -> 1904 if Suite == all -> 1905 {[DS|Found],NotFound}; 1906 true -> 1907 Beam = filename:join(TestDir, 1908 atom_to_list(Suite)++ 1909 ".beam"), 1910 case filelib:is_regular(Beam) of 1911 true -> 1912 {[DS|Found],NotFound}; 1913 false -> 1914 case code:is_loaded(Suite) of 1915 {file,SuiteFile} -> 1916 %% test suite is already 1917 %% loaded and since 1918 %% auto_compile == false, 1919 %% let's assume the user has 1920 %% loaded the beam file 1921 %% explicitly 1922 ActualDir = 1923 filename:dirname(SuiteFile), 1924 {[{ActualDir,Suite}|Found], 1925 NotFound}; 1926 false -> 1927 Name = 1928 filename:join(TestDir, 1929 atom_to_list( 1930 Suite)), 1931 io:format(user, 1932 "Suite ~w not found " 1933 "in directory ~ts~n", 1934 [Suite,TestDir]), 1935 {Found,[{DS,[Name]}|NotFound]} 1936 end 1937 end 1938 end; 1939 {error,_Reason} -> 1940 case code:is_loaded(Suite) of 1941 {file,SuiteFile} -> 1942 %% test suite is already loaded and since 1943 %% auto_compile == false, let's assume the 1944 %% user has loaded the beam file explicitly 1945 ActualDir = filename:dirname(SuiteFile), 1946 {[{ActualDir,Suite}|Found],NotFound}; 1947 false -> 1948 io:format(user, "Directory ~ts is " 1949 "invalid~n", [Dir]), 1950 Name = filename:join(Dir, atom_to_list(Suite)), 1951 {Found,[{DS,[Name]}|NotFound]} 1952 end 1953 end 1954 end, 1955 {ActualFound,Missing} = lists:foldl(Verify, {[],[]}, TestSuites), 1956 {lists:reverse(ActualFound),lists:reverse(Missing)}. 1957 1958save_make_errors([]) -> 1959 []; 1960save_make_errors(Errors) -> 1961 Suites = get_bad_suites(Errors,[]), 1962 ct_logs:log("MAKE RESULTS", 1963 "Error compiling or locating the " 1964 "following suites: ~n~p",[Suites]), 1965 %% save the info for logger 1966 ok = file:write_file(?missing_suites_info,term_to_binary(Errors)), 1967 Errors. 1968 1969get_bad_suites([{{_TestDir,_Suite},Failed}|Errors], BadSuites) -> 1970 get_bad_suites(Errors,BadSuites++Failed); 1971get_bad_suites([], BadSuites) -> 1972 BadSuites. 1973 1974 1975step(TestDir, Suite, Case) -> 1976 step(TestDir, Suite, Case, []). 1977 1978step(TestDir, Suite, Case, Opts) when is_list(TestDir), 1979 is_atom(Suite), is_atom(Case), 1980 Suite =/= all, Case =/= all -> 1981 do_run([{TestDir,Suite,Case}], [{step,Opts}]). 1982 1983 1984%%%----------------------------------------------------------------- 1985%%% Internal 1986suite_tuples([{TestDir,Suites,_} | Tests]) when is_list(Suites) -> 1987 lists:map(fun(S) -> {TestDir,S} end, Suites) ++ suite_tuples(Tests); 1988suite_tuples([{TestDir,Suite,_} | Tests]) when is_atom(Suite) -> 1989 [{TestDir,Suite} | suite_tuples(Tests)]; 1990suite_tuples([]) -> 1991 []. 1992 1993final_tests(Tests, Skip, Bad) -> 1994 {Tests1,Skip1} = final_tests1(Tests, [], Skip, Bad), 1995 Skip2 = final_skip(Skip1, []), 1996 {Tests1,Skip2}. 1997 1998final_tests1([{TestDir,Suites,_}|Tests], Final, Skip, Bad) when 1999 is_list(Suites), is_atom(hd(Suites)) -> 2000 Skip1 = [{TD,S,make_failed} || {{TD,S},_} <- Bad, S1 <- Suites, 2001 S == S1, TD == TestDir], 2002 Final1 = [{TestDir,S,all} || S <- Suites], 2003 final_tests1(Tests, lists:reverse(Final1)++Final, Skip++Skip1, Bad); 2004 2005final_tests1([{TestDir,all,all}|Tests], Final, Skip, Bad) -> 2006 MissingSuites = 2007 case lists:keysearch({TestDir,all}, 1, Bad) of 2008 {value,{_,Failed}} -> 2009 [list_to_atom(filename:basename(F)) || F <- Failed]; 2010 false -> 2011 [] 2012 end, 2013 Missing = [{TestDir,S,make_failed} || S <- MissingSuites], 2014 Final1 = [{TestDir,all,all}|Final], 2015 final_tests1(Tests, Final1, Skip++Missing, Bad); 2016 2017final_tests1([{TestDir,Suite,Cases}|Tests], Final, Skip, Bad) when 2018 Cases==[]; Cases==all -> 2019 final_tests1([{TestDir,[Suite],all}|Tests], Final, Skip, Bad); 2020 2021final_tests1([{TestDir,Suite,GrsOrCs}|Tests], Final, Skip, Bad) when 2022 is_list(GrsOrCs) -> 2023 case lists:keymember({TestDir,Suite}, 1, Bad) of 2024 true -> 2025 Skip1 = Skip ++ [{TestDir,Suite,all,make_failed}], 2026 final_tests1(Tests, [{TestDir,Suite,all}|Final], Skip1, Bad); 2027 false -> 2028 GrsOrCs1 = 2029 lists:flatmap( 2030 %% for now, only flat group defs are allowed as 2031 %% start options and test spec terms 2032 fun({all,all}) -> 2033 [ct_groups:make_conf(TestDir, Suite, all, [], all)]; 2034 ({skipped,Group,TCs}) -> 2035 [ct_groups:make_conf(TestDir, Suite, 2036 Group, [skipped], TCs)]; 2037 ({skipped,TC}) -> 2038 case lists:member(TC, GrsOrCs) of 2039 true -> 2040 []; 2041 false -> 2042 [TC] 2043 end; 2044 ({GrSpec = {GroupName,_},TCs}) -> 2045 Props = [{override,GrSpec}], 2046 [ct_groups:make_conf(TestDir, Suite, 2047 GroupName, Props, TCs)]; 2048 ({GrSpec = {GroupName,_,_},TCs}) -> 2049 Props = [{override,GrSpec}], 2050 [ct_groups:make_conf(TestDir, Suite, 2051 GroupName, Props, TCs)]; 2052 ({GroupOrGroups,TCs}) -> 2053 [ct_groups:make_conf(TestDir, Suite, 2054 GroupOrGroups, [], TCs)]; 2055 (TC) -> 2056 [TC] 2057 end, GrsOrCs), 2058 Do = {TestDir,Suite,GrsOrCs1}, 2059 final_tests1(Tests, [Do|Final], Skip, Bad) 2060 end; 2061 2062final_tests1([], Final, Skip, _Bad) -> 2063 {lists:reverse(Final),Skip}. 2064 2065final_skip([{TestDir,Suite,{all,all},Reason}|Skips], Final) -> 2066 SkipConf = ct_groups:make_conf(TestDir, Suite, all, [], all), 2067 Skip = {TestDir,Suite,SkipConf,Reason}, 2068 final_skip(Skips, [Skip|Final]); 2069 2070final_skip([{TestDir,Suite,{Group,TCs},Reason}|Skips], Final) -> 2071 Conf = ct_groups:make_conf(TestDir, Suite, Group, [], TCs), 2072 Skip = {TestDir,Suite,Conf,Reason}, 2073 final_skip(Skips, [Skip|Final]); 2074 2075final_skip([Skip|Skips], Final) -> 2076 final_skip(Skips, [Skip|Final]); 2077 2078final_skip([], Final) -> 2079 lists:reverse(Final). 2080 2081continue([], _) -> 2082 true; 2083continue(_MakeErrors, true) -> 2084 false; 2085continue(_MakeErrors, _AbortIfMissingSuites) -> 2086 io:nl(), 2087 OldGL = group_leader(), 2088 case set_group_leader_same_as_shell(OldGL) of 2089 true -> 2090 S = self(), 2091 io:format("Failed to compile or locate one " 2092 "or more test suites\n" 2093 "Press \'c\' to continue or \'a\' to abort.\n" 2094 "Will continue in 15 seconds if no " 2095 "answer is given!\n"), 2096 Pid = spawn(fun() -> 2097 case io:get_line('(c/a) ') of 2098 "c\n" -> 2099 S ! true; 2100 _ -> 2101 S ! false 2102 end 2103 end), 2104 group_leader(OldGL, self()), 2105 receive R when R==true; R==false -> 2106 R 2107 after 15000 -> 2108 exit(Pid, kill), 2109 io:format("... timeout - continuing!!\n"), 2110 true 2111 end; 2112 false -> % no shell process to use 2113 true 2114 end. 2115 2116set_group_leader_same_as_shell(OldGL) -> 2117 %% find the group leader process on the node in a dirty fashion 2118 %% (check initial function call and look in the process dictionary) 2119 GS2or3 = fun(P) -> 2120 case process_info(P,initial_call) of 2121 {initial_call,{group,server,X}} when X == 2 ; X == 3 -> 2122 true; 2123 _ -> 2124 false 2125 end 2126 end, 2127 case [P || P <- processes(), GS2or3(P), 2128 true == lists:keymember(shell,1, 2129 element(2,process_info(P,dictionary)))] of 2130 [GL|_] -> 2131 %% check if started from remote node (skip interaction) 2132 if node(OldGL) /= node(GL) -> false; 2133 true -> group_leader(GL, self()) 2134 end; 2135 [] -> 2136 false 2137 end. 2138 2139check_and_add([{TestDir0,M,_} | Tests], Added, PA) -> 2140 case locate_test_dir(TestDir0, M) of 2141 {ok,TestDir} -> 2142 case lists:member(TestDir, Added) of 2143 true -> 2144 check_and_add(Tests, Added, PA); 2145 false -> 2146 case lists:member(rm_trailing_slash(TestDir), 2147 code:get_path()) of 2148 false -> 2149 true = code:add_patha(TestDir), 2150 check_and_add(Tests, [TestDir|Added], [TestDir|PA]); 2151 true -> 2152 check_and_add(Tests, [TestDir|Added], PA) 2153 end 2154 end; 2155 {error,_} -> 2156 {error,{invalid_directory,TestDir0}} 2157 end; 2158check_and_add([], _, PA) -> 2159 {ok,PA}. 2160 2161do_run_test(Tests, Skip, Opts0) -> 2162 case check_and_add(Tests, [], []) of 2163 {ok,AddedToPath} -> 2164 ct_util:set_testdata({stats,{0,0,{0,0}}}), 2165 2166 %% test_server needs to know the include path too 2167 InclPath = case application:get_env(common_test, include) of 2168 {ok,Incls} -> Incls; 2169 _ -> [] 2170 end, 2171 application:set_env(test_server, include, InclPath), 2172 2173 %% copy the escape characters setting to test_server 2174 EscChars = 2175 case application:get_env(common_test, esc_chars) of 2176 {ok,ECBool} -> ECBool; 2177 _ -> true 2178 end, 2179 application:set_env(test_server, esc_chars, EscChars), 2180 2181 {ok, _} = test_server_ctrl:start_link(local), 2182 2183 %% let test_server expand the test tuples and count no of cases 2184 {Suites,NoOfCases} = count_test_cases(Tests, Skip), 2185 Suites1 = delete_dups(Suites), 2186 NoOfTests = length(Tests), 2187 NoOfSuites = length(Suites1), 2188 ct_util:warn_duplicates(Suites1), 2189 {ok,Cwd} = file:get_cwd(), 2190 io:format("~nCWD set to: ~tp~n", [Cwd]), 2191 if NoOfCases == unknown -> 2192 io:format("~nTEST INFO: ~w test(s), ~w suite(s)~n~n", 2193 [NoOfTests,NoOfSuites]), 2194 ct_logs:log("TEST INFO","~w test(s), ~w suite(s)", 2195 [NoOfTests,NoOfSuites]); 2196 true -> 2197 io:format("~nTEST INFO: ~w test(s), ~w case(s) " 2198 "in ~w suite(s)~n~n", 2199 [NoOfTests,NoOfCases,NoOfSuites]), 2200 ct_logs:log("TEST INFO","~w test(s), ~w case(s) " 2201 "in ~w suite(s)", 2202 [NoOfTests,NoOfCases,NoOfSuites]) 2203 end, 2204 %% if the verbosity level is set lower than ?STD_IMPORTANCE, tell 2205 %% test_server to ignore stdout printouts to the test case log file 2206 case proplists:get_value(default, Opts0#opts.verbosity) of 2207 VLvl when is_integer(VLvl), (?STD_IMPORTANCE < (100-VLvl)) -> 2208 test_server_ctrl:reject_io_reqs(true); 2209 _Lower -> 2210 ok 2211 end, 2212 2213 case Opts0#opts.multiply_timetraps of 2214 undefined -> MultTT = 1; 2215 MultTT -> MultTT 2216 end, 2217 case Opts0#opts.scale_timetraps of 2218 undefined -> ScaleTT = false; 2219 ScaleTT -> ScaleTT 2220 end, 2221 ct_logs:log("TEST INFO","Timetrap time multiplier = ~w~n" 2222 "Timetrap scaling enabled = ~w", [MultTT,ScaleTT]), 2223 test_server_ctrl:multiply_timetraps(MultTT), 2224 test_server_ctrl:scale_timetraps(ScaleTT), 2225 2226 test_server_ctrl:create_priv_dir(choose_val( 2227 Opts0#opts.create_priv_dir, 2228 auto_per_run)), 2229 2230 {ok,LogDir} = ct_logs:get_log_dir(true), 2231 {TsCoverInfo,Opts} = maybe_start_cover(Opts0, LogDir), 2232 2233 ct_event:notify(#event{name=start_info, 2234 node=node(), 2235 data={NoOfTests,NoOfSuites,NoOfCases}}), 2236 CleanUp = add_jobs(Tests, Skip, Opts, []), 2237 unlink(whereis(test_server_ctrl)), 2238 catch test_server_ctrl:wait_finish(), 2239 2240 maybe_stop_cover(Opts, TsCoverInfo, LogDir), 2241 2242 %% check if last testcase has left a "dead" trace window 2243 %% behind, and if so, kill it 2244 case ct_util:get_testdata(interpret) of 2245 {_What,kill,{TCPid,AttPid}} -> 2246 ct_util:kill_attached(TCPid, AttPid); 2247 _ -> 2248 ok 2249 end, 2250 lists:foreach(fun(Suite) -> 2251 maybe_cleanup_interpret(Suite, Opts#opts.step) 2252 end, CleanUp), 2253 _ = [code:del_path(Dir) || Dir <- AddedToPath], 2254 2255 %% If a severe error has occurred in the test_server, 2256 %% we will generate an exception here. 2257 case ct_util:get_testdata(severe_error) of 2258 undefined -> ok; 2259 SevereError -> 2260 ct_logs:log("SEVERE ERROR", "~tp\n", [SevereError]), 2261 exit(SevereError) 2262 end, 2263 2264 case ct_util:get_testdata(stats) of 2265 Stats = {_Ok,_Failed,{_UserSkipped,_AutoSkipped}} -> 2266 Stats; 2267 _ -> 2268 {error,test_result_unknown} 2269 end; 2270 Error -> 2271 exit(Error) 2272 end. 2273 2274maybe_start_cover(Opts=#opts{cover=Cover,cover_stop=CoverStop0},LogDir) -> 2275 if Cover == undefined -> 2276 {undefined,Opts}; 2277 true -> 2278 case ct_cover:get_spec(Cover) of 2279 {error,Reason} -> 2280 exit({error,Reason}); 2281 CoverSpec -> 2282 CoverStop = 2283 case CoverStop0 of 2284 undefined -> true; 2285 Stop -> Stop 2286 end, 2287 start_cover(Opts#opts{coverspec=CoverSpec, 2288 cover_stop=CoverStop}, 2289 LogDir) 2290 end 2291 end. 2292 2293start_cover(Opts=#opts{coverspec=CovData,cover_stop=CovStop},LogDir) -> 2294 {CovFile, 2295 CovNodes, 2296 CovImport, 2297 _CovExport, 2298 #cover{app = CovApp, 2299 local_only = LocalOnly, 2300 level = CovLevel, 2301 excl_mods = CovExcl, 2302 incl_mods = CovIncl, 2303 cross = CovCross, 2304 src = _CovSrc}} = CovData, 2305 case LocalOnly of 2306 true -> cover:local_only(); 2307 false -> ok 2308 end, 2309 ct_logs:log("COVER INFO", 2310 "Using cover specification file: ~ts~n" 2311 "App: ~w~n" 2312 "Local only: ~w~n" 2313 "Cross cover: ~w~n" 2314 "Including ~w modules~n" 2315 "Excluding ~w modules", 2316 [CovFile,CovApp,LocalOnly,CovCross, 2317 length(CovIncl),length(CovExcl)]), 2318 2319 %% Tell test_server to print a link in its coverlog 2320 %% pointing to the real coverlog which will be written in 2321 %% maybe_stop_cover/2 2322 test_server_ctrl:cover({log,LogDir}), 2323 2324 %% Cover compile all modules 2325 {ok,TsCoverInfo} = test_server_ctrl:cover_compile(CovApp,CovFile, 2326 CovExcl,CovIncl, 2327 CovCross,CovLevel, 2328 CovStop), 2329 ct_logs:log("COVER INFO", 2330 "Compilation completed - test_server cover info: ~tp", 2331 [TsCoverInfo]), 2332 2333 %% start cover on specified nodes 2334 if (CovNodes /= []) and (CovNodes /= undefined) -> 2335 ct_logs:log("COVER INFO", 2336 "Nodes included in cover " 2337 "session: ~tw", 2338 [CovNodes]), 2339 cover:start(CovNodes); 2340 true -> 2341 ok 2342 end, 2343 lists:foreach( 2344 fun(Imp) -> 2345 case cover:import(Imp) of 2346 ok -> 2347 ok; 2348 {error,Reason} -> 2349 ct_logs:log("COVER INFO", 2350 "Importing cover data from: ~ts fails! " 2351 "Reason: ~tp", [Imp,Reason]) 2352 end 2353 end, CovImport), 2354 {TsCoverInfo,Opts}. 2355 2356maybe_stop_cover(_,undefined,_) -> 2357 ok; 2358maybe_stop_cover(#opts{coverspec=CovData},TsCoverInfo,LogDir) -> 2359 {_CovFile, 2360 _CovNodes, 2361 _CovImport, 2362 CovExport, 2363 _AppData} = CovData, 2364 case CovExport of 2365 undefined -> ok; 2366 _ -> 2367 ct_logs:log("COVER INFO","Exporting cover data to ~tp",[CovExport]), 2368 cover:export(CovExport) 2369 end, 2370 ct_logs:log("COVER INFO","Analysing cover data to ~tp",[LogDir]), 2371 test_server_ctrl:cover_analyse(TsCoverInfo,LogDir), 2372 ct_logs:log("COVER INFO","Analysis completed.",[]), 2373 ok. 2374 2375 2376delete_dups([S | Suites]) -> 2377 Suites1 = lists:delete(S, Suites), 2378 [S | delete_dups(Suites1)]; 2379delete_dups([]) -> 2380 []. 2381 2382count_test_cases(Tests, Skip) -> 2383 SendResult = fun(Me, Result) -> Me ! {no_of_cases,Result} end, 2384 TSPid = test_server_ctrl:start_get_totals(SendResult), 2385 Ref = erlang:monitor(process, TSPid), 2386 _ = add_jobs(Tests, Skip, #opts{}, []), 2387 Counted = (catch count_test_cases1(length(Tests), 0, [], Ref)), 2388 erlang:demonitor(Ref, [flush]), 2389 case Counted of 2390 {error,{test_server_died}} = Error -> 2391 throw(Error); 2392 {error,Reason} -> 2393 unlink(whereis(test_server_ctrl)), 2394 test_server_ctrl:stop(), 2395 throw({user_error,Reason}); 2396 Result -> 2397 test_server_ctrl:stop_get_totals(), 2398 Result 2399 end. 2400 2401count_test_cases1(0, N, Suites, _) -> 2402 {lists:flatten(Suites), N}; 2403count_test_cases1(Jobs, N, Suites, Ref) -> 2404 receive 2405 {_,{error,_Reason} = Error} -> 2406 throw(Error); 2407 {no_of_cases,{Ss,N1}} -> 2408 count_test_cases1(Jobs-1, add_known(N,N1), [Ss|Suites], Ref); 2409 {'DOWN', Ref, _, _, Info} -> 2410 throw({error,{test_server_died,Info}}) 2411 end. 2412 2413add_known(unknown, _) -> 2414 unknown; 2415add_known(_, unknown) -> 2416 unknown; 2417add_known(N, N1) -> 2418 N+N1. 2419 2420add_jobs([{TestDir,all,_}|Tests], Skip, Opts, CleanUp) -> 2421 Name = get_name(TestDir), 2422 case catch test_server_ctrl:add_dir_with_skip(Name, TestDir, 2423 skiplist(TestDir,Skip)) of 2424 {'EXIT',_} -> 2425 CleanUp; 2426 _ -> 2427 case wait_for_idle() of 2428 ok -> 2429 add_jobs(Tests, Skip, Opts, CleanUp); 2430 _ -> 2431 CleanUp 2432 end 2433 end; 2434add_jobs([{TestDir,[Suite],all}|Tests], Skip, 2435 Opts, CleanUp) when is_atom(Suite) -> 2436 add_jobs([{TestDir,Suite,all}|Tests], Skip, Opts, CleanUp); 2437add_jobs([{TestDir,Suites,all}|Tests], Skip, 2438 Opts, CleanUp) when is_list(Suites) -> 2439 Name = get_name(TestDir) ++ ".suites", 2440 case catch test_server_ctrl:add_module_with_skip(Name, Suites, 2441 skiplist(TestDir,Skip)) of 2442 {'EXIT',_} -> 2443 CleanUp; 2444 _ -> 2445 case wait_for_idle() of 2446 ok -> 2447 add_jobs(Tests, Skip, Opts, CleanUp); 2448 _ -> 2449 CleanUp 2450 end 2451 end; 2452add_jobs([{TestDir,Suite,all}|Tests], Skip, Opts, CleanUp) -> 2453 case maybe_interpret(Suite, all, Opts) of 2454 ok -> 2455 Name = get_name(TestDir) ++ "." ++ atom_to_list(Suite), 2456 case catch test_server_ctrl:add_module_with_skip(Name, [Suite], 2457 skiplist(TestDir, 2458 Skip)) of 2459 {'EXIT',_} -> 2460 CleanUp; 2461 _ -> 2462 case wait_for_idle() of 2463 ok -> 2464 add_jobs(Tests, Skip, Opts, [Suite|CleanUp]); 2465 _ -> 2466 CleanUp 2467 end 2468 end; 2469 Error -> 2470 Error 2471 end; 2472 2473%% group (= conf case in test_server) 2474add_jobs([{TestDir,Suite,Confs}|Tests], Skip, Opts, CleanUp) when 2475 element(1, hd(Confs)) == conf -> 2476 Group = fun(Conf) -> proplists:get_value(name, element(2, Conf)) end, 2477 TestCases = fun(Conf) -> element(4, Conf) end, 2478 TCTestName = fun(all) -> ""; 2479 ([C]) when is_atom(C) -> "." ++ atom_to_list(C); 2480 (Cs) when is_list(Cs) -> ".cases" 2481 end, 2482 GrTestName = 2483 case Confs of 2484 [Conf] -> 2485 case Group(Conf) of 2486 GrName when is_atom(GrName) -> 2487 "." ++ atom_to_list(GrName) ++ 2488 TCTestName(TestCases(Conf)); 2489 _ -> 2490 ".groups" ++ TCTestName(TestCases(Conf)) 2491 end; 2492 _ -> 2493 ".groups" 2494 end, 2495 TestName = get_name(TestDir) ++ "." ++ atom_to_list(Suite) ++ GrTestName, 2496 case maybe_interpret(Suite, init_per_group, Opts) of 2497 ok -> 2498 case catch test_server_ctrl:add_conf_with_skip(TestName, 2499 Suite, 2500 Confs, 2501 skiplist(TestDir, 2502 Skip)) of 2503 {'EXIT',_} -> 2504 CleanUp; 2505 _ -> 2506 case wait_for_idle() of 2507 ok -> 2508 add_jobs(Tests, Skip, Opts, [Suite|CleanUp]); 2509 _ -> 2510 CleanUp 2511 end 2512 end; 2513 Error -> 2514 Error 2515 end; 2516 2517%% test case 2518add_jobs([{TestDir,Suite,[Case]}|Tests], 2519 Skip, Opts, CleanUp) when is_atom(Case) -> 2520 add_jobs([{TestDir,Suite,Case}|Tests], Skip, Opts, CleanUp); 2521 2522add_jobs([{TestDir,Suite,Cases}|Tests], 2523 Skip, Opts, CleanUp) when is_list(Cases) -> 2524 Cases1 = lists:map(fun({GroupName,_}) when is_atom(GroupName) -> GroupName; 2525 (Case) -> Case 2526 end, Cases), 2527 case maybe_interpret(Suite, Cases1, Opts) of 2528 ok -> 2529 Name = get_name(TestDir) ++ "." ++ atom_to_list(Suite) ++ ".cases", 2530 case catch test_server_ctrl:add_cases_with_skip(Name, Suite, Cases1, 2531 skiplist(TestDir, 2532 Skip)) of 2533 {'EXIT',_} -> 2534 CleanUp; 2535 _ -> 2536 case wait_for_idle() of 2537 ok -> 2538 add_jobs(Tests, Skip, Opts, [Suite|CleanUp]); 2539 _ -> 2540 CleanUp 2541 end 2542 end; 2543 Error -> 2544 Error 2545 end; 2546add_jobs([{TestDir,Suite,Case}|Tests], Skip, Opts, CleanUp) when is_atom(Case) -> 2547 case maybe_interpret(Suite, Case, Opts) of 2548 ok -> 2549 Name = get_name(TestDir) ++ "." ++ atom_to_list(Suite) ++ "." ++ 2550 atom_to_list(Case), 2551 case catch test_server_ctrl:add_case_with_skip(Name, Suite, Case, 2552 skiplist(TestDir, 2553 Skip)) of 2554 {'EXIT',_} -> 2555 CleanUp; 2556 _ -> 2557 case wait_for_idle() of 2558 ok -> 2559 add_jobs(Tests, Skip, Opts, [Suite|CleanUp]); 2560 _ -> 2561 CleanUp 2562 end 2563 end; 2564 Error -> 2565 Error 2566 end; 2567add_jobs([], _, _, CleanUp) -> 2568 CleanUp. 2569 2570wait_for_idle() -> 2571 ct_util:update_last_run_index(), 2572 Notify = fun(Me,IdleState) -> Me ! {idle,IdleState}, 2573 receive 2574 {Me,proceed} -> ok 2575 after 2576 30000 -> ok 2577 end 2578 end, 2579 case catch test_server_ctrl:idle_notify(Notify) of 2580 {'EXIT',_} -> 2581 error; 2582 TSPid -> 2583 %% so we don't hang forever if test_server dies 2584 Ref = erlang:monitor(process, TSPid), 2585 Result = receive 2586 {idle,abort} -> aborted; 2587 {idle,_} -> ok; 2588 {'DOWN', Ref, _, _, _} -> error 2589 end, 2590 erlang:demonitor(Ref, [flush]), 2591 ct_util:update_last_run_index(), 2592 %% let test_server_ctrl proceed (and possibly shut down) now 2593 TSPid ! {self(),proceed}, 2594 Result 2595 end. 2596 2597skiplist(Dir, [{Dir,all,Cmt}|Skip]) -> 2598 %% we need to turn 'all' into list of modules since 2599 %% test_server doesn't do skips on Dir level 2600 Ss = filelib:wildcard(filename:join(Dir, "*_SUITE.beam")), 2601 [{list_to_atom(filename:basename(S,".beam")),Cmt} || S <- Ss] ++ 2602 skiplist(Dir,Skip); 2603skiplist(Dir, [{Dir,S,Cmt}|Skip]) -> 2604 [{S,Cmt} | skiplist(Dir, Skip)]; 2605skiplist(Dir, [{Dir,S,C,Cmt}|Skip]) -> 2606 [{S,C,Cmt} | skiplist(Dir, Skip)]; 2607skiplist(Dir, [_|Skip]) -> 2608 skiplist(Dir, Skip); 2609skiplist(_Dir, []) -> 2610 []. 2611 2612get_name(Dir) -> 2613 TestDir = 2614 case filename:basename(Dir) of 2615 "test" -> 2616 filename:dirname(Dir); 2617 _ -> 2618 Dir 2619 end, 2620 Base = filename:basename(TestDir), 2621 case filename:basename(filename:dirname(TestDir)) of 2622 "" -> 2623 Base; 2624 TopDir -> 2625 TopDir ++ "." ++ Base 2626 end. 2627 2628run_make(TestDir, Mod, UserInclude) -> 2629 run_make(suites, TestDir, Mod, UserInclude, [nowarn_export_all]). 2630 2631run_make(Targets, TestDir0, Mod, UserInclude, COpts) when is_list(Mod) -> 2632 run_make(Targets, TestDir0, list_to_atom(Mod), UserInclude, COpts); 2633 2634run_make(Targets, TestDir0, Mod, UserInclude, COpts) -> 2635 case locate_test_dir(TestDir0, Mod) of 2636 {ok,TestDir} -> 2637 %% send a start_make notification which may suspend 2638 %% the process if some other node is compiling files 2639 %% in the same directory 2640 ct_event:sync_notify(#event{name=start_make, 2641 node=node(), 2642 data=TestDir}), 2643 {ok,Cwd} = file:get_cwd(), 2644 ok = file:set_cwd(TestDir), 2645 CtInclude = get_dir(common_test, "include"), 2646 XmerlInclude = get_dir(xmerl, "include"), 2647 ErlFlags = UserInclude ++ [{i,CtInclude}, 2648 {i,XmerlInclude}, 2649 debug_info] ++ COpts, 2650 Result = 2651 if Mod == all ; Targets == helpmods -> 2652 case (catch ct_make:all([noexec|ErlFlags])) of 2653 {'EXIT',_} = Failure -> 2654 Failure; 2655 MakeInfo -> 2656 FileTest = fun(F, suites) -> is_suite(F); 2657 (F, helpmods) -> not is_suite(F) 2658 end, 2659 Files = 2660 lists:flatmap(fun({F,out_of_date}) -> 2661 case FileTest(F, 2662 Targets) of 2663 true -> [F]; 2664 false -> [] 2665 end; 2666 (_) -> 2667 [] 2668 end, MakeInfo), 2669 (catch ct_make:files(Files, [load|ErlFlags])) 2670 end; 2671 true -> 2672 (catch ct_make:files([Mod], [load|ErlFlags])) 2673 end, 2674 2675 ok = file:set_cwd(Cwd), 2676 %% send finished_make notification 2677 ct_event:notify(#event{name=finished_make, 2678 node=node(), 2679 data=TestDir}), 2680 case Result of 2681 {up_to_date,_} -> 2682 ok; 2683 {'EXIT',Reason} -> 2684 io:format("{error,{make_crashed,~tp}\n", [Reason]), 2685 {error,{make_crashed,TestDir,Reason}}; 2686 {error,ModInfo} -> 2687 io:format("{error,make_failed}\n", []), 2688 Bad = [filename:join(TestDir, M) || {M,R} <- ModInfo, 2689 R == error], 2690 {error,{make_failed,Bad}} 2691 end; 2692 {error,_} -> 2693 io:format("{error,{invalid_directory,~tp}}\n", [TestDir0]), 2694 {error,{invalid_directory,TestDir0}} 2695 end. 2696 2697get_dir(App, Dir) -> 2698 filename:join(code:lib_dir(App), Dir). 2699 2700maybe_interpret(Suite, Cases, #opts{step = StepOpts}) when StepOpts =/= undefined -> 2701 %% if other suite has run before this one, check if last testcase 2702 %% has left a "dead" trace window behind, and if so, kill it 2703 case ct_util:get_testdata(interpret) of 2704 {_What,kill,{TCPid,AttPid}} -> 2705 ct_util:kill_attached(TCPid, AttPid); 2706 _ -> 2707 ok 2708 end, 2709 maybe_interpret1(Suite, Cases, StepOpts); 2710maybe_interpret(_, _, _) -> 2711 ok. 2712 2713maybe_interpret1(Suite, all, StepOpts) -> 2714 case i:ii(Suite) of 2715 {module,_} -> 2716 i:iaa([break]), 2717 case get_all_testcases(Suite) of 2718 {error,_} -> 2719 {error,no_testcases_found}; 2720 Cases -> 2721 maybe_interpret2(Suite, Cases, StepOpts) 2722 end; 2723 error -> 2724 {error,could_not_interpret_module} 2725 end; 2726maybe_interpret1(Suite, Case, StepOpts) when is_atom(Case) -> 2727 maybe_interpret1(Suite, [Case], StepOpts); 2728maybe_interpret1(Suite, Cases, StepOpts) when is_list(Cases) -> 2729 case i:ii(Suite) of 2730 {module,_} -> 2731 i:iaa([break]), 2732 maybe_interpret2(Suite, Cases, StepOpts); 2733 error -> 2734 {error,could_not_interpret_module} 2735 end. 2736 2737maybe_interpret2(Suite, Cases, StepOpts) -> 2738 set_break_on_config(Suite, StepOpts), 2739 _ = [begin try i:ib(Suite, Case, 1) of 2740 _ -> ok 2741 catch 2742 _:_Error -> 2743 io:format(user, "Invalid breakpoint: ~w:~tw/1~n", 2744 [Suite,Case]) 2745 end 2746 end || Case <- Cases, is_atom(Case)], 2747 test_server_ctrl:multiply_timetraps(infinity), 2748 WinOp = case lists:member(keep_inactive, ensure_atom(StepOpts)) of 2749 true -> no_kill; 2750 false -> kill 2751 end, 2752 ct_util:set_testdata({interpret,{{Suite,Cases},WinOp, 2753 {undefined,undefined}}}), 2754 ok. 2755 2756set_break_on_config(Suite, StepOpts) -> 2757 case lists:member(config, ensure_atom(StepOpts)) of 2758 true -> 2759 SetBPIfExists = fun(F,A) -> 2760 case erlang:function_exported(Suite, F, A) of 2761 true -> i:ib(Suite, F, A); 2762 false -> ok 2763 end 2764 end, 2765 ok = SetBPIfExists(init_per_suite, 1), 2766 ok = SetBPIfExists(init_per_group, 2), 2767 ok = SetBPIfExists(init_per_testcase, 2), 2768 ok = SetBPIfExists(end_per_testcase, 2), 2769 ok = SetBPIfExists(end_per_group, 2), 2770 ok = SetBPIfExists(end_per_suite, 1); 2771 false -> 2772 ok 2773 end. 2774 2775maybe_cleanup_interpret(_, undefined) -> 2776 ok; 2777maybe_cleanup_interpret(Suite, _) -> 2778 i:iq(Suite). 2779 2780log_ts_names([]) -> 2781 ok; 2782log_ts_names(Specs) -> 2783 List = lists:map(fun(Name) -> 2784 Name ++ " " 2785 end, Specs), 2786 ct_logs:log("Test Specification file(s)", "~ts", 2787 [lists:flatten(List)]). 2788 2789merge_arguments(Args) -> 2790 merge_arguments(Args, []). 2791 2792merge_arguments([LogDir={logdir,_}|Args], Merged) -> 2793 merge_arguments(Args, handle_arg(replace, LogDir, Merged)); 2794 2795merge_arguments([CoverFile={cover,_}|Args], Merged) -> 2796 merge_arguments(Args, handle_arg(replace, CoverFile, Merged)); 2797 2798merge_arguments([CoverStop={cover_stop,_}|Args], Merged) -> 2799 merge_arguments(Args, handle_arg(replace, CoverStop, Merged)); 2800 2801merge_arguments([{'case',TC}|Args], Merged) -> 2802 merge_arguments(Args, handle_arg(merge, {testcase,TC}, Merged)); 2803 2804merge_arguments([Arg|Args], Merged) -> 2805 merge_arguments(Args, handle_arg(merge, Arg, Merged)); 2806 2807merge_arguments([], Merged) -> 2808 Merged. 2809 2810handle_arg(replace, {Key,Elems}, [{Key,_}|Merged]) -> 2811 [{Key,Elems}|Merged]; 2812handle_arg(merge, {event_handler_init,Elems}, [{event_handler_init,PrevElems}|Merged]) -> 2813 [{event_handler_init,PrevElems++["add"|Elems]}|Merged]; 2814handle_arg(merge, {userconfig,Elems}, [{userconfig,PrevElems}|Merged]) -> 2815 [{userconfig,PrevElems++["add"|Elems]}|Merged]; 2816handle_arg(merge, {Key,Elems}, [{Key,PrevElems}|Merged]) -> 2817 [{Key,PrevElems++Elems}|Merged]; 2818handle_arg(Op, Arg, [Other|Merged]) -> 2819 [Other|handle_arg(Op, Arg, Merged)]; 2820handle_arg(_,Arg,[]) -> 2821 [Arg]. 2822 2823get_start_opt(Key, IfExists, Args) -> 2824 get_start_opt(Key, IfExists, undefined, Args). 2825 2826get_start_opt(Key, IfExists, IfNotExists, Args) -> 2827 try try_get_start_opt(Key, IfExists, IfNotExists, Args) of 2828 Result -> 2829 Result 2830 catch 2831 error:_ -> 2832 exit({user_error,{bad_argument,Key}}) 2833 end. 2834 2835try_get_start_opt(Key, IfExists, IfNotExists, Args) -> 2836 case lists:keysearch(Key, 1, Args) of 2837 {value,{Key,Val}} when is_function(IfExists) -> 2838 IfExists(Val); 2839 {value,{Key,Val}} when IfExists == value -> 2840 Val; 2841 {value,{Key,_Val}} -> 2842 IfExists; 2843 _ -> 2844 IfNotExists 2845 end. 2846 2847ct_hooks_args2opts(Args) -> 2848 lists:foldl(fun({ct_hooks,Hooks}, Acc) -> 2849 ct_hooks_args2opts(Hooks,Acc); 2850 (_,Acc) -> 2851 Acc 2852 end,[],Args). 2853 2854ct_hooks_args2opts([CTH,Arg,Prio,"and"| Rest],Acc) when Arg /= "and" -> 2855 ct_hooks_args2opts(Rest,[{list_to_atom(CTH), 2856 parse_cth_args(Arg), 2857 parse_cth_args(Prio)}|Acc]); 2858ct_hooks_args2opts([CTH,Arg,"and"| Rest],Acc) -> 2859 ct_hooks_args2opts(Rest,[{list_to_atom(CTH), 2860 parse_cth_args(Arg)}|Acc]); 2861ct_hooks_args2opts([CTH], Acc) -> 2862 ct_hooks_args2opts([CTH,"and"],Acc); 2863ct_hooks_args2opts([CTH, "and" | Rest], Acc) -> 2864 ct_hooks_args2opts(Rest,[list_to_atom(CTH)|Acc]); 2865ct_hooks_args2opts([CTH, Args], Acc) -> 2866 ct_hooks_args2opts([CTH, Args, "and"],Acc); 2867ct_hooks_args2opts([CTH, Args, Prio], Acc) -> 2868 ct_hooks_args2opts([CTH, Args, Prio, "and"],Acc); 2869ct_hooks_args2opts([],Acc) -> 2870 lists:reverse(Acc). 2871 2872parse_cth_args(String) -> 2873 try 2874 true = io_lib:printable_unicode_list(String), 2875 {ok,Toks,_} = erl_scan:string(String++"."), 2876 {ok, Args} = erl_parse:parse_term(Toks), 2877 Args 2878 catch _:_ -> 2879 String 2880 end. 2881 2882event_handler_args2opts(Args) -> 2883 case proplists:get_value(event_handler, Args) of 2884 undefined -> 2885 event_handler_args2opts([], Args); 2886 EHs -> 2887 event_handler_args2opts([{list_to_atom(EH),[]} || EH <- EHs], Args) 2888 end. 2889event_handler_args2opts(Default, Args) -> 2890 case proplists:get_value(event_handler_init, Args) of 2891 undefined -> 2892 Default; 2893 EHs -> 2894 event_handler_init_args2opts(EHs) 2895 end. 2896event_handler_init_args2opts([EH, Arg, "and" | EHs]) -> 2897 [{list_to_atom(EH),lists:flatten(io_lib:format("~ts",[Arg]))} | 2898 event_handler_init_args2opts(EHs)]; 2899event_handler_init_args2opts([EH, Arg]) -> 2900 [{list_to_atom(EH),lists:flatten(io_lib:format("~ts",[Arg]))}]; 2901event_handler_init_args2opts([]) -> 2902 []. 2903 2904verbosity_args2opts(Args) -> 2905 case proplists:get_value(verbosity, Args) of 2906 undefined -> 2907 []; 2908 VArgs -> 2909 GetVLvls = 2910 fun("and", {new,SoFar}) when is_list(SoFar) -> 2911 {new,SoFar}; 2912 ("and", {Lvl,SoFar}) when is_list(SoFar) -> 2913 {new,[{'$unspecified',list_to_integer(Lvl)} | SoFar]}; 2914 (CatOrLvl, {new,SoFar}) when is_list(SoFar) -> 2915 {CatOrLvl,SoFar}; 2916 (Lvl, {Cat,SoFar}) -> 2917 {new,[{list_to_atom(Cat),list_to_integer(Lvl)} | SoFar]} 2918 end, 2919 case lists:foldl(GetVLvls, {new,[]}, VArgs) of 2920 {new,Parsed} -> 2921 Parsed; 2922 {Lvl,Parsed} -> 2923 [{'$unspecified',list_to_integer(Lvl)} | Parsed] 2924 end 2925 end. 2926 2927add_verbosity_defaults(VLvls) -> 2928 case {proplists:get_value('$unspecified', VLvls), 2929 proplists:get_value(default, VLvls)} of 2930 {undefined,undefined} -> 2931 ?default_verbosity ++ VLvls; 2932 {Lvl,undefined} -> 2933 [{default,Lvl} | VLvls]; 2934 {undefined,_Lvl} -> 2935 [{'$unspecified',?MAX_VERBOSITY} | VLvls]; 2936 _ -> 2937 VLvls 2938 end. 2939 2940%% This function reads pa and pz arguments, converts dirs from relative 2941%% to absolute, and re-inserts them in the code path. The order of the 2942%% dirs in the code path remain the same. Note however that since this 2943%% function is only used for arguments "pre run_test erl_args", the order 2944%% relative dirs "post run_test erl_args" is not kept! 2945rel_to_abs(CtArgs) -> 2946 {PA,PZ} = get_pa_pz(CtArgs, [], []), 2947 _ = [begin 2948 Dir = rm_trailing_slash(D), 2949 Abs = make_abs(Dir), 2950 _ = if Dir /= Abs -> 2951 _ = code:del_path(Dir), 2952 _ = code:del_path(Abs), 2953 io:format(user, "Converting ~tp to ~tp and re-inserting " 2954 "with add_pathz/1~n", 2955 [Dir, Abs]); 2956 true -> 2957 _ = code:del_path(Dir) 2958 end, 2959 code:add_pathz(Abs) 2960 end || D <- PZ], 2961 _ = [begin 2962 Dir = rm_trailing_slash(D), 2963 Abs = make_abs(Dir), 2964 _ = if Dir /= Abs -> 2965 _ = code:del_path(Dir), 2966 _ = code:del_path(Abs), 2967 io:format(user, "Converting ~tp to ~tp and re-inserting " 2968 "with add_patha/1~n", 2969 [Dir, Abs]); 2970 true -> 2971 _ = code:del_path(Dir) 2972 end, 2973 code:add_patha(Abs) 2974 end || D <- PA], 2975 io:format(user, "~n", []). 2976 2977rm_trailing_slash(Dir) -> 2978 filename:join(filename:split(Dir)). 2979 2980get_pa_pz([{pa,Dirs} | Args], PA, PZ) -> 2981 get_pa_pz(Args, PA ++ Dirs, PZ); 2982get_pa_pz([{pz,Dirs} | Args], PA, PZ) -> 2983 get_pa_pz(Args, PA, PZ ++ Dirs); 2984get_pa_pz([_ | Args], PA, PZ) -> 2985 get_pa_pz(Args, PA, PZ); 2986get_pa_pz([], PA, PZ) -> 2987 {PA,PZ}. 2988 2989make_abs(RelDir) -> 2990 Tokens = filename:split(filename:absname(RelDir)), 2991 filename:join(lists:reverse(make_abs1(Tokens, []))). 2992 2993make_abs1([".."|Dirs], [_Dir|Path]) -> 2994 make_abs1(Dirs, Path); 2995make_abs1(["."|Dirs], Path) -> 2996 make_abs1(Dirs, Path); 2997make_abs1([Dir|Dirs], Path) -> 2998 make_abs1(Dirs, [Dir|Path]); 2999make_abs1([], Path) -> 3000 Path. 3001 3002%% This function translates ct:run_test/1 start options 3003%% to ct_run start arguments (on the init arguments format) - 3004%% this is useful mainly for testing the ct_run start functions. 3005opts2args(EnvStartOpts) -> 3006 lists:flatmap(fun({exit_status,ExitStatusOpt}) when is_atom(ExitStatusOpt) -> 3007 [{exit_status,[atom_to_list(ExitStatusOpt)]}]; 3008 ({halt_with,{HaltM,HaltF}}) -> 3009 [{halt_with,[atom_to_list(HaltM), 3010 atom_to_list(HaltF)]}]; 3011 ({interactive_mode,true}) -> 3012 [{shell,[]}]; 3013 ({config,CfgFile}) when is_integer(hd(CfgFile)) -> 3014 [{ct_config,[CfgFile]}]; 3015 ({config,CfgFiles}) when is_list(hd(CfgFiles)) -> 3016 [{ct_config,CfgFiles}]; 3017 ({userconfig,{CBM,CfgStr=[X|_]}}) when is_integer(X) -> 3018 [{userconfig,[atom_to_list(CBM),CfgStr]}]; 3019 ({userconfig,{CBM,CfgStrs}}) when is_list(CfgStrs) -> 3020 [{userconfig,[atom_to_list(CBM) | CfgStrs]}]; 3021 ({userconfig,UserCfg}) when is_list(UserCfg) -> 3022 Strs = 3023 lists:map(fun({CBM,CfgStr=[X|_]}) 3024 when is_integer(X) -> 3025 [atom_to_list(CBM), 3026 CfgStr,"and"]; 3027 ({CBM,CfgStrs}) 3028 when is_list(CfgStrs) -> 3029 [atom_to_list(CBM) | CfgStrs] ++ 3030 ["and"] 3031 end, UserCfg), 3032 [_LastAnd|StrsR] = lists:reverse(lists:flatten(Strs)), 3033 [{userconfig,lists:reverse(StrsR)}]; 3034 ({group,G}) when is_atom(G) -> 3035 [{group,[atom_to_list(G)]}]; 3036 ({group,Gs}) when is_list(Gs) -> 3037 LOfGStrs = [lists:flatten(io_lib:format("~tw",[G])) || 3038 G <- Gs], 3039 [{group,LOfGStrs}]; 3040 ({testcase,Case}) when is_atom(Case) -> 3041 [{'case',[atom_to_list(Case)]}]; 3042 ({testcase,Cases}) -> 3043 [{'case',[atom_to_list(C) || C <- Cases]}]; 3044 ({'case',Cases}) -> 3045 [{'case',[atom_to_list(C) || C <- Cases]}]; 3046 ({allow_user_terms,true}) -> 3047 [{allow_user_terms,[]}]; 3048 ({allow_user_terms,false}) -> 3049 []; 3050 ({join_specs,true}) -> 3051 [{join_specs,[]}]; 3052 ({join_specs,false}) -> 3053 []; 3054 ({auto_compile,false}) -> 3055 [{no_auto_compile,[]}]; 3056 ({auto_compile,true}) -> 3057 []; 3058 ({scale_timetraps,true}) -> 3059 [{scale_timetraps,[]}]; 3060 ({scale_timetraps,false}) -> 3061 []; 3062 ({create_priv_dir,auto_per_run}) -> 3063 []; 3064 ({create_priv_dir,PD}) when is_atom(PD) -> 3065 [{create_priv_dir,[atom_to_list(PD)]}]; 3066 ({force_stop,skip_rest}) -> 3067 [{force_stop,["skip_rest"]}]; 3068 ({force_stop,true}) -> 3069 [{force_stop,[]}]; 3070 ({force_stop,false}) -> 3071 []; 3072 ({decrypt,{key,Key}}) -> 3073 [{ct_decrypt_key,[Key]}]; 3074 ({decrypt,{file,File}}) -> 3075 [{ct_decrypt_file,[File]}]; 3076 ({basic_html,true}) -> 3077 [{basic_html,[]}]; 3078 ({basic_html,false}) -> 3079 []; 3080 ({esc_chars,false}) -> 3081 [{no_esc_chars,[]}]; 3082 ({esc_chars,true}) -> 3083 []; 3084 ({event_handler,EH}) when is_atom(EH) -> 3085 [{event_handler,[atom_to_list(EH)]}]; 3086 ({event_handler,EHs}) when is_list(EHs) -> 3087 [{event_handler,[atom_to_list(EH) || EH <- EHs]}]; 3088 ({event_handler,{EH,Arg}}) when is_atom(EH) -> 3089 ArgStr = lists:flatten(io_lib:format("~tp", [Arg])), 3090 [{event_handler_init,[atom_to_list(EH),ArgStr]}]; 3091 ({event_handler,{EHs,Arg}}) when is_list(EHs) -> 3092 ArgStr = lists:flatten(io_lib:format("~tp", [Arg])), 3093 Strs = lists:flatmap(fun(EH) -> 3094 [atom_to_list(EH), 3095 ArgStr,"and"] 3096 end, EHs), 3097 [_LastAnd | StrsR] = lists:reverse(Strs), 3098 [{event_handler_init,lists:reverse(StrsR)}]; 3099 ({logopts,LOs}) when is_list(LOs) -> 3100 [{logopts,[atom_to_list(LO) || LO <- LOs]}]; 3101 ({verbosity,?default_verbosity}) -> 3102 []; 3103 ({verbosity,VLvl}) when is_integer(VLvl) -> 3104 [{verbosity,[integer_to_list(VLvl)]}]; 3105 ({verbosity,VLvls}) when is_list(VLvls) -> 3106 VLvlArgs = 3107 lists:flatmap(fun({'$unspecified',Lvl}) -> 3108 [integer_to_list(Lvl), 3109 "and"]; 3110 ({Cat,Lvl}) -> 3111 [atom_to_list(Cat), 3112 integer_to_list(Lvl), 3113 "and"]; 3114 (Lvl) -> 3115 [integer_to_list(Lvl), 3116 "and"] 3117 end, VLvls), 3118 [_LastAnd|VLvlArgsR] = lists:reverse(VLvlArgs), 3119 [{verbosity,lists:reverse(VLvlArgsR)}]; 3120 ({ct_hooks,[]}) -> 3121 []; 3122 ({ct_hooks,CTHs}) when is_list(CTHs) -> 3123 io:format(user,"ct_hooks: ~tp",[CTHs]), 3124 Strs = lists:flatmap( 3125 fun({CTH,Arg,Prio}) -> 3126 [atom_to_list(CTH), 3127 lists:flatten( 3128 io_lib:format("~tp",[Arg])), 3129 lists:flatten( 3130 io_lib:format("~tp",[Prio])), 3131 "and"]; 3132 ({CTH,Arg}) -> 3133 [atom_to_list(CTH), 3134 lists:flatten( 3135 io_lib:format("~tp",[Arg])), 3136 "and"]; 3137 (CTH) when is_atom(CTH) -> 3138 [atom_to_list(CTH),"and"] 3139 end,CTHs), 3140 [_LastAnd|StrsR] = lists:reverse(Strs), 3141 io:format(user,"return: ~tp",[lists:reverse(StrsR)]), 3142 [{ct_hooks,lists:reverse(StrsR)}]; 3143 ({Opt,As=[A|_]}) when is_atom(A) -> 3144 [{Opt,[atom_to_list(Atom) || Atom <- As]}]; 3145 ({Opt,Strs=[S|_]}) when is_list(S) -> 3146 [{Opt,Strs}]; 3147 ({Opt,A}) when is_atom(A) -> 3148 [{Opt,[atom_to_list(A)]}]; 3149 ({Opt,I}) when is_integer(I) -> 3150 [{Opt,[integer_to_list(I)]}]; 3151 ({Opt,I}) when is_float(I) -> 3152 [{Opt,[float_to_list(I)]}]; 3153 ({Opt,S}) when is_list(S) -> 3154 [{Opt,[S]}]; 3155 (Opt) -> 3156 Opt 3157 end, EnvStartOpts). 3158 3159locate_test_dir(Dir, Suite) -> 3160 TestDir = case ct_util:is_test_dir(Dir) of 3161 true -> Dir; 3162 false -> ct_util:get_testdir(Dir, Suite) 3163 end, 3164 case filelib:is_dir(TestDir) of 3165 true -> {ok,TestDir}; 3166 false -> {error,invalid} 3167 end. 3168 3169is_suite(Mod) when is_atom(Mod) -> 3170 is_suite(atom_to_list(Mod)); 3171is_suite(ModOrFile) when is_list(ModOrFile) -> 3172 case lists:reverse(filename:basename(ModOrFile, ".erl")) of 3173 [$E,$T,$I,$U,$S,$_|_] -> 3174 true; 3175 _ -> 3176 case lists:reverse(filename:basename(ModOrFile, ".beam")) of 3177 [$E,$T,$I,$U,$S,$_|_] -> 3178 true; 3179 _ -> 3180 false 3181 end 3182 end. 3183 3184get_all_testcases(Suite) -> 3185 try ct_framework:get_all_cases(Suite) of 3186 {error,_Reason} = Error -> 3187 Error; 3188 SuiteCases -> 3189 Cases = [C || {_S,C} <- SuiteCases], 3190 try Suite:sequences() of 3191 [] -> 3192 Cases; 3193 Seqs -> 3194 TCs1 = lists:flatten([TCs || {_,TCs} <- Seqs]), 3195 lists:reverse( 3196 lists:foldl(fun(TC, Acc) -> 3197 case lists:member(TC, Acc) of 3198 true -> Acc; 3199 false -> [TC | Acc] 3200 end 3201 end, [], Cases ++ TCs1)) 3202 catch 3203 _:_ -> 3204 Cases 3205 end 3206 catch 3207 _:Error -> 3208 {error,Error} 3209 end. 3210 3211%% Internal tracing support. If {ct_trace,TraceSpec} is present, the 3212%% TraceSpec file will be consulted and dbg used to trace function 3213%% calls during test run. Expected terms in TraceSpec: 3214%% {m,Mod} or {f,Mod,Func}. 3215start_trace(Args) -> 3216 case lists:keysearch(ct_trace,1,Args) of 3217 {value,{ct_trace,File}} -> 3218 TraceSpec = delistify(File), 3219 case file:consult(TraceSpec) of 3220 {ok,Terms} -> 3221 case catch do_trace(Terms) of 3222 ok -> 3223 true; 3224 {_,Error} -> 3225 io:format("Warning! Tracing not started. Reason: ~tp~n~n", 3226 [Error]), 3227 false 3228 end; 3229 {_,Error} -> 3230 io:format("Warning! Tracing not started. Reason: ~ts~n~n", 3231 [file:format_error(Error)]), 3232 false 3233 end; 3234 false -> 3235 false 3236 end. 3237 3238do_trace(Terms) -> 3239 dbg:tracer(), 3240 dbg:p(self(), [sos,call]), 3241 lists:foreach(fun({m,M}) -> 3242 case dbg:tpl(M,x) of 3243 {error,What} -> exit({error,{tracing_failed,What}}); 3244 _ -> ok 3245 end; 3246 ({me,M}) -> 3247 case dbg:tp(M,[{'_',[],[{exception_trace}, 3248 {message,{caller}}]}]) of 3249 {error,What} -> exit({error,{tracing_failed,What}}); 3250 _ -> ok 3251 end; 3252 ({f,M,F}) -> 3253 case dbg:tpl(M,F,[{'_',[],[{exception_trace}, 3254 {message,{caller}}]}]) of 3255 {error,What} -> exit({error,{tracing_failed,What}}); 3256 _ -> ok 3257 end; 3258 (Huh) -> 3259 exit({error,{unrecognized_trace_term,Huh}}) 3260 end, Terms), 3261 ok. 3262 3263stop_trace(true) -> 3264 dbg:stop_clear(); 3265stop_trace(false) -> 3266 ok. 3267 3268list_to_number(S) -> 3269 try list_to_integer(S) 3270 catch error:badarg -> list_to_float(S) 3271 end. 3272 3273ensure_atom(Atom) when is_atom(Atom) -> 3274 Atom; 3275ensure_atom(String) when is_list(String), is_integer(hd(String)) -> 3276 list_to_atom(String); 3277ensure_atom(List) when is_list(List) -> 3278 [ensure_atom(Item) || Item <- List]; 3279ensure_atom(Other) -> 3280 Other. 3281