%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2007-2018. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
%%% doc Common Test Framework module that handles repeated test runs
%%%
%%% This module exports functions for repeating tests. The following
%%% start flags (or equivalent ct:run_test/1 options) are supported:
%%% -until <StopTime>, StopTime = YYMoMoDDHHMMSS | HHMMSS
%%% -duration <DurTime>, DurTime = HHMMSS
%%% -force_stop [skip_rest]
%%% -repeat <N>, N = integer()
-module(ct_repeat).
%% Script interface
-export([loop_test/2]).
-export([log_loop_info/1]).
%%----------------------------------------------------------
%% Flags:
%%----------------------------------------------------------
loop_test(If,Args) when is_list(Args) ->
{ok,Cwd} = file:get_cwd(),
case get_loop_info(Args) of
no_loop ->
false;
E = {error,_} ->
io:format("Common Test error: ~tp\n\n",[E]),
ok = file:set_cwd(Cwd),
E;
{repeat,N} ->
io:format("\nCommon Test: Will repeat tests ~w times.\n\n",[N]),
Args1 = [{loop_info,[{repeat,1,N}]} | Args],
Result = loop(If,repeat,0,N,undefined,Args1,undefined,[]),
ok = file:set_cwd(Cwd),
Result;
{stop_time,StopTime} ->
Result =
case remaining_time(StopTime) of
0 ->
io:format("\nCommon Test: "
"No time left to run tests.\n\n",[]),
{error,not_enough_time};
Secs ->
io:format("\nCommon Test: "
"Will repeat tests for ~s.\n\n",[ts(Secs)]),
TPid =
case proplists:get_value(force_stop,Args) of
False when False==false; False==undefined ->
undefined;
ForceStop ->
CtrlPid = self(),
spawn(
fun() ->
ct_util:mark_process(),
stop_after(CtrlPid,Secs,ForceStop)
end)
end,
Args1 = [{loop_info,[{stop_time,Secs,StopTime,1}]} | Args],
loop(If,stop_time,0,Secs,StopTime,Args1,TPid,[])
end,
ok = file:set_cwd(Cwd),
Result
end.
loop(_,repeat,N,N,_,_Args,_,AccResult) ->
lists:reverse(AccResult);
loop(If,Type,N,Data0,Data1,Args,TPid,AccResult) ->
Pid = spawn_tester(If,self(),Args),
receive
{'EXIT',Pid,Reason} ->
case Reason of
{user_error,What} ->
io:format("\nTest run failed!\nReason: ~tp\n\n\n", [What]),
cancel(TPid),
{error,What};
_ ->
io:format("Test run crashed! This could be an internal error "
"- please report!\n\n"
"~tp\n\n\n",[Reason]),
cancel(TPid),
{error,Reason}
end;
{Pid,{error,Reason}} ->
io:format("\nTest run failed!\nReason: ~tp\n\n\n",[Reason]),
cancel(TPid),
{error,Reason};
{Pid,Result} ->
if Type == repeat ->
io:format("\nTest run ~w(~w) complete.\n\n\n",[N+1,Data0]),
lists:keydelete(loop_info,1,Args),
Args1 = [{loop_info,[{repeat,N+2,Data0}]} | Args],
loop(If,repeat,N+1,Data0,Data1,Args1,TPid,[Result|AccResult]);
Type == stop_time ->
case remaining_time(Data1) of
0 ->
io:format("\nTest time (~s) has run out.\n\n\n",
[ts(Data0)]),
cancel(TPid),
lists:reverse([Result|AccResult]);
Secs ->
io:format("\n~s of test time remaining, "
"starting run #~w...\n\n\n",
[ts(Secs),N+2]),
lists:keydelete(loop_info,1,Args),
ST = {stop_time,Data0,Data1,N+2},
Args1 = [{loop_info,[ST]} | Args],
loop(If,stop_time,N+1,Data0,Data1,Args1,TPid,
[Result|AccResult])
end
end
end.
spawn_tester(script,Ctrl,Args) ->
spawn_link(fun() -> ct_run:script_start1(Ctrl,Args) end);
spawn_tester(func,Ctrl,Opts) ->
Tester = fun() ->
ct_util:mark_process(),
case catch ct_run:run_test2(Opts) of
{'EXIT',Reason} ->
exit(Reason);
Result ->
Ctrl ! {self(),Result}
end
end,
spawn_link(Tester).
remaining_time(StopTime) ->
Now = calendar:datetime_to_gregorian_seconds(calendar:local_time()),
Diff = StopTime - Now,
if Diff > 0 ->
Diff;
true ->
0
end.
get_loop_info(Args) when is_list(Args) ->
case lists:keysearch(until,1,Args) of
{value,{until,Time}} ->
Time1 = delistify(Time),
case catch get_stop_time(until,Time1) of
{'EXIT',_} ->
{error,{bad_time_format,Time1}};
Stop ->
{stop_time,Stop}
end;
false ->
case lists:keysearch(duration,1,Args) of
{value,{duration,Time}} ->
Time1 = delistify(Time),
case catch get_stop_time(duration,Time1) of
{'EXIT',_} ->
{error,{bad_time_format,Time1}};
Stop ->
{stop_time,Stop}
end;
false ->
case lists:keysearch(repeat,1,Args) of
{value,{repeat,R}} ->
case R of
N when is_integer(N), N>0 ->
{repeat,N};
[Str] ->
case catch list_to_integer(Str) of
N when is_integer(N), N>0 ->
{repeat,N};
_ ->
{error,{invalid_repeat_value,Str}}
end;
_ ->
{error,{invalid_repeat_value,R}}
end;
false ->
no_loop
end
end
end.
get_stop_time(until,[Y1,Y2,Mo1,Mo2,D1,D2,H1,H2,Mi1,Mi2,S1,S2]) ->
Date =
case [Mo1,Mo2] of
"00" ->
date();
_ ->
Y = list_to_integer([Y1,Y2]),
Mo = list_to_integer([Mo1,Mo2]),
D = list_to_integer([D1,D2]),
{YNow,_,_} = date(),
Dec = trunc(YNow/100),
Year =
if Y < (YNow-Dec*100) -> (Dec+1)*100 + Y;
true -> Dec*100 + Y
end,
{Year,Mo,D}
end,
Time = {list_to_integer([H1,H2]),
list_to_integer([Mi1,Mi2]),
list_to_integer([S1,S2])},
calendar:datetime_to_gregorian_seconds({Date,Time});
get_stop_time(until,Time=[_,_,_,_,_,_]) ->
get_stop_time(until,"000000"++Time);
get_stop_time(duration,[H1,H2,Mi1,Mi2,S1,S2]) ->
Secs =
list_to_integer([H1,H2]) * 3600 +
list_to_integer([Mi1,Mi2]) * 60 +
list_to_integer([S1,S2]),
calendar:datetime_to_gregorian_seconds(calendar:local_time()) + Secs.
cancel(Pid) ->
catch exit(Pid,kill).
%% After Secs, abort will make the test_server finish the current
%% job, then empty the job queue and stop.
stop_after(_CtrlPid,Secs,ForceStop) ->
timer:sleep(Secs*1000),
case ForceStop of
SkipRest when SkipRest==skip_rest; SkipRest==["skip_rest"] ->
ct_util:set_testdata({skip_rest,true});
_ ->
ok
end,
test_server_ctrl:abort().
%% Callback from ct_run to print loop info to system log.
log_loop_info(Args) ->
case lists:keysearch(loop_info,1,Args) of
false ->
ok;
{value,{_,[{repeat,C,N}]}} ->
ct_logs:log("Test loop info","Test run ~w of ~w",[C,N]);
{value,{_,[{stop_time,Secs0,StopTime,N}]}} ->
LogStr1 =
case lists:keysearch(duration,1,Args) of
{value,{_,Dur}} ->
io_lib:format("Specified test duration: ~s (~w secs)\n",
[delistify(Dur),Secs0]);
_ ->
case lists:keysearch(until,1,Args) of
{value,{_,Until}} ->
io_lib:format("Specified end time: ~s (duration ~w secs)\n",
[delistify(Until),Secs0]);
_ ->
ok
end
end,
LogStr2 = io_lib:format("Test run #~w\n", [N]),
Secs = remaining_time(StopTime),
LogStr3 =
io_lib:format("Test time remaining: ~w secs (~w%)\n",
[Secs,trunc((Secs/Secs0)*100)]),
LogStr4 =
case proplists:get_value(force_stop,Args) of
False when False==false; False==undefined ->
"";
ForceStop ->
io_lib:format("force_stop is set to: ~w",[ForceStop])
end,
ct_logs:log("Test loop info","~ts", [LogStr1++LogStr2++LogStr3++LogStr4])
end.
ts(Secs) ->
integer_to_list(Secs) ++ " secs".
delistify([X]) ->
X;
delistify(X) ->
X.