diff options
Diffstat (limited to 'lib/test_server')
21 files changed, 926 insertions, 1582 deletions
diff --git a/lib/test_server/doc/src/ts.xml b/lib/test_server/doc/src/ts.xml index 7a356755ba..4a2c536e96 100644 --- a/lib/test_server/doc/src/ts.xml +++ b/lib/test_server/doc/src/ts.xml @@ -498,29 +498,6 @@ This option is mandatory for remote targets </desc> </func> <func> - <name>index() -> ok | {error, Reason}</name> - <fsummary>Updates local index page</fsummary> - <type> - <v>Reason = term()</v> - </type> - <desc> - <p>This function updates the local index page. This can be - useful if a previous test run was not completed and the index - is incomplete.</p> - </desc> - </func> - <func> - <name>clean() -> ok</name> - <name>clean(all) -> ok</name> - <fsummary>Cleans up the log directories created when running tests. </fsummary> - <desc> - <p>This function cleans up log directories created when - running test cases. <c>clean/0</c> cleans up all but the last - run of each application. <c>clean/1</c> cleans up all test - runs found.</p> - </desc> - </func> - <func> <name>estone() -> ok | {error, Reason}</name> <name>estone(Opts) -> ok</name> <fsummary>Runs the EStone test</fsummary> diff --git a/lib/test_server/src/Makefile b/lib/test_server/src/Makefile index ada9bac05a..20e7a5942c 100644 --- a/lib/test_server/src/Makefile +++ b/lib/test_server/src/Makefile @@ -40,6 +40,8 @@ RELSYSDIR = $(RELEASE_PATH)/lib/test_server-$(VSN) # ---------------------------------------------------- MODULES= test_server_ctrl \ + test_server_gl \ + test_server_io \ test_server_node \ test_server \ test_server_sup \ @@ -49,7 +51,6 @@ MODULES= test_server_ctrl \ TS_MODULES= \ ts \ ts_run \ - ts_reports \ ts_lib \ ts_make \ ts_erl_config \ diff --git a/lib/test_server/src/test_server.app.src b/lib/test_server/src/test_server.app.src index faf7db835e..26330f9695 100644 --- a/lib/test_server/src/test_server.app.src +++ b/lib/test_server/src/test_server.app.src @@ -24,6 +24,7 @@ test_server_ctrl, test_server, test_server_h, + test_server_io, test_server_node, test_server_sup ]}, diff --git a/lib/test_server/src/test_server.erl b/lib/test_server/src/test_server.erl index 8beed9bd3e..bcffe896c4 100644 --- a/lib/test_server/src/test_server.erl +++ b/lib/test_server/src/test_server.erl @@ -524,7 +524,7 @@ stick_all_sticky(Node,Sticky) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% run_test_case_apply(Mod,Func,Args,Name,RunInit,TimetrapData,RejectIoReqs) -> +%% run_test_case_apply(Mod,Func,Args,Name,RunInit,TimetrapData) -> %% {Time,Value,Loc,Opts,Comment} | {died,Reason,unknown,Comment} %% %% Time = float() (seconds) @@ -538,7 +538,6 @@ stick_all_sticky(Node,Sticky) -> %% it possible to capture all it's output from io:format/2, etc. %% %% The job process then sits down and waits for news from the case process. -%% This might be io requests (which are redirected to the log files). %% %% Returns a tuple with the time spent (in seconds) in the test case, %% the return value from the test case or an {'EXIT',Reason} if the case @@ -559,12 +558,9 @@ stick_all_sticky(Node,Sticky) -> %% ScaleTimetrap indicates if test_server should attemp to automatically %% compensate timetraps for runtime delays introduced by e.g. tools like %% cover. -%% -%% RejectIoReqs (bool) is information about whether printouts to stdout -%% should be visible in the minor log file or not. run_test_case_apply({CaseNum,Mod,Func,Args,Name, - RunInit,TimetrapData,RejectIoReqs}) -> + RunInit,TimetrapData}) -> purify_format("Test case #~w ~w:~w/1", [CaseNum, Mod, Func]), case os:getenv("TS_RUN_VALGRIND") of false -> @@ -576,18 +572,18 @@ run_test_case_apply({CaseNum,Mod,Func,Args,Name, test_server_h:testcase({Mod,Func,1}), ProcBef = erlang:system_info(process_count), Result = run_test_case_apply(Mod, Func, Args, Name, RunInit, - TimetrapData, RejectIoReqs), + TimetrapData), ProcAft = erlang:system_info(process_count), purify_new_leaks(), DetFail = get(test_server_detected_fail), {Result,DetFail,ProcBef,ProcAft}. -run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData, RejectIoReqs) -> +run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData) -> case get(test_server_job_dir) of undefined -> %% i'm a local target do_run_test_case_apply(Mod, Func, Args, Name, RunInit, - TimetrapData, RejectIoReqs); + TimetrapData); JobDir -> %% i'm a remote target case Args of @@ -602,14 +598,14 @@ run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData, RejectIoReqs) Config2 = lists:keyreplace(priv_dir, 1, Config1, {priv_dir,TargetPrivDir}), do_run_test_case_apply(Mod, Func, [Config2], Name, RunInit, - TimetrapData, RejectIoReqs); + TimetrapData); _other -> do_run_test_case_apply(Mod, Func, Args, Name, RunInit, - TimetrapData, RejectIoReqs) + TimetrapData) end end. -do_run_test_case_apply(Mod, Func, Args, Name, RunInit, - TimetrapData, RejectIoReqs) -> + +do_run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData) -> {ok,Cwd} = file:get_cwd(), Args2Print = case Args of [Args1] when is_list(Args1) -> @@ -624,9 +620,6 @@ do_run_test_case_apply(Mod, Func, Args, Name, RunInit, TCCallback = get(test_server_testcase_callback), LogOpts = get(test_server_logopts), Ref = make_ref(), - OldGLeader = group_leader(), - %% Set ourself to group leader for the spawned process - group_leader(self(),self()), Pid = spawn_link( fun() -> @@ -634,9 +627,8 @@ do_run_test_case_apply(Mod, Func, Args, Name, RunInit, RunInit, TimetrapData, LogOpts, TCCallback) end), - group_leader(OldGLeader, self()), put(test_server_detected_fail, []), - run_test_case_msgloop(Ref, Pid, false, RejectIoReqs, false, "", + run_test_case_msgloop(Ref, Pid, false, "", undefined, starting). %% Ugly bug (pre R5A): @@ -648,8 +640,7 @@ do_run_test_case_apply(Mod, Func, Args, Name, RunInit, %% A test case is known to have failed if it returns {'EXIT', _} tuple, %% or sends a message {failed, File, Line} to it's group_leader %% -run_test_case_msgloop(Ref, Pid, CaptureStdout, RejectIoReqs, Terminate, - Comment, CurrConf, Status) -> +run_test_case_msgloop(Ref, Pid, Terminate, Comment, CurrConf, Status) -> %% NOTE: Keep job_proxy_msgloop/0 up to date when changes %% are made in this function! {Timeout,ReturnValue} = @@ -664,7 +655,7 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, RejectIoReqs, Terminate, end, receive {test_case_initialized,Pid} -> - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate, + run_test_case_msgloop(Ref,Pid,Terminate, Comment,CurrConf,running); Abort = {abort_current_testcase,_,_} when Status == starting -> %% we're in init phase, must must postpone this operation @@ -672,7 +663,7 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, RejectIoReqs, Terminate, %% gets killed) self() ! Abort, erlang:yield(), - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate, + run_test_case_msgloop(Ref,Pid,Terminate, Comment,CurrConf,Status); {abort_current_testcase,Reason,From} -> Line = case is_process_alive(Pid) of @@ -703,92 +694,15 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, RejectIoReqs, Terminate, Error1 end end, - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate, + run_test_case_msgloop(Ref,Pid,Terminate, NewComment,CurrConf,Status); - {permit_io,FromPid} -> - put({permit_io,FromPid},true), - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate, - Comment,CurrConf,Status); - {io_request,From,ReplyAs,{put_chars,io_lib,Func,[Format,Args]}} - when is_list(Format) -> - Msg = (catch io_lib:Func(Format,Args)), - run_test_case_msgloop_io(From,ReplyAs,CaptureStdout,RejectIoReqs, - Msg,From,Func), - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate, - Comment,CurrConf,Status); - {io_request,From,ReplyAs,{put_chars,io_lib,Func,[Format,Args]}} - when is_atom(Format) -> - Msg = (catch io_lib:Func(Format,Args)), - run_test_case_msgloop_io(From,ReplyAs,CaptureStdout,RejectIoReqs, - Msg,From,Func), - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate, - Comment,CurrConf,Status); - {io_request,From,ReplyAs,{put_chars,Bytes}} -> - run_test_case_msgloop_io(From,ReplyAs,CaptureStdout,RejectIoReqs, - Bytes,From,put_chars), - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate, - Comment,CurrConf,Status); - {io_request,From,ReplyAs,{put_chars,unicode,io_lib,Func,[Format,Args]}} - when is_list(Format) -> - Msg = unicode_to_latin1(catch io_lib:Func(Format,Args)), - run_test_case_msgloop_io(From,ReplyAs,CaptureStdout,RejectIoReqs, - Msg,From,Func), - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate, - Comment,CurrConf,Status); - {io_request,From,ReplyAs,{put_chars,latin1,io_lib,Func,[Format,Args]}} - when is_list(Format) -> - Msg = (catch io_lib:Func(Format,Args)), - run_test_case_msgloop_io(From,ReplyAs,CaptureStdout,RejectIoReqs, - Msg,From,Func), - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate, - Comment,CurrConf,Status); - {io_request,From,ReplyAs,{put_chars,unicode,io_lib,Func,[Format,Args]}} - when is_atom(Format) -> - Msg = unicode_to_latin1(catch io_lib:Func(Format,Args)), - run_test_case_msgloop_io(From,ReplyAs,CaptureStdout,RejectIoReqs, - Msg,From,Func), - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate, - Comment,CurrConf,Status); - {io_request,From,ReplyAs,{put_chars,latin1,io_lib,Func,[Format,Args]}} - when is_atom(Format) -> - Msg = (catch io_lib:Func(Format,Args)), - run_test_case_msgloop_io(From,ReplyAs,CaptureStdout,RejectIoReqs, - Msg,From,Func), - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate, - Comment,CurrConf,Status); - {io_request,From,ReplyAs,{put_chars,unicode,Bytes}} -> - run_test_case_msgloop_io(From,ReplyAs,CaptureStdout,RejectIoReqs, - unicode_to_latin1(Bytes),From,put_chars), - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate, - Comment,CurrConf,Status); - {io_request,From,ReplyAs,{put_chars,latin1,Bytes}} -> - run_test_case_msgloop_io(From,ReplyAs,CaptureStdout,RejectIoReqs, - Bytes,From,put_chars), - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate, - Comment,CurrConf,Status); - IoReq when element(1, IoReq) == io_request -> - %% something else, just pass it on - group_leader() ! IoReq, - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate, - Comment,CurrConf,Status); - {structured_io,ClientPid,Msg} -> - output(Msg, ClientPid), - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate, - Comment,CurrConf,Status); - {capture,NewCapture} -> - run_test_case_msgloop(Ref,Pid,NewCapture,RejectIoReqs,Terminate, - Comment,CurrConf,Status); {sync_apply,From,MFA} -> sync_local_or_remote_apply(false,From,MFA), - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate, + run_test_case_msgloop(Ref,Pid,Terminate, Comment,CurrConf,Status); {sync_apply_proxy,Proxy,From,MFA} -> sync_local_or_remote_apply(Proxy,From,MFA), - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate, - Comment,CurrConf,Status); - {printout,Detail,Format,Args} -> - print(Detail,Format,Args), - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate, + run_test_case_msgloop(Ref,Pid,Terminate, Comment,CurrConf,Status); {comment,NewComment} -> NewComment1 = test_server_ctrl:to_string(NewComment), @@ -802,19 +716,19 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, RejectIoReqs, Terminate, Other -> Other end, - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate1, + run_test_case_msgloop(Ref,Pid,Terminate1, NewComment2,CurrConf,Status); {read_comment,From} -> From ! {self(),read_comment,Comment}, - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate, + run_test_case_msgloop(Ref,Pid,Terminate, Comment,CurrConf,Status); {set_curr_conf,From,NewCurrConf} -> From ! {self(),set_curr_conf,ok}, - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate, + run_test_case_msgloop(Ref,Pid,Terminate, Comment,NewCurrConf,Status); {make_priv_dir,From} when CurrConf == undefined -> From ! {self(),make_priv_dir,{error,no_priv_dir_in_config}}, - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate, + run_test_case_msgloop(Ref,Pid,Terminate, Comment,CurrConf,Status); {make_priv_dir,From} -> Result = @@ -832,11 +746,11 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, RejectIoReqs, Terminate, end end, From ! {self(),make_priv_dir,Result}, - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate, + run_test_case_msgloop(Ref,Pid,Terminate, Comment,CurrConf,Status); {'EXIT',Pid,{Ref,Time,Value,Loc,Opts}} -> RetVal = {Time/1000000,Value,mod_loc(Loc),Opts,Comment}, - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs, + run_test_case_msgloop(Ref,Pid, {true,RetVal},Comment,undefined,Status); {'EXIT',Pid,Reason} -> case Reason of @@ -849,7 +763,6 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, RejectIoReqs, Terminate, {framework_error,{timetrap,TVal}}, unknown,self()), run_test_case_msgloop(Ref,Pid, - CaptureStdout,RejectIoReqs, Terminate,Comment, undefined,Status); Loc1 -> @@ -883,7 +796,6 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, RejectIoReqs, Terminate, undefined end, run_test_case_msgloop(Ref,Pid, - CaptureStdout,RejectIoReqs, Terminate,Comment, NewCurrConf,Status) end; @@ -900,7 +812,7 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, RejectIoReqs, Terminate, {timetrap_timeout,TVal}, Loc1,self()) end, - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs, + run_test_case_msgloop(Ref,Pid, Terminate,Comment,CurrConf,Status); {testcase_aborted,ErrorMsg={user_timetrap_error,_},AbortLoc} -> %% user timetrap function caused exit @@ -909,7 +821,6 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, RejectIoReqs, Terminate, spawn_fw_call(Mod,Func,CurrConf,Pid, ErrorMsg,unknown,self()), run_test_case_msgloop(Ref,Pid, - CaptureStdout,RejectIoReqs, Terminate,Comment, undefined,Status); {testcase_aborted,AbortReason,AbortLoc} -> @@ -921,7 +832,6 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, RejectIoReqs, Terminate, {framework_error,ErrorMsg}, unknown,self()), run_test_case_msgloop(Ref,Pid, - CaptureStdout,RejectIoReqs, Terminate,Comment, undefined,Status); Loc1 -> @@ -954,7 +864,6 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, RejectIoReqs, Terminate, undefined end, run_test_case_msgloop(Ref,Pid, - CaptureStdout,RejectIoReqs, Terminate,Comment, NewCurrConf,Status) end; @@ -969,13 +878,13 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, RejectIoReqs, Terminate, spawn_fw_call(Mod,Func,CurrConf,Pid, testcase_aborted_or_killed, unknown,self()), - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs, + run_test_case_msgloop(Ref,Pid, Terminate,Comment,CurrConf,Status); {fw_error,{FwMod,FwFunc,FwError}} -> spawn_fw_call(FwMod,FwFunc,CurrConf,Pid, {framework_error,FwError}, unknown,self()), - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs, + run_test_case_msgloop(Ref,Pid, Terminate,Comment,CurrConf,Status); _Other -> %% the testcase has terminated because of Reason (e.g. an exit @@ -986,7 +895,7 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, RejectIoReqs, Terminate, end, spawn_fw_call(Mod,Func,CurrConf,Pid, Reason,unknown,self()), - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs, + run_test_case_msgloop(Ref,Pid, Terminate,Comment,CurrConf,Status) end; {EndConfPid,{call_end_conf,Data,_Result}} -> @@ -995,10 +904,10 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, RejectIoReqs, Terminate, {_Mod,_Func,TCPid,TCExitReason,Loc} = Data, spawn_fw_call(Mod,Func,CurrConf,TCPid, TCExitReason,Loc,self()), - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs, + run_test_case_msgloop(Ref,Pid, Terminate,Comment,undefined,Status); _ -> - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs, + run_test_case_msgloop(Ref,Pid, Terminate,Comment,CurrConf,Status) end; {_FwCallPid,fw_notify_done,{T,Value,Loc,Opts,AddToComment}} -> @@ -1019,7 +928,7 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, RejectIoReqs, Terminate, end, {T,Value,Loc,Opts,Comment1} end, - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs, + run_test_case_msgloop(Ref,Pid, {true,RetVal},Comment,undefined,Status); {'EXIT',_FwCallPid,{fw_notify_done,Func,Error}} -> %% a framework function failed @@ -1031,12 +940,12 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, RejectIoReqs, Terminate, {list_to_atom(CB),Func} end, RetVal = {died,{framework_error,Loc,Error},Loc,"Framework error"}, - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs, + run_test_case_msgloop(Ref,Pid, {true,RetVal},Comment,undefined,Status); {failed,File,Line} -> put(test_server_detected_fail, [{File, Line}| get(test_server_detected_fail)]), - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs, + run_test_case_msgloop(Ref,Pid, Terminate,Comment,CurrConf,Status); {user_timetrap,Pid,_TrapTime,StartTime,E={user_timetrap_error,_},_} -> @@ -1046,7 +955,7 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, RejectIoReqs, Terminate, ignore -> ok end, - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs, + run_test_case_msgloop(Ref,Pid, Terminate,Comment,CurrConf,Status); {user_timetrap,Pid,TrapTime,StartTime,ElapsedTime,Scale} -> %% a user timetrap is triggered, ignore it if new @@ -1062,68 +971,41 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, RejectIoReqs, Terminate, ignore -> ok end, - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs, + run_test_case_msgloop(Ref,Pid, Terminate,Comment,CurrConf,Status); {timetrap_cancel_one,Handle,_From} -> timetrap_cancel_one(Handle, false), - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs, + run_test_case_msgloop(Ref,Pid, Terminate,Comment,CurrConf,Status); {timetrap_cancel_all,TCPid,_From} -> timetrap_cancel_all(TCPid, false), - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs, + run_test_case_msgloop(Ref,Pid, Terminate,Comment,CurrConf,Status); - {get_timetrap_info,TCPid,From} -> + {get_timetrap_info,From,TCPid} -> Info = get_timetrap_info(TCPid, false), From ! {self(),get_timetrap_info,Info}, - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs, + run_test_case_msgloop(Ref,Pid, Terminate,Comment,CurrConf,Status); _Other when not is_tuple(_Other) -> %% ignore anything not generated by test server - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs, + run_test_case_msgloop(Ref,Pid, Terminate,Comment,CurrConf,Status); _Other when element(1, _Other) /= 'EXIT', element(1, _Other) /= started, element(1, _Other) /= finished, element(1, _Other) /= print -> %% ignore anything not generated by test server - run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs, + run_test_case_msgloop(Ref,Pid, Terminate,Comment,CurrConf,Status) after Timeout -> ReturnValue end. -run_test_case_msgloop_io(From,ReplyAs,CaptureStdout,RejectIoReqs, - Msg,From,Func) -> - case Msg of - {'EXIT',_} -> - From ! {io_reply,ReplyAs,{error,Func}}; - _ -> - From ! {io_reply,ReplyAs,ok} - end, - Proceed = if RejectIoReqs -> get({permit_io,From}); - true -> true - end, - if Proceed -> - if CaptureStdout /= false -> - CaptureStdout ! {captured,Msg}; - true -> - ok - end, - output({minor,Msg},From); - true -> - ok - end. - -output(Msg,Sender) -> - local_or_remote_apply({test_server_ctrl,output,[Msg,Sender]}). - call_end_conf(Mod,Func,TCPid,TCExitReason,Loc,Conf,TVal) -> - %% Starter is also the group leader process Starter = self(), Data = {Mod,Func,TCPid,TCExitReason,Loc}, EndConfProc = fun() -> - group_leader(Starter, self()), Supervisor = self(), EndConfApply = fun() -> @@ -1161,9 +1043,6 @@ spawn_fw_call(Mod,{init_per_testcase,Func},_,Pid,{timetrap_timeout,TVal}=Why, Loc,SendTo) -> FwCall = fun() -> - %% set group leader so that printouts/comments - %% from the framework get printed in the logs - group_leader(SendTo, self()), Skip = {skip,{failed,{Mod,init_per_testcase,Why}}}, %% if init_per_testcase fails, the test case %% should be skipped @@ -1192,9 +1071,6 @@ spawn_fw_call(Mod,{end_per_testcase,Func},EndConf,Pid, end, FwCall = fun() -> - %% set group leader so that printouts/comments - %% from the framework get printed in the logs - group_leader(SendTo, self()), {RetVal,Report} = case proplists:get_value(tc_status, EndConf1) of undefined -> @@ -1230,9 +1106,6 @@ spawn_fw_call(Mod,{end_per_testcase,Func},EndConf,Pid, spawn_fw_call(FwMod,FwFunc,_,_Pid,{framework_error,FwError},_,SendTo) -> FwCall = fun() -> - %% set group leader so that printouts/comments - %% from the framework get printed in the logs - group_leader(SendTo, self()), test_server_sup:framework_call(report, [framework_error, {{FwMod,FwFunc}, FwError}]), @@ -1256,9 +1129,6 @@ spawn_fw_call(Mod,Func,CurrConf,Pid,Error,Loc,SendTo) -> end, FwCall = fun() -> - %% set group leader so that printouts/comments - %% from the framework get printed in the logs - group_leader(SendTo, self()), case catch fw_error_notify(Mod1,Func1,[], Error,Loc) of {'EXIT',FwErrorNotifyErr} -> @@ -1366,8 +1236,7 @@ run_test_case_eval(Mod, Func, Args0, Name, Ref, RunInit, run_test_case_eval1(Mod, Func, Args, Name, RunInit, TCCallback) -> %% save current state in controller loop - sync_send(group_leader(),set_curr_conf,{{Mod,Func},hd(Args)}, - 5000, fun() -> exit(no_answer_from_group_leader) end), + tc_supervisor_req(set_curr_conf, {{Mod,Func},hd(Args)}), case RunInit of run_init -> put(test_server_init_or_end_conf,{init_per_testcase,Func}), @@ -1397,8 +1266,7 @@ run_test_case_eval1(Mod, Func, Args, Name, RunInit, TCCallback) -> %% call user callback function if defined NewConf1 = user_callback(TCCallback, Mod, Func, init, NewConf), %% save current state in controller loop - sync_send(group_leader(),set_curr_conf,{{Mod,Func},NewConf1}, - 5000, fun() -> exit(no_answer_from_group_leader) end), + tc_supervisor_req(set_curr_conf, {{Mod,Func},NewConf1}), put(test_server_loc, {Mod,Func}), %% execute the test case {{T,Return},Loc} = {ts_tc(Mod, Func, [NewConf1]),get_loc()}, @@ -1426,8 +1294,7 @@ run_test_case_eval1(Mod, Func, Args, Name, RunInit, TCCallback) -> %% call user callback function if defined EndConf1 = user_callback(TCCallback, Mod, Func, 'end', EndConf), %% update current state in controller loop - sync_send(group_leader(),set_curr_conf,EndConf1, 5000, - fun() -> exit(no_answer_from_group_leader) end), + tc_supervisor_req(set_curr_conf, EndConf1), {FWReturn1,TSReturn1,EndConf2} = case end_per_testcase(Mod, Func, EndConf1) of SaveCfg1={save_config,_} -> @@ -1447,8 +1314,7 @@ run_test_case_eval1(Mod, Func, Args, Name, RunInit, TCCallback) -> {FWReturn,TSReturn,EndConf1} end, %% clear current state in controller loop - sync_send(group_leader(),set_curr_conf,undefined, - 5000, fun() -> exit(no_answer_from_group_leader) end), + tc_supervisor_req(set_curr_conf, undefined), put(test_server_init_or_end_conf,undefined), case do_end_tc_call(Mod,Func, Loc, {FWReturn1,[EndConf2]}, TSReturn1) of @@ -1908,16 +1774,6 @@ rewrite_loc_item({M,F,_,Loc}) -> %% Note: Some of these functions have been moved to test_server_sup %% %% in an attempt to keep this modules small (yeah, right!) %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -unicode_to_latin1(Chars) when is_list(Chars); is_binary(Chars) -> - lists:flatten( - [ case X of - High when High > 255 -> - io_lib:format("\\{~.8B}",[X]); - Low -> - Low - end || X <- unicode:characters_to_list(Chars,unicode) ]); -unicode_to_latin1(Garbage) -> - Garbage. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% format(Format) -> IoLibReturn @@ -2170,24 +2026,19 @@ continue(Pid) when is_pid(Pid) -> %% %% Returns the amount to scale timetraps with. +%% {X, fun() -> check() end} <- multiply scale with X if Fun() is true timetrap_scale_factor() -> - F0 = case test_server:purify_is_running() of - true -> 5; - false -> 1 - end, - F1 = case {is_debug(), has_lock_checking()} of - {true,_} -> 6 * F0; - {false,true} -> 2 * F0; - {false,false} -> F0 - end, - F = case has_superfluous_schedulers() of - true -> 3*F1; - false -> F1 - end, - case test_server:is_cover() of - true -> 10 * F; - false -> F - end. + timetrap_scale_factor([ + { 2, fun() -> has_lock_checking() end}, + { 3, fun() -> has_superfluous_schedulers() end}, + { 5, fun() -> purify_is_running() end}, + { 6, fun() -> is_debug() end}, + {10, fun() -> is_cover() end} + ]). + +timetrap_scale_factor(Scales) -> + %% The fun in {S, Fun} a filter input to the list comprehension + lists:foldl(fun(S,O) -> O*S end, 1, [ S || {S,F} <- Scales, F()]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -2515,11 +2366,7 @@ get_timetrap_info(TCPid, SendToServer) -> [I|_] -> I; [] when SendToServer == true -> - MsgLooper = group_leader(), - MsgLooper ! {get_timetrap_info,TCPid,self()}, - receive - {MsgLooper,get_timetrap_info,I} -> I - end; + tc_supervisor_req({get_timetrap_info,TCPid}); [] -> undefined end @@ -2538,17 +2385,29 @@ hours(N) -> trunc(N * 1000 * 60 * 60). minutes(N) -> trunc(N * 1000 * 60). seconds(N) -> trunc(N * 1000). - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% sync_send(Pid,Tag,Msg,Timeout,DoAfter) -> Result +%% tc_supervisor_req(Tag) -> Result +%% tc_supervisor_req(Tag, Msg) -> Result %% -sync_send(Pid,Tag,Msg,Timeout,DoAfter) -> + +tc_supervisor_req(Tag) -> + Pid = test_server_gl:get_tc_supervisor(group_leader()), + Pid ! {Tag,self()}, + receive + {Pid,Tag,Result} -> + Result + after 5000 -> + error(no_answer_from_tc_supervisor) + end. + +tc_supervisor_req(Tag, Msg) -> + Pid = test_server_gl:get_tc_supervisor(group_leader()), Pid ! {Tag,self(),Msg}, receive {Pid,Tag,Result} -> Result - after Timeout -> - DoAfter() + after 5000 -> + error(no_answer_from_tc_supervisor) end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -2943,13 +2802,7 @@ comment(String) -> %% Read the current comment string stored in %% state during test case execution. read_comment() -> - MsgLooper = group_leader(), - MsgLooper ! {read_comment,self()}, - receive - {MsgLooper,read_comment,Comment} -> Comment - after - 5000 -> "" - end. + tc_supervisor_req(read_comment). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% make_priv_dir() -> ok @@ -2957,13 +2810,7 @@ read_comment() -> %% Order test server to create the private directory %% for the current test case. make_priv_dir() -> - MsgLooper = group_leader(), - group_leader() ! {make_priv_dir,self()}, - receive - {MsgLooper,make_priv_dir,Result} -> Result - after - 5000 -> error - end. + tc_supervisor_req(make_priv_dir). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% os_type() -> OsType diff --git a/lib/test_server/src/test_server_ctrl.erl b/lib/test_server/src/test_server_ctrl.erl index a38e2be98e..7f04e2eb23 100644 --- a/lib/test_server/src/test_server_ctrl.erl +++ b/lib/test_server/src/test_server_ctrl.erl @@ -172,7 +172,7 @@ -export([kill_slavenodes/0]). %%% TEST_SERVER INTERFACE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --export([output/2, print/2, print/3, print/4, print_timestamp/2]). +-export([print/2, print/3, print/4, print_timestamp/2]). -export([start_node/3, stop_node/1, wait_for_node/1, is_release_available/1]). -export([format/1, format/2, format/3, to_string/1]). -export([get_target_info/0]). @@ -203,6 +203,7 @@ -define(coverlog_name, "cover.html"). -define(cross_coverlog_name, "cross_cover.html"). -define(cover_total, "total_cover.log"). +-define(unexpected_io_log, "unexpected_io.log"). -define(last_file, "last_name"). -define(last_link, "last_link"). -define(last_test, "last_test"). @@ -1370,24 +1371,22 @@ kill_all_jobs([]) -> spawn_tester(Mod, Func, Args, Dir, Name, Levels, RejectIoReqs, CreatePrivDir, TCCallback, ExtraTools) -> - spawn_link( - fun() -> init_tester(Mod, Func, Args, Dir, Name, Levels, RejectIoReqs, + spawn_link(fun() -> + init_tester(Mod, Func, Args, Dir, Name, Levels, RejectIoReqs, CreatePrivDir, TCCallback, ExtraTools) end). -init_tester(Mod, Func, Args, Dir, Name, {SumLev,MajLev,MinLev}, RejectIoReqs, - CreatePrivDir, TCCallback, ExtraTools) -> +init_tester(Mod, Func, Args, Dir, Name, {_,_,MinLev}=Levels, + RejectIoReqs, CreatePrivDir, TCCallback, ExtraTools) -> process_flag(trap_exit, true), + test_server_io:start_link(), put(test_server_name, Name), put(test_server_dir, Dir), put(test_server_total_time, 0), put(test_server_ok, 0), put(test_server_failed, 0), put(test_server_skipped, {0,0}), - put(test_server_summary_level, SumLev), - put(test_server_major_level, MajLev), put(test_server_minor_level, MinLev), - put(test_server_reject_io_reqs, RejectIoReqs), put(test_server_create_priv_dir, CreatePrivDir), put(test_server_random_seed, proplists:get_value(random_seed, ExtraTools)), put(test_server_testcase_callback, TCCallback), @@ -1403,23 +1402,29 @@ init_tester(Mod, Func, Args, Dir, Name, {SumLev,MajLev,MinLev}, RejectIoReqs, put(test_server_framework_name, list_to_atom(FWName)) end end, + %% before first print, read and set logging options LogOpts = test_server_sup:framework_call(get_logopts, [], []), put(test_server_logopts, LogOpts), - put(test_server_log_nl, not lists:member(no_nl, LogOpts)), + StartedExtraTools = start_extra_tools(ExtraTools), + + test_server_io:set_job_name(Name), + test_server_io:set_gl_props([{levels,Levels}, + {auto_nl,not lists:member(no_nl, LogOpts)}, + {reject_io_reqs,RejectIoReqs}]), + group_leader(test_server_io:get_gl(true), self()), {TimeMy,Result} = ts_tc(Mod, Func, Args), - put(test_server_common_io_handler, undefined), - stop_extra_tools(StartedExtraTools), + set_io_buffering(undefined), + catch stop_extra_tools(StartedExtraTools), case Result of {'EXIT',test_suites_done} -> - print(25, "DONE, normal exit", []); + ok; {'EXIT',_Pid,Reason} -> print(1, "EXIT, reason ~p", [Reason]); {'EXIT',Reason} -> - print(1, "EXIT, reason ~p", [Reason]); - _Other -> - print(25, "DONE", []) + report_severe_error(Reason), + print(1, "EXIT, reason ~p", [Reason]) end, Time = TimeMy/1000000, SuccessStr = @@ -1438,7 +1443,11 @@ init_tester(Mod, Func, Args, Dir, Name, {SumLev,MajLev,MinLev}, RejectIoReqs, "<tr><td></td><td><b>TOTAL</b></td><td></td><td></td><td></td>" "<td>~.3fs</td><td><b>~s</b></td><td>~p Ok, ~p Failed~s of ~p</td></tr>\n" "</tfoot>\n", - [Time,SuccessStr,OkN,FailedN,SkipStr,OkN+FailedN+SkippedN]). + [Time,SuccessStr,OkN,FailedN,SkipStr,OkN+FailedN+SkippedN]), + test_server_io:stop(). + +report_severe_error(Reason) -> + test_server_sup:framework_call(report, [severe_error,Reason]). %% timer:tc/3 ts_tc(M, F, A) -> @@ -1812,8 +1821,9 @@ do_test_cases(TopCases, SkipCases, print(html, "<p><ul>\n" "<li><a href=\"~s\">Full textual log</a></li>\n" - "<li><a href=\"~s\">Coverage log</a></li>\n</ul></p>\n", - [?suitelog_name,?coverlog_name]), + "<li><a href=\"~s\">Coverage log</a></li>\n" + "<li><a href=\"~s\">Unexpected I/O log</a></li>\n</ul></p>\n", + [?suitelog_name,?coverlog_name,?unexpected_io_log]), print(html, "<p>~s</p>\n" ++ xhtml("<table bgcolor=\"white\" border=\"3\" cellpadding=\"5\">", @@ -1873,7 +1883,7 @@ start_log_file() -> {error, eexist} -> ok; MkDirError -> - exit({cant_create_log_dir,{MkDirError,Dir}}) + log_file_error(MkDirError, Dir) end, TestDir = timestamp_filename_get(filename:join(Dir, "run.")), TestDir1 = @@ -1888,20 +1898,26 @@ start_log_file() -> ok -> TestDirX; MkDirError2 -> - exit({cant_create_log_dir,{MkDirError2,TestDirX}}) + log_file_error(MkDirError2, TestDirX) end; MkDirError2 -> - exit({cant_create_log_dir,{MkDirError2,TestDir}}) + log_file_error(MkDirError2, TestDir) end, ok = file:write_file(filename:join(Dir, ?last_file), TestDir1 ++ "\n"), ok = file:write_file(?last_file, TestDir1 ++ "\n"), put(test_server_log_dir_base,TestDir1), MajorName = filename:join(TestDir1, ?suitelog_name), HtmlName = MajorName ++ ?html_ext, + UnexpectedName = filename:join(TestDir1, ?unexpected_io_log), {ok,Major} = file:open(MajorName, [write]), {ok,Html} = file:open(HtmlName, [write]), + {ok,Unexpected} = file:open(UnexpectedName, [write]), + test_server_io:set_fd(major, Major), + test_server_io:set_fd(html, Html), + test_server_io:set_fd(unexpected_io, Unexpected), put(test_server_major_fd,Major), put(test_server_html_fd,Html), + put(test_server_unexpected_io, Unexpected), make_html_link(filename:absname(?last_test ++ ?html_ext), HtmlName, filename:basename(Dir)), @@ -1912,12 +1928,15 @@ start_log_file() -> PrivDir = filename:join(TestDir1, ?priv_dir), ok = file:make_dir(PrivDir), put(test_server_priv_dir,PrivDir++"/"), - print_timestamp(13,"Suite started at "), + print_timestamp(major, "Suite started at "), LogInfo = [{topdir,Dir},{rundir,lists:flatten(TestDir1)}], test_server_sup:framework_call(report, [loginfo,LogInfo]), {ok,TestDir1}. +log_file_error(Error, Dir) -> + exit({cannot_create_log_dir,{Error,lists:flatten(Dir)}}). + make_html_link(LinkName, Target, Explanation) -> %% if possible use a relative reference to Target. TargetL = filename:split(Target), @@ -1951,13 +1970,14 @@ make_html_link(LinkName, Target, Explanation) -> %% Some header info will also be inserted into the log file. start_minor_log_file(Mod, Func) -> + MFA = {Mod,Func,1}, LogDir = get(test_server_log_dir_base), Name0 = lists:flatten(io_lib:format("~s.~s~s", [Mod,Func,?html_ext])), Name = downcase(Name0), AbsName = filename:join(LogDir, Name), case file:read_file_info(AbsName) of {error,_} -> %% normal case, unique name - start_minor_log_file1(Mod, Func, LogDir, AbsName); + start_minor_log_file1(Mod, Func, LogDir, AbsName, MFA); {ok,_} -> %% special case, duplicate names {_,S,Us} = now(), Name1_0 = @@ -1966,14 +1986,15 @@ start_minor_log_file(Mod, Func) -> ?html_ext])), Name1 = downcase(Name1_0), AbsName1 = filename:join(LogDir, Name1), - start_minor_log_file1(Mod, Func, LogDir, AbsName1) + start_minor_log_file1(Mod, Func, LogDir, AbsName1, MFA) end. -start_minor_log_file1(Mod, Func, LogDir, AbsName) -> +start_minor_log_file1(Mod, Func, LogDir, AbsName, MFA) -> {ok,Fd} = file:open(AbsName, [write]), Lev = get(test_server_minor_level)+1000, %% far down in the minor levels put(test_server_minor_fd, Fd), - + test_server_gl:set_minor_fd(group_leader(), Fd, MFA), + TestDescr = io_lib:format("Test ~p:~p result", [Mod,Func]), {Header,Footer} = case test_server_sup:framework_call(get_html_wrapper, @@ -2021,6 +2042,7 @@ start_minor_log_file1(Mod, Func, LogDir, AbsName) -> AbsName. stop_minor_log_file() -> + test_server_gl:unset_minor_fd(group_leader()), Fd = get(test_server_minor_fd), Footer = get(test_server_minor_footer), io:fwrite(Fd, "</pre>\n" ++ Footer, []), @@ -2441,27 +2463,38 @@ maybe_get_privdir() -> %% reason, the Mode argument specifies if a parallel group is currently %% being executed. %% -%% A parallel test case process will always set the dictionary value -%% 'test_server_common_io_handler' to the pid of the main (starting) -%% process. With this value set, the print/3 function will send print -%% messages to the main process instead of writing the data to file -%% (only true for printouts to common log files). +%% The low-level mechanism for buffering IO for the common log files +%% is handled by the test_server_io module. Buffering is turned on by +%% test_server_io:start_transaction/0 and off by calling +%% test_server_io:end_transaction/0. The buffered data for the transaction +%% can printed by calling test_server_io:print_buffered/1. +%% +%% This module is responsible for turning on IO buffering and to later +%% test_server_io:print_buffered/1 to print the data. To help with this, +%% two variables in the process dictionary are used: +%% 'test_server_common_io_handler' and 'test_server_queued_io'. The values +%% are set to as follwing: +%% +%% Value Meaning +%% ----- ------- +%% undefined No parallel test cases running +%% {tc,Pid} Running test cases in a top-level parallel group +%% {Ref,Pid} Running sequential test case inside a parallel group +%% +%% FIXME: The Pid is no longer used. %% %% If a conf group nested under a parallel group in the test %% specification should be started, the 'test_server_common_io_handler' -%% value gets set also on the main process. This causes all printouts -%% to common files - both from parallel test cases and from cases -%% executed by the main process - to all end up as messages in the -%% inbox of the main process. +%% value gets set also on the main process. %% %% During execution of a parallel group (or of a group nested under a %% parallel group), *any* new test case being started gets registered %% in a list saved in the dictionary with 'test_server_queued_io' as key. %% When the top level parallel group is finished (only then can we be %% sure all parallel test cases have finished and "reported in"), the -%% list of test cases is traversed in order and printout messages from -%% each process - including the main process - are handled in turn. See -%% handle_test_case_io_and_status/0 for details. +%% list of test cases is traversed in order and test_server_io:print_buffered/1 +%% can be called for each test case. See handle_test_case_io_and_status/0 +%% for details. %% %% To be able to handle nested conf groups with different properties, %% the Mode argument specifies a list of {Ref,Properties} tuples. @@ -2604,16 +2637,15 @@ run_test_cases_loop([{auto_skip_case,{Type,Ref,Case,Comment},SkipMode}|Cases], run_test_cases_loop([{auto_skip_case,{Case,Comment},SkipMode}|Cases], Config, TimetrapData, Mode, Status) -> - {Mod,Func} = skip_case(auto, undefined, get(test_server_case_num)+1, Case, Comment, - (undefined /= get(test_server_common_io_handler)), SkipMode), + {Mod,Func} = skip_case(auto, undefined, get(test_server_case_num)+1, + Case, Comment, is_io_buffered(), SkipMode), test_server_sup:framework_call(report, [tc_auto_skip,{?pl2a(Mod),Func,Comment}]), run_test_cases_loop(Cases, Config, TimetrapData, Mode, update_status(skipped, Mod, Func, Status)); run_test_cases_loop([{skip_case,{conf,Ref,Case,Comment}}|Cases0], Config, TimetrapData, Mode, Status) -> - {Mod,Func} = skip_case(user, Ref, 0, Case, Comment, - (undefined /= get(test_server_common_io_handler))), + {Mod,Func} = skip_case(user, Ref, 0, Case, Comment, is_io_buffered()), {Cases,Config1} = case curr_ref(Mode) of Ref -> @@ -2629,8 +2661,8 @@ run_test_cases_loop([{skip_case,{conf,Ref,Case,Comment}}|Cases0], run_test_cases_loop([{skip_case,{Case,Comment}}|Cases], Config, TimetrapData, Mode, Status) -> - {Mod,Func} = skip_case(user, undefined, get(test_server_case_num)+1, Case, Comment, - (undefined /= get(test_server_common_io_handler))), + {Mod,Func} = skip_case(user, undefined, get(test_server_case_num)+1, + Case, Comment, is_io_buffered()), test_server_sup:framework_call(report, [tc_user_skip,{?pl2a(Mod),Func,Comment}]), run_test_cases_loop(Cases, Config, TimetrapData, Mode, update_status(skipped, Mod, Func, Status)); @@ -3029,21 +3061,19 @@ run_test_cases_loop([{Mod,Case}|Cases], Config, TimetrapData, Mode, Status) -> run_test_cases_loop([{Mod,Func,Args}|Cases], Config, TimetrapData, Mode, Status) -> Num = put(test_server_case_num, get(test_server_case_num)+1), + %% check the current execution mode and save info about the case if %% detected that printouts to common log files is handled later - case check_prop(parallel, Mode) of + + case check_prop(parallel, Mode) =:= false andalso is_io_buffered() of + true -> + %% sequential test case nested in a parallel group; + %% io is buffered, so we must queue this test case + queue_test_case_io(undefined, self(), Num+1, Mod, Func); false -> - case get(test_server_common_io_handler) of - undefined -> - %% io printouts are written to straight to file - ok; - _ -> - %% io messages are buffered, put test case in queue - queue_test_case_io(undefined, self(), Num+1, Mod, Func) - end; - _ -> ok end, + case run_test_case(undefined, Num+1, Mod, Func, Args, run_init, target, TimetrapData, Mode) of %% callback to framework module failed, exit immediately @@ -3092,8 +3122,8 @@ run_test_cases_loop([{Mod,Func,Args}|Cases], Config, TimetrapData, Mode, Status) %% the test case is being executed in parallel with the main process (and %% other test cases) and Pid is the dedicated process executing the case Pid -> - %% io from Pid will be buffered in the main process inbox and handled - %% later, so we have to save info about the case + %% io from Pid will be buffered by the test_server_io process and + %% handled later, so we have to save info about the case queue_test_case_io(undefined, Pid, Num+1, Mod, Func), run_test_cases_loop(Cases, Config, TimetrapData, Mode, Status) end; @@ -3200,11 +3230,17 @@ get_data_dir(Mod, Suite) -> non_existing -> print(12, "The module ~p is not loaded", [Mod]), []; + cover_compiled -> + MainCoverNode = cover:get_main_node(), + {file,File} = rpc:call(MainCoverNode,cover,is_compiled,[UseMod]), + do_get_data_dir(UseMod,File); FullPath -> - filename:dirname(FullPath) ++ "/" ++ cast_to_list(UseMod) ++ - ?data_dir_suffix + do_get_data_dir(UseMod,FullPath) end. +do_get_data_dir(Mod,File) -> + filename:dirname(File) ++ "/" ++ cast_to_list(Mod) ++ ?data_dir_suffix. + print_conf_time(0) -> ok; print_conf_time(ConfTime) -> @@ -3348,7 +3384,9 @@ skip_case(Type, Ref, CaseNum, Case, Comment, SendSync, Mode) -> if SendSync -> queue_test_case_io(Ref, self(), CaseNum, Mod, Func), self() ! {started,Ref,self(),CaseNum,Mod,Func}, + test_server_io:start_transaction(), skip_case1(Type, CaseNum, Mod, Func, Comment, Mode), + test_server_io:end_transaction(), self() ! {finished,Ref,self(),CaseNum,Mod,Func,skipped,{0,skipped,[]}}; not SendSync -> skip_case1(Type, CaseNum, Mod, Func, Comment, Mode) @@ -3489,13 +3527,20 @@ modify_cases_upto1(Ref, CopyOp, [C|T], Orig, Alt) -> %% %% Save info about current process (always the main process) buffering %% io printout messages from parallel test case processes (*and* possibly -%% also the main process). If the value is the default 'undefined', -%% io is not buffered but printed directly to file (see print/3). +%% also the main process). set_io_buffering(IOHandler) -> put(test_server_common_io_handler, IOHandler). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% is_io_buffered() -> true|false +%% +%% Test whether is being buffered. + +is_io_buffered() -> + get(test_server_common_io_handler) =/= undefined. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% queue_test_case_io(Pid, Num, Mod, Func) -> ok %% %% Save info about test case that gets its io buffered. This can @@ -3542,7 +3587,7 @@ wait_and_resend(Ref, [{_,CurrPid,CaseNum,Mod,Func}|Ps] = Cases, Ok,Skip,Fail) -> receive {finished,_Ref,CurrPid,CaseNum,Mod,Func,Result,_RetVal} = Msg -> %% resend message to main process so that it can be used - %% to handle buffered io messages later + %% to test_server_io:print_buffered/1 later self() ! Msg, MF = {Mod,Func}, {Ok1,Skip1,Fail1} = @@ -3573,16 +3618,18 @@ rm_cases_upto(Ref, [_|Ps]) -> %% %% Each parallel test case process prints to its own minor log file during %% execution. The common log files (major, html etc) must however be -%% written to sequentially. The test case processes send print requests -%% to the main (starting) process (the same process executing -%% run_test_cases_loop/4), which handles these requests in the same -%% order that the test case processes were started. -%% -%% An io session is always started with a {started,Ref,Pid,Num,Mod,Func} -%% message and terminated with {finished,Ref,Pid,Num,Mod,Func,Result,RetVal}. -%% The result shipped with the finished message from a parallel process -%% is used to update status data of the current test run. An 'EXIT' -%% message from each parallel test case process (after finishing and +%% written to sequentially. This is handled by calling +%% test_server_io:start_transaction/0 to tell the test_server_io process +%% to buffer all print requests. +%% +%% An io session is always started with a +%% {started,Ref,Pid,Num,Mod,Func} message (and +%% test_server_io:start_transaction/0 will be called) and terminated +%% with {finished,Ref,Pid,Num,Mod,Func,Result,RetVal} (and +%% test_server_io:end_transaction/0 will be called). The result +%% shipped with the finished message from a parallel process is used +%% to update status data of the current test run. An 'EXIT' message +%% from each parallel test case process (after finishing and %% terminating) is also received and handled here. %% %% During execution of a parallel group, any cases (conf or normal) @@ -3591,13 +3638,13 @@ rm_cases_upto(Ref, [_|Ps]) -> %% correct sequence. This function handles also the print messages %% generated by nested group cases that have been executed sequentially %% by the main process (note that these cases do not generate 'EXIT' -%% messages, only 'start', 'print' and 'finished' messages). +%% messages, only 'start' and 'finished' messages). %% %% See the header comment for run_test_cases_loop/4 for more %% info about IO handling. %% %% Note: It is important that the type of messages handled here -%% do not get consumated by test_server:run_test_case_msgloop/5 +%% do not get consumed by test_server:run_test_case_msgloop/5 %% during the test case execution (e.g. in the catch clause of %% the receive)! @@ -3624,7 +3671,7 @@ handle_test_case_io_and_status() -> %% Handle cases (without Ref) that belong to the top parallel group (i.e. when Refs = []) handle_io_and_exit_loop([], [{undefined,CurrPid,CaseNum,Mod,Func}|Ps] = Cases, Ok,Skip,Fail) -> - %% retreive the start message for the current io session (= testcase) + %% retrieve the start message for the current io session (= testcase) receive {started,_,CurrPid,CaseNum,Mod,Func} -> {Ok1,Skip1,Fail1} = @@ -3666,9 +3713,11 @@ handle_io_and_exits(Main, CurrPid, CaseNum, Mod, Func, Cases) -> receive %% end of io session from test case executed by main process {finished,_,Main,CaseNum,Mod,Func,Result,_RetVal} -> + test_server_io:print_buffered(CurrPid), {Result,{Mod,Func}}; %% end of io session from test case executed by parallel process {finished,_,CurrPid,CaseNum,Mod,Func,Result,RetVal} -> + test_server_io:print_buffered(CurrPid), case Result of ok -> put(test_server_ok, get(test_server_ok)+1); @@ -3681,13 +3730,9 @@ handle_io_and_exits(Main, CurrPid, CaseNum, Mod, Func, Cases) -> end, {Result,{Mod,Func}}; - %% print to common log file - {print,CurrPid,Detail,Msg} -> - output({Detail,Msg}, internal), - handle_io_and_exits(Main, CurrPid, CaseNum, Mod, Func, Cases); - %% unexpected termination of test case process {'EXIT',TCPid,Reason} when Reason /= normal -> + test_server_io:print_buffered(CurrPid), {value,{_,_,Num,M,F}} = lists:keysearch(TCPid, 2, Cases), print(1, "Error! Process for test case #~p (~p:~p) died! Reason: ~p", [Num, M, F, Reason]), @@ -3722,48 +3767,46 @@ handle_io_and_exits(Main, CurrPid, CaseNum, Mod, Func, Cases) -> run_test_case(Ref, Num, Mod, Func, Args, RunInit, Where, TimetrapData) -> file:set_cwd(filename:dirname(get(test_server_dir))), run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, - TimetrapData, [], [], self()). + TimetrapData, [], self()). run_test_case(Ref, Num, Mod, Func, Args, skip_init, Where, TimetrapData, Mode) -> %% a conf case is always executed by the main process run_test_case1(Ref, Num, Mod, Func, Args, skip_init, Where, - TimetrapData, [], Mode, self()); + TimetrapData, Mode, self()); run_test_case(Ref, Num, Mod, Func, Args, RunInit, Where, TimetrapData, Mode) -> file:set_cwd(filename:dirname(get(test_server_dir))), + Main = self(), case check_prop(parallel, Mode) of false -> %% this is a sequential test case run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, - TimetrapData, [], Mode, self()); + TimetrapData, Mode, Main); _Ref -> %% this a parallel test case, spawn the new process - Main = self(), - {dictionary,State} = process_info(self(), dictionary), - spawn_link(fun() -> - run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, - TimetrapData, State, Mode, Main) - end) + Dictionary = get(), + {dictionary,Dictionary} = process_info(self(), dictionary), + spawn_link( + fun() -> + process_flag(trap_exit, true), + [put(Key, Val) || {Key,Val} <- Dictionary], + set_io_buffering({tc,Main}), + run_test_case1(Ref, Num, Mod, Func, Args, RunInit, + Where, TimetrapData, Mode, Main) + end) end. run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, - TimetrapData, State, Mode, Main) -> - %% if this runs on a parallel test case process, - %% copy the dictionary from the main process - do_if_parallel(Main, fun() -> process_flag(trap_exit, true) end, ok), - CopyDict = fun() -> lists:foreach(fun({Key,Val}) -> - put(Key, Val) - end, State) - end, - do_if_parallel(Main, CopyDict, ok), - do_if_parallel(Main, fun() -> - put(test_server_common_io_handler, {tc,Main}) - end, ok), + TimetrapData, Mode, Main) -> + group_leader(test_server_io:get_gl(Main == self()), self()), + %% if io is being buffered, send start io session message %% (no matter if case runs on parallel or main process) - case get(test_server_common_io_handler) of - undefined -> ok; - _ -> Main ! {started,Ref,self(),Num,Mod,Func} + case is_io_buffered() of + false -> ok; + true -> + test_server_io:start_transaction(), + Main ! {started,Ref,self(),Num,Mod,Func} end, TSDir = get(test_server_dir), case Where of @@ -3772,6 +3815,7 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, host -> ok end, + print(major, "=case ~p:~p", [Mod, Func]), MinorName = start_minor_log_file(Mod, Func), print(minor, "<a name=\"top\"></a>", [], internal_raw), @@ -3823,13 +3867,12 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, [num2str(Num),fw_name(Mod),GroupName,MinorBase,Func, MinorBase,MinorBase]), - do_if_parallel(Main, ok, fun erlang:yield/0), + do_unless_parallel(Main, fun erlang:yield/0), - RejectIoReqs = get(test_server_reject_io_reqs), %% run the test case {Result,DetectedFail,ProcsBefore,ProcsAfter} = run_test_case_apply(Num, Mod, Func, [UpdatedArgs], get_name(Mode), - RunInit, Where, TimetrapData, RejectIoReqs), + RunInit, Where, TimetrapData), {Time,RetVal,Loc,Opts,Comment} = case Result of Normal={_Time,_RetVal,_Loc,_Opts,_Comment} -> Normal; @@ -3841,7 +3884,7 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, print_timestamp(minor, "Ended at "), print(major, "=ended ~s", [lists:flatten(timestamp_get(""))]), - do_if_parallel(Main, ok, fun() -> file:set_cwd(filename:dirname(TSDir)) end), + do_unless_parallel(Main, fun() -> file:set_cwd(filename:dirname(TSDir)) end), %% call the appropriate progress function clause to print the results to log Status = @@ -3950,10 +3993,13 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, %% if io is being buffered, send finished message %% (no matter if case runs on parallel or main process) - case get(test_server_common_io_handler) of - undefined -> ok; - _ -> Main ! {finished,Ref,self(),Num,Mod,Func, - ?mod_result(Status),{Time,RetVal,Opts}} + case is_io_buffered() of + false -> + ok; + true -> + test_server_io:end_transaction(), + Main ! {finished,Ref,self(),Num,Mod,Func, + ?mod_result(Status),{Time,RetVal,Opts}} end, {Time,RetVal,Opts}. @@ -3961,18 +4007,11 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, %%-------------------------------------------------------------------- %% various help functions -%% Call If() if we're on parallel process, or -%% call Else() if we're on main process -do_if_parallel(Pid, If, Else) -> +%% Call Action if we are running on the main process (not parallel). +do_unless_parallel(Main, Action) when is_function(Action, 0) -> case self() of - Pid -> - if is_function(Else) -> Else(); - true -> Else - end; - _ -> - if is_function(If) -> If(); - true -> If - end + Main -> Action(); + _ -> ok end. num2str(0) -> ""; @@ -4448,7 +4487,7 @@ do_format_exception(Reason={Error,Stack}) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% run_test_case_apply(CaseNum, Mod, Func, Args, Name, RunInit, -%% Where, TimetrapData, RejectIoReqs) -> +%% Where, TimetrapData) -> %% {{Time,RetVal,Loc,Opts,Comment},DetectedFail,ProcessesBefore,ProcessesAfter} | %% {{died,Reason,unknown,Comment},DetectedFail,ProcessesBefore,ProcessesAfter} %% Name = atom() @@ -4468,20 +4507,20 @@ do_format_exception(Reason={Error,Stack}) -> %% result back over the socket. Else test_server runs the case directly on host. run_test_case_apply(CaseNum, Mod, Func, Args, Name, RunInit, host, - TimetrapData, RejectIoReqs) -> + TimetrapData) -> test_server:run_test_case_apply({CaseNum,Mod,Func,Args,Name,RunInit, - TimetrapData,RejectIoReqs}); + TimetrapData}); run_test_case_apply(CaseNum, Mod, Func, Args, Name, RunInit, target, - TimetrapData, RejectIoReqs) -> + TimetrapData) -> case get(test_server_ctrl_job_sock) of undefined -> %% local target test_server:run_test_case_apply({CaseNum,Mod,Func,Args,Name,RunInit, - TimetrapData,RejectIoReqs}); + TimetrapData}); JobSock -> %% remote target request(JobSock, {test_case,{CaseNum,Mod,Func,Args,Name,RunInit, - TimetrapData,RejectIoReqs}}), + TimetrapData}}), read_job_sock_loop(JobSock) end. @@ -4493,16 +4532,6 @@ run_test_case_apply(CaseNum, Mod, Func, Args, Name, RunInit, target, %% %% Just like io:format, except that depending on the Detail value, the output %% is directed to console, major and/or minor log files. -%% -%% To handle printouts to common (not minor) log files from parallel test -%% case processes, the test_server_common_io_handler value is checked. If -%% set, the data is sent to the main controlling process. Note that test -%% cases that belong to a conf group nested under a parallel group will also -%% get its io data sent to main rather than immediately printed out, even -%% if the test cases are executed by the same, main, process (ie the main -%% process sends messages to itself then). -%% -%% Buffered io is handled by the handle_test_case_io_and_status/0 function. print(Detail, Format) -> print(Detail, Format, []). @@ -4515,19 +4544,7 @@ print(Detail, Format, Args, Printer) -> print_or_buffer(Detail, Msg, Printer). print_or_buffer(Detail, Msg, Printer) -> - case get(test_server_minor_level) of - _ when Detail == minor -> - output({Detail,Msg}, Printer); - MinLevel when is_number(Detail), Detail >= MinLevel -> - output({Detail,Msg}, Printer); - _ -> % Detail < Minor | major | html - case get(test_server_common_io_handler) of - undefined -> - output({Detail,Msg}, Printer); - {_,MainPid} -> - MainPid ! {print,self(),Detail,Msg} - end - end. + test_server_gl:print(group_leader(), Detail, Msg, Printer). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% print_timestamp(Detail, Leader) -> ok @@ -4591,112 +4608,6 @@ format(Detail, Format, Args) -> print_or_buffer(Detail, Str, self()). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% output({Level,Message}, Sender) -> ok -%% Level = integer() | minor | major | html -%% Message = string() | [integer()] -%% Sender = string() | internal -%% -%% Outputs the message on the channels indicated by Level. If Level is an -%% atom, only the corresponding channel receives the output. When Level is -%% an integer console, major and/or minor log file will receive output -%% depending on the user set thresholds (see get_levels/0, set_levels/3) -%% -%% When printing on the console, the message is prefixed with the test -%% suite's name. In case a name is not set (yet), Sender is used. -%% -%% When not outputting to the console, and the Sender is 'internal', -%% the message is prefixed with "=== ", so that it will be apparent that -%% the message comes from the test server and not the test suite itself. - -output({Level,Msg}, Sender) when is_integer(Level) -> - SumLev = get(test_server_summary_level), - if Level =< SumLev -> - output_to_fd(stdout, Msg, Sender); - true -> - ok - end, - MajLev = get(test_server_major_level), - if Level =< MajLev -> - output_to_fd(get(test_server_major_fd), Msg, Sender); - true -> - ok - end, - MinLev = get(test_server_minor_level), - if Level >= MinLev -> - output_to_fd(get(test_server_minor_fd), Msg, Sender); - true -> - ok - end; -output({minor,Bytes}, Sender) when is_list(Bytes) -> - output_to_fd(get(test_server_minor_fd), Bytes, Sender); -output({major,Bytes}, Sender) when is_list(Bytes) -> - output_to_fd(get(test_server_major_fd), Bytes, Sender); -output({minor,Bytes}, Sender) when is_binary(Bytes) -> - output_to_fd(get(test_server_minor_fd),binary_to_list(Bytes), Sender); -output({major,Bytes}, Sender) when is_binary(Bytes) -> - output_to_fd(get(test_server_major_fd),binary_to_list(Bytes), Sender); -output({html,Msg}, _Sender) -> - case get(test_server_html_fd) of - undefined -> - ok; - Fd -> - io:put_chars(Fd,Msg), - case file:position(Fd, {cur, 0}) of - {ok, Pos} -> - %% We are writing to a seekable file. Finalise so - %% we get complete valid (and viewable) HTML code. - %% Then rewind to overwrite the finalising code. - io:put_chars(Fd, "\n</table>\n"), - case get(test_server_html_footer) of - undefined -> - io:put_chars(Fd, "</body>\n</html>\n"); - Footer -> - io:put_chars(Fd, Footer) - end, - file:position(Fd, Pos); - {error, epipe} -> - %% The file is not seekable. We cannot erase what - %% we've already written --- so the reader will - %% have to wait until we're done. - ok - end - end; -output({minor,Data}, Sender) -> - output_to_fd(get(test_server_minor_fd), - lists:flatten(io_lib:format( - "Unexpected output: ~p~n", [Data])),Sender); -output({major,Data}, Sender) -> - output_to_fd(get(test_server_major_fd), - lists:flatten(io_lib:format( - "Unexpected output: ~p~n", [Data])),Sender). - -output_to_fd(stdout, Msg, Sender) -> - Name = - case get(test_server_name) of - undefined -> Sender; - Other -> Other - end, - io:format("Testing ~s: ~s\n", [Name, lists:flatten(Msg)]); -output_to_fd(undefined, _Msg, _Sender) -> - ok; -output_to_fd(Fd, [$=|Msg], internal) -> - io:put_chars(Fd, [$=]), - io:put_chars(Fd, Msg), - io:put_chars(Fd, "\n"); - -output_to_fd(Fd, Msg, internal) -> - io:put_chars(Fd, [$=,$=,$=,$ ]), - io:put_chars(Fd, Msg), - io:put_chars(Fd, "\n"); - -output_to_fd(Fd, Msg, _Sender) -> - io:put_chars(Fd, Msg), - case get(test_server_log_nl) of - false -> ok; - _ -> io:put_chars(Fd, "\n") - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% xhtml(BasicHtml, XHtml) -> BasicHtml | XHtml %% xhtml(HTML, XHTML) -> @@ -5827,11 +5738,11 @@ write_default_cross_coverlog(TestDir) -> {ok,CrossCoverLog} = file:open(filename:join(TestDir,?cross_coverlog_name), [write]), write_coverlog_header(CrossCoverLog), - io:fwrite(CrossCoverLog, - ["No cross cover modules exist for this application,", - xhtml("<br>","<br />"), - "or cross cover analysis is not completed.\n" - "</body></html>\n"], []), + io:put_chars(CrossCoverLog, + ["No cross cover modules exist for this application,", + xhtml("<br>","<br />"), + "or cross cover analysis is not completed.\n" + "</body></html>\n"]), file:close(CrossCoverLog). write_cover_result_table(CoverLog,Coverage) -> diff --git a/lib/test_server/src/test_server_gl.erl b/lib/test_server/src/test_server_gl.erl new file mode 100644 index 0000000000..d32c7c07dc --- /dev/null +++ b/lib/test_server/src/test_server_gl.erl @@ -0,0 +1,293 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2012. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%% This module implements group leader processes for test cases. +%% Each group leader process handles output to the minor log file for +%% a test case, and calls test_server_io to handle output to the common +%% log files. The group leader processes are created and destroyed +%% through the test_server_io module/process. + +-module(test_server_gl). +-export([start_link/0,stop/1,set_minor_fd/3,unset_minor_fd/1, + get_tc_supervisor/1,print/4,set_props/2]). + +-export([init/1,handle_call/3,handle_cast/2,handle_info/2,terminate/2]). + +-record(st, {tc_supervisor :: 'none'|pid(), %Test case supervisor + tc :: mfa(), %Current test case MFA + minor :: 'none'|pid(), %Minor fd + minor_monitor, %Monitor ref for minor fd + capture :: 'none'|pid(), %Capture output + reject_io :: boolean(), %Reject I/O requests... + permit_io, %... and exceptions + auto_nl=true :: boolean(), %Automatically add NL + levels %{Stdout,Major,Minor} + }). + +%% start_link() +%% Start a new group leader process. Only to be called by +%% the test_server_io process. + +start_link() -> + case gen_server:start_link(?MODULE, [], []) of + {ok,Pid} -> + {ok,Pid}; + Other -> + Other + end. + + +%% stop(Pid) +%% Stop a group leader process. Only to be called by +%% the test_server_io process. + +stop(GL) -> + gen_server:cast(GL, stop). + + +%% set_minor_fd(GL, Fd, MFA) +%% GL = Pid for the group leader process +%% Fd = file descriptor for the minor log file +%% MFA = {M,F,A} for the test case owning the minor log file +%% +%% Register the file descriptor for the minor log file. Subsequent +%% IO directed to the minor log file will be written to this file. +%% Also register the currently executing process at the testcase +%% supervisor corresponding to this group leader process. + +set_minor_fd(GL, Fd, MFA) -> + req(GL, {set_minor_fd,Fd,MFA,self()}). + + +%% unset_minor_fd(GL, Fd, MFA) +%% GL = Pid for the group leader process +%% +%% Unregister the file descriptor for minor log file (typically +%% because the test case has ended the minor log file is about +%% to be closed). Subsequent IO (for example, by a process spawned +%% by the testcase process) will go to the unexpected_io log file. + +unset_minor_fd(GL) -> + req(GL, unset_minor_fd). + + +%% get_tc_supervisor(GL) +%% GL = Pid for the group leader process +%% +%% Return the Pid for the process that supervises the test case +%% that has this group leader. + +get_tc_supervisor(GL) -> + req(GL, get_tc_supervisor). + + +%% print(GL, Detail, Format, Args) -> ok +%% GL = Pid for the group leader process +%% Detail = integer() | minor | major | html | stdout +%% Msg = iodata() +%% Printer = internal | pid() +%% +%% Print a message to one of the log files. If Detail is an integer, +%% it will be compared to the levels (set by set_props/2) to +%% determine which log file(s) that are to receive the output. If +%% Detail is an atom, the value of the atom will directly determine +%% which log file to use. IO to the minor log file will be handled +%% directly by this group leader process (printing to the file set by +%% set_minor_fd/3), and all other IO will be handled by calling +%% test_server_io:print/3. + +print(GL, Detail, Msg, Printer) -> + req(GL, {print,Detail,Msg,Printer}). + + +%% set_props(GL, [PropertyTuple]) +%% GL = Pid for the group leader process +%% PropertyTuple = {levels,{Show,Major,Minor}} | +%% {auto_nl,boolean()} | +%% {reject_io_reqs,boolean()} +%% +%% Set properties for this group leader process. + +set_props(GL, PropList) -> + req(GL, {set_props,PropList}). + +%%% Internal functions. + +init([]) -> + {ok,#st{tc_supervisor=none, + minor=none, + minor_monitor=none, + capture=none, + reject_io=false, + permit_io=gb_sets:empty(), + auto_nl=true, + levels={1,19,10} + }}. + +req(GL, Req) -> + gen_server:call(GL, Req, infinity). + +handle_call(get_tc_supervisor, _From, #st{tc_supervisor=Pid}=St) -> + {reply,Pid,St}; +handle_call({set_minor_fd,Fd,MFA,Supervisor}, _From, St) -> + Ref = erlang:monitor(process, Fd), + {reply,ok,St#st{tc=MFA,minor=Fd,minor_monitor=Ref, + tc_supervisor=Supervisor}}; +handle_call(unset_minor_fd, _From, St) -> + {reply,ok,St#st{minor=none,tc_supervisor=none}}; +handle_call({set_props,PropList}, _From, St) -> + {reply,ok,do_set_props(PropList, St)}; +handle_call({print,Detail,Msg,Printer}, {From,_}, St) -> + output(Detail, Msg, Printer, From, St), + {reply,ok,St}. + +handle_cast(stop, St) -> + {stop,normal,St}. + +handle_info({'DOWN',Ref,process,_,_}, #st{minor_monitor=Ref}=St) -> + {noreply,St#st{minor=none,minor_monitor=none}}; +handle_info({permit_io,Pid}, #st{permit_io=P}=St) -> + {noreply,St#st{permit_io=gb_sets:add(Pid, P)}}; +handle_info({capture,Cap0}, St) -> + Cap = case Cap0 of + false -> none; + Pid when is_pid(Cap0) -> Pid + end, + {noreply,St#st{capture=Cap}}; +handle_info({io_request,From,ReplyAs,Req}=IoReq, St) -> + try io_req(Req, From, St) of + passthrough -> + group_leader() ! IoReq; + Data -> + case is_io_permitted(From, St) of + false -> + ok; + true -> + case St of + #st{capture=none} -> + ok; + #st{capture=CapturePid} -> + CapturePid ! {captured,Data} + end, + output(minor, Data, From, From, St) + end, + From ! {io_reply,ReplyAs,ok} + catch + _:_ -> + {io_reply,ReplyAs,{error,arguments}} + end, + {noreply,St}; +handle_info({structured_io,ClientPid,{Detail,Str}}, St) -> + output(Detail, Str, ClientPid, ClientPid, St), + {noreply,St}; +handle_info({printout,Detail,Format,Args}, St) -> + Str = io_lib:format(Format, Args), + output(Detail, Str, internal, none, St), + {noreply,St}; +handle_info(Msg, #st{tc_supervisor=Pid}=St) when is_pid(Pid) -> + %% The process overseeing the testcase process also used to be + %% the group leader; thus, it is widely expected that it can be + %% reached by sending a message to the group leader. Therefore + %% we'll need to forward any non-recognized messaged to the test + %% case supervisor. + Pid ! Msg, + {noreply,St}; +handle_info(_Msg, #st{}=St) -> + %% There is no known supervisor process. Ignore this message. + {noreply,St}. + +terminate(_, _) -> + ok. + +do_set_props([{levels,Levels}|Ps], St) -> + do_set_props(Ps, St#st{levels=Levels}); +do_set_props([{auto_nl,AutoNL}|Ps], St) -> + do_set_props(Ps, St#st{auto_nl=AutoNL}); +do_set_props([{reject_io_reqs,Bool}|Ps], St) -> + do_set_props(Ps, St#st{reject_io=Bool}); +do_set_props([], St) -> St. + +io_req({put_chars,Enc,Bytes}, _, _) when Enc =:= latin1; Enc =:= unicode -> + to_latin1(Enc, Bytes); +io_req({put_chars,Encoding,Mod,Func,[Format,Args]}, _, _) -> + Str = Mod:Func(Format, Args), + to_latin1(Encoding, Str); +io_req(_, _, _) -> passthrough. + +to_latin1(unicode, Str) -> + [if C > 255 -> + io_lib:format("\\{~.8B}", [C]); + true -> + C + end || C <- unicode:characters_to_list(Str, unicode)]; +to_latin1(latin1, Str) -> Str. + +output(Level, Str, Sender, From, St) when is_integer(Level) -> + case selected_by_level(Level, stdout, St) of + true -> output(stdout, Str, Sender, From, St); + false -> ok + end, + case selected_by_level(Level, major, St) of + true -> output(major, Str, Sender, From, St); + false -> ok + end, + case selected_by_level(Level, minor, St) of + true -> output(minor, Str, Sender, From, St); + false -> ok + end; +output(stdout, Str, _Sender, From, St) -> + output_to_file(stdout, Str, From, St); +output(html, Str, _Sender, From, St) -> + output_to_file(html, Str, From, St); +output(Level, Str, Sender, From, St) when is_atom(Level) -> + output_to_file(Level, dress_output(Str, Sender, St), From, St). + +output_to_file(minor, Data0, From, #st{tc={M,F,A},minor=none}) -> + Data = [io_lib:format("=== ~p:~p/~p\n", [M,F,A]),Data0], + test_server_io:print(From, unexpected_io, Data), + ok; +output_to_file(minor, Data, From, #st{minor=Fd}) -> + try + io:put_chars(Fd, Data) + catch + _:_ -> + test_server_io:print(From, unexpected_io, Data) + end; +output_to_file(Detail, Data, From, _) -> + test_server_io:print(From, Detail, Data). + +is_io_permitted(From, #st{reject_io=true,permit_io=P}) -> + gb_sets:is_member(From, P); +is_io_permitted(_, #st{reject_io=false}) -> true. + +selected_by_level(Level, stdout, #st{levels={Stdout,_,_}}) -> + Level =< Stdout; +selected_by_level(Level, major, #st{levels={_,Major,_}}) -> + Level =< Major; +selected_by_level(Level, minor, #st{levels={_,_,Minor}}) -> + Level >= Minor. + +dress_output([$=|_]=Str, internal, _) -> + [Str,$\n]; +dress_output(Str, internal, _) -> + ["=== ",Str,$\n]; +dress_output(Str, _, #st{auto_nl=AutoNL}) -> + case AutoNL of + true -> [Str,$\n]; + false -> Str + end. diff --git a/lib/test_server/src/test_server_io.erl b/lib/test_server/src/test_server_io.erl new file mode 100644 index 0000000000..abdfb71241 --- /dev/null +++ b/lib/test_server/src/test_server_io.erl @@ -0,0 +1,315 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2012. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%% This module implements a process with the registered name 'test_server_io', +%% which has two main responsibilities: +%% +%% * Manage group leader processes (see the test_server_gl module) +%% for test cases. A group_leader process is obtained by calling +%% get_gl/1. Group leader processes will be kept alive as along as +%% the 'test_server_io' process is alive. +%% +%% * Handle output to the common log files (stdout, major, html, +%% unexpected_io). +%% + +-module(test_server_io). +-export([start_link/0,stop/0,get_gl/1,set_fd/2, + start_transaction/0,end_transaction/0,print_buffered/1,print/3, + set_footer/1,set_job_name/1,set_gl_props/1]). + +-export([init/1,handle_call/3,handle_info/2,terminate/2]). + +-record(st, {fds, %Singleton fds (gb_tree) + shared_gl :: pid(), %Shared group leader + gls, %Group leaders (gb_set) + io_buffering=false, %I/O buffering + buffered, %Buffered I/O requests + html_footer, %HTML footer + job_name, %Name of current job. + gl_props, %Properties for GL. + stopping + }). + +start_link() -> + case gen_server:start_link({local,?MODULE}, ?MODULE, [], []) of + {ok,Pid} -> + {ok,Pid}; + Other -> + Other + end. + +stop() -> + OldGL = group_leader(), + group_leader(self(), self()), + req(stop), + group_leader(OldGL, self()), + ok. + +%% get_gl(Shared) -> Pid +%% Shared = boolean() +%% Pid = pid() +%% +%% Return a group leader (a process using the test_server_gl module). +%% If Shared is true, the shared group leader is returned (suitable for +%% running sequential test cases), otherwise a new group leader process +%% is spawned. Group leader processes will live until they are garbaged +%% collected by a call to gc/0. + +get_gl(Shared) when is_boolean(Shared) -> + req({get_gl,Shared}). + +%% set_fd(Tag, Fd) -> ok. +%% Tag = major | html | unexpected_io +%% Fd = a file descriptor (as returned by file:open/2) +%% +%% Associate a file descriptor with the given Tag. This +%% Tag can later be used in when calling to print/3. + +set_fd(Tag, Fd) -> + req({set_fd,Tag,Fd}). + +%% start_transaction() +%% +%% Subsequent calls to print/3 from the process executing start_transaction/0 +%% will cause the messages to be buffered instead of printed directly. + +start_transaction() -> + req({start_transaction,self()}). + +%% end_transaction() +%% +%% End the transaction started by start_transaction/0. Subsequent calls to +%% print/3 will cause the message to be printed directory. + +end_transaction() -> + req({end_transaction,self()}). + +%% print(From, Tag, Msg) +%% From = pid() +%% Tag = stdout, or any tag that has been registered using set_fd/2 +%% Msg = string or iolist +%% +%% Either print Msg to the file identified by Tag, or buffer the message +%% start_transaction/0 has been called from the process From. +%% +%% NOTE: The tags have various special meanings. For example, 'html' +%% is assumed to be a HTML file. + +print(From, Tag, Msg) -> + req({print,From,Tag,Msg}). + +%% print_buffered(Pid) +%% Pid = pid() +%% +%% Print all messages buffered in the *first* transaction buffered for Pid. +%% (If start_transaction/0 and end_transaction/0 has been called N times, +%% print_buffered/1 must be called N times to print all transactions.) + +print_buffered(Pid) -> + req({print_buffered,Pid}). + +%% set_footer(IoData) +%% +%% Set a footer for the file associated with the 'html' tag. +%% It will be used by print/3 to print a footer for the HTML file. + +set_footer(Footer) -> + req({set_footer,Footer}). + +%% set_job_name(Name) +%% Set a name for the currently running job. The name will be used +%% when printing to 'stdout'. +%% +set_job_name(Name) -> + req({set_job_name,Name}). + +%% set_gl_props(PropList) +%% Set properties for group leader processes. When a group_leader process +%% is created, test_server_gl:set_props(PropList) will be called. + +set_gl_props(PropList) -> + req({set_gl_props,PropList}). + + +%%% Internal functions. + +init([]) -> + process_flag(trap_exit, true), + Empty = gb_trees:empty(), + {ok,Shared} = test_server_gl:start_link(), + {ok,#st{fds=Empty,shared_gl=Shared,gls=gb_sets:empty(), + io_buffering=gb_sets:empty(), + buffered=Empty, + html_footer="</body>\n</html>\n", + job_name="<name not set>", + gl_props=[]}}. + +req(Req) -> + gen_server:call(?MODULE, Req, infinity). + +handle_call({get_gl,false}, _From, #st{gls=Gls,gl_props=Props}=St) -> + {ok,Pid} = test_server_gl:start_link(), + test_server_gl:set_props(Pid, Props), + {reply,Pid,St#st{gls=gb_sets:insert(Pid, Gls)}}; +handle_call({get_gl,true}, _From, #st{shared_gl=Shared}=St) -> + {reply,Shared,St}; +handle_call({set_fd,Tag,Fd}, _From, #st{fds=Fds0}=St) -> + Fds = gb_trees:enter(Tag, Fd, Fds0), + {reply,ok,St#st{fds=Fds}}; +handle_call({start_transaction,Pid}, _From, #st{io_buffering=Buffer0, + buffered=Buf0}=St) -> + Buf = case gb_trees:is_defined(Pid, Buf0) of + false -> gb_trees:insert(Pid, queue:new(), Buf0); + true -> Buf0 + end, + Buffer = gb_sets:add(Pid, Buffer0), + {reply,ok,St#st{io_buffering=Buffer,buffered=Buf}}; +handle_call({print,From,Tag,Str}, _From, St0) -> + St = output(From, Tag, Str, St0), + {reply,ok,St}; +handle_call({end_transaction,Pid}, _From, #st{io_buffering=Buffer0, + buffered=Buffered0}=St0) -> + Q0 = gb_trees:get(Pid, Buffered0), + Q = queue:in(eot, Q0), + Buffered = gb_trees:update(Pid, Q, Buffered0), + Buffer = gb_sets:delete_any(Pid, Buffer0), + St = St0#st{io_buffering=Buffer,buffered=Buffered}, + {reply,ok,St}; +handle_call({print_buffered,Pid}, _From, #st{buffered=Buffered0}=St0) -> + Q0 = gb_trees:get(Pid, Buffered0), + Q = do_print_buffered(Q0, St0), + Buffered = gb_trees:update(Pid, Q, Buffered0), + St = St0#st{buffered=Buffered}, + {reply,ok,St}; +handle_call({set_footer,Footer}, _From, St) -> + {reply,ok,St#st{html_footer=Footer}}; +handle_call({set_job_name,Name}, _From, St) -> + {reply,ok,St#st{job_name=Name}}; +handle_call({set_gl_props,Props}, _From, #st{shared_gl=Shared}=St) -> + test_server_gl:set_props(Shared, Props), + {reply,ok,St#st{gl_props=Props}}; +handle_call(stop, From, #st{shared_gl=SGL,gls=Gls0}=St0) -> + St = St0#st{gls=gb_sets:insert(SGL, Gls0),stopping=From}, + gc(St), + %% Give the users of the surviving group leaders some + %% time to finish. + erlang:send_after(2000, self(), stop_group_leaders), + {noreply,St}. + +handle_info({'EXIT',Pid,normal}, #st{gls=Gls0,stopping=From}=St) -> + Gls = gb_sets:delete_any(Pid, Gls0), + case gb_sets:is_empty(Gls) andalso stopping =/= undefined of + true -> + %% No more group leaders left. + gen_server:reply(From, ok), + {stop,normal,St#st{gls=Gls,stopping=undefined}}; + false -> + %% Wait for more group leaders to finish. + {noreply,St#st{gls=Gls}} + end; +handle_info({'EXIT',_Pid,Reason}, _St) -> + exit(Reason); +handle_info(stop_group_leaders, #st{gls=Gls}=St) -> + %% Stop the remaining group leaders. + [test_server_gl:stop(GL) || GL <- gb_sets:to_list(Gls)], + erlang:send_after(2000, self(), kill_group_leaders), + {noreply,St}; +handle_info(kill_group_leaders, #st{gls=Gls,stopping=From}=St) -> + [exit(GL, kill) || GL <- gb_sets:to_list(Gls)], + gen_server:reply(From, ok), + {stop,normal,St}; +handle_info(Other, St) -> + io:format("Ignoring: ~p\n", [Other]), + {noreply,St}. + +terminate(_, _) -> + ok. + +output(From, Tag, Str, #st{io_buffering=Buffered,buffered=Buf0}=St) -> + case gb_sets:is_member(From, Buffered) of + false -> + do_output(Tag, Str, St), + St; + true -> + Q0 = gb_trees:get(From, Buf0), + Q = queue:in({Tag,Str}, Q0), + Buf = gb_trees:update(From, Q, Buf0), + St#st{buffered=Buf} + end. + +do_output(stdout, Str0, #st{job_name=Name}) -> + Str = io_lib:format("Testing ~s: ~s\n", [Name,Str0]), + io:put_chars(Str); +do_output(Tag, Str, #st{fds=Fds}=St) -> + case gb_trees:lookup(Tag, Fds) of + none -> + S = io_lib:format("\n*** ERROR: ~p, line ~p: No known '~p' log file\n", + [?MODULE,?LINE,Tag]), + do_output(stdout, [S,Str], St); + {value,Fd} -> + try + io:put_chars(Fd, Str), + case Tag of + html -> finalise_table(Fd, St); + _ -> ok + end + catch _:Error -> + S = io_lib:format("\n*** ERROR: ~p, line ~p: Error writing to " + "log file '~p': ~p\n", + [?MODULE,?LINE,Tag,Error]), + do_output(stdout, [S,Str], St) + end + end. + +finalise_table(Fd, #st{html_footer=Footer}) -> + case file:position(Fd, {cur,0}) of + {ok,Pos} -> + %% We are writing to a seekable file. Finalise so + %% we get complete valid (and viewable) HTML code. + %% Then rewind to overwrite the finalising code. + io:put_chars(Fd, ["\n</table>\n",Footer]), + file:position(Fd, Pos); + {error,epipe} -> + %% The file is not seekable. We cannot erase what + %% we've already written --- so the reader will + %% have to wait until we're done. + ok + end. + +do_print_buffered(Q0, St) -> + Item = queue:get(Q0), + Q = queue:drop(Q0), + case Item of + eot -> + Q; + {Tag,Str} -> + do_output(Tag, Str, St), + do_print_buffered(Q, St) + end. + +gc(#st{gls=Gls0}) -> + InUse0 = [begin + {group_leader,GL} = process_info(P, group_leader), + GL + end || P <- processes()], + InUse = ordsets:from_list(InUse0), + Gls = gb_sets:to_list(Gls0), + NotUsed = ordsets:subtract(Gls, InUse), + [test_server_gl:stop(Pid) || Pid <- NotUsed], + ok. diff --git a/lib/test_server/src/test_server_node.erl b/lib/test_server/src/test_server_node.erl index 17c02dfbe5..872f15f2be 100644 --- a/lib/test_server/src/test_server_node.erl +++ b/lib/test_server/src/test_server_node.erl @@ -424,10 +424,12 @@ start_node_peer(SlaveName, OptList, From, TI) -> %% Bad environment can cause open port to fail. If this happens, %% we ignore it and let the testcase handle the situation... catch open_port({spawn, Cmd}, [stream|Opts]), + + Tmo = 60000 * test_server:timetrap_scale_factor(), case start_node_get_option_value(wait, OptList, true) of true -> - Ret = wait_for_node_started(LSock,60000,undefined,Cleanup,TI,self()), + Ret = wait_for_node_started(LSock,Tmo,undefined,Cleanup,TI,self()), case {Ret,FailOnError} of {{{ok, Node}, Warning},_} -> gen_server:reply(From,{{ok,Node},HostStr,Cmd,[],Warning}); @@ -443,7 +445,7 @@ start_node_peer(SlaveName, OptList, From, TI) -> Self = self(), spawn_link( fun() -> - wait_for_node_started(LSock,60000,undefined, + wait_for_node_started(LSock,Tmo,undefined, Cleanup,TI,Self), receive after infinity -> ok end end), diff --git a/lib/test_server/src/test_server_sup.erl b/lib/test_server/src/test_server_sup.erl index 9d111ff769..4a27c1ebae 100644 --- a/lib/test_server/src/test_server_sup.erl +++ b/lib/test_server/src/test_server_sup.erl @@ -473,10 +473,8 @@ getenv_any([]) -> "". %% %% Returns the OS family get_os_family() -> - case os:type() of - {OsFamily,_OsName} -> OsFamily; - OsFamily -> OsFamily - end. + {OsFamily,_OsName} = os:type(), + OsFamily. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/test_server/src/ts.erl b/lib/test_server/src/ts.erl index a30f6c65fe..db16b6ecd2 100644 --- a/lib/test_server/src/ts.erl +++ b/lib/test_server/src/ts.erl @@ -25,9 +25,8 @@ -module(ts). -export([run/0, run/1, run/2, run/3, run/4, - clean/0, clean/1, tests/0, tests/1, - install/0, install/1, index/0, + install/0, install/1, bench/0, bench/1, bench/2, benchmarks/0, estone/0, estone/1, cross_cover_analyse/1, @@ -42,17 +41,11 @@ %%% %%% +-- ts_install --+------ ts_autoconf_win32 %%% | -%%% | -%%% | %%% ts ---+ +------ ts_erl_config %%% | | ts_lib -%%% | +------ ts_make -%%% | | -%%% +-- ts_run -----+ +%%% +-- ts_run -----+------ ts_make %%% | | ts_filelib %%% | +------ ts_make_erl -%%% | | -%%% | +------ ts_reports (indirectly) %%% | %%% +-- ts_benchmark %%% @@ -77,8 +70,6 @@ %%% and other platforms. %%% ts_make_erl A corrected version of the standar Erlang module %%% make (used for rebuilding test suites). -%%% ts_reports Generates index pages in HTML, providing a summary -%%% of the tests run. %%% ts_lib Miscellanous utility functions, each used by several %%% other modules. %%% ts_benchmark Supervises otp benchmarks and collects results. @@ -163,9 +154,6 @@ help(installed) -> " ts:tests() - Shows all available families of tests.\n", " ts:tests(Spec) - Shows all available test modules in Spec,\n", " i.e. ../Spec_test/*_SUITE.erl\n", - " ts:index() - Updates local index page.\n", - " ts:clean() - Cleans up all but the last tests run.\n", - " ts:clean(all) - Cleans up all test runs found.\n", " ts:estone() - Run estone_SUITE in kernel application with\n" " no run options\n", " ts:estone(Opts) - Run estone_SUITE in kernel application with\n" @@ -201,33 +189,6 @@ install() -> install(Options) when is_list(Options) -> ts_install:install(install_local,Options). -%% Updates the local index page. - -index() -> - check_and_run(fun(_Vars) -> ts_reports:make_index(), ok end). - -%% -%% clean(all) -%% Deletes all logfiles. -%% -clean(all) -> - delete_files(filelib:wildcard("*" ++ ?logdir_ext)). - -%% clean/0 -%% -%% Cleans up run logfiles, all but the last run. -clean() -> - clean1(filelib:wildcard("*" ++ ?logdir_ext)). - -clean1([Dir|Dirs]) -> - List0 = filelib:wildcard(filename:join(Dir, "run.*")), - case lists:reverse(lists:sort(List0)) of - [] -> ok; - [_Last|Rest] -> delete_files(Rest) - end, - clean1(Dirs); -clean1([]) -> ok. - %% run/0 %% Runs all specs found by ts:tests(), if any, or returns %% {error, no_tests_available}. (batch) @@ -579,32 +540,6 @@ run_test(File, Args, Options) -> run_test(File, Args, Options, Vars) -> ts_run:run(File, Args, Options, Vars). - -delete_files([]) -> ok; -delete_files([Item|Rest]) -> - case file:delete(Item) of - ok -> - delete_files(Rest); - {error,eperm} -> - file:change_mode(Item, 8#777), - delete_files(filelib:wildcard(filename:join(Item, "*"))), - file:del_dir(Item), - ok; - {error,eacces} -> - %% We'll see about that! - file:change_mode(Item, 8#777), - case file:delete(Item) of - ok -> ok; - {error,_} -> - erlang:yield(), - file:change_mode(Item, 8#777), - file:delete(Item), - ok - end; - {error,_} -> ok - end, - delete_files(Rest). - %% This module provides some convenient shortcuts to running %% the test server from within a started Erlang shell. diff --git a/lib/test_server/src/ts_lib.erl b/lib/test_server/src/ts_lib.erl index 3dce19ed65..d9a699ca9f 100644 --- a/lib/test_server/src/ts_lib.erl +++ b/lib/test_server/src/ts_lib.erl @@ -25,9 +25,8 @@ -compile({no_auto_import,[error/1]}). -export([error/1, var/2, erlang_type/0, erlang_type/1, - initial_capital/1, interesting_logs/1, - specs/1, suites/2, last_test/1, - force_write_file/2, force_delete/1, + initial_capital/1, + specs/1, suites/2, subst_file/3, subst/2, print_data/1, make_non_erlang/2, maybe_atom_to_list/1, progress/4 @@ -91,21 +90,6 @@ initial_capital([C|Rest]) when $a =< C, C =< $z -> initial_capital(String) -> String. -%% Returns a list of the "interesting logs" in a directory, -%% i.e. those that correspond to spec files. - -interesting_logs(Dir) -> - Logs = filelib:wildcard(filename:join(Dir, [$*|?logdir_ext])), - Interesting = - case specs(Dir) of - [] -> - Logs; - Specs0 -> - Specs = ordsets:from_list(Specs0), - [L || L <- Logs, ordsets:is_element(filename_to_atom(L), Specs)] - end, - sort_tests(Interesting). - specs(Dir) -> Specs = filelib:wildcard(filename:join([filename:dirname(Dir), "*_test", "*.{dyn,}spec"])), @@ -165,42 +149,6 @@ suite_order(mnesia) -> 44; suite_order(system) -> 999; %% IMPORTANT: system SHOULD always be last! suite_order(_) -> 200. -last_test(Dir) -> - last_test(filelib:wildcard(filename:join(Dir, "run.[1-2]*")), false). - -last_test([Run|Rest], false) -> - last_test(Rest, Run); -last_test([Run|Rest], Latest) when Run > Latest -> - last_test(Rest, Run); -last_test([_|Rest], Latest) -> - last_test(Rest, Latest); -last_test([], Latest) -> - Latest. - -%% Do the utmost to ensure that the file is written, by deleting or -%% renaming an old file with the same name. - -force_write_file(Name, Contents) -> - force_delete(Name), - file:write_file(Name, Contents). - -force_delete(Name) -> - case file:delete(Name) of - {error, eacces} -> - force_rename(Name, Name ++ ".old.", 0); - Other -> - Other - end. - -force_rename(From, To, Number) -> - Dest = [To|integer_to_list(Number)], - case file:read_file_info(Dest) of - {ok, _} -> - force_rename(From, To, Number+1); - {error, _} -> - file:rename(From, Dest) - end. - %% Substitute all occurrences of @var@ in the In file, using %% the list of variables in Vars, producing the output file Out. %% Returns: ok | {error, Reason} diff --git a/lib/test_server/src/ts_reports.erl b/lib/test_server/src/ts_reports.erl deleted file mode 100644 index f981a77ae4..0000000000 --- a/lib/test_server/src/ts_reports.erl +++ /dev/null @@ -1,545 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1997-2010. All Rights Reserved. -%% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% %CopyrightEnd% -%% - -%%% Purpose : Produces reports in HTML from the outcome of test suite runs. - --module(ts_reports). - --export([make_index/0, make_master_index/2, make_progress_index/2]). --export([count_cases/1, year/0, current_time/0]). - --include_lib("kernel/include/file.hrl"). --include("ts.hrl"). - --compile({no_auto_import,[error/1]}). - --import(filename, [basename/1, rootname/1]). --import(ts_lib, [error/1]). - - -%% Make master index page which points out index pages for all platforms. - -make_master_index(Dir, Vars) -> - IndexName = filename:join(Dir, "index.html"), - {ok, Index0} = make_master_index1(directories(Dir), master_header(Vars)), - Index = [Index0|master_footer()], - io:put_chars("Updating " ++ IndexName ++ "... "), - ok = ts_lib:force_write_file(IndexName, Index), - io:put_chars("done\n"). - -make_master_index1([Dir|Rest], Result) -> - NewResult = - case catch read_variables(Dir) of - {'EXIT',{{bad_installation,Reason},_}} -> - io:put_chars("Failed to read " ++ filename:join(Dir,?variables)++ - ": " ++ Reason ++ " - Ignoring this directory\n"), - Result; - Vars -> - Platform = ts_lib:var(platform_label, Vars), - case make_index(Dir, Vars, false) of - {ok, Summary} -> - make_master_index(Platform, Dir, Summary, Result); - {error, _} -> - Result - end - end, - make_master_index1(Rest, NewResult); -make_master_index1([], Result) -> - {ok, Result}. - -make_progress_index(Dir, Vars) -> - IndexName = filename:join(Dir, "index.html"), - io:put_chars("Updating " ++ IndexName ++ "... "), - Index0=progress_header(Vars), - ts_lib:force_delete(IndexName), - Dirs=find_progress_runs(Dir), - Index1=[Index0|make_progress_links(Dirs, [])], - IndexF=[Index1|progress_footer()], - ok = ts_lib:force_write_file(IndexName, IndexF), - io:put_chars("done\n"). - -find_progress_runs(Dir) -> - case file:list_dir(Dir) of - {ok, Dirs0} -> - Dirs1= [filename:join(Dir,X) || X <- Dirs0, - filelib:is_dir(filename:join(Dir,X))], - lists:sort(Dirs1); - _ -> - [] - end. - -name_from_vars(Dir, Platform) -> - VarFile=filename:join([Dir, Platform, "variables"]), - case file:consult(VarFile) of - {ok, Vars} -> - ts_lib:var(platform_id, Vars); - _Other -> - Platform - end. - -make_progress_links([], Acc) -> - Acc; -make_progress_links([RDir|Rest], Acc) -> - Dir=filename:basename(RDir), - Platforms=[filename:basename(X) || - X <- find_progress_runs(RDir)], - PlatformLinks=["<A HREF=\""++filename:join([Dir,X,"index.html"]) - ++"\">"++name_from_vars(RDir, X)++"</A><BR>" || - X <- Platforms], - LinkName=Dir++"/index.html", - Link = - [ - "<TR valign=top>\n", - "<TD><A HREF=\"", LinkName, "\">", Dir, "</A></TD>", "\n", - "<TD>", PlatformLinks, "</TD>", "\n" - ], - make_progress_links(Rest, [Link|Acc]). - -read_variables(Dir) -> - case file:consult(filename:join(Dir, ?variables)) of - {ok, Vars} -> Vars; - {error, Reason} -> - erlang:error({bad_installation,file:format_error(Reason)}, [Dir]) - end. - -make_master_index(Platform, Dirname, {Succ, Fail, UserSkip,AutoSkip}, Result) -> - Link = filename:join(filename:basename(Dirname), "index.html"), - FailStr = - if Fail > 0 -> - ["<FONT color=\"red\">", - integer_to_list(Fail),"</FONT>"]; - true -> - integer_to_list(Fail) - end, - AutoSkipStr = - if AutoSkip > 0 -> - ["<FONT color=\"brown\">", - integer_to_list(AutoSkip),"</FONT>"]; - true -> integer_to_list(AutoSkip) - end, - [Result, - "<TR valign=top>\n", - "<TD><A HREF=\"", Link, "\">", Platform, "</A></TD>", "\n", - make_row(integer_to_list(Succ), false), - make_row(FailStr, false), - make_row(integer_to_list(UserSkip), false), - make_row(AutoSkipStr, false), - "</TR>\n"]. - -%% Make index page which points out individual test suites for a single platform. - -make_index() -> - {ok, Pwd} = file:get_cwd(), - Vars = read_variables(Pwd), - make_index(Pwd, Vars, true). - -make_index(Dir, Vars, IncludeLast) -> - IndexName = filename:absname("index.html", Dir), - io:put_chars("Updating " ++ IndexName ++ "... "), - case catch make_index1(Dir, IndexName, Vars, IncludeLast) of - {'EXIT', Reason} -> - io:put_chars("CRASHED!\n"), - io:format("~p~n", [Reason]), - {error, Reason}; - {error, Reason} -> - io:put_chars("FAILED\n"), - io:format("~p~n", [Reason]), - {error, Reason}; - {ok, Summary} -> - io:put_chars("done\n"), - {ok, Summary}; - Err -> - io:format("Unknown internal error. Please report.\n(Err: ~p, ID: 1)", - [Err]), - {error, Err} - end. - -make_index1(Dir, IndexName, Vars, IncludeLast) -> - Logs0 = ts_lib:interesting_logs(Dir), - Logs = - case IncludeLast of - true -> add_last_name(Logs0); - false -> Logs0 - end, - {ok, {Index0, Summary}} = make_index(Logs, header(Vars), 0, 0, 0, 0, 0), - Index = [Index0|footer()], - case ts_lib:force_write_file(IndexName, Index) of - ok -> - {ok, Summary}; - {error, Reason} -> - error({index_write_error, Reason}) - end. - -make_index([Name|Rest], Result, TotSucc, TotFail, UserSkip, AutoSkip, TotNotBuilt) -> - case ts_lib:last_test(Name) of - false -> - %% Silently skip. - make_index(Rest, Result, TotSucc, TotFail, UserSkip, AutoSkip, TotNotBuilt); - Last -> - case count_cases(Last) of - {Succ, Fail, USkip, ASkip} -> - Cov = - case file:read_file(filename:join(Last,?cover_total)) of - {ok,Bin} -> - TotCoverage = binary_to_term(Bin), - io_lib:format("~w %",[TotCoverage]); - _error -> - "" - end, - Link = filename:join(basename(Name), basename(Last)), - JustTheName = rootname(basename(Name)), - NotBuilt = not_built(JustTheName), - NewResult = [Result, make_index1(JustTheName, - Link, Succ, Fail, USkip, ASkip, - NotBuilt, Cov, false)], - make_index(Rest, NewResult, TotSucc+Succ, TotFail+Fail, - UserSkip+USkip, AutoSkip+ASkip, TotNotBuilt+NotBuilt); - error -> - make_index(Rest, Result, TotSucc, TotFail, UserSkip, AutoSkip, - TotNotBuilt) - end - end; -make_index([], Result, TotSucc, TotFail, UserSkip, AutoSkip, TotNotBuilt) -> - {ok, {[Result|make_index1("Total", no_link, - TotSucc, TotFail, UserSkip, AutoSkip, - TotNotBuilt, "", true)], - {TotSucc, TotFail, UserSkip, AutoSkip}}}. - -make_index1(SuiteName, Link, Success, Fail, UserSkip, AutoSkip, NotBuilt, Coverage, Bold) -> - Name = test_suite_name(SuiteName), - FailStr = - if Fail > 0 -> - ["<FONT color=\"red\">", - integer_to_list(Fail),"</FONT>"]; - true -> - integer_to_list(Fail) - end, - AutoSkipStr = - if AutoSkip > 0 -> - ["<FONT color=\"brown\">", - integer_to_list(AutoSkip),"</FONT>"]; - true -> integer_to_list(AutoSkip) - end, - ["<TR valign=top>\n", - "<TD>", - case Link of - no_link -> - ["<B>", Name|"</B>"]; - _Other -> - CrashDumpName = SuiteName ++ "_erl_crash.dump", - CrashDumpLink = - case filelib:is_file(CrashDumpName) of - true -> - [" <A HREF=\"", CrashDumpName, - "\">(CrashDump)</A>"]; - false -> - "" - end, - LogFile = filename:join(Link, ?suitelog_name ++ ".html"), - ["<A HREF=\"", LogFile, "\">", Name, "</A>\n", CrashDumpLink, - "</TD>\n"] - end, - make_row(integer_to_list(Success), Bold), - make_row(FailStr, Bold), - make_row(integer_to_list(UserSkip), Bold), - make_row(AutoSkipStr, Bold), - make_row(integer_to_list(NotBuilt), Bold), - make_row(Coverage, Bold), - "</TR>\n"]. - -make_row(Row, true) -> - ["<TD ALIGN=right><B>", Row|"</B></TD>"]; -make_row(Row, false) -> - ["<TD ALIGN=right>", Row|"</TD>"]. - -not_built(BaseName) -> - Dir = filename:join("..", BaseName++"_test"), - Erl = length(filelib:wildcard(filename:join(Dir,"*_SUITE.erl"))), - Beam = length(filelib:wildcard(filename:join(Dir,"*_SUITE.beam"))), - Erl-Beam. - - -%% Add the log file directory for the very last test run (according to -%% last_name). - -add_last_name(Logs) -> - case file:read_file("last_name") of - {ok, Bin} -> - Name = filename:dirname(lib:nonl(binary_to_list(Bin))), - case lists:member(Name, Logs) of - true -> Logs; - false -> [Name|Logs] - end; - _ -> - Logs - end. - -term_to_text(Term) -> - lists:flatten(io_lib:format("~p.\n", [Term])). - -test_suite_name(Name) -> - ts_lib:initial_capital(Name) ++ " suite". - -directories(Dir) -> - {ok, Files} = file:list_dir(Dir), - [filename:join(Dir, X) || X <- Files, - filelib:is_dir(filename:join(Dir, X))]. - - -%%% Headers and footers. - -header(Vars) -> - Platform = ts_lib:var(platform_id, Vars), - ["<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n" - "<!-- autogenerated by '"++atom_to_list(?MODULE)++"'. -->\n" - "<HTML>\n", - "<HEAD>\n", - "<TITLE>Test Results for ", Platform, "</TITLE>\n", - "</HEAD>\n", - - body_tag(), - - "<!-- ---- DOCUMENT TITLE ---- -->\n", - - "<CENTER>\n", - "<H1>Test Results for ", Platform, "</H1>\n", - "</CENTER>\n", - - "<!-- ---- CONTENT ---- -->\n", - "<CENTER>\n", - - "<TABLE border=3 cellpadding=5>\n", - "<th><B>Family</B></th>\n", - "<th>Successful</th>\n", - "<th>Failed</th>\n", - "<th>User Skipped</th>\n" - "<th>Auto Skipped</th>\n" - "<th>Missing Suites</th>\n" - "<th>Coverage</th>\n" - "\n"]. - -footer() -> - ["</TABLE>\n" - "</CENTER>\n" - "<P><CENTER>\n" - "<HR>\n" - "<P><FONT SIZE=-1>\n" - "Copyright © ", year(), - " <A HREF=\"http://erlang.ericsson.se\">Open Telecom Platform</A><BR>\n" - "Updated: <!date>", current_time(), "<!/date><BR>\n" - "</FONT>\n" - "</CENTER>\n" - "</body>\n" - "</HTML>\n"]. - -progress_header(Vars) -> - Release = ts_lib:var(erl_release, Vars), - ["<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n" - "<!-- autogenerated by '"++atom_to_list(?MODULE)++"'. -->\n" - "<HTML>\n", - "<HEAD>\n", - "<TITLE>", Release, " Progress Test Results</TITLE>\n", - "</HEAD>\n", - - body_tag(), - - "<!-- ---- DOCUMENT TITLE ---- -->\n", - - "<CENTER>\n", - "<H1>", Release, " Progress Test Results</H1>\n", - "<TABLE border=3 cellpadding=5>\n", - "<th><b>Test Run</b></th><th>Platforms</th>\n"]. - -progress_footer() -> - ["</TABLE>\n", - "</CENTER>\n", - "<P><CENTER>\n", - "<HR>\n", - "<P><FONT SIZE=-1>\n", - "Copyright © ", year(), - " <A HREF=\"http://erlang.ericsson.se\">Open Telecom Platform</A><BR>\n", - "Updated: <!date>", current_time(), "<!/date><BR>\n", - "</FONT>\n", - "</CENTER>\n", - "</body>\n", - "</HTML>\n"]. - -master_header(Vars) -> - Release = ts_lib:var(erl_release, Vars), - Vsn = erlang:system_info(version), - ["<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n" - "<!-- autogenerated by '"++atom_to_list(?MODULE)++"'. -->\n" - "<HTML>\n", - "<HEAD>\n", - "<TITLE>", Release, " Test Results (", Vsn, ")</TITLE>\n", - "</HEAD>\n", - - body_tag(), - - "<!-- ---- DOCUMENT TITLE ---- -->\n", - - "<CENTER>\n", - "<H1>", Release, " Test Results (", Vsn, ")</H1>\n", - "</CENTER>\n", - - "<!-- ---- CONTENT ---- -->\n", - - "<CENTER>\n", - - "<TABLE border=3 cellpadding=5>\n", - "<th><b>Platform</b></th>\n", - "<th>Successful</th>\n", - "<th>Failed</th>\n", - "<th>User Skipped</th>\n" - "<th>Auto Skipped</th>\n" - "\n"]. - -master_footer() -> - ["</TABLE>\n", - "</CENTER>\n", - "<P><CENTER>\n", - "<HR>\n", - "<P><FONT SIZE=-1>\n", - "Copyright © ", year(), - " <A HREF=\"http://erlang.ericsson.se\">Open Telecom Platform</A><BR>\n", - "Updated: <!date>", current_time(), "<!/date><BR>\n", - "</FONT>\n", - "</CENTER>\n", - "</body>\n", - "</HTML>\n"]. - -body_tag() -> - "<body bgcolor=\"#FFFFFF\" text=\"#000000\" link=\"#0000FF\"" - "vlink=\"#800080\" alink=\"#FF0000\">". - -year() -> - {Y, _, _} = date(), - integer_to_list(Y). - -current_time() -> - {{Y, Mon, D}, {H, Min, S}} = calendar:local_time(), - Weekday = weekday(calendar:day_of_the_week(Y, Mon, D)), - lists:flatten(io_lib:format("~s ~s ~p ~2.2.0w:~2.2.0w:~2.2.0w ~w", - [Weekday, month(Mon), D, H, Min, S, Y])). - -weekday(1) -> "Mon"; -weekday(2) -> "Tue"; -weekday(3) -> "Wed"; -weekday(4) -> "Thu"; -weekday(5) -> "Fri"; -weekday(6) -> "Sat"; -weekday(7) -> "Sun". - -month(1) -> "Jan"; -month(2) -> "Feb"; -month(3) -> "Mar"; -month(4) -> "Apr"; -month(5) -> "May"; -month(6) -> "Jun"; -month(7) -> "Jul"; -month(8) -> "Aug"; -month(9) -> "Sep"; -month(10) -> "Oct"; -month(11) -> "Nov"; -month(12) -> "Dec". - -%% Count test cases in the given directory (a directory of the type -%% run.1997-08-04_09.58.52). - -count_cases(Dir) -> - SumFile = filename:join(Dir, ?run_summary), - case read_summary(SumFile, [summary]) of - {ok, [{Succ,Fail,Skip}]} -> - {Succ,Fail,Skip,0}; - {ok, [Summary]} -> - Summary; - {error, _} -> - LogFile = filename:join(Dir, ?suitelog_name), - case file:read_file(LogFile) of - {ok, Bin} -> - Summary = count_cases1(binary_to_list(Bin), {0, 0, 0, 0}), - write_summary(SumFile, Summary), - Summary; - {error, _Reason} -> - io:format("\nFailed to read ~p (skipped)\n", [LogFile]), - error - end - end. - -write_summary(Name, Summary) -> - File = [term_to_text({summary, Summary})], - ts_lib:force_write_file(Name, File). - -% XXX: This function doesn't do what the writer expect. It can't handle -% the case if there are several different keys and I had to add a special -% case for the empty file. The caller also expect just one tuple as -% a result so this function is written way to general for no reason. -% But it works sort of. /kgb - -read_summary(Name, Keys) -> - case file:consult(Name) of - {ok, []} -> - {error, "Empty summary file"}; - {ok, Terms} -> - {ok, lists:map(fun(Key) -> {value, {_, Value}} = - lists:keysearch(Key, 1, Terms), - Value end, - Keys)}; - {error, Reason} -> - {error, Reason} - end. - -count_cases1("=failed" ++ Rest, {Success, _Fail, UserSkip,AutoSkip}) -> - {NextLine, Count} = get_number(Rest), - count_cases1(NextLine, {Success, Count, UserSkip,AutoSkip}); -count_cases1("=successful" ++ Rest, {_Success, Fail, UserSkip,AutoSkip}) -> - {NextLine, Count} = get_number(Rest), - count_cases1(NextLine, {Count, Fail, UserSkip,AutoSkip}); -count_cases1("=skipped" ++ Rest, {Success, Fail, _UserSkip,AutoSkip}) -> - {NextLine, Count} = get_number(Rest), - count_cases1(NextLine, {Success, Fail, Count,AutoSkip}); -count_cases1("=user_skipped" ++ Rest, {Success, Fail, _UserSkip,AutoSkip}) -> - {NextLine, Count} = get_number(Rest), - count_cases1(NextLine, {Success, Fail, Count,AutoSkip}); -count_cases1("=auto_skipped" ++ Rest, {Success, Fail, UserSkip,_AutoSkip}) -> - {NextLine, Count} = get_number(Rest), - count_cases1(NextLine, {Success, Fail, UserSkip,Count}); -count_cases1([], Counters) -> - Counters; -count_cases1(Other, Counters) -> - count_cases1(skip_to_nl(Other), Counters). - -get_number([$\s|Rest]) -> - get_number(Rest); -get_number([Digit|Rest]) when $0 =< Digit, Digit =< $9 -> - get_number(Rest, Digit-$0). - -get_number([Digit|Rest], Acc) when $0 =< Digit, Digit =< $9 -> - get_number(Rest, Acc*10+Digit-$0); -get_number([$\n|Rest], Acc) -> - {Rest, Acc}; -get_number([_|Rest], Acc) -> - get_number(Rest, Acc). - -skip_to_nl([$\n|Rest]) -> - Rest; -skip_to_nl([_|Rest]) -> - skip_to_nl(Rest); -skip_to_nl([]) -> - []. diff --git a/lib/test_server/src/ts_run.erl b/lib/test_server/src/ts_run.erl index e108a22839..57d1b8806e 100644 --- a/lib/test_server/src/ts_run.erl +++ b/lib/test_server/src/ts_run.erl @@ -21,7 +21,7 @@ -module(ts_run). --export([run/4]). +-export([run/4,ct_run_test/2]). -define(DEFAULT_MAKE_TIMETRAP_MINUTES, 60). -define(DEFAULT_UNMAKE_TIMETRAP_MINUTES, 15). @@ -87,6 +87,24 @@ execute([Hook|Rest], Vars0, Spec0, St0) -> execute([], Vars, Spec, St) -> {ok, Vars, Spec, St}. +%% Wrapper to run tests using ct:run_test/1 and handle any errors. + +ct_run_test(Dir, CommonTestArgs) -> + try + ok = file:set_cwd(Dir), + case ct:run_test(CommonTestArgs) of + {_,_,_} -> + ok; + {error,Error} -> + io:format("ERROR: ~P\n", [Error,20]); + Other -> + io:format("~P\n", [Other,20]) + end + catch + _:Crash -> + io:format("CRASH: ~P\n", [Crash,20]) + end. + %% %% Deletes File from Files when File is on the form .../<SUITE>_data/<file> %% when all of <SUITE> has been skipped in Spec, i.e. there @@ -230,8 +248,7 @@ make_command(Vars, Spec, State) -> " -boot start_sasl -sasl errlog_type error", " -pz \"",Cwd,"\"", " -ct_test_vars ",TestVars, - " -eval \"file:set_cwd(\\\"",TestDir,"\\\")\" " - " -eval \"ct:run_test(", + " -eval \"ts_run:ct_run_test(\\\"",TestDir,"\\\", ", backslashify(lists:flatten(State#state.test_server_args)),")\"" " ", ExtraArgs], @@ -363,13 +380,7 @@ make_common_test_args(Args0, Options0, _Vars) -> [{logdir,"../test_server"}] end, - TimeTrap = case test_server:timetrap_scale_factor() of - 1 -> - []; - Scale -> - [{multiply_timetraps, Scale}, - {scale_timetraps, true}] - end, + TimeTrap = [{scale_timetraps, true}], {ConfigPath, Options} = case {os:getenv("TEST_CONFIG_PATH"), diff --git a/lib/test_server/src/ts_selftest.erl b/lib/test_server/src/ts_selftest.erl deleted file mode 100644 index 655aa4bab3..0000000000 --- a/lib/test_server/src/ts_selftest.erl +++ /dev/null @@ -1,120 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1997-2009. All Rights Reserved. -%% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% %CopyrightEnd% -%% --module(ts_selftest). --export([selftest/0]). - -selftest() -> - case node() of - nonode@nohost -> - io:format("Sorry, you have to start this node distributed.~n"), - exit({error, node_not_distributed}); - _ -> - ok - end, - case catch ts:tests(test_server) of - {'EXIT', _} -> - io:format("Test Server self test not availiable."); - Other -> - selftest1() - end. - -selftest1() -> - % Batch starts - io:format("Selftest #1: Whole spec, batch mode:~n"), - io:format("------------------------------------~n"), - ts:run(test_server, [batch]), - ok=check_result(1, "test_server.logs", 2), - - io:format("Selftest #2: One module, batch mode:~n"), - io:format("------------------------------------~n"), - ts:run(test_server, test_server_SUITE, [batch]), - ok=check_result(2, "test_server_SUITE.logs", 2), - - io:format("Selftest #3: One testcase, batch mode:~n"), - io:format("--------------------------------------~n"), - ts:run(test_server, test_server_SUITE, msgs, [batch]), - ok=check_result(3, "test_server_SUITE.logs", 0), - - % Interactive starts - io:format("Selftest #4: Whole spec, interactive mode:~n"), - io:format("------------------------------------------~n"), - ts:run(test_server), - kill_test_server(), - ok=check_result(4, "test_server.logs", 2), - - io:format("Selftest #5: One module, interactive mode:~n"), - io:format("------------------------------------------~n"), - ts:run(test_server, test_server_SUITE), - kill_test_server(), - ok=check_result(5, "test_server_SUITE.logs", 2), - - io:format("Selftest #6: One testcase, interactive mode:~n"), - io:format("--------------------------------------------~n"), - ts:run(test_server, test_server_SUITE, msgs), - kill_test_server(), - ok=check_result(6, "test_server_SUITE.logs", 0), - - ok. - -check_result(Test, TDir, ExpSkip) -> - Dir=ts_lib:last_test(TDir), - {Total, Failed, Skipped}=ts_reports:count_cases(Dir), - io:format("Selftest #~p:",[Test]), - case {Total, Failed, Skipped} of - {_, 0, ExpSkip} -> % 2 test cases should be skipped. - io:format("All ok.~n~n"), - ok; - {_, _, _} -> - io:format("Not completely successful.~n~n"), - error - end. - - -%% Wait for test server to get started. -kill_test_server() -> - Node=list_to_atom("test_server@"++atom_to_list(hostname())), - net_adm:ping(Node), - case whereis(test_server_ctrl) of - undefined -> - kill_test_server(); - Pid -> - kill_test_server(0, Pid) - end. - -%% Wait for test server to finish. -kill_test_server(30, Pid) -> - exit(self(), test_server_is_dead); -kill_test_server(Num, Pid) -> - case whereis(test_server_ctrl) of - undefined -> - slave:stop(node(Pid)); - Pid -> - receive - after - 1000 -> - kill_test_server(Num+1, Pid) - end - end. - - -hostname() -> - list_to_atom(from($@, atom_to_list(node()))). -from(H, [H | T]) -> T; -from(H, [_ | T]) -> from(H, T); -from(H, []) -> []. diff --git a/lib/test_server/test/Makefile b/lib/test_server/test/Makefile index afe5aff196..a3f9820d7f 100644 --- a/lib/test_server/test/Makefile +++ b/lib/test_server/test/Makefile @@ -26,7 +26,6 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk MODULES= \ test_server_SUITE \ - test_server_line_SUITE \ test_server_test_lib ERL_FILES= $(MODULES:%=%.erl) @@ -65,7 +64,6 @@ make_emakefile: >> $(EMAKEFILE) tests debug opt: make_emakefile - cd ../src && $(MAKE) ../ebin/test_server_line.beam erl $(ERL_MAKE_FLAGS) -make clean: diff --git a/lib/test_server/test/test_server_SUITE.erl b/lib/test_server/test/test_server_SUITE.erl index a8532b08ab..d20528d43b 100644 --- a/lib/test_server/test/test_server_SUITE.erl +++ b/lib/test_server/test/test_server_SUITE.erl @@ -92,8 +92,8 @@ test_server_SUITE(Config) -> % rpc:call(Node,dbg, tracer,[]), % rpc:call(Node,dbg, p,[all,c]), % rpc:call(Node,dbg, tpl,[test_server_ctrl,x]), - run_test_server_tests("test_server_SUITE", 39, 1, 31, - 20, 9, 1, 11, 2, 26, Config). + run_test_server_tests("test_server_SUITE", 38, 1, 30, + 19, 9, 1, 11, 2, 25, Config). test_server_parallel01_SUITE(Config) -> run_test_server_tests("test_server_parallel01_SUITE", 37, 0, 19, @@ -120,9 +120,9 @@ run_test_server_tests(SuiteName, NCases, NFail, NExpected, NSucc, NUsrSkip, NAutoSkip, NActualSkip, NActualFail, NActualSucc, Config) -> - ct:log("See test case log files under:~n~p~n", - [filename:join([proplists:get_value(priv_dir, Config), - SuiteName++".logs"])]), + WorkDir = proplists:get_value(work_dir, Config), + ct:log("<a href=\"file://~s\">Test case log files</a>\n", + [filename:join(WorkDir, SuiteName++".logs")]), Node = proplists:get_value(node, Config), {ok,_Pid} = rpc:call(Node,test_server_ctrl, start, []), @@ -138,17 +138,16 @@ run_test_server_tests(SuiteName, NCases, NFail, NExpected, NSucc, rpc:call(Node,test_server_ctrl, stop, []), - {ok,#suite{ n_cases = NCases, - n_cases_failed = NFail, - n_cases_expected = NExpected, - n_cases_succ = NSucc, - n_cases_user_skip = NUsrSkip, - n_cases_auto_skip = NAutoSkip, - cases = Cases }} = Data = - test_server_test_lib:parse_suite( - hd(filelib:wildcard( - filename:join([proplists:get_value(priv_dir, Config), - SuiteName++".logs","run*","suite.log"])))), + {ok,Data} = test_server_test_lib:parse_suite( + hd(filelib:wildcard( + filename:join([WorkDir,SuiteName++".logs", + "run*","suite.log"])))), + check([{"Number of cases",NCases,Data#suite.n_cases}, + {"Number failed",NFail,Data#suite.n_cases_failed}, + {"Number expected",NExpected,Data#suite.n_cases_expected}, + {"Number successful",NSucc,Data#suite.n_cases_succ}, + {"Number user skipped",NUsrSkip,Data#suite.n_cases_user_skip}, + {"Number auto skipped",NAutoSkip,Data#suite.n_cases_auto_skip}], ok), {NActualSkip,NActualFail,NActualSucc} = lists:foldl(fun(#tc{ result = skip },{S,F,Su}) -> {S+1,F,Su}; @@ -156,9 +155,18 @@ run_test_server_tests(SuiteName, NCases, NFail, NExpected, NSucc, {S,F,Su+1}; (#tc{ result = failed },{S,F,Su}) -> {S,F+1,Su} - end,{0,0,0},Cases), + end,{0,0,0},Data#suite.cases), Data. +check([{Str,Same,Same}|T], Status) -> + io:format("~s: ~p\n", [Str,Same]), + check(T, Status); +check([{Str,Expected,Actual}|T], _) -> + io:format("~s: expected ~p, actual ~p\n", [Str,Expected,Actual]), + check(T, error); +check([], ok) -> ok; +check([], error) -> ?t:fail(). + until(Fun) -> case Fun() of true -> diff --git a/lib/test_server/test/test_server_SUITE_data/test_server_SUITE.erl b/lib/test_server/test/test_server_SUITE_data/test_server_SUITE.erl index dfcdff0c3e..ab25e4ad2f 100644 --- a/lib/test_server/test/test_server_SUITE_data/test_server_SUITE.erl +++ b/lib/test_server/test/test_server_SUITE_data/test_server_SUITE.erl @@ -34,7 +34,7 @@ do_times/1, do_times_mfa/1, do_times_fun/1, skip_cases/1, skip_case1/1, skip_case2/1, skip_case3/1, skip_case4/1, skip_case5/1, skip_case6/1, skip_case7/1, - skip_case8/1, skip_case9/1, undefined_functions/1, + skip_case8/1, skip_case9/1, conf_init/1, check_new_conf/1, conf_cleanup/1, check_old_conf/1, conf_init_fail/1, start_stop_node/1, cleanup_nodes_init/1, check_survive_nodes/1, cleanup_nodes_fin/1, @@ -47,7 +47,7 @@ all(suite) -> [config, comment, timetrap, timetrap_cancel, multiply_timetrap, init_per_s, init_per_tc, end_per_tc, timeconv, msgs, capture, timecall, do_times, skip_cases, - undefined_functions, commercial, + commercial, {conf, conf_init, [check_new_conf], conf_cleanup}, check_old_conf, {conf, conf_init_fail,[conf_member_skip],conf_cleanup_skip}, @@ -386,50 +386,6 @@ skip_case9(Config) when is_list(Config) -> %% returning {skip, Reason} from init_per_testcase/2 for this case. ?t:fail("This case should have been Skipped by init_per_testcase/2"). -undefined_functions(suite) -> []; -undefined_functions(doc) -> ["Check for calls to undefined functions in" - " test_server." - "Skip if cover is running"]; -undefined_functions(Config) when is_list(Config) -> - case whereis(cover_server) of - Pid when is_pid(Pid) -> - {skip,"Cover is running"}; - undefined -> - undefined_functions() - end. - -undefined_functions() -> - TestServerDir = filename:dirname(code:which(test_server)), - Res = xref:d(TestServerDir), - - {value,{unused,Unused}} = lists:keysearch(unused, 1, Res), - case Unused of - [] -> ok; - _ -> - lists:foreach(fun (MFA) -> - io:format("~s unused", [format_mfa(MFA)]) - end, Unused) - end, - - {value,{undefined,Undef0}} = lists:keysearch(undefined, 1, Res), - Undef = [U || U <- Undef0, not unresolved(U)], - case Undef of - [] -> ok; - _ -> - lists:foreach(fun ({MFA1,MFA2}) -> - io:format("~s calls undefined ~s", - [format_mfa(MFA1),format_mfa(MFA2)]) - end, Undef), - ?t:fail({length(Undef),undefined_functions_in_otp}) - end, - ok. - -unresolved({_,{_,'$F_EXPR',_}}) -> true; -unresolved(_) -> false. - -format_mfa({M,F,A}) -> - lists:flatten(io_lib:format("~s:~s/~p", [M,F,A])). - conf_init(doc) -> ["Test successful conf case: Change Config parameter"]; conf_init(Config) when is_list(Config) -> [{conf_init_var,1389}|Config]. diff --git a/lib/test_server/test/test_server_line_SUITE.erl b/lib/test_server/test/test_server_line_SUITE.erl deleted file mode 100644 index 0aba54f6b5..0000000000 --- a/lib/test_server/test/test_server_line_SUITE.erl +++ /dev/null @@ -1,131 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2004-2011. All Rights Reserved. -%% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% %CopyrightEnd% -%% - -%%%------------------------------------------------------------------ -%%% Test Server self test. -%%%------------------------------------------------------------------ --module(test_server_line_SUITE). --include_lib("test_server/include/test_server.hrl"). - --export([all/0,suite/0]). --export([init_per_suite/1,end_per_suite/1, - init_per_testcase/2, end_per_testcase/2]). --export([parse_transform/1, lines/1]). - -suite() -> - [{ct_hooks,[ts_install_cth]}, - {doc,["Test of parse transform for collection line numbers"]}]. - -all() -> [parse_transform,lines]. - -init_per_suite(Config) -> - Config. - -end_per_suite(_Config) -> - ok. - -init_per_testcase(_Case, Config) -> - ?line test_server_line:clear(), - Dog = ?t:timetrap(?t:minutes(2)), - [{watchdog, Dog}|Config]. - -end_per_testcase(_Case, Config) -> - ?line test_server_line:clear(), - Dog=?config(watchdog, Config), - ?t:timetrap_cancel(Dog), - ok. - -parse_transform(suite) -> []; -parse_transform(doc) -> []; -parse_transform(Config) when is_list(Config) -> - ?line DataDir = ?config(data_dir,Config), - code:add_pathz(DataDir), - - ?line ok = parse_transform_test:excluded(), - ?line [] = test_server_line:get_lines(), - - ?line test_server_line:clear(), - ?line ok = parse_transform_test:func(), - - ?line [{parse_transform_test,func4,58}, - {parse_transform_test,func,49}, - {parse_transform_test,func3,56}, - {parse_transform_test,func,39}, - {parse_transform_test,func2,54}, - {parse_transform_test,func,36}, - {parse_transform_test,func1,52}, - {parse_transform_test,func,35}] = test_server_line:get_lines(), - - code:del_path(DataDir), - ok. - -lines(suite) -> []; -lines(doc) -> ["Test parse transform for collection line numbers"]; -lines(Config) when is_list(Config) -> - ?line L0 = [{mod,func,1},{mod,func,2},{mod,func,3}, - {m,f,4},{m,f,5},{m,f,6}, - {mo,fu,7},{mo,fu,8},{mo,fu,9}], - ?line LL = string:copies(L0, 1000), - ?line T1 = erlang:now(), - ?line lists:foreach(fun ({M,F,L}) -> - test_server_line:'$test_server_line'(M, F, L) - end, LL), - ?line T2 = erlang:now(), - ?line Long = test_server_line:get_lines(), - ?line test_server_line:clear(), - - ?line T3 = erlang:now(), - ?line lists:foreach(fun ({M,F,L}) -> - test_server_line:'$test_server_lineQ'(M, F, L) - end, LL), - ?line T4 = erlang:now(), - ?line LongQ = test_server_line:get_lines(), - - ?line io:format("'$test_server_line': ~f~n'$test_server_lineQ': ~f~n", - [timer:now_diff(T2, T1)/1000, timer:now_diff(T4, T3)/1000]), - ?line io:format("'$test_server_line' result long:~p~n", [Long]), - ?line io:format("'$test_server_lineQ' result long:~p~n", [LongQ]), - - if Long =:= LongQ -> - ?line ok; - true -> - ?line ?t:fail("The two methods did not produce same result for" - " long lists of lines") - end, - - ?line test_server_line:clear(), - ?line lists:foreach(fun ({M,F,L}) -> - test_server_line:'$test_server_line'(M, F, L) - end, L0), - ?line Short = test_server_line:get_lines(), - ?line test_server_line:clear(), - ?line lists:foreach(fun ({M,F,L}) -> - test_server_line:'$test_server_lineQ'(M, F, L) - end, L0), - ?line ShortQ = test_server_line:get_lines(), - - ?line io:format("'$test_server_line' result short:~p~n", [Short]), - ?line io:format("'$test_server_lineQ' result short:~p~n", [ShortQ]), - - if Short =:= ShortQ -> - ?line ok; - true -> - ?line ?t:fail("The two methods did not produce same result for" - " shot lists of lines\n") - end. diff --git a/lib/test_server/test/test_server_line_SUITE_data/Makefile.src b/lib/test_server/test/test_server_line_SUITE_data/Makefile.src deleted file mode 100644 index a077648934..0000000000 --- a/lib/test_server/test/test_server_line_SUITE_data/Makefile.src +++ /dev/null @@ -1,6 +0,0 @@ -EFLAGS=+debug_info -pa ../../test_server -I../../test_server - -all: parse_transform_test.@EMULATOR@ - -parse_transform_test.@EMULATOR@: parse_transform_test.erl - erlc $(EFLAGS) parse_transform_test.erl diff --git a/lib/test_server/test/test_server_line_SUITE_data/parse_transform_test.erl b/lib/test_server/test/test_server_line_SUITE_data/parse_transform_test.erl deleted file mode 100644 index 8f3477d3ac..0000000000 --- a/lib/test_server/test/test_server_line_SUITE_data/parse_transform_test.erl +++ /dev/null @@ -1,59 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2004-2011. All Rights Reserved. -%% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% %CopyrightEnd% -%% --module(parse_transform_test). - --include("test_server_line.hrl"). --no_lines([{excluded,0}]). - --export([excluded/0, func/0]). - - -excluded() -> - line1, - line2, - ok. - - -func() -> - hello, - func1(), - case func2() of - ok -> - helloagain, - case func3() of - ok -> - ok; - error -> - error - end; - error -> - error - end, - excluded(), - func4(). - -func1() -> - ok. -func2() -> - ok. -func3() -> - error. -func4() -> - ok. - diff --git a/lib/test_server/test/test_server_test_lib.erl b/lib/test_server/test/test_server_test_lib.erl index 5ca24f3df7..8a808c5360 100644 --- a/lib/test_server/test/test_server_test_lib.erl +++ b/lib/test_server/test/test_server_test_lib.erl @@ -54,9 +54,13 @@ start_slave(Config,_Level) -> ok end, DataDir = proplists:get_value(data_dir, Config), - PrivDir = proplists:get_value(priv_dir, Config), + %% We would normally use priv_dir for temporary data, + %% but the pathnames gets too long on Windows. + %% Until the run-time system can support long pathnames, + %% use the data dir. + WorkDir = DataDir, - %% PrivDir as well as directory of Test Server suites + %% WorkDir as well as directory of Test Server suites %% have to be in code path on Test Server node. [_ | Parts] = lists:reverse(filename:split(DataDir)), TSDir = filename:join(lists:reverse(Parts)), @@ -64,7 +68,7 @@ start_slave(Config,_Level) -> undefined -> []; Ds -> Ds end, - PathDirs = [PrivDir,TSDir | AddPathDirs], + PathDirs = [WorkDir,TSDir | AddPathDirs], [true = rpc:call(Node, code, add_patha, [D]) || D <- PathDirs], io:format("Dirs added to code path (on ~w):~n", [Node]), @@ -73,8 +77,8 @@ start_slave(Config,_Level) -> true = rpc:call(Node, os, putenv, ["TEST_SERVER_FRAMEWORK", "undefined"]), - ok = rpc:call(Node, file, set_cwd, [PrivDir]), - [{node,Node} | Config] + ok = rpc:call(Node, file, set_cwd, [WorkDir]), + [{node,Node}, {work_dir,WorkDir} | Config] end. post_end_per_testcase(_TC, Config, Return, State) -> |