Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

347 wiersze
14 KiB

10 lat temu
10 lat temu
10 lat temu
10 lat temu
10 lat temu
10 lat temu
10 lat temu
10 lat temu
10 lat temu
10 lat temu
10 lat temu
10 lat temu
10 lat temu
10 lat temu
10 lat temu
  1. %% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
  2. %% ex: ts=4 sw=4 et
  3. -module(rebar_prv_common_test).
  4. -behaviour(provider).
  5. -export([init/1,
  6. do/1,
  7. format_error/1]).
  8. -include("rebar.hrl").
  9. -define(PROVIDER, ct).
  10. -define(DEPS, [compile]).
  11. %% ===================================================================
  12. %% Public API
  13. %% ===================================================================
  14. -spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
  15. init(State) ->
  16. Provider = providers:create([{name, ?PROVIDER},
  17. {module, ?MODULE},
  18. {deps, ?DEPS},
  19. {bare, false},
  20. {example, "rebar ct"},
  21. {short_desc, "Run Common Tests."},
  22. {desc, ""},
  23. {opts, ct_opts(State)},
  24. {profiles, [test]}]),
  25. State1 = rebar_state:add_provider(State, Provider),
  26. {ok, State1}.
  27. -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
  28. do(State) ->
  29. ?INFO("Running Common Test suites...", []),
  30. {RawOpts, _} = rebar_state:command_parsed_args(State),
  31. {InDirs, OutDir} = split_ct_dirs(State, RawOpts),
  32. Opts = transform_opts(RawOpts),
  33. TestApps = filter_checkouts(rebar_state:project_apps(State)),
  34. ok = create_dirs(Opts),
  35. ?DEBUG("Compiling Common Test suites in: ~p", [OutDir]),
  36. lists:foreach(fun(App) ->
  37. AppDir = rebar_app_info:dir(App),
  38. C = rebar_config:consult(AppDir),
  39. S = rebar_state:new(State, C, AppDir),
  40. %% combine `erl_first_files` and `common_test_first_files` and
  41. %% adjust compile opts to include `common_test_compile_opts`
  42. %% and `{src_dirs, "test"}`
  43. TestState = test_state(S, InDirs, OutDir),
  44. ok = rebar_erlc_compiler:compile(TestState, AppDir)
  45. end, TestApps),
  46. ok = maybe_compile_extra_tests(TestApps, State, InDirs, OutDir),
  47. Path = code:get_path(),
  48. true = code:add_patha(OutDir),
  49. CTOpts = resolve_ct_opts(State, Opts, OutDir),
  50. Result = handle_results(ct:run_test(CTOpts)),
  51. true = code:set_path(Path),
  52. case Result of
  53. {error, Reason} ->
  54. {error, {?MODULE, Reason}};
  55. ok ->
  56. {ok, State}
  57. end.
  58. -spec format_error(any()) -> iolist().
  59. format_error({failures_running_tests, FailedCount}) ->
  60. io_lib:format("Failures occured running tests: ~p", [FailedCount]);
  61. format_error({error_running_tests, Reason}) ->
  62. io_lib:format("Error running tests: ~p", [Reason]).
  63. ct_opts(State) ->
  64. DefaultLogsDir = filename:join([rebar_state:dir(State), "logs"]),
  65. [{dir, undefined, "dir", string, help(dir)}, %% comma-seperated list
  66. {outdir, undefined, "outdir", string, help(outdir)}, %% string
  67. {suite, undefined, "suite", string, help(suite)}, %% comma-seperated list
  68. {group, undefined, "group", string, help(group)}, %% comma-seperated list
  69. {testcase, undefined, "case", string, help(testcase)}, %% comma-seperated list
  70. {spec, undefined, "spec", string, help(spec)}, %% comma-seperated list
  71. {join_specs, undefined, "join_specs", boolean, help(join_specs)}, %% Boolean
  72. {label, undefined, "label", string, help(label)}, %% String
  73. {config, undefined, "config", string, help(config)}, %% comma-seperated list
  74. {userconfig, undefined, "userconfig", string, help(userconfig)}, %% [{CallbackMod, CfgStrings}] | {CallbackMod, CfgStrings}
  75. {allow_user_terms, undefined, "allow_user_terms", boolean, help(allow_user_terms)}, %% Bool
  76. {logdir, undefined, "logdir", {string, DefaultLogsDir}, help(logdir)}, %% dir
  77. {logopts, undefined, "logopts", string, help(logopts)}, %% enum, no_nl | no_src
  78. {verbosity, undefined, "verbosity", string, help(verbosity)}, %% Integer OR [{Category, VLevel}]
  79. {silent_connections, undefined, "silent_connections", string,
  80. help(silent_connections)}, % all OR %% comma-seperated list
  81. {stylesheet, undefined, "stylesheet", string, help(stylesheet)}, %% file
  82. {cover, undefined, "cover", string, help(cover)}, %% file
  83. {cover_stop, undefined, "cover_stop", boolean, help(cover_stop)}, %% Boolean
  84. {event_handler, undefined, "event_handler", string, help(event_handler)}, %% EH | [EH] WHERE EH atom() | {atom(), InitArgs} | {[atom()], InitArgs}
  85. {include, undefined, "include", string, help(include)}, % comma-seperated list
  86. {abort_if_missing_suites, undefined, "abort_if_missing_suites", {boolean, true},
  87. help(abort_if_missing_suites)}, %% boolean
  88. {multiply_timetraps, undefined, "multiply_timetraps", integer,
  89. help(multiply_timetraps)}, %% integer
  90. {scale_timetraps, undefined, "scale_timetraps", boolean, help(scale_timetraps)}, %% Boolean
  91. {create_priv_dir, undefined, "create_priv_dir", string, help(create_priv_dir)}, %% enum: auto_per_run | auto_per_tc | manual_per_tc
  92. {repeat, undefined, "repeat", integer, help(repeat)}, %% integer
  93. {duration, undefined, "duration", string, help(duration)}, % format: HHMMSS
  94. {until, undefined, "until", string, help(until)}, %% format: YYMoMoDD[HHMMSS]
  95. {force_stop, undefined, "force_stop", string, help(force_stop)}, % enum: skip_rest, bool
  96. {basic_html, undefined, "basic_html", boolean, help(basic_html)}, %% Booloean
  97. {ct_hooks, undefined, "ct_hooks", string, help(ct_hooks)} %% List: [CTHModule | {CTHModule, CTHInitArgs}] where CTHModule is atom CthInitArgs is term
  98. ].
  99. help(outdir) ->
  100. "Output directory for compiled modules";
  101. help(dir) ->
  102. "List of additional directories containing test suites";
  103. help(suite) ->
  104. "List of test suites to run";
  105. help(group) ->
  106. "List of test groups to run";
  107. help(testcase) ->
  108. "List of test cases to run";
  109. help(spec) ->
  110. "List of test specs to run";
  111. help(join_specs) ->
  112. ""; %% ??
  113. help(label) ->
  114. "Test label";
  115. help(config) ->
  116. "List of config files";
  117. help(allow_user_terms) ->
  118. ""; %% ??
  119. help(logdir) ->
  120. "Log folder";
  121. help(logopts) ->
  122. ""; %% ??
  123. help(verbosity) ->
  124. "Verbosity";
  125. help(silent_connections) ->
  126. ""; %% ??
  127. help(stylesheet) ->
  128. "Stylesheet to use for test results";
  129. help(cover) ->
  130. "Cover file to use";
  131. help(cover_stop) ->
  132. ""; %% ??
  133. help(event_handler) ->
  134. "Event handlers to attach to the runner";
  135. help(include) ->
  136. "Include folder";
  137. help(abort_if_missing_suites) ->
  138. "Abort if suites are missing";
  139. help(multiply_timetraps) ->
  140. ""; %% ??
  141. help(scale_timetraps) ->
  142. ""; %% ??
  143. help(create_priv_dir) ->
  144. ""; %% ??
  145. help(repeat) ->
  146. "How often to repeat tests";
  147. help(duration) ->
  148. "Max runtime (format: HHMMSS)";
  149. help(until) ->
  150. "Run until (format: HHMMSS)";
  151. help(force_stop) ->
  152. "Force stop after time";
  153. help(basic_html) ->
  154. "Show basic HTML";
  155. help(ct_hooks) ->
  156. "";
  157. help(userconfig) ->
  158. "".
  159. split_ct_dirs(State, RawOpts) ->
  160. %% preserve the override nature of command line opts by only checking
  161. %% `rebar.config` defined additional test dirs if none are defined via
  162. %% command line flag
  163. InDirs = case proplists:get_value(dir, RawOpts) of
  164. undefined ->
  165. CTOpts = rebar_state:get(State, common_test_opts, []),
  166. proplists:get_value(dir, CTOpts, []);
  167. Dirs -> split_string(Dirs)
  168. end,
  169. OutDir = proplists:get_value(outdir, RawOpts, default_test_dir(State)),
  170. {InDirs, OutDir}.
  171. default_test_dir(State) ->
  172. Tmp = rebar_file_utils:system_tmpdir(),
  173. Root = filename:join([rebar_state:dir(State), Tmp]),
  174. Project = filename:basename(rebar_state:dir(State)),
  175. OutDir = filename:join([Root, Project ++ "_rebar3_ct"]),
  176. ok = rebar_file_utils:reset_dir(OutDir),
  177. OutDir.
  178. transform_opts(Opts) ->
  179. transform_opts(Opts, []).
  180. transform_opts([], Acc) -> Acc;
  181. %% drop `outdir` so it's not passed to common_test
  182. transform_opts([{outdir, _}|Rest], Acc) ->
  183. transform_opts(Rest, Acc);
  184. transform_opts([{ct_hooks, CtHooks}|Rest], Acc) ->
  185. transform_opts(Rest, [{ct_hooks, parse_term(CtHooks)}|Acc]);
  186. transform_opts([{force_stop, "skip_rest"}|Rest], Acc) ->
  187. transform_opts(Rest, [{force_stop, skip_rest}|Acc]);
  188. transform_opts([{force_stop, _}|Rest], Acc) ->
  189. transform_opts(Rest, [{force_stop, true}|Acc]);
  190. transform_opts([{repeat, Repeat}|Rest], Acc) ->
  191. transform_opts(Rest, [{repeat,
  192. ec_cnv:to_integer(Repeat)}|Acc]);
  193. transform_opts([{create_priv_dir, CreatePrivDir}|Rest], Acc) ->
  194. transform_opts(Rest, [{create_priv_dir,
  195. to_atoms(CreatePrivDir)}|Acc]);
  196. transform_opts([{multiply_timetraps, MultiplyTimetraps}|Rest], Acc) ->
  197. transform_opts(Rest, [{multiply_timetraps,
  198. ec_cnv:to_integer(MultiplyTimetraps)}|Acc]);
  199. transform_opts([{event_handler, EventHandler}|Rest], Acc) ->
  200. transform_opts(Rest, [{event_handler, parse_term(EventHandler)}|Acc]);
  201. transform_opts([{silent_connections, "all"}|Rest], Acc) ->
  202. transform_opts(Rest, [{silent_connections, all}|Acc]);
  203. transform_opts([{silent_connections, SilentConnections}|Rest], Acc) ->
  204. transform_opts(Rest, [{silent_connections,
  205. to_atoms(split_string(SilentConnections))}|Acc]);
  206. transform_opts([{verbosity, Verbosity}|Rest], Acc) ->
  207. transform_opts(Rest, [{verbosity, parse_term(Verbosity)}|Acc]);
  208. transform_opts([{logopts, LogOpts}|Rest], Acc) ->
  209. transform_opts(Rest, [{logopts, to_atoms(split_string(LogOpts))}|Acc]);
  210. transform_opts([{userconfig, UserConfig}|Rest], Acc) ->
  211. transform_opts(Rest, [{userconfig, parse_term(UserConfig)}|Acc]);
  212. transform_opts([{testcase, Testcase}|Rest], Acc) ->
  213. transform_opts(Rest, [{testcase, to_atoms(split_string(Testcase))}|Acc]);
  214. transform_opts([{group, Group}|Rest], Acc) -> % @TODO handle ""
  215. % Input is a list or an atom. It can also be a nested list.
  216. transform_opts(Rest, [{group, parse_term(Group)}|Acc]);
  217. transform_opts([{Key, Val}|Rest], Acc) when is_list(Val) ->
  218. % Default to splitting a string on comma, that works fine for both flat
  219. % lists of which there are many and single-items.
  220. Val1 = case split_string(Val) of
  221. [Val2] ->
  222. Val2;
  223. Val2 ->
  224. Val2
  225. end,
  226. transform_opts(Rest, [{Key, Val1}|Acc]);
  227. transform_opts([{Key, Val}|Rest], Acc) ->
  228. transform_opts(Rest, [{Key, Val}|Acc]).
  229. to_atoms(List) ->
  230. lists:map(fun(X) -> list_to_atom(X) end, List).
  231. split_string(String) ->
  232. string:tokens(String, ",").
  233. parse_term(String) ->
  234. String1 = "[" ++ String ++ "].",
  235. {ok, Tokens, _} = erl_scan:string(String1),
  236. case erl_parse:parse_term(Tokens) of
  237. {ok, [Terms]} ->
  238. Terms;
  239. Term ->
  240. Term
  241. end.
  242. filter_checkouts(Apps) -> filter_checkouts(Apps, []).
  243. filter_checkouts([], Acc) -> lists:reverse(Acc);
  244. filter_checkouts([App|Rest], Acc) ->
  245. AppDir = filename:absname(rebar_app_info:dir(App)),
  246. CheckoutsDir = filename:absname("_checkouts"),
  247. case lists:prefix(CheckoutsDir, AppDir) of
  248. true -> filter_checkouts(Rest, Acc);
  249. false -> filter_checkouts(Rest, [App|Acc])
  250. end.
  251. create_dirs(Opts) ->
  252. LogDir = proplists:get_value(logdir, Opts),
  253. ensure_dir([LogDir]),
  254. ok.
  255. ensure_dir([]) -> ok;
  256. ensure_dir([Dir|Rest]) ->
  257. case ec_file:is_dir(Dir) of
  258. true ->
  259. ok;
  260. false ->
  261. ec_file:mkdir_path(Dir)
  262. end,
  263. ensure_dir(Rest).
  264. test_state(State, InDirs, OutDir) ->
  265. ErlOpts = rebar_state:get(State, common_test_compile_opts, []) ++
  266. rebar_utils:erl_opts(State),
  267. TestOpts = [{outdir, OutDir}] ++
  268. add_test_dir(ErlOpts, InDirs),
  269. first_files(rebar_state:set(State, erl_opts, TestOpts)).
  270. add_test_dir(Opts, InDirs) ->
  271. %% if no src_dirs are set we have to specify `src` or it won't
  272. %% be built
  273. case proplists:append_values(src_dirs, Opts) of
  274. [] -> [{src_dirs, ["src", "test" | InDirs]} | Opts];
  275. _ -> [{src_dirs, ["test" | InDirs]} | Opts]
  276. end.
  277. first_files(State) ->
  278. BaseFirst = rebar_state:get(State, erl_first_files, []),
  279. CTFirst = rebar_state:get(State, common_test_first_files, []),
  280. rebar_state:set(State, erl_first_files, BaseFirst ++ CTFirst).
  281. resolve_ct_opts(State, CmdLineOpts, OutDir) ->
  282. CTOpts = rebar_state:get(State, common_test_opts, []),
  283. Opts = lists:ukeymerge(1,
  284. lists:ukeysort(1, CmdLineOpts),
  285. lists:ukeysort(1, CTOpts)),
  286. %% rebar has seperate input and output directories whereas `common_test`
  287. %% uses only a single directory so set `dir` to our precompiled `OutDir`
  288. %% and disable `auto_compile`
  289. [{auto_compile, false}, {dir, OutDir}] ++ lists:keydelete(dir, 1, Opts).
  290. maybe_compile_extra_tests(TestApps, State, InDirs, OutDir) ->
  291. F = fun(App) -> rebar_app_info:dir(App) == rebar_dir:get_cwd() end,
  292. case lists:filter(F, TestApps) of
  293. %% compile just the `test` and extra test directories of the base dir
  294. [] ->
  295. ErlOpts = rebar_state:get(State, common_test_compile_opts, []) ++
  296. rebar_utils:erl_opts(State),
  297. TestOpts = [{outdir, OutDir}] ++
  298. [{src_dirs, ["test"|InDirs]}] ++
  299. lists:keydelete(src_dirs, 1, ErlOpts),
  300. TestState = first_files(rebar_state:set(State, erl_opts, TestOpts)),
  301. rebar_erlc_compiler:compile(TestState, rebar_dir:get_cwd());
  302. %% already compiled `./test` so do nothing
  303. _ -> ok
  304. end.
  305. handle_results([Result]) ->
  306. handle_results(Result);
  307. handle_results([Result|Results]) when is_list(Results) ->
  308. case handle_results(Result) of
  309. ok ->
  310. handle_results(Results);
  311. Error ->
  312. Error
  313. end;
  314. handle_results({_, 0, _}) ->
  315. ok;
  316. handle_results({_, FailedCount, _}) ->
  317. {error, {failures_running_tests, FailedCount}};
  318. handle_results({error, Reason}) ->
  319. {error, {error_running_tests, Reason}}.