1-module(cth_retry). 2 3%% Callbacks 4-export([id/1]). 5-export([init/2]). 6 7-export([pre_init_per_suite/3]). 8-export([post_init_per_suite/4]). 9-export([pre_end_per_suite/3]). 10-export([post_end_per_suite/4]). 11 12-export([pre_init_per_group/3]). 13-export([post_init_per_group/4]). 14-export([pre_end_per_group/3]). 15-export([post_end_per_group/4]). 16 17-export([pre_init_per_testcase/3]). 18-export([post_end_per_testcase/4]). 19 20-export([on_tc_fail/3]). 21-export([on_tc_skip/3, on_tc_skip/4]). 22 23-export([terminate/1]). 24 25-record(state, {id, suite, groups, acc=[]}). 26 27%% @doc Return a unique id for this CTH. 28id(_Opts) -> 29 {?MODULE, make_ref()}. 30 31%% @doc Always called before any other callback function. Use this to initiate 32%% any common state. 33init(Id, _Opts) -> 34 {ok, #state{id=Id}}. 35 36%% @doc Called before init_per_suite is called. 37pre_init_per_suite(Suite,Config,State) -> 38 {Config, State#state{suite=Suite, groups=[]}}. 39 40%% @doc Called after init_per_suite. 41post_init_per_suite(_Suite,_Config,Return,State) -> 42 {Return, State}. 43 44%% @doc Called before end_per_suite. 45pre_end_per_suite(_Suite,Config,State) -> 46 {Config, State}. 47 48%% @doc Called after end_per_suite. 49post_end_per_suite(_Suite,_Config,Return,State) -> 50 {Return, State#state{suite=undefined, groups=[]}}. 51 52%% @doc Called before each init_per_group. 53pre_init_per_group(_Group,Config,State) -> 54 {Config, State}. 55 56%% @doc Called after each init_per_group. 57post_init_per_group(Group,_Config,Return, State=#state{groups=Groups}) -> 58 {Return, State#state{groups=[Group|Groups]}}. 59 60%% @doc Called after each end_per_group. 61pre_end_per_group(_Group,Config,State) -> 62 {Config, State}. 63 64%% @doc Called after each end_per_group. 65post_end_per_group(_Group,_Config,Return, State=#state{groups=Groups}) -> 66 {Return, State#state{groups=tl(Groups)}}. 67 68%% @doc Called before each test case. 69pre_init_per_testcase(_TC,Config,State) -> 70 {Config, State}. 71 72%% @doc Called after each test case. 73post_end_per_testcase(_TC,_Config,ok,State) -> 74 {ok, State}; 75post_end_per_testcase(TC,_Config,Error,State=#state{suite=Suite, groups=Groups, acc=Acc}) -> 76 Test = case TC of 77 {_Group, Case} -> Case; 78 TC -> TC 79 end, 80 {Error, State#state{acc=[{Suite, Groups, Test}|Acc]}}. 81 82%% @doc Called after post_init_per_suite, post_end_per_suite, post_init_per_group, 83%% post_end_per_group and post_end_per_testcase if the suite, group or test case failed. 84on_tc_fail(_TC, _Reason, State) -> 85 State. 86 87%% @doc Called when a test case is skipped by either user action 88%% or due to an init function failing. (>= 19.3) 89on_tc_skip(Suite, TC, {tc_auto_skip, _}, State=#state{suite=Suite, groups=Groups, acc=Acc}) -> 90 NewAcc = case TC of 91 init_per_testcase -> Acc; 92 end_per_testcase -> Acc; 93 {init_per_group,_} -> Acc; 94 {end_per_group, _} -> Acc; 95 init_per_suite -> Acc; 96 end_per_suite -> Acc; 97 {Case, _Group} -> [{Suite, Groups, Case}|Acc]; 98 TC -> [{Suite, Groups, TC}|Acc] 99 end, 100 State#state{suite=Suite, acc=NewAcc}; 101on_tc_skip(Suite, _TC, _Reason, State) -> 102 State#state{suite=Suite}. 103 104%% @doc Called when a test case is skipped by either user action 105%% or due to an init function failing. (Pre-19.3) 106on_tc_skip(TC, {tc_auto_skip, _}, State=#state{suite=Suite, groups=Groups, acc=Acc}) -> 107 NewAcc = case TC of 108 init_per_testcase -> Acc; 109 end_per_testcase -> Acc; 110 {init_per_group,_} -> Acc; 111 {end_per_group, _} -> Acc; 112 init_per_suite -> Acc; 113 end_per_suite -> Acc; 114 {Case, _Group} -> [{Suite, Groups, Case}|Acc]; 115 TC -> [{Suite, Groups, TC}|Acc] 116 end, 117 State#state{acc=NewAcc}; 118on_tc_skip(_TC, _Reason, State) -> 119 State. 120 121%% @doc Called when the scope of the CTH is done 122terminate(#state{acc=[]}) -> 123 ok; 124terminate(#state{acc=Acc}) -> 125 Spec = to_spec(Acc), 126 {ok, Cwd} = file:get_cwd(), 127 Path = filename:join(lists:droplast(filename:split(Cwd))++["retry.spec"]), 128 io:format(user, 129 "EXPERIMENTAL: Writing retry specification at ~s~n" 130 " call rebar3 ct with '--retry' to re-run failing cases.~n", 131 [Path]), 132 file:write_file(Path, Spec), 133 ok. 134 135%%% Helpers 136to_spec(List) -> 137 [to_spec_entry(X) || X <- merge(List)]. 138 139merge([]) -> []; 140merge([{Suite, Groups, Case}|T]) when is_atom(Case) -> 141 merge([{Suite, Groups, [Case]}|T]); 142merge([{Suite, Groups, Cases}, {Suite, Groups, Case} | T]) -> 143 merge([{Suite, Groups, [Case|Cases]}|T]); 144merge([{Suite, Groups, Cases} | T]) -> 145 [{Suite, Groups, Cases} | merge(T)]. 146 147to_spec_entry({Suite, [], Cases}) -> 148 Dir = filename:dirname(proplists:get_value(source, Suite:module_info(compile))), 149 io_lib:format("~p.~n", [{cases, Dir, Suite, Cases}]); 150to_spec_entry({Suite, Groups, Cases}) -> 151 Dir = filename:dirname(proplists:get_value(source, Suite:module_info(compile))), 152 ExpandedGroups = expand_groups(lists:reverse(Groups)), 153 io_lib:format("~p.~n", [{groups, Dir, Suite, ExpandedGroups, {cases,Cases}}]). 154 155expand_groups([Group]) -> 156 {Group, []}; 157expand_groups([H|T]) -> 158 {H,[],[expand_groups(T)]}. 159 160