%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2007-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%
%%
%%% @doc Common Test Framework module that handles repeated test runs
%%%
%%% <p>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
%%% -repeat <N>, N = integer()</p>
-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;
{error,E} ->
io:format("Common Test error: ~p\n\n",[E]),
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],
loop(If,repeat,0,N,undefined,Args1,undefined),
file:set_cwd(Cwd);
{stop_time,StopTime} ->
case remaining_time(StopTime) of
0 ->
io:format("\nCommon Test: No time left to run tests.\n\n",[]),
ok;
Secs ->
io:format("\nCommon Test: Will repeat tests for ~s.\n\n",
[ts(Secs)]),
TPid =
case lists:keymember(force_stop,1,Args) of
true ->
CtrlPid = self(),
spawn(fun() -> stop_after(CtrlPid,Secs) end);
false ->
undefined
end,
Args1 = [{loop_info,[{stop_time,Secs,StopTime,1}]} | Args],
loop(If,stop_time,0,Secs,StopTime,Args1,TPid)
end,
file:set_cwd(Cwd)
end.
loop(_,repeat,N,N,_,_Args,_) ->
ok;
loop(If,Type,N,Data0,Data1,Args,TPid) ->
Pid = spawn_tester(If,self(),Args),
receive
{'EXIT',Pid,Reason} ->
io:format("Test run crashed! This could be an internal error "
"- please report!\n\n"
"~p\n\n",[Reason]),
cancel(TPid),
{error,Reason};
{Pid,{error,Reason}} ->
io:format("\nTest run failed!\nReason: ~p\n\n",[Reason]),
cancel(TPid),
{error,Reason};
{Pid,Result} ->
if Type == repeat ->
io:format("\nTest run ~w(~w) complete.\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);
Type == stop_time ->
case remaining_time(Data1) of
0 ->
io:format("\nTest time (~s) has run out.\n\n",[ts(Data0)]),
cancel(TPid),
Result;
Secs ->
io:format("\n~s of test time remaining, "
"starting run #~w...\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)
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() ->
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) ->
timer:sleep(Secs*1000),
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 lists:keymember(force_stop,1,Args) of
true ->
io_lib:format("force_stop is enabled",[]);
_ ->
""
end,
ct_logs:log("Test loop info",LogStr1++LogStr2++LogStr3++LogStr4,[])
end.
ts(Secs) ->
integer_to_list(Secs) ++ " secs".
delistify([X]) ->
X;
delistify(X) ->
X.