1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2008-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%%% Test support functions 22%%% 23%%% This is a support module for testing the Common Test Framework. 24%%% 25-module(ct_test_support). 26 27-include_lib("common_test/include/ct_event.hrl"). 28-include_lib("common_test/include/ct.hrl"). 29 30-export([init_per_suite/1, init_per_suite/2, end_per_suite/1, 31 init_per_testcase/2, end_per_testcase/2, 32 write_testspec/2, write_testspec/3, 33 run/2, run/3, run/4, run_ct_run_test/2, run_ct_script_start/2, 34 get_opts/1, wait_for_ct_stop/1]). 35 36-export([handle_event/2, start_event_receiver/1, get_events/2, 37 verify_events/3, verify_events/4, reformat/2, log_events/4, 38 join_abs_dirs/2]). 39 40-export([start_slave/3, slave_stop/1]). 41 42-export([ct_test_halt/1, ct_rpc/2]). 43 44-export([random_error/1]). 45 46-export([unique_timestamp/0]). 47 48-export([rm_dir/1]). 49 50-include_lib("kernel/include/file.hrl"). 51 52%%%----------------------------------------------------------------- 53%%% init_per_suite/1 54 55init_per_suite(Config) -> 56 init_per_suite(Config, 50). 57 58init_per_suite(Config, Level) -> 59 ScaleFactor = test_server:timetrap_scale_factor(), 60 case os:type() of 61 {win32, _} -> 62 %% Extend timeout to 1 hour for windows as starting node 63 %% can take a long time there 64 test_server:timetrap( 60*60*1000 * ScaleFactor ); 65 _ -> 66 ok 67 end, 68 case delete_old_logs(os:type(), Config) of 69 {'EXIT',DelLogsReason} -> 70 test_server:format(0, "Failed to delete old log directories: ~tp~n", 71 [DelLogsReason]); 72 _ -> 73 ok 74 end, 75 76 {Mult,Scale} = test_server_ctrl:get_timetrap_parameters(), 77 test_server:format(Level, "Timetrap multiplier: ~w~n", [Mult]), 78 if Scale == true -> 79 test_server:format(Level, "Timetrap scale factor: ~w~n", 80 [ScaleFactor]); 81 true -> 82 ok 83 end, 84 85 start_slave(Config, Level). 86 87start_slave(Config, Level) -> 88 start_slave(ct, Config, Level). 89 90start_slave(NodeName, Config, Level) -> 91 [_,Host] = string:lexemes(atom_to_list(node()), "@"), 92 test_server:format(0, "Trying to start ~s~n", 93 [atom_to_list(NodeName)++"@"++Host]), 94 PR = proplists:get_value(printable_range,Config,io:printable_range()), 95 case slave:start(Host, NodeName, "+pc " ++ atom_to_list(PR)) of 96 {error,Reason} -> 97 ct:fail(Reason); 98 {ok,CTNode} -> 99 test_server:format(0, "Node ~p started~n", [CTNode]), 100 IsCover = test_server:is_cover(), 101 if IsCover -> 102 cover:start(CTNode); 103 true -> 104 ok 105 end, 106 107 DataDir = proplists:get_value(data_dir, Config), 108 PrivDir = proplists:get_value(priv_dir, Config), 109 110 %% PrivDir as well as directory of Test Server suites 111 %% have to be in code path on Common Test node. 112 [_ | Parts] = lists:reverse(filename:split(DataDir)), 113 TSDir = filename:join(lists:reverse(Parts)), 114 AddPathDirs = case proplists:get_value(path_dirs, Config) of 115 undefined -> []; 116 Ds -> Ds 117 end, 118 TestSupDir = filename:dirname(code:which(?MODULE)), 119 PathDirs = [PrivDir,TSDir,TestSupDir | AddPathDirs], 120 [true = rpc:call(CTNode, code, add_patha, [D]) || D <- PathDirs], 121 test_server:format(Level, "Dirs added to code path (on ~w):~n", 122 [CTNode]), 123 [io:format("~ts~n", [D]) || D <- PathDirs], 124 125 case proplists:get_value(start_sasl, Config) of 126 true -> 127 rpc:call(CTNode, application, start, [sasl]), 128 test_server:format(Level, "SASL started on ~w~n", [CTNode]); 129 _ -> 130 ok 131 end, 132 TraceFile = filename:join(DataDir, "ct.trace"), 133 case file:read_file_info(TraceFile) of 134 {ok,_} -> 135 [{trace_level,0}, 136 {ct_opts,[{ct_trace,TraceFile}]}, 137 {ct_node,CTNode} | Config]; 138 _ -> 139 [{trace_level,Level}, 140 {ct_opts,[]}, 141 {ct_node,CTNode} | Config] 142 end 143 end. 144 145%%%----------------------------------------------------------------- 146%%% end_per_suite/1 147 148end_per_suite(Config) -> 149 CTNode = proplists:get_value(ct_node, Config), 150 PrivDir = proplists:get_value(priv_dir, Config), 151 true = rpc:call(CTNode, code, del_path, [filename:join(PrivDir,"")]), 152 slave_stop(CTNode), 153 ok. 154 155%%%----------------------------------------------------------------- 156%%% init_per_testcase/2 157 158init_per_testcase(_TestCase, Config) -> 159 Opts = get_opts(Config), 160 NetDir = proplists:get_value(net_dir, Opts), 161 LogDir = join_abs_dirs(NetDir, proplists:get_value(logdir, Opts)), 162 case lists:keysearch(master, 1, Config) of 163 false-> 164 test_server:format("See Common Test logs here:\n\n" 165 "<a href=\"file://~ts/all_runs.html\">~ts/all_runs.html</a>\n" 166 "<a href=\"file://~ts/index.html\">~ts/index.html</a>", 167 [LogDir,LogDir,LogDir,LogDir]); 168 {value, _}-> 169 test_server:format("See CT Master Test logs here:\n\n" 170 "<a href=\"file://~ts/master_runs.html\">~ts/master_runs.html</a>", 171 [LogDir,LogDir]) 172 end, 173 Config. 174 175%%%----------------------------------------------------------------- 176%%% end_per_testcase/2 177 178end_per_testcase(_TestCase, Config) -> 179 CTNode = proplists:get_value(ct_node, Config), 180 case wait_for_ct_stop(CTNode) of 181 %% Common test was not stopped to we restart node. 182 false -> 183 slave_stop(CTNode), 184 start_slave(Config,proplists:get_value(trace_level,Config)), 185 {fail, "Could not stop common_test"}; 186 true -> 187 ok 188 end. 189 190%%%----------------------------------------------------------------- 191%%% 192write_testspec(TestSpec, Dir, Name) -> 193 write_testspec(TestSpec, filename:join(Dir, Name)). 194 195write_testspec(TestSpec, TSFile) -> 196 {ok,Dev} = file:open(TSFile, [write,{encoding,utf8}]), 197 [io:format(Dev, "~tp.~n", [Entry]) || Entry <- TestSpec], 198 file:close(Dev), 199 io:format("Test specification written to: ~tp~n", [TSFile]), 200 io:format(user, "Test specification written to: ~tp~n", [TSFile]), 201 TSFile. 202 203 204%%%----------------------------------------------------------------- 205%%% 206 207get_opts(Config) -> 208 PrivDir = proplists:get_value(priv_dir, Config), 209 TempDir = case os:getenv("TMP") of 210 false -> 211 case os:getenv("TEMP") of 212 false -> 213 undefined; 214 Tmp -> 215 create_tmp_logdir(Tmp) 216 end; 217 Tmp -> 218 create_tmp_logdir(Tmp) 219 end, 220 LogDir = 221 case os:getenv("CT_USE_TMP_DIR") of 222 false -> PrivDir; 223 _ -> TempDir 224 end, 225 226 %% Copy test variables to app environment on new node 227 CtTestVars = 228 case init:get_argument(ct_test_vars) of 229 {ok,[Vars]} -> 230 [begin {ok,Ts,_} = erl_scan:string(Str++"."), 231 {ok,Expr} = erl_parse:parse_term(Ts), 232 Expr 233 end || Str <- Vars]; 234 _ -> 235 [] 236 end, 237 %% test_server:format("Test variables added to Config: ~p\n\n", 238 %% [CtTestVars]), 239 InitOpts = 240 case proplists:get_value(ct_opts, Config) of 241 undefined -> []; 242 CtOpts -> CtOpts 243 end, 244 [{logdir,LogDir} | InitOpts ++ CtTestVars]. 245 246 247%%%----------------------------------------------------------------- 248%%% 249run(Opts0, Config) when is_list(Opts0) -> 250 Opts = 251 %% read (and override) opts from env variable, the form expected: 252 %% "[{some_key1,SomeVal2}, {some_key2,SomeVal2}]" 253 case os:getenv("CT_TEST_OPTS") of 254 false -> Opts0; 255 "" -> Opts0; 256 Terms -> 257 case erl_scan:string(Terms++".", 0) of 258 {ok,Tokens,_} -> 259 case erl_parse:parse_term(Tokens) of 260 {ok,OROpts} -> 261 Override = 262 fun(O={Key,_}, Os) -> 263 io:format(user, "ADDING START " 264 "OPTION: ~tp~n", [O]), 265 [O | lists:keydelete(Key, 1, Os)] 266 end, 267 lists:foldl(Override, Opts0, OROpts); 268 _ -> 269 Opts0 270 end; 271 _ -> 272 Opts0 273 end 274 end, 275 276 %% use ct interface 277 CtRunTestResult=run_ct_run_test(Opts,Config), 278 %% use run_test interface (simulated) 279 ExitStatus=run_ct_script_start(Opts,Config), 280 281 check_result(CtRunTestResult,ExitStatus,Opts). 282 283run_ct_run_test(Opts,Config) -> 284 CTNode = proplists:get_value(ct_node, Config), 285 Level = proplists:get_value(trace_level, Config), 286 test_server:format(Level, "~n[RUN #1] Calling ct:run_test(~tp) on ~p~n", 287 [Opts, CTNode]), 288 289 T0 = erlang:monotonic_time(), 290 CtRunTestResult = rpc:call(CTNode, ct, run_test, [Opts]), 291 T1 = erlang:monotonic_time(), 292 Elapsed = erlang:convert_time_unit(T1-T0, native, milli_seconds), 293 test_server:format(Level, "~n[RUN #1] Got return value ~tp after ~p ms~n", 294 [CtRunTestResult,Elapsed]), 295 case rpc:call(CTNode, erlang, whereis, [ct_util_server]) of 296 undefined -> 297 ok; 298 _ -> 299 test_server:format(Level, 300 "ct_util_server not stopped on ~p yet, waiting 5 s...~n", 301 [CTNode]), 302 timer:sleep(5000), 303 undefined = rpc:call(CTNode, erlang, whereis, [ct_util_server]) 304 end, 305 CtRunTestResult. 306 307run_ct_script_start(Opts, Config) -> 308 CTNode = proplists:get_value(ct_node, Config), 309 Level = proplists:get_value(trace_level, Config), 310 Opts1 = [{halt_with,{?MODULE,ct_test_halt}} | Opts], 311 test_server:format(Level, "Saving start opts on ~p: ~tp~n", 312 [CTNode, Opts1]), 313 rpc:call(CTNode, application, set_env, 314 [common_test, run_test_start_opts, Opts1]), 315 test_server:format(Level, "[RUN #2] Calling ct_run:script_start() on ~p~n", 316 [CTNode]), 317 T0 = erlang:monotonic_time(), 318 ExitStatus = rpc:call(CTNode, ct_run, script_start, []), 319 T1 = erlang:monotonic_time(), 320 Elapsed = erlang:convert_time_unit(T1-T0, native, milli_seconds), 321 test_server:format(Level, "[RUN #2] Got exit status value ~tp after ~p ms~n", 322 [ExitStatus,Elapsed]), 323 ExitStatus. 324 325check_result({_Ok,Failed,{_UserSkipped,_AutoSkipped}},1,_Opts) 326 when Failed > 0 -> 327 ok; 328check_result({_Ok,0,{_UserSkipped,AutoSkipped}},ExitStatus,Opts) 329 when AutoSkipped > 0 -> 330 case proplists:get_value(exit_status, Opts) of 331 ignore_config when ExitStatus == 1 -> 332 {error,{wrong_exit_status,ExitStatus}}; 333 _ -> 334 ok 335 end; 336check_result({error,_}=Error,2,_Opts) -> 337 Error; 338check_result({error,_},ExitStatus,_Opts) -> 339 {error,{wrong_exit_status,ExitStatus}}; 340check_result({_Ok,0,{_UserSkipped,_AutoSkipped}},0,_Opts) -> 341 ok; 342check_result(CtRunTestResult,ExitStatus,Opts) 343 when is_list(CtRunTestResult) -> % repeated testruns 344 try check_result(sum_testruns(CtRunTestResult,0,0,0,0),ExitStatus,Opts) 345 catch _:_ -> 346 {error,{unexpected_return_value,{CtRunTestResult,ExitStatus}}} 347 end; 348check_result(done,0,_Opts) -> 349 %% refresh_logs return 350 ok; 351check_result(CtRunTestResult,ExitStatus,_Opts) -> 352 {error,{unexpected_return_value,{CtRunTestResult,ExitStatus}}}. 353 354sum_testruns([{O,F,{US,AS}}|T],Ok,Failed,UserSkipped,AutoSkipped) -> 355 sum_testruns(T,Ok+O,Failed+F,UserSkipped+US,AutoSkipped+AS); 356sum_testruns([],Ok,Failed,UserSkipped,AutoSkipped) -> 357 {Ok,Failed,{UserSkipped,AutoSkipped}}. 358 359run(M, F, A, Config) -> 360 run({M,F,A}, [], Config). 361 362run({M,F,A}, InitCalls, Config) -> 363 CTNode = proplists:get_value(ct_node, Config), 364 Level = proplists:get_value(trace_level, Config), 365 lists:foreach( 366 fun({IM,IF,IA}) -> 367 test_server:format(Level, "~nInit call ~w:~tw(~tp) on ~p...~n", 368 [IM, IF, IA, CTNode]), 369 Result = rpc:call(CTNode, IM, IF, IA), 370 test_server:format(Level, "~n...with result: ~tp~n", [Result]) 371 end, InitCalls), 372 test_server:format(Level, "~nStarting test with ~w:~tw(~tp) on ~p~n", 373 [M, F, A, CTNode]), 374 rpc:call(CTNode, M, F, A). 375 376%% this is the last function that ct_run:script_start() calls, so the 377%% return value here is what rpc:call/4 above returns 378ct_test_halt(ExitStatus) -> 379 ExitStatus. 380 381%%%----------------------------------------------------------------- 382%%% wait_for_ct_stop/1 383 384wait_for_ct_stop(CTNode) -> 385 %% Give CT at least 15 sec to stop (in case of bad make). 386 wait_for_ct_stop(5, CTNode). 387 388wait_for_ct_stop(0, CTNode) -> 389 test_server:format(0, "Giving up! Stopping ~p.", [CTNode]), 390 false; 391wait_for_ct_stop(Retries, CTNode) -> 392 case rpc:call(CTNode, erlang, whereis, [ct_util_server]) of 393 undefined -> 394 true; 395 Pid -> 396 Info = (catch process_info(Pid)), 397 test_server:format(0, "Waiting for CT (~p) to finish (~p)...", 398 [Pid,Retries]), 399 test_server:format(0, "Process info for ~p:~n~tp", [Pid,Info]), 400 timer:sleep(5000), 401 wait_for_ct_stop(Retries-1, CTNode) 402 end. 403 404%%%----------------------------------------------------------------- 405%%% ct_rpc/1 406ct_rpc({M,F,A}, Config) -> 407 CTNode = proplists:get_value(ct_node, Config), 408 Level = proplists:get_value(trace_level, Config), 409 test_server:format(Level, "~nCalling ~w:~tw(~tp) on ~p...", 410 [M,F,A, CTNode]), 411 rpc:call(CTNode, M, F, A). 412 413 414%%%----------------------------------------------------------------- 415%%% random_error/1 416random_error(Config) when is_list(Config) -> 417 rand:seed(exsplus), 418 Gen = fun(0,_) -> ok; (N,Fun) -> Fun(N-1, Fun) end, 419 Gen(rand:uniform(100), Gen), 420 421 ErrorTypes = ['BADMATCH','BADARG','CASE_CLAUSE','FUNCTION_CLAUSE', 422 'EXIT','THROW','UNDEF'], 423 Type = lists:nth(rand:uniform(length(ErrorTypes)), ErrorTypes), 424 Where = case rand:uniform(2) of 425 1 -> 426 io:format("ct_test_support *returning* error of type ~w", 427 [Type]), 428 tc; 429 2 -> 430 io:format("ct_test_support *generating* error of type ~w", 431 [Type]), 432 lib 433 end, 434 ErrorFun = 435 fun() -> 436 case Type of 437 'BADMATCH' -> 438 ok = proplists:get_value(undefined, Config); 439 'BADARG' -> 440 size(proplists:get_value(priv_dir, Config)); 441 'FUNCTION_CLAUSE' -> 442 random_error(x); 443 'EXIT' -> 444 spawn_link(fun() -> 445 undef_proc ! hello, 446 ok 447 end); 448 'THROW' -> 449 PrivDir = proplists:get_value(priv_dir, Config), 450 if is_list(PrivDir) -> throw(generated_throw) end; 451 'UNDEF' -> 452 apply(?MODULE, random_error, []) 453 end 454 end, 455 %% either call the fun here or return it to the caller (to be 456 %% executed in a test case instead) 457 case Where of 458 tc -> ErrorFun; 459 lib -> ErrorFun() 460 end. 461 462 463%%%----------------------------------------------------------------- 464%%% EVENT HANDLING 465 466handle_event(EH, Event) -> 467 event_receiver ! {self(),{event,EH,Event}}, 468 receive {event_receiver,ok} -> ok end, 469 ok. 470 471start_event_receiver(Config) -> 472 CTNode = proplists:get_value(ct_node, Config), 473 Level = proplists:get_value(trace_level, Config), 474 ER = spawn_link(CTNode, fun() -> er() end), 475 test_server:format(Level, "~nEvent receiver ~w started!~n", [ER]), 476 ER. 477 478get_events(_, Config) -> 479 CTNode = proplists:get_value(ct_node, Config), 480 Level = proplists:get_value(trace_level, Config), 481 {event_receiver,CTNode} ! {self(),get_events}, 482 Events = receive {event_receiver,Evs} -> Evs end, 483 test_server:format(Level, "Stopping event receiver!~n", []), 484 {event_receiver,CTNode} ! {self(),stop}, 485 receive {event_receiver,stopped} -> ok end, 486 Events. 487 488er() -> 489 register(event_receiver, self()), 490 er_loop([]). 491 492er_loop(Evs) -> 493 receive 494 {From,{event,EH,Ev}} -> 495 From ! {event_receiver,ok}, 496 er_loop([{EH,Ev} | Evs]); 497 {From,get_events} -> 498 From ! {event_receiver,lists:reverse(Evs)}, 499 er_loop(Evs); 500 {From,stop} -> 501 unregister(event_receiver), 502 From ! {event_receiver,stopped}, 503 ok 504 end. 505 506verify_events(TEvs, Evs, Config) -> 507 Node = proplists:get_value(ct_node, Config), 508 case catch verify_events1(TEvs, Evs, Node, Config) of 509 {'EXIT',Reason} -> 510 Reason; 511 _ -> 512 ok 513 end. 514 515verify_events(TEvs, Evs, Node, Config) -> 516 case catch verify_events1(TEvs, Evs, Node, Config) of 517 {'EXIT',Reason} -> 518 Reason; 519 _ -> 520 ok 521 end. 522 523verify_events1([TestEv|_], [{TEH,#event{name=stop_logging,node=Node,data=_}}|_], Node, _) 524 when element(1,TestEv) == TEH, element(2,TestEv) =/= stop_logging -> 525 test_server:format("Failed to find ~tp in the list of events!~n", [TestEv]), 526 exit({event_not_found,TestEv}); 527 528verify_events1(TEvs = [TestEv | TestEvs], Evs = [_|Events], Node, Config) -> 529 case catch locate(TestEv, Node, Evs, Config) of 530 nomatch -> 531 verify_events1(TEvs, Events, Node, Config); 532 {'EXIT',Reason} -> 533 test_server:format("Failed to find ~tp in ~tp~n" 534 "Reason: ~tp~n", [TestEv,Evs,Reason]), 535 exit(Reason); 536 {Config1,Events1} -> 537 if is_list(TestEv) -> 538 ok; 539 element(1,TestEv) == parallel ; element(1,TestEv) == shuffle -> 540 ok; 541 true -> 542 test_server:format("Found ~tp!", [TestEv]) 543 end, 544 verify_events1(TestEvs, Events1, Node, Config1) 545 end; 546 547verify_events1([TestEv|_], [], _, _) -> 548 test_server:format("Failed to find ~tp in the list of events!~n", [TestEv]), 549 exit({event_not_found,TestEv}); 550 551verify_events1([], Evs, _, Config) -> 552 {Config,Evs}. 553 554%%%---------------------------------------------------------------------------- 555%%% locate({TEHandler,TEName,TEData}, TENode, Events, Config) -> {Config1,Evs1} 556%%% 557%%% A group is represented as either: 558%%% {parallel,ListOfCasesAndGroups}, 559%%% {shuffle,ListOfCasesAndGroups}, or 560%%% ListOfCasesAndGroups. 561%%% 562%%% The two first and two last events in a group *may* be tc_start and tc_done 563%%% for init_per_group and end_per_group. 564 565%% group (not parallel or shuffle) 566locate(TEvs, Node, Evs, Config) when is_list(TEvs) -> 567 case TEvs of 568 [InitStart = {TEH,tc_start,{M,{init_per_group,GroupName,Props}}}, 569 InitDone = {TEH,tc_done,{M,{init_per_group,GroupName,Props},R}} | TEvs1] -> 570 case Evs of 571 [{TEH,#event{name=tc_start, 572 node=Node, 573 data={M,{init_per_group,GroupName,Props}}}}, 574 {TEH,#event{name=tc_done, 575 node=Node, 576 data={M,{init_per_group,GroupName,Props},Res}}} | Evs1] -> 577 case result_match(R, Res) of 578 false -> 579 nomatch; 580 true -> 581 test_server:format("Found ~tp!", [InitStart]), 582 test_server:format("Found ~tp!", [InitDone]), 583 verify_events1(TEvs1, Evs1, Node, Config) 584 end; 585 _ -> 586 nomatch 587 end; 588 _ -> 589 verify_events1(TEvs, Evs, Node, Config) 590 end; 591 592%% Parallel events: Each test case in the group should be specified in a list 593%% with the tc_start, followed by the tc_done event. The order of the cases 594%% is irrelevant, but it must be checked that every test case exists and 595%% that tc_done comes after tc_start. 596locate({parallel,TEvs}, Node, Evs, Config) -> 597 Start = 598 case TEvs of 599 [InitStart = {TEH,tc_start,{M,{init_per_group,GroupName,Props}}}, 600 InitDone = {TEH,tc_done,{M,{init_per_group,GroupName,Props},R}} | TEs] -> 601 case Evs of 602 [{TEH,#event{name=tc_start, 603 node=Node, 604 data={M,{init_per_group, 605 GroupName,Props}}}}|Es] -> 606 %% Use dropwhile here as a tc_done from a 607 %% previous testcase might sneak in here 608 EvsG = lists:dropwhile( 609 fun({EH,#event{name=tc_done, 610 node=EvNode, 611 data={EvM,{init_per_group, 612 EvGroupName, 613 EvProps},EvR}}}) 614 when TEH == EH, EvNode == Node, EvM == M, 615 EvGroupName == GroupName, 616 EvProps == Props -> 617 case result_match(R, EvR) of 618 true -> false; 619 false -> true 620 end; 621 ({EH,#event{name=stop_logging, 622 node=EvNode,data=_}}) 623 when EH == TEH, EvNode == Node -> 624 exit({group_init_done_not_found, 625 GroupName,Props}); 626 (_) -> 627 true 628 end, Es), 629 630 test_server:format("Found ~tp!", [InitStart]), 631 test_server:format("Found ~tp!", [InitDone]), 632 {TEs,EvsG}; 633 _ -> 634 nomatch 635 end; 636 _ -> 637 {TEvs,Evs} 638 end, 639 case Start of 640 nomatch -> 641 nomatch; 642 {TEvs1,Evs1} -> 643 {TcDoneEvs,RemainEvs,_} = 644 lists:foldl( 645 %% tc_start event for a parallel test case 646 fun(TEv={TEH,tc_start,{M,F}}, {Done,RemEvs,RemSize}) -> 647 %% drop events until TEv is found 648 Evs2 = lists:dropwhile( 649 fun({EH,#event{name=tc_start, 650 node=EvNode, 651 data={Mod,Func}}}) when 652 EH == TEH, EvNode == Node, 653 Mod == M, Func == F -> 654 false; 655 ({EH,#event{name=stop_logging, 656 node=EvNode,data=_}}) when 657 EH == TEH, EvNode == Node -> 658 exit({tc_start_not_found,TEv}); 659 (_) -> 660 true 661 end, Evs1), 662 %% split the list at the tc_done event and record the smallest 663 %% list of remaining events (Evs) as possible 664 RemEvs1 = 665 lists:dropwhile( 666 fun({EH,#event{name=tc_done, 667 node=EvNode, 668 data={Mod,Func,_}}}) when 669 EH == TEH, EvNode == Node, 670 Mod == M, Func == F -> 671 false; 672 ({EH,#event{name=stop_logging, 673 node=EvNode,data=_}}) when 674 EH == TEH, EvNode == Node -> 675 exit({tc_done_not_found,TEv}); 676 (_) -> 677 true 678 end, Evs2), 679 case RemEvs1 of 680 [] when Evs2 == [] -> 681 exit({unmatched,TEv}); 682 [] -> 683 test_server:format("Found ~tp!", [TEv]), 684 exit({tc_done_not_found,TEv}); 685 [TcDone|Evs3] -> 686 test_server:format("Found ~tp!", [TEv]), 687 RemSize1 = length(Evs3), 688 if RemSize1 < RemSize -> 689 {[TcDone|Done],Evs3,RemSize1}; 690 true -> 691 {[TcDone|Done],RemEvs,RemSize} 692 end 693 end; 694 %% tc_done event for a parallel test case 695 (TEv={TEH,tc_done,{M,F,R}}, {Done,RemEvs,RemSize}) -> 696 case [E || E={EH,#event{name=tc_done, 697 node=EvNode, 698 data={Mod,Func,Result}}} <- Done, 699 EH == TEH, EvNode == Node, Mod == M, 700 Func == F, result_match(R, Result)] of 701 [TcDone|_] -> 702 test_server:format("Found ~tp!", [TEv]), 703 {lists:delete(TcDone, Done),RemEvs,RemSize}; 704 [] -> 705 exit({unmatched,TEv}) 706 end; 707 %% tc_start event for end_per_group 708 (TEv={TEH,tc_start,{M,{end_per_group,GroupName,Props}}}, 709 {Done,RemEvs,_RemSize}) -> 710 RemEvs1 = 711 lists:dropwhile( 712 fun({EH,#event{name=tc_start, 713 node=EvNode, 714 data={Mod,{end_per_group, 715 EvGName,EvProps}}}}) when 716 EH == TEH, EvNode == Node, Mod == M, 717 EvGName == GroupName, EvProps == Props -> 718 false; 719 ({EH,#event{name=stop_logging, 720 node=EvNode,data=_}}) when 721 EH == TEH, EvNode == Node -> 722 exit({tc_start_not_found,TEv}); 723 (_) -> 724 true 725 end, RemEvs), 726 case RemEvs1 of 727 [] -> 728 exit({end_per_group_not_found,TEv}); 729 [_ | RemEvs2] -> 730 test_server:format("Found ~tp!", [TEv]), 731 {Done,RemEvs2,length(RemEvs2)} 732 end; 733 %% tc_done event for end_per_group 734 (TEv={TEH,tc_done,{M,{end_per_group,GroupName,Props},R}}, 735 {Done,RemEvs,_RemSize}) -> 736 RemEvs1 = 737 lists:dropwhile( 738 fun({EH,#event{name=tc_done, 739 node=EvNode, 740 data={Mod,{end_per_group, 741 EvGName,EvProps},Res}}}) when 742 EH == TEH, EvNode == Node, Mod == M, 743 EvGName == GroupName, EvProps == Props -> 744 case result_match(R, Res) of 745 true -> 746 false; 747 false -> 748 true 749 end; 750 ({EH,#event{name=stop_logging, 751 node=EvNode,data=_}}) when 752 EH == TEH, EvNode == Node -> 753 exit({tc_done_not_found,TEv}); 754 (_) -> 755 true 756 end, RemEvs), 757 case RemEvs1 of 758 [] -> 759 exit({end_per_group_not_found,TEv}); 760 [_ | RemEvs2] -> 761 test_server:format("Found ~tp!", [TEv]), 762 {Done,RemEvs2,length(RemEvs2)} 763 end; 764 %% end_per_group auto- or user skipped 765 (TEv={TEH,AutoOrUserSkip,{M,{end_per_group,G},R}}, {Done,RemEvs,_RemSize}) 766 when AutoOrUserSkip == tc_auto_skip; 767 AutoOrUserSkip == tc_user_skip -> 768 RemEvs1 = 769 lists:dropwhile( 770 fun({EH,#event{name=tc_auto_skip, 771 node=EvNode, 772 data={Mod,{end_per_group,EvGroupName},Reason}}}) when 773 EH == TEH, EvNode == Node, Mod == M, EvGroupName == G -> 774 case match_data(R, Reason) of 775 match -> false; 776 _ -> true 777 end; 778 ({EH,#event{name=tc_user_skip, 779 node=EvNode, 780 data={Mod,{end_per_group,EvGroupName},Reason}}}) when 781 EH == TEH, EvNode == Node, Mod == M, EvGroupName == G -> 782 case match_data(R, Reason) of 783 match -> false; 784 _ -> true 785 end; 786 ({EH,#event{name=stop_logging, 787 node=EvNode,data=_}}) when 788 EH == TEH, EvNode == Node -> 789 exit({tc_auto_or_user_skip_not_found,TEv}); 790 (_) -> 791 true 792 end, RemEvs), 793 case RemEvs1 of 794 [] -> 795 exit({end_per_group_not_found,TEv}); 796 [_AutoSkip | RemEvs2] -> 797 {Done,RemEvs2,length(RemEvs2)} 798 end; 799 (TEv={TEH,N,D}, Acc) -> 800 case [E || E={EH,#event{name=Name, 801 node=EvNode, 802 data=Data}} <- Evs1, 803 EH == TEH, EvNode == Node, Name == N, 804 match == match_data(D,Data)] of 805 [] -> 806 exit({unmatched,TEv}); 807 _ -> 808 test_server:format("Found ~tp!", [TEv]), 809 Acc 810 end; 811 %% start of a sub-group 812 (SubGroupTEvs, Acc) when is_list(SubGroupTEvs) -> 813 verify_events1(SubGroupTEvs, Evs1, Node, Config), 814 Acc; 815 (TEv={Prop,_SubGroupTEvs}, Acc) when 816 Prop == shuffle ; Prop == parallel -> 817 verify_events1([TEv], Evs1, Node, Config), 818 Acc 819 end, {[],Evs1,length(Evs1)}, TEvs1), 820 case TcDoneEvs of 821 [] -> 822 test_server:format("Found all parallel events!", []), 823 {Config,RemainEvs}; 824 _ -> 825 exit({unexpected_events,TcDoneEvs}) 826 end 827 end; 828 829%% Shuffled events: Each test case in the group should be specified in a list 830%% with the tc_start, followed by the tc_done event. The order of the cases 831%% is irrelevant, but it must be checked that every test case exists and 832%% that the tc_done event follows the tc_start. 833locate({shuffle,TEvs}, Node, Evs, Config) -> 834 Start = 835 case TEvs of 836 [InitStart = {TEH,tc_start,{M,{init_per_group,GroupName,Props}}}, 837 InitDone = {TEH,tc_done,{M,{init_per_group,GroupName,Props},R}} | TEs] -> 838 case Evs of 839 [{TEH,#event{name=tc_start, 840 node=Node, 841 data={M,{init_per_group,GroupName,EvProps}}}}, 842 {TEH,#event{name=tc_done, 843 node=Node, 844 data={M,{init_per_group,GroupName,EvProps},Res}}} | Es] -> 845 case result_match(R, Res) of 846 true -> 847 case proplists:get_value(shuffle, Props) of 848 '_' -> 849 case proplists:get_value(shuffle, EvProps) of 850 false -> 851 exit({no_shuffle_prop_found, 852 {M,init_per_group, 853 GroupName,EvProps}}); 854 _ -> 855 PropsCmp = proplists:delete(shuffle, EvProps), 856 PropsCmp = proplists:delete(shuffle, Props) 857 end; 858 _ -> 859 Props = EvProps 860 end, 861 test_server:format("Found ~tp!", [InitStart]), 862 test_server:format("Found ~tp!", [InitDone]), 863 {TEs,Es}; 864 false -> 865 nomatch 866 end; 867 _ -> 868 nomatch 869 end; 870 _ -> 871 {TEvs,Evs} 872 end, 873 case Start of 874 nomatch -> 875 nomatch; 876 {TEvs1,Evs1} -> 877 {TcDoneEvs,RemainEvs,_} = 878 lists:foldl( 879 %% tc_start event for a test case 880 fun(TEv={TEH,tc_start,{M,F}}, {Done,RemEvs,RemSize}) -> 881 %% drop events until TEv is found 882 Evs2 = lists:dropwhile( 883 fun({EH,#event{name=tc_start, 884 node=EvNode, 885 data={Mod,Func}}}) when 886 EH == TEH, EvNode == Node, 887 Mod == M, Func == F -> 888 false; 889 ({EH,#event{name=stop_logging, 890 node=EvNode,data=_}}) when 891 EH == TEH, EvNode == Node -> 892 exit({tc_start_not_found,TEv}); 893 (_) -> 894 true 895 end, Evs1), 896 %% verify the tc_done event comes next in Evs 897 case Evs2 of 898 [] -> 899 exit({unmatched,TEv}); 900 [_TcStart, TcDone={TEH,#event{name=tc_done, 901 node=Node, 902 data={M,F,_}}} | Evs3] -> 903 test_server:format("Found ~tp!", [TEv]), 904 RemSize1 = length(Evs3), 905 if RemSize1 < RemSize -> 906 {[TcDone|Done],Evs3,RemSize1}; 907 true -> 908 {[TcDone|Done],RemEvs,RemSize} 909 end 910 end; 911 %% tc_done event for a test case 912 (TEv={TEH,tc_done,{M,F,R}}, {Done,RemEvs,RemSize}) -> 913 case [E || E={EH,#event{name=tc_done, 914 node=EvNode, 915 data={Mod,Func,Result}}} <- Done, 916 EH == TEH, EvNode == Node, Mod == M, 917 Func == F, result_match(R, Result)] of 918 [TcDone|_] -> 919 test_server:format("Found ~tp!", [TEv]), 920 {lists:delete(TcDone, Done),RemEvs,RemSize}; 921 [] -> 922 exit({unmatched,TEv}) 923 end; 924 %% tc_start event for end_per_group 925 (TEv={TEH,tc_start,{M,{end_per_group,GroupName,Props}}}, 926 {Done,RemEvs,_RemSize}) -> 927 RemEvs1 = 928 lists:dropwhile( 929 fun({EH,#event{name=tc_start, 930 node=EvNode, 931 data={Mod,{end_per_group, 932 EvGName,_}}}}) when 933 EH == TEH, EvNode == Node, Mod == M, 934 EvGName == GroupName -> 935 false; 936 ({EH,#event{name=stop_logging, 937 node=EvNode,data=_}}) when 938 EH == TEH, EvNode == Node -> 939 exit({tc_start_not_found,TEv}); 940 (_) -> 941 true 942 end, RemEvs), 943 case RemEvs1 of 944 [] -> 945 exit({end_per_group_not_found,TEv}); 946 [{_,#event{data={_,{_,_,EvProps1}}}} | RemEvs2] -> 947 case proplists:get_value(shuffle, Props) of 948 '_' -> 949 case proplists:get_value(shuffle, EvProps1) of 950 false -> 951 exit({no_shuffle_prop_found, 952 {M,end_per_group,GroupName,EvProps1}}); 953 _ -> 954 PropsCmp1 = proplists:delete(shuffle, EvProps1), 955 PropsCmp1 = proplists:delete(shuffle, Props) 956 end; 957 _ -> 958 Props = EvProps1 959 end, 960 test_server:format("Found ~tp!", [TEv]), 961 {Done,RemEvs2,length(RemEvs2)} 962 end; 963 %% tc_done event for end_per_group 964 (TEv={TEH,tc_done,{M,{end_per_group,GroupName,Props},R}}, 965 {Done,RemEvs,_RemSize}) -> 966 RemEvs1 = 967 lists:dropwhile( 968 fun({EH,#event{name=tc_done, 969 node=EvNode, 970 data={Mod,{end_per_group, 971 EvGName,_},Res}}}) when 972 EH == TEH, EvNode == Node, Mod == M, 973 EvGName == GroupName -> 974 case result_match(R, Res) of 975 true -> 976 false; 977 false -> 978 true 979 end; 980 ({EH,#event{name=stop_logging, 981 node=EvNode,data=_}}) when 982 EH == TEH, EvNode == Node -> 983 exit({tc_done_not_found,TEv}); 984 (_) -> 985 true 986 end, RemEvs), 987 case RemEvs1 of 988 [] -> 989 exit({end_per_group_not_found,TEv}); 990 [{_,#event{data={_,{_,_,EvProps1},_}}} | RemEvs2] -> 991 case proplists:get_value(shuffle, Props) of 992 '_' -> 993 case proplists:get_value(shuffle, EvProps1) of 994 false -> 995 exit({no_shuffle_prop_found, 996 {M,end_per_group,GroupName,EvProps1}}); 997 _ -> 998 PropsCmp1 = proplists:delete(shuffle, EvProps1), 999 PropsCmp1 = proplists:delete(shuffle, Props) 1000 end; 1001 _ -> 1002 Props = EvProps1 1003 end, 1004 test_server:format("Found ~tp!", [TEv]), 1005 {Done,RemEvs2,length(RemEvs2)} 1006 end; 1007 %% end_per_group auto-or user skipped 1008 (TEv={TEH,AutoOrUserSkip,{M,{end_per_group,G},R}}, {Done,RemEvs,_RemSize}) 1009 when AutoOrUserSkip == tc_auto_skip; 1010 AutoOrUserSkip == tc_user_skip -> 1011 RemEvs1 = 1012 lists:dropwhile( 1013 fun({EH,#event{name=tc_auto_skip, 1014 node=EvNode, 1015 data={Mod,{end_per_group,EvGroupName},Reason}}}) when 1016 EH == TEH, EvNode == Node, Mod == M, EvGroupName == G, Reason == R -> 1017 false; 1018 ({EH,#event{name=tc_user_skip, 1019 node=EvNode, 1020 data={Mod,{end_per_group,EvGroupName},Reason}}}) when 1021 EH == TEH, EvNode == Node, Mod == M, EvGroupName == G, Reason == R -> 1022 false; 1023 ({EH,#event{name=stop_logging, 1024 node=EvNode,data=_}}) when 1025 EH == TEH, EvNode == Node -> 1026 exit({tc_auto_skip_not_found,TEv}); 1027 (_) -> 1028 true 1029 end, RemEvs), 1030 case RemEvs1 of 1031 [] -> 1032 exit({end_per_group_not_found,TEv}); 1033 [_AutoSkip | RemEvs2] -> 1034 {Done,RemEvs2,length(RemEvs2)} 1035 end; 1036 %% match other event than test case 1037 (TEv={TEH,N,D}, Acc) when D == '_' -> 1038 case [E || E={EH,#event{name=Name, 1039 node=EvNode, 1040 data=_}} <- Evs1, 1041 EH == TEH, EvNode == Node, Name == N] of 1042 [] -> 1043 exit({unmatched,TEv}); 1044 _ -> 1045 test_server:format("Found ~tp!", [TEv]), 1046 Acc 1047 end; 1048 (TEv={TEH,N,D}, Acc) -> 1049 case [E || E={EH,#event{name=Name, 1050 node=EvNode, 1051 data=Data}} <- Evs1, 1052 EH == TEH, EvNode == Node, Name == N, Data == D] of 1053 [] -> 1054 exit({unmatched,TEv}); 1055 _ -> 1056 test_server:format("Found ~tp!", [TEv]), 1057 Acc 1058 end; 1059 %% start of a sub-group 1060 (SubGroupTEvs, Acc) when is_list(SubGroupTEvs) -> 1061 verify_events1(SubGroupTEvs, Evs1, Node, Config), 1062 Acc; 1063 (TEv={Prop,_SubGroupTEvs}, Acc) when 1064 Prop == shuffle ; Prop == parallel -> 1065 verify_events1([TEv], Evs1, Node, Config), 1066 Acc 1067 end, {[],Evs1,length(Evs1)}, TEvs1), 1068 case TcDoneEvs of 1069 [] -> 1070 test_server:format("Found all shuffled events!", []), 1071 {Config,RemainEvs}; 1072 _ -> 1073 exit({unexpected_events,TcDoneEvs}) 1074 end 1075 end; 1076 1077locate({TEH,Name,{'DEF','RUNDIR'}}, Node, [Ev|Evs], Config) -> 1078 case Ev of 1079 {TEH,#event{name=Name, node=Node, data=EvData}} -> 1080 {_,{_,LogDir}} = lists:keysearch(logdir, 1, get_opts(Config)), 1081 D = filename:join(LogDir, "ct_run." ++ atom_to_list(Node)), 1082 case string:find(EvData, D) of 1083 nomatch -> exit({badmatch,EvData}); 1084 _ -> ok 1085 end, 1086 {Config,Evs}; 1087 _ -> 1088 nomatch 1089 end; 1090 1091locate({TEH,Name,{'DEF',{'START_TIME','LOGDIR'}}}, Node, [Ev|Evs], Config) -> 1092 case Ev of 1093 {TEH,#event{name=Name, node=Node, data=EvData}} -> 1094 case EvData of 1095 {DT={{_,_,_},{_,_,_}},Dir} when is_list(Dir) -> 1096 {_,{_,LogDir}} = lists:keysearch(logdir, 1, get_opts(Config)), 1097 D = filename:join(LogDir, "ct_run." ++ atom_to_list(Node)), 1098 case string:find(Dir, D) of 1099 nomatch -> exit({badmatch,Dir}); 1100 _ -> ok 1101 end, 1102 {[{start_time,DT}|Config],Evs}; 1103 Data -> 1104 exit({badmatch,Data}) 1105 end; 1106 _ -> 1107 nomatch 1108 end; 1109 1110locate({TEH,Name,{'DEF','STOP_TIME'}}, Node, [Ev|Evs], Config) -> 1111 case Ev of 1112 {TEH,#event{name=Name, node=Node, data=EvData}} -> 1113 case EvData of 1114 DT={{_,_,_},{_,_,_}} -> 1115 {[{stop_time,DT}|Config],Evs}; 1116 Data -> 1117 exit({badmatch,Data}) 1118 end; 1119 _ -> 1120 nomatch 1121 end; 1122 1123%% to match variable data as a result of an aborted test case 1124locate({TEH,tc_done,{undefined,undefined,{testcase_aborted, 1125 {abort_current_testcase,Func},'_'}}}, 1126 Node, [Ev|Evs], Config) -> 1127 case Ev of 1128 {TEH,#event{name=tc_done, node=Node, 1129 data={undefined,undefined, 1130 {testcase_aborted,{abort_current_testcase,Func},_}}}} -> 1131 {Config,Evs}; 1132 _ -> 1133 nomatch 1134 end; 1135 1136%% to match variable data as a result of a failed test case 1137locate({TEH,tc_done,{Mod,Func,R={SkipOrFail,{_ErrInd,ErrInfo}}}}, 1138 Node, [Ev|Evs], Config) when ((SkipOrFail == skipped) or 1139 (SkipOrFail == failed)) and 1140 ((size(ErrInfo) == 2) or 1141 (size(ErrInfo) == 3)) -> 1142 case Ev of 1143 {TEH,#event{name=tc_done, node=Node, 1144 data={Mod,Func,Result}}} -> 1145 case result_match(R, Result) of 1146 true -> 1147 {Config,Evs}; 1148 false -> 1149 nomatch 1150 end; 1151 _ -> 1152 nomatch 1153 end; 1154 1155%% Negative matching: Given two events, the first should not be present before 1156%% the other is matched. 1157locate({negative,NotMatch, Match} = Neg, Node, Evs, Config) -> 1158 case locate(NotMatch, Node, Evs, Config) of 1159 nomatch -> 1160 locate(Match, Node, Evs, Config); 1161 _ -> 1162 exit({found_negative_event,Neg}) 1163 end; 1164 1165%% matches any event of type Name 1166locate({TEH,Name,Data}, Node, [{TEH,#event{name=Name, 1167 data = EvData, 1168 node = Node}}|Evs], 1169 Config) -> 1170 case match_data(Data, EvData) of 1171 match -> 1172 {Config,Evs}; 1173 _ -> 1174 nomatch 1175 end; 1176 1177locate({_TEH,_Name,_Data}, _Node, [_|_Evs], _Config) -> 1178 nomatch. 1179 1180match_data(Data, EvData) -> 1181 try do_match_data(Data, EvData) 1182 catch _:_ -> 1183 nomatch 1184 end. 1185 1186do_match_data(D,D) -> 1187 match; 1188do_match_data('_',_) -> 1189 match; 1190do_match_data(Fun,Data) when is_function(Fun) -> 1191 Fun(Data); 1192do_match_data('$proplist',Proplist) -> 1193 do_match_data( 1194 fun(List) -> 1195 lists:foreach(fun({_,_}) -> ok end,List) 1196 end,Proplist); 1197do_match_data([H1|MatchT],[H2|ValT]) -> 1198 do_match_data(H1,H2), 1199 do_match_data(MatchT,ValT); 1200do_match_data(Tuple1,Tuple2) when is_tuple(Tuple1),is_tuple(Tuple2) -> 1201 do_match_data(tuple_to_list(Tuple1),tuple_to_list(Tuple2)); 1202do_match_data([],[]) -> 1203 match. 1204 1205result_match({SkipOrFail,{ErrorInd,{Why,'_'}}}, 1206 {SkipOrFail,{ErrorInd,{Why,_Stack}}}) -> 1207 true; 1208result_match({SkipOrFail,{ErrorInd,{EMod,EFunc,{Why,'_'}}}}, 1209 {SkipOrFail,{ErrorInd,{EMod,EFunc,{Why,_Stack}}}}) -> 1210 true; 1211result_match({failed,{timetrap_timeout,{'$approx',Num}}}, 1212 {failed,{timetrap_timeout,Value}}) -> 1213 if Value >= trunc(Num-0.05*Num), 1214 Value =< trunc(Num+0.05*Num) -> true; 1215 true -> false 1216 end; 1217result_match({user_timetrap_error,{Why,'_'}}, 1218 {user_timetrap_error,{Why,_Stack}}) -> 1219 true; 1220result_match(Result, Result) -> 1221 true; 1222result_match(_, _) -> 1223 false. 1224 1225log_events(TC, Events, EvLogDir, Opts) -> 1226 LogFile = filename:join(EvLogDir, atom_to_list(TC)++".events"), 1227 {ok,Dev} = file:open(LogFile, [write,{encoding,utf8}]), 1228 io:format(Dev, "[~n", []), 1229 log_events1(Events, Dev, " "), 1230 file:close(Dev), 1231 FullLogFile = join_abs_dirs(proplists:get_value(net_dir, Opts), 1232 LogFile), 1233 ct:log("Events written to logfile: <a href=\"file://~ts\">~ts</a>~n", 1234 [FullLogFile,FullLogFile],[no_css]), 1235 io:format(user, "Events written to logfile: ~tp~n", [LogFile]). 1236 1237log_events1(Evs, Dev, "") -> 1238 log_events1(Evs, Dev, " "); 1239log_events1([E={_EH,tc_start,{_M,{init_per_group,_GrName,Props}}} | Evs], Dev, Ind) -> 1240 case get_prop(Props) of 1241 undefined -> 1242 io:format(Dev, "~s[~tp,~n", [Ind,E]), 1243 log_events1(Evs, Dev, Ind++" "); 1244 Prop -> 1245 io:format(Dev, "~s{~w,~n~s[~tp,~n", [Ind,Prop,Ind++" ",E]), 1246 log_events1(Evs, Dev, Ind++" ") 1247 end; 1248log_events1([E={_EH,tc_done,{_M,{init_per_group,_GrName,_Props},_R}} | Evs], Dev, Ind) -> 1249 io:format(Dev, "~s~tp,~n", [Ind,E]), 1250 log_events1(Evs, Dev, Ind++" "); 1251log_events1([E={_EH,tc_start,{_M,{end_per_group,_GrName,_Props}}} | Evs], Dev, Ind) -> 1252 Ind1 = Ind -- " ", 1253 io:format(Dev, "~s~tp,~n", [Ind1,E]), 1254 log_events1(Evs, Dev, Ind1); 1255log_events1([E={_EH,tc_done,{_M,{end_per_group,_GrName,Props},_R}} | Evs], Dev, Ind) -> 1256 case get_prop(Props) of 1257 undefined -> 1258 io:format(Dev, "~s~tp],~n", [Ind,E]), 1259 log_events1(Evs, Dev, Ind--" "); 1260 _Prop -> 1261 io:format(Dev, "~s~tp]},~n", [Ind,E]), 1262 log_events1(Evs, Dev, Ind--" ") 1263 end; 1264log_events1([E={_EH,tc_auto_skip,{_M,{end_per_group,_GrName},_Reason}} | Evs], Dev, Ind) -> 1265 io:format(Dev, "~s~tp],~n", [Ind,E]), 1266 log_events1(Evs, Dev, Ind--" "); 1267log_events1([E={_EH,tc_user_skip,{_M,{end_per_group,_GrName},_Reason}} | Evs], Dev, Ind) -> 1268 io:format(Dev, "~s~tp],~n", [Ind,E]), 1269 log_events1(Evs, Dev, Ind--" "); 1270log_events1([E], Dev, Ind) -> 1271 io:format(Dev, "~s~tp~n].~n", [Ind,E]), 1272 ok; 1273log_events1([E | Evs], Dev, Ind) -> 1274 io:format(Dev, "~s~tp,~n", [Ind,E]), 1275 log_events1(Evs, Dev, Ind); 1276log_events1([], _Dev, _Ind) -> 1277 ok. 1278 1279get_prop(Props) -> 1280 case lists:member(parallel, Props) of 1281 true -> parallel; 1282 false -> case lists:member(shuffle, Props) of 1283 true -> shuffle; 1284 false -> case lists:keysearch(shuffle, 1, Props) of 1285 {value,_} -> shuffle; 1286 _ -> undefined 1287 end 1288 end 1289 end. 1290 1291reformat([{_EH,#event{name=start_write_file,data=_}} | Events], EH) -> 1292 reformat(Events, EH); 1293reformat([{_EH,#event{name=finished_write_file,data=_}} | Events], EH) -> 1294 reformat(Events, EH); 1295reformat([{_EH,#event{name=start_make,data=_}} | Events], EH) -> 1296 reformat(Events, EH); 1297reformat([{_EH,#event{name=finished_make,data=_}} | Events], EH) -> 1298 reformat(Events, EH); 1299reformat([{_EH,#event{name=start_logging,data=_}} | Events], EH) -> 1300 [{EH,start_logging,{'DEF','RUNDIR'}} | reformat(Events, EH)]; 1301reformat([{_EH,#event{name=test_start,data=_}} | Events], EH) -> 1302 [{EH,test_start,{'DEF',{'START_TIME','LOGDIR'}}} | reformat(Events, EH)]; 1303reformat([{_EH,#event{name=test_done,data=_}} | Events], EH) -> 1304 [{EH,test_done,{'DEF','STOP_TIME'}} | reformat(Events, EH)]; 1305reformat([{_EH,#event{name=tc_logfile,data=_}} | Events], EH) -> 1306 reformat(Events, EH); 1307reformat([{_EH,#event{name=test_stats,data=Data}} | Events], EH) -> 1308 [{EH,test_stats,Data} | reformat(Events, EH)]; 1309%% use this to only print the last test_stats event: 1310%% case [N || {_,#event{name=N}} <- Events, N == test_stats] of 1311%% [] -> % last stats event 1312%% [{EH,test_stats,Data} | reformat(Events, EH)]; 1313%% _ -> 1314%% reformat(Events, EH) 1315%% end; 1316reformat([{_EH,#event{name=Name,data=Data}} | Events], EH) -> 1317 [{EH,Name,Data} | reformat(Events, EH)]; 1318reformat([], _EH) -> 1319 []. 1320 1321 1322%%%----------------------------------------------------------------- 1323%%% MISC HELP FUNCTIONS 1324 1325join_abs_dirs(undefined, Dir2) -> 1326 Dir2; 1327join_abs_dirs(Dir1, Dir2) -> 1328 case filename:pathtype(Dir2) of 1329 relative -> 1330 filename:join(Dir1, Dir2); 1331 _ -> 1332 [_Abs|Parts] = filename:split(Dir2), 1333 filename:join(Dir1, filename:join(Parts)) 1334 end. 1335 1336create_tmp_logdir(Tmp) -> 1337 LogDir = filename:join(Tmp,"ct"), 1338 file:make_dir(LogDir), 1339 LogDir. 1340 1341delete_old_logs({win32,_}, Config) -> 1342 case {proplists:get_value(priv_dir, Config), 1343 proplists:get_value(logdir, get_opts(Config))} of 1344 {LogDir,LogDir} -> 1345 ignore; 1346 {_,LogDir} -> % using tmp for logs 1347 catch delete_dirs(LogDir) 1348 end; 1349 1350delete_old_logs(_, Config) -> 1351 case os:getenv("CT_USE_TMP_DIR") of 1352 false -> 1353 ignore; 1354 _ -> 1355 catch delete_dirs(proplists:get_value(logdir, 1356 get_opts(Config))) 1357 end. 1358 1359delete_dirs(LogDir) -> 1360 Now = calendar:datetime_to_gregorian_seconds(calendar:local_time()), 1361 SaveTime = list_to_integer(os:getenv("CT_SAVE_OLD_LOGS", "28800")), 1362 Deadline = Now - SaveTime, 1363 Dirs = filelib:wildcard(filename:join(LogDir,"ct_run*")), 1364 Dirs2Del = 1365 lists:foldl(fun(Dir, Del) -> 1366 [S,Mi,H,D,Mo,Y|_] = 1367 lists:reverse(string:lexemes(Dir, [$.,$-,$_])), 1368 S2I = fun(Str) -> list_to_integer(Str) end, 1369 DT = {{S2I(Y),S2I(Mo),S2I(D)}, {S2I(H),S2I(Mi),S2I(S)}}, 1370 Then = calendar:datetime_to_gregorian_seconds(DT), 1371 if Then > Deadline -> 1372 Del; 1373 true -> 1374 [Dir | Del] 1375 end 1376 end, [], Dirs), 1377 case length(Dirs2Del) of 1378 0 -> 1379 test_server:format(0, "No log directories older than ~w secs.", [SaveTime]); 1380 N -> 1381 test_server:format(0, "Deleting ~w directories older than ~w secs.", [N,SaveTime]) 1382 end, 1383 delete_dirs(LogDir, Dirs2Del). 1384 1385delete_dirs(_, []) -> 1386 ok; 1387delete_dirs(LogDir, [Dir | Dirs]) -> 1388 test_server:format(0, "Removing old log directory: ~ts", [Dir]), 1389 case catch rm_rec(Dir) of 1390 {_,Reason} -> 1391 test_server:format(0, "Delete failed! (~tp)", [Reason]); 1392 ok -> 1393 ok 1394 end, 1395 delete_dirs(LogDir, Dirs). 1396 1397rm_rec(Dir) -> 1398 %% ensure we're removing the ct_run directory 1399 case lists:reverse(filename:split(Dir)) of 1400 [[$c,$t,$_,$r,$u,$n,$.|_]|_] -> 1401 rm_dir(filename:absname(Dir)); 1402 _ -> 1403 {error,{invalid_logdir,Dir}} 1404 end. 1405 1406rm_dir(Dir) -> 1407 case file:list_dir(Dir) of 1408 {error,Errno} -> 1409 exit({ls_failed,Dir,Errno}); 1410 {ok,Files} -> 1411 rm_files([filename:join(Dir, F) || F <- Files]), 1412 file:del_dir(Dir) 1413 end. 1414 1415rm_files([F | Fs]) -> 1416 Base = filename:basename(F), 1417 if Base == "." ; Base == ".." -> 1418 rm_files(Fs); 1419 true -> 1420 case file:read_file_info(F) of 1421 {ok,#file_info{type=directory}} -> 1422 rm_dir(F), 1423 rm_files(Fs); 1424 {ok,_Regular} -> 1425 case file:delete(F) of 1426 ok -> 1427 rm_files(Fs); 1428 {error,Errno} -> 1429 exit({del_failed,F,Errno}) 1430 end 1431 end 1432 end; 1433rm_files([]) -> 1434 ok. 1435 1436unique_timestamp() -> 1437 unique_timestamp(os:timestamp(), 100000). 1438 1439unique_timestamp(TS, 0) -> 1440 TS; 1441unique_timestamp(TS0, N) -> 1442 case os:timestamp() of 1443 TS0 -> 1444 timer:sleep(1), 1445 unique_timestamp(TS0, N-1); 1446 TS1 -> 1447 TS1 1448 end. 1449 1450%%%----------------------------------------------------------------- 1451%%% 1452slave_stop(Node) -> 1453 Cover = test_server:is_cover(), 1454 if Cover-> cover:flush(Node); 1455 true -> ok 1456 end, 1457 erlang:monitor_node(Node, true), 1458 slave:stop(Node), 1459 receive 1460 {nodedown, Node} -> 1461 if Cover -> cover:stop(Node); 1462 true -> ok 1463 end 1464 after 5000 -> 1465 erlang:monitor_node(Node, false), 1466 receive {nodedown, Node} -> ok after 0 -> ok end %flush 1467 end, 1468 ok. 1469