您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

159 行
5.0 KiB

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