|
|
@ -0,0 +1,159 @@ |
|
|
|
-module(cth_retry). |
|
|
|
|
|
|
|
%% Callbacks |
|
|
|
-export([id/1]). |
|
|
|
-export([init/2]). |
|
|
|
|
|
|
|
-export([pre_init_per_suite/3]). |
|
|
|
-export([post_init_per_suite/4]). |
|
|
|
-export([pre_end_per_suite/3]). |
|
|
|
-export([post_end_per_suite/4]). |
|
|
|
|
|
|
|
-export([pre_init_per_group/3]). |
|
|
|
-export([post_init_per_group/4]). |
|
|
|
-export([pre_end_per_group/3]). |
|
|
|
-export([post_end_per_group/4]). |
|
|
|
|
|
|
|
-export([pre_init_per_testcase/3]). |
|
|
|
-export([post_end_per_testcase/4]). |
|
|
|
|
|
|
|
-export([on_tc_fail/3]). |
|
|
|
-export([on_tc_skip/3, on_tc_skip/4]). |
|
|
|
|
|
|
|
-export([terminate/1]). |
|
|
|
|
|
|
|
-record(state, {id, suite, groups, acc=[]}). |
|
|
|
|
|
|
|
%% @doc Return a unique id for this CTH. |
|
|
|
id(_Opts) -> |
|
|
|
{?MODULE, make_ref()}. |
|
|
|
|
|
|
|
%% @doc Always called before any other callback function. Use this to initiate |
|
|
|
%% any common state. |
|
|
|
init(Id, _Opts) -> |
|
|
|
{ok, #state{id=Id}}. |
|
|
|
|
|
|
|
%% @doc Called before init_per_suite is called. |
|
|
|
pre_init_per_suite(Suite,Config,State) -> |
|
|
|
{Config, State#state{suite=Suite, groups=[]}}. |
|
|
|
|
|
|
|
%% @doc Called after init_per_suite. |
|
|
|
post_init_per_suite(_Suite,_Config,Return,State) -> |
|
|
|
{Return, State}. |
|
|
|
|
|
|
|
%% @doc Called before end_per_suite. |
|
|
|
pre_end_per_suite(_Suite,Config,State) -> |
|
|
|
{Config, State}. |
|
|
|
|
|
|
|
%% @doc Called after end_per_suite. |
|
|
|
post_end_per_suite(_Suite,_Config,Return,State) -> |
|
|
|
{Return, State#state{suite=undefined, groups=[]}}. |
|
|
|
|
|
|
|
%% @doc Called before each init_per_group. |
|
|
|
pre_init_per_group(_Group,Config,State) -> |
|
|
|
{Config, State}. |
|
|
|
|
|
|
|
%% @doc Called after each init_per_group. |
|
|
|
post_init_per_group(Group,_Config,Return, State=#state{groups=Groups}) -> |
|
|
|
{Return, State#state{groups=[Group|Groups]}}. |
|
|
|
|
|
|
|
%% @doc Called after each end_per_group. |
|
|
|
pre_end_per_group(_Group,Config,State) -> |
|
|
|
{Config, State}. |
|
|
|
|
|
|
|
%% @doc Called after each end_per_group. |
|
|
|
post_end_per_group(_Group,_Config,Return, State=#state{groups=Groups}) -> |
|
|
|
{Return, State#state{groups=tl(Groups)}}. |
|
|
|
|
|
|
|
%% @doc Called before each test case. |
|
|
|
pre_init_per_testcase(_TC,Config,State) -> |
|
|
|
{Config, State}. |
|
|
|
|
|
|
|
%% @doc Called after each test case. |
|
|
|
post_end_per_testcase(_TC,_Config,ok,State) -> |
|
|
|
{ok, State}; |
|
|
|
post_end_per_testcase(TC,_Config,Error,State=#state{suite=Suite, groups=Groups, acc=Acc}) -> |
|
|
|
Test = case TC of |
|
|
|
{_Group, Case} -> Case; |
|
|
|
TC -> TC |
|
|
|
end, |
|
|
|
{Error, State#state{acc=[{Suite, Groups, Test}|Acc]}}. |
|
|
|
|
|
|
|
%% @doc Called after post_init_per_suite, post_end_per_suite, post_init_per_group, |
|
|
|
%% post_end_per_group and post_end_per_testcase if the suite, group or test case failed. |
|
|
|
on_tc_fail(_TC, _Reason, State) -> |
|
|
|
State. |
|
|
|
|
|
|
|
%% @doc Called when a test case is skipped by either user action |
|
|
|
%% or due to an init function failing. (>= 19.3) |
|
|
|
on_tc_skip(Suite, TC, {tc_auto_skip, _}, State=#state{suite=Suite, groups=Groups, acc=Acc}) -> |
|
|
|
NewAcc = case TC of |
|
|
|
init_per_testcase -> Acc; |
|
|
|
end_per_testcase -> Acc; |
|
|
|
{init_per_group,_} -> Acc; |
|
|
|
{end_per_group, _} -> Acc; |
|
|
|
init_per_suite -> Acc; |
|
|
|
end_per_suite -> Acc; |
|
|
|
{_Group, Case} -> [{Suite, Groups, Case}|Acc]; |
|
|
|
TC -> [{Suite, Groups, TC}|Acc] |
|
|
|
end, |
|
|
|
State#state{suite=Suite, acc=NewAcc}; |
|
|
|
on_tc_skip(Suite, _TC, _Reason, State) -> |
|
|
|
State#state{suite=Suite}. |
|
|
|
|
|
|
|
%% @doc Called when a test case is skipped by either user action |
|
|
|
%% or due to an init function failing. (Pre-19.3) |
|
|
|
on_tc_skip(TC, {tc_auto_skip, _}, State=#state{suite=Suite, groups=Groups, acc=Acc}) -> |
|
|
|
NewAcc = case TC of |
|
|
|
init_per_testcase -> Acc; |
|
|
|
end_per_testcase -> Acc; |
|
|
|
{init_per_group,_} -> Acc; |
|
|
|
{end_per_group, _} -> Acc; |
|
|
|
init_per_suite -> Acc; |
|
|
|
end_per_suite -> Acc; |
|
|
|
{_Group, Case} -> [{Suite, Groups, Case}|Acc]; |
|
|
|
TC -> [{Suite, Groups, TC}|Acc] |
|
|
|
end, |
|
|
|
State#state{acc=NewAcc}; |
|
|
|
on_tc_skip(_TC, _Reason, State) -> |
|
|
|
State. |
|
|
|
|
|
|
|
%% @doc Called when the scope of the CTH is done |
|
|
|
terminate(#state{acc=[]}) -> |
|
|
|
ok; |
|
|
|
terminate(#state{acc=Acc}) -> |
|
|
|
Spec = to_spec(Acc), |
|
|
|
{ok, Cwd} = file:get_cwd(), |
|
|
|
Path = filename:join(lists:droplast(filename:split(Cwd))++["retry.spec"]), |
|
|
|
io:format(user, |
|
|
|
"EXPERIMENTAL: Writing retry specification at ~s~n" |
|
|
|
" call rebar3 ct with '--retry' to re-run failing cases.~n", |
|
|
|
[Path]), |
|
|
|
file:write_file(Path, Spec), |
|
|
|
ok. |
|
|
|
|
|
|
|
%%% Helpers |
|
|
|
to_spec(List) -> |
|
|
|
[to_spec_entry(X) || X <- merge(List)]. |
|
|
|
|
|
|
|
merge([]) -> []; |
|
|
|
merge([{Suite, Groups, Case}|T]) when is_atom(Case) -> |
|
|
|
merge([{Suite, Groups, [Case]}|T]); |
|
|
|
merge([{Suite, Groups, Cases}, {Suite, Groups, Case} | T]) -> |
|
|
|
merge([{Suite, Groups, [Case|Cases]}|T]); |
|
|
|
merge([{Suite, Groups, Cases} | T]) -> |
|
|
|
[{Suite, Groups, Cases} | merge(T)]. |
|
|
|
|
|
|
|
to_spec_entry({Suite, [], Cases}) -> |
|
|
|
Dir = filename:dirname(proplists:get_value(source, Suite:module_info(compile))), |
|
|
|
io_lib:format("~p.~n", [{cases, Dir, Suite, Cases}]); |
|
|
|
to_spec_entry({Suite, Groups, Cases}) -> |
|
|
|
Dir = filename:dirname(proplists:get_value(source, Suite:module_info(compile))), |
|
|
|
ExpandedGroups = expand_groups(lists:reverse(Groups)), |
|
|
|
io_lib:format("~p.~n", [{groups, Dir, Suite, ExpandedGroups, {cases,Cases}}]). |
|
|
|
|
|
|
|
expand_groups([Group]) -> |
|
|
|
{Group, []}; |
|
|
|
expand_groups([H|T]) -> |
|
|
|
{H,[],[expand_groups(T)]}. |
|
|
|
|