No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

579 líneas
23 KiB

hace 10 años
hace 10 años
hace 10 años
hace 10 años
hace 10 años
hace 10 años
hace 10 años
hace 10 años
hace 10 años
hace 10 años
hace 10 años
hace 10 años
hace 10 años
hace 10 años
  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. %% exported for test purposes, consider private
  9. -export([compile/2, prepare_tests/1, translate_paths/2]).
  10. -include("rebar.hrl").
  11. -include_lib("providers/include/providers.hrl").
  12. -define(PROVIDER, ct).
  13. %% we need to modify app_info state before compile
  14. -define(DEPS, [lock]).
  15. %% ===================================================================
  16. %% Public API
  17. %% ===================================================================
  18. -spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
  19. init(State) ->
  20. Provider = providers:create([{name, ?PROVIDER},
  21. {module, ?MODULE},
  22. {deps, ?DEPS},
  23. {bare, true},
  24. {example, "rebar3 ct"},
  25. {short_desc, "Run Common Tests."},
  26. {desc, "Run Common Tests."},
  27. {opts, ct_opts(State)},
  28. {profiles, [test]}]),
  29. {ok, rebar_state:add_provider(State, Provider)}.
  30. -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
  31. do(State) ->
  32. Tests = prepare_tests(State),
  33. case compile(State, Tests) of
  34. %% successfully compiled apps
  35. {ok, S} -> do(S, Tests);
  36. %% this should look like a compiler error, not a ct error
  37. Error -> Error
  38. end.
  39. do(State, Tests) ->
  40. ?INFO("Running Common Test suites...", []),
  41. rebar_utils:update_code(rebar_state:code_paths(State, all_deps), [soft_purge]),
  42. %% Run ct provider prehooks
  43. Providers = rebar_state:providers(State),
  44. Cwd = rebar_dir:get_cwd(),
  45. rebar_hooks:run_all_hooks(Cwd, pre, ?PROVIDER, Providers, State),
  46. case Tests of
  47. {ok, T} ->
  48. case run_tests(State, T) of
  49. ok ->
  50. %% Run ct provider posthooks
  51. rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, State),
  52. rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)),
  53. {ok, State};
  54. Error ->
  55. rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)),
  56. Error
  57. end;
  58. Error ->
  59. rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)),
  60. Error
  61. end.
  62. run_tests(State, Opts) ->
  63. T = translate_paths(State, Opts),
  64. Opts1 = setup_logdir(State, T),
  65. Opts2 = turn_off_auto_compile(Opts1),
  66. ?DEBUG("ct_opts ~p", [Opts2]),
  67. {RawOpts, _} = rebar_state:command_parsed_args(State),
  68. Result = case proplists:get_value(verbose, RawOpts, false) of
  69. true -> run_test_verbose(Opts2);
  70. false -> run_test_quiet(Opts2)
  71. end,
  72. ok = maybe_write_coverdata(State),
  73. Result.
  74. -spec format_error(any()) -> iolist().
  75. format_error({error, Reason}) ->
  76. io_lib:format("Error running tests:~n ~p", [Reason]);
  77. format_error({error_running_tests, Reason}) ->
  78. format_error({error, Reason});
  79. format_error({failures_running_tests, {Failed, AutoSkipped}}) ->
  80. io_lib:format("Failures occured running tests: ~b", [Failed+AutoSkipped]);
  81. format_error({multiple_errors, Errors}) ->
  82. io_lib:format(lists:concat(["Error running tests:"] ++
  83. lists:map(fun(Error) -> "~n " ++ Error end, Errors)), []).
  84. %% ===================================================================
  85. %% Internal functions
  86. %% ===================================================================
  87. prepare_tests(State) ->
  88. %% command line test options
  89. CmdOpts = cmdopts(State),
  90. %% rebar.config test options
  91. CfgOpts = cfgopts(State),
  92. ProjectApps = rebar_state:project_apps(State),
  93. %% prioritize tests to run first trying any command line specified
  94. %% tests falling back to tests specified in the config file finally
  95. %% running a default set if no other tests are present
  96. select_tests(State, ProjectApps, CmdOpts, CfgOpts).
  97. cmdopts(State) ->
  98. {RawOpts, _} = rebar_state:command_parsed_args(State),
  99. %% filter out opts common_test doesn't know about and convert
  100. %% to ct acceptable forms
  101. transform_opts(RawOpts, []).
  102. transform_opts([], Acc) -> lists:reverse(Acc);
  103. transform_opts([{dir, Dirs}|Rest], Acc) ->
  104. transform_opts(Rest, [{dir, split_string(Dirs)}|Acc]);
  105. transform_opts([{suite, Suites}|Rest], Acc) ->
  106. transform_opts(Rest, [{suite, split_string(Suites)}|Acc]);
  107. transform_opts([{group, Groups}|Rest], Acc) ->
  108. transform_opts(Rest, [{group, split_string(Groups)}|Acc]);
  109. transform_opts([{testcase, Cases}|Rest], Acc) ->
  110. transform_opts(Rest, [{testcase, split_string(Cases)}|Acc]);
  111. transform_opts([{config, Configs}|Rest], Acc) ->
  112. transform_opts(Rest, [{config, split_string(Configs)}|Acc]);
  113. transform_opts([{logopts, LogOpts}|Rest], Acc) ->
  114. transform_opts(Rest, [{logopts, lists:map(fun(P) -> list_to_atom(P) end, split_string(LogOpts))}|Acc]);
  115. transform_opts([{force_stop, "true"}|Rest], Acc) ->
  116. transform_opts(Rest, [{force_stop, true}|Acc]);
  117. transform_opts([{force_stop, "false"}|Rest], Acc) ->
  118. transform_opts(Rest, [{force_stop, false}|Acc]);
  119. transform_opts([{force_stop, "skip_rest"}|Rest], Acc) ->
  120. transform_opts(Rest, [{force_stop, skip_rest}|Acc]);
  121. transform_opts([{create_priv_dir, CreatePrivDir}|Rest], Acc) ->
  122. transform_opts(Rest, [{create_priv_dir, list_to_atom(CreatePrivDir)}|Acc]);
  123. %% drop cover from opts, ct doesn't care about it
  124. transform_opts([{cover, _}|Rest], Acc) ->
  125. transform_opts(Rest, Acc);
  126. %% drop verbose from opts, ct doesn't care about it
  127. transform_opts([{verbose, _}|Rest], Acc) ->
  128. transform_opts(Rest, Acc);
  129. %% getopt should handle anything else
  130. transform_opts([Opt|Rest], Acc) ->
  131. transform_opts(Rest, [Opt|Acc]).
  132. split_string(String) ->
  133. string:tokens(String, [$,]).
  134. cfgopts(State) ->
  135. Opts = rebar_state:get(State, ct_opts, []),
  136. add_hooks(rebar_utils:filtermap(fun filter_opts/1, Opts), State).
  137. filter_opts({test_spec, _}) ->
  138. ?WARN("Test specs not supported", []),
  139. false;
  140. filter_opts({auto_compile, _}) ->
  141. ?WARN("Auto compile not supported", []),
  142. false;
  143. filter_opts({suite, Suite}) when is_integer(hd(Suite)) -> true;
  144. filter_opts({suite, Suite}) when is_atom(Suite) ->
  145. {true, {suite, atom_to_list(Suite)}};
  146. filter_opts({suite, Suites}) ->
  147. {true, {suite, lists:map(fun(S) when is_atom(S) -> atom_to_list(S);
  148. (S) when is_list(S) -> S
  149. end,
  150. Suites)}};
  151. filter_opts(_) -> true.
  152. add_hooks(Opts, State) ->
  153. case {readable(State), lists:keyfind(ct_hooks, 1, Opts)} of
  154. {false, _} ->
  155. Opts;
  156. {true, false} ->
  157. [{ct_hooks, [cth_readable_failonly, cth_readable_shell]} | Opts];
  158. {true, {ct_hooks, Hooks}} ->
  159. %% Make sure hooks are there once only.
  160. ReadableHooks = [cth_readable_failonly, cth_readable_shell],
  161. NewHooks = (Hooks -- ReadableHooks) ++ ReadableHooks,
  162. lists:keyreplace(ct_hooks, 1, Opts, {ct_hooks, NewHooks})
  163. end.
  164. select_tests(State, ProjectApps, CmdOpts, CfgOpts) ->
  165. FixedOpts = lists:filter(fun({_, _}) -> true; (V) -> ?WARN("`~p` is not a valid option for `ct_opts`", [V]) end, CfgOpts),
  166. Merged = lists:ukeymerge(1,
  167. lists:ukeysort(1, CmdOpts),
  168. lists:ukeysort(1, FixedOpts)),
  169. %% make sure `dir` and/or `suite` from command line go in as
  170. %% a pair overriding both `dir` and `suite` from config if
  171. %% they exist
  172. Opts = case {proplists:get_value(suite, CmdOpts), proplists:get_value(dir, CmdOpts)} of
  173. {undefined, undefined} -> Merged;
  174. {_Suite, undefined} -> lists:keydelete(dir, 1, Merged);
  175. {undefined, _Dir} -> lists:keydelete(suite, 1, Merged);
  176. {_Suite, _Dir} -> Merged
  177. end,
  178. discover_tests(State, ProjectApps, Opts).
  179. discover_tests(State, ProjectApps, Opts) ->
  180. case {proplists:get_value(suite, Opts), proplists:get_value(dir, Opts)} of
  181. %% no dirs or suites defined, try using `$APP/test` and `$ROOT/test`
  182. %% as suites
  183. {undefined, undefined} -> {ok, [default_tests(State, ProjectApps)|Opts]};
  184. {_, _} -> {ok, Opts}
  185. end.
  186. default_tests(State, ProjectApps) ->
  187. BareTest = filename:join([rebar_state:dir(State), "test"]),
  188. F = fun(App) -> rebar_app_info:dir(App) == rebar_state:dir(State) end,
  189. AppTests = application_dirs(ProjectApps, []),
  190. case filelib:is_dir(BareTest) andalso not lists:any(F, ProjectApps) of
  191. %% `test` dir at root of project is already scheduled to be
  192. %% included or `test` does not exist
  193. false -> {dir, AppTests};
  194. %% need to add `test` dir at root to dirs to be included
  195. true -> {dir, AppTests ++ [BareTest]}
  196. end.
  197. application_dirs([], []) -> [];
  198. application_dirs([], Acc) -> lists:reverse(Acc);
  199. application_dirs([App|Rest], Acc) ->
  200. TestDir = filename:join([rebar_app_info:dir(App), "test"]),
  201. case filelib:is_dir(TestDir) of
  202. true -> application_dirs(Rest, [TestDir|Acc]);
  203. false -> application_dirs(Rest, Acc)
  204. end.
  205. compile(State, {ok, Tests}) ->
  206. %% inject `ct_first_files` and `ct_compile_opts` into the applications
  207. %% to be compiled
  208. case inject_ct_state(State, Tests) of
  209. {ok, NewState} -> do_compile(NewState);
  210. Error -> Error
  211. end;
  212. %% maybe compile even in the face of errors?
  213. compile(_State, Error) -> Error.
  214. do_compile(State) ->
  215. case rebar_prv_compile:do(State) of
  216. %% successfully compiled apps
  217. {ok, S} ->
  218. ok = maybe_cover_compile(S),
  219. {ok, S};
  220. %% this should look like a compiler error, not an eunit error
  221. Error -> Error
  222. end.
  223. inject_ct_state(State, Tests) ->
  224. Apps = rebar_state:project_apps(State),
  225. ModdedApps = lists:map(fun(App) ->
  226. NewOpts = inject(rebar_app_info:opts(App), State),
  227. rebar_app_info:opts(App, NewOpts)
  228. end, Apps),
  229. NewOpts = inject(rebar_state:opts(State), State),
  230. NewState = rebar_state:opts(State, NewOpts),
  231. test_dirs(NewState, ModdedApps, Tests).
  232. inject(Opts, State) ->
  233. %% append `ct_compile_opts` to app defined `erl_opts`
  234. ErlOpts = rebar_opts:get(Opts, erl_opts, []),
  235. CTOpts = rebar_state:get(State, ct_compile_opts, []),
  236. NewErlOpts = add_transforms(CTOpts, State) ++ ErlOpts,
  237. %% append `ct_first_files` to app defined `erl_first_files`
  238. FirstFiles = rebar_opts:get(Opts, erl_first_files, []),
  239. CTFirstFiles = rebar_state:get(State, ct_first_files, []),
  240. NewFirstFiles = CTFirstFiles ++ FirstFiles,
  241. %% insert the new keys into the opts
  242. lists:foldl(fun({K, V}, NewOpts) -> rebar_opts:set(NewOpts, K, V) end,
  243. Opts,
  244. [{erl_opts, NewErlOpts}, {erl_first_files, NewFirstFiles}]).
  245. add_transforms(CTOpts, State) ->
  246. case readable(State) of
  247. true ->
  248. ReadableTransform = [{parse_transform, cth_readable_transform}],
  249. (CTOpts -- ReadableTransform) ++ ReadableTransform;
  250. false ->
  251. CTOpts
  252. end.
  253. readable(State) ->
  254. {RawOpts, _} = rebar_state:command_parsed_args(State),
  255. case proplists:get_value(readable, RawOpts) of
  256. true -> true;
  257. false -> false;
  258. undefined -> rebar_state:get(State, ct_readable, true)
  259. end.
  260. test_dirs(State, Apps, Opts) ->
  261. case {proplists:get_value(suite, Opts), proplists:get_value(dir, Opts)} of
  262. {Suites, undefined} -> set_compile_dirs(State, Apps, {suite, Suites});
  263. {undefined, Dirs} -> set_compile_dirs(State, Apps, {dir, Dirs});
  264. {Suites, Dir} when is_integer(hd(Dir)) ->
  265. set_compile_dirs(State, Apps, join(Suites, Dir));
  266. {Suites, [Dir]} when is_integer(hd(Dir)) ->
  267. set_compile_dirs(State, Apps, join(Suites, Dir));
  268. {_Suites, _Dirs} -> {error, "Only a single directory may be specified when specifying suites"}
  269. end.
  270. join(Suite, Dir) when is_integer(hd(Suite)) ->
  271. {suite, [filename:join([Dir, Suite])]};
  272. join(Suites, Dir) ->
  273. {suite, lists:map(fun(S) -> filename:join([Dir, S]) end, Suites)}.
  274. set_compile_dirs(State, Apps, {dir, Dir}) when is_integer(hd(Dir)) ->
  275. %% single directory
  276. %% insert `Dir` into an app if relative, or the base state if not
  277. %% app relative but relative to the root or not at all if outside
  278. %% project scope
  279. {NewState, NewApps} = maybe_inject_test_dir(State, [], Apps, Dir),
  280. {ok, rebar_state:project_apps(NewState, NewApps)};
  281. set_compile_dirs(State, Apps, {dir, Dirs}) ->
  282. %% multiple directories
  283. F = fun(Dir, {S, A}) -> maybe_inject_test_dir(S, [], A, Dir) end,
  284. {NewState, NewApps} = lists:foldl(F, {State, Apps}, Dirs),
  285. {ok, rebar_state:project_apps(NewState, NewApps)};
  286. set_compile_dirs(State, Apps, {suite, Suites}) ->
  287. %% suites with dir component
  288. Dirs = find_suite_dirs(Suites),
  289. F = fun(Dir, {S, A}) -> maybe_inject_test_dir(S, [], A, Dir) end,
  290. {NewState, NewApps} = lists:foldl(F, {State, Apps}, Dirs),
  291. {ok, rebar_state:project_apps(NewState, NewApps)}.
  292. find_suite_dirs(Suites) ->
  293. AllDirs = lists:map(fun(S) -> filename:dirname(filename:absname(S)) end, Suites),
  294. %% eliminate duplicates
  295. lists:usort(AllDirs).
  296. maybe_inject_test_dir(State, AppAcc, [App|Rest], Dir) ->
  297. case rebar_file_utils:path_from_ancestor(Dir, rebar_app_info:dir(App)) of
  298. {ok, Path} ->
  299. Opts = inject_test_dir(rebar_app_info:opts(App), Path),
  300. {State, AppAcc ++ [rebar_app_info:opts(App, Opts)] ++ Rest};
  301. {error, badparent} ->
  302. maybe_inject_test_dir(State, AppAcc ++ [App], Rest, Dir)
  303. end;
  304. maybe_inject_test_dir(State, AppAcc, [], Dir) ->
  305. case rebar_file_utils:path_from_ancestor(Dir, rebar_state:dir(State)) of
  306. {ok, []} ->
  307. ?WARN("Can't have suites in root of project dir, dropping from tests", []),
  308. {State, AppAcc};
  309. {ok, Path} ->
  310. Opts = inject_test_dir(rebar_state:opts(State), Path),
  311. {rebar_state:opts(State, Opts), AppAcc};
  312. {error, badparent} ->
  313. {State, AppAcc}
  314. end.
  315. inject_test_dir(Opts, Dir) ->
  316. %% append specified test targets to app defined `extra_src_dirs`
  317. ExtraSrcDirs = rebar_opts:get(Opts, extra_src_dirs, []),
  318. rebar_opts:set(Opts, extra_src_dirs, ExtraSrcDirs ++ [Dir]).
  319. translate_paths(State, Opts) ->
  320. case {proplists:get_value(suite, Opts), proplists:get_value(dir, Opts)} of
  321. {_Suites, undefined} -> translate_suites(State, Opts, []);
  322. {undefined, _Dirs} -> translate_dirs(State, Opts, []);
  323. %% both dirs and suites are defined, only translate dir paths
  324. _ -> translate_dirs(State, Opts, [])
  325. end.
  326. translate_dirs(_State, [], Acc) -> lists:reverse(Acc);
  327. translate_dirs(State, [{dir, Dir}|Rest], Acc) when is_integer(hd(Dir)) ->
  328. %% single dir
  329. Apps = rebar_state:project_apps(State),
  330. translate_dirs(State, Rest, [{dir, translate(State, Apps, Dir)}|Acc]);
  331. translate_dirs(State, [{dir, Dirs}|Rest], Acc) ->
  332. %% multiple dirs
  333. Apps = rebar_state:project_apps(State),
  334. NewDirs = {dir, lists:map(fun(Dir) -> translate(State, Apps, Dir) end, Dirs)},
  335. translate_dirs(State, Rest, [NewDirs|Acc]);
  336. translate_dirs(State, [Test|Rest], Acc) ->
  337. translate_dirs(State, Rest, [Test|Acc]).
  338. translate_suites(_State, [], Acc) -> lists:reverse(Acc);
  339. translate_suites(State, [{suite, Suite}|Rest], Acc) when is_integer(hd(Suite)) ->
  340. %% single suite
  341. Apps = rebar_state:project_apps(State),
  342. translate_suites(State, Rest, [{suite, translate(State, Apps, Suite)}|Acc]);
  343. translate_suites(State, [{suite, Suites}|Rest], Acc) ->
  344. %% multiple suites
  345. Apps = rebar_state:project_apps(State),
  346. NewSuites = {suite, lists:map(fun(Suite) -> translate(State, Apps, Suite) end, Suites)},
  347. translate_suites(State, Rest, [NewSuites|Acc]);
  348. translate_suites(State, [Test|Rest], Acc) ->
  349. translate_suites(State, Rest, [Test|Acc]).
  350. translate(State, [App|Rest], Path) ->
  351. case rebar_file_utils:path_from_ancestor(Path, rebar_app_info:dir(App)) of
  352. {ok, P} -> filename:join([rebar_app_info:out_dir(App), P]);
  353. {error, badparent} -> translate(State, Rest, Path)
  354. end;
  355. translate(State, [], Path) ->
  356. case rebar_file_utils:path_from_ancestor(Path, rebar_state:dir(State)) of
  357. {ok, P} -> filename:join([rebar_dir:base_dir(State), "extras", P]);
  358. %% not relative, leave as is
  359. {error, badparent} -> Path
  360. end.
  361. setup_logdir(State, Opts) ->
  362. Logdir = case proplists:get_value(logdir, Opts) of
  363. undefined -> filename:join([rebar_dir:base_dir(State), "logs"]);
  364. Dir -> Dir
  365. end,
  366. filelib:ensure_dir(filename:join([Logdir, "dummy.beam"])),
  367. [{logdir, Logdir}|lists:keydelete(logdir, 1, Opts)].
  368. turn_off_auto_compile(Opts) ->
  369. [{auto_compile, false}|lists:keydelete(auto_compile, 1, Opts)].
  370. run_test_verbose(Opts) -> handle_results(ct:run_test(Opts)).
  371. run_test_quiet(Opts) ->
  372. Pid = self(),
  373. Ref = erlang:make_ref(),
  374. LogDir = proplists:get_value(logdir, Opts),
  375. {_, Monitor} = erlang:spawn_monitor(fun() ->
  376. {ok, F} = file:open(filename:join([LogDir, "ct.latest.log"]),
  377. [write]),
  378. true = group_leader(F, self()),
  379. Pid ! {Ref, ct:run_test(Opts)}
  380. end),
  381. receive
  382. {Ref, Result} -> handle_quiet_results(Opts, Result);
  383. {'DOWN', Monitor, _, _, Reason} -> handle_results(?PRV_ERROR(Reason))
  384. end.
  385. handle_results(Results) when is_list(Results) ->
  386. Result = lists:foldl(fun sum_results/2, {0, 0, {0,0}}, Results),
  387. handle_results(Result);
  388. handle_results({_, Failed, {_, AutoSkipped}})
  389. when Failed > 0 orelse AutoSkipped > 0 ->
  390. ?PRV_ERROR({failures_running_tests, {Failed, AutoSkipped}});
  391. handle_results({error, Reason}) ->
  392. ?PRV_ERROR({error_running_tests, Reason});
  393. handle_results(_) ->
  394. ok.
  395. sum_results({Passed, Failed, {UserSkipped, AutoSkipped}},
  396. {Passed2, Failed2, {UserSkipped2, AutoSkipped2}}) ->
  397. {Passed+Passed2, Failed+Failed2,
  398. {UserSkipped+UserSkipped2, AutoSkipped+AutoSkipped2}}.
  399. handle_quiet_results(_, {error, _} = Result) ->
  400. handle_results(Result);
  401. handle_quiet_results(CTOpts, Results) when is_list(Results) ->
  402. _ = [format_result(Result) || Result <- Results],
  403. case handle_results(Results) of
  404. ?PRV_ERROR({failures_running_tests, _}) = Error ->
  405. LogDir = proplists:get_value(logdir, CTOpts),
  406. Index = filename:join([LogDir, "index.html"]),
  407. ?CONSOLE("Results written to ~p.", [Index]),
  408. Error;
  409. Other ->
  410. Other
  411. end;
  412. handle_quiet_results(CTOpts, Result) ->
  413. handle_quiet_results(CTOpts, [Result]).
  414. format_result({Passed, 0, {0, 0}}) ->
  415. ?CONSOLE("All ~p tests passed.", [Passed]);
  416. format_result({Passed, Failed, Skipped}) ->
  417. Format = [format_failed(Failed), format_skipped(Skipped),
  418. format_passed(Passed)],
  419. ?CONSOLE("~s", [Format]).
  420. format_failed(0) ->
  421. [];
  422. format_failed(Failed) ->
  423. io_lib:format("Failed ~p tests. ", [Failed]).
  424. format_passed(Passed) ->
  425. io_lib:format("Passed ~p tests. ", [Passed]).
  426. format_skipped({0, 0}) ->
  427. [];
  428. format_skipped({User, Auto}) ->
  429. io_lib:format("Skipped ~p (~p, ~p) tests. ", [User+Auto, User, Auto]).
  430. maybe_cover_compile(State) ->
  431. {RawOpts, _} = rebar_state:command_parsed_args(State),
  432. State1 = case proplists:get_value(cover, RawOpts, false) of
  433. true -> rebar_state:set(State, cover_enabled, true);
  434. false -> State
  435. end,
  436. rebar_prv_cover:maybe_cover_compile(State1).
  437. maybe_write_coverdata(State) ->
  438. {RawOpts, _} = rebar_state:command_parsed_args(State),
  439. State1 = case proplists:get_value(cover, RawOpts, false) of
  440. true -> rebar_state:set(State, cover_enabled, true);
  441. false -> State
  442. end,
  443. rebar_prv_cover:maybe_write_coverdata(State1, ?PROVIDER).
  444. ct_opts(_State) ->
  445. [{dir, undefined, "dir", string, help(dir)}, %% comma-seperated list
  446. {suite, undefined, "suite", string, help(suite)}, %% comma-seperated list
  447. {group, undefined, "group", string, help(group)}, %% comma-seperated list
  448. {testcase, undefined, "case", string, help(testcase)}, %% comma-seperated list
  449. {label, undefined, "label", string, help(label)}, %% String
  450. {config, undefined, "config", string, help(config)}, %% comma-seperated list
  451. {allow_user_terms, undefined, "allow_user_terms", boolean, help(allow_user_terms)}, %% Bool
  452. {logdir, undefined, "logdir", string, help(logdir)}, %% dir
  453. {logopts, undefined, "logopts", string, help(logopts)}, %% comma seperated list
  454. {verbosity, undefined, "verbosity", integer, help(verbosity)}, %% Integer
  455. {cover, $c, "cover", {boolean, false}, help(cover)},
  456. {repeat, undefined, "repeat", integer, help(repeat)}, %% integer
  457. {duration, undefined, "duration", string, help(duration)}, % format: HHMMSS
  458. {until, undefined, "until", string, help(until)}, %% format: YYMoMoDD[HHMMSS]
  459. {force_stop, undefined, "force_stop", string, help(force_stop)}, %% String
  460. {basic_html, undefined, "basic_html", boolean, help(basic_html)}, %% Boolean
  461. {stylesheet, undefined, "stylesheet", string, help(stylesheet)}, %% String
  462. {decrypt_key, undefined, "decrypt_key", string, help(decrypt_key)}, %% String
  463. {decrypt_file, undefined, "decrypt_file", string, help(decrypt_file)}, %% String
  464. {abort_if_missing_suites, undefined, "abort_if_missing_suites", {boolean, true}, help(abort_if_missing_suites)}, %% Boolean
  465. {multiply_timetraps, undefined, "multiply_timetraps", integer, help(multiple_timetraps)}, %% Integer
  466. {scale_timetraps, undefined, "scale_timetraps", boolean, help(scale_timetraps)},
  467. {create_priv_dir, undefined, "create_priv_dir", string, help(create_priv_dir)},
  468. {readable, undefined, "readable", boolean, help(readable)},
  469. {verbose, $v, "verbose", boolean, help(verbose)}
  470. ].
  471. help(dir) ->
  472. "List of additional directories containing test suites";
  473. help(suite) ->
  474. "List of test suites to run";
  475. help(group) ->
  476. "List of test groups to run";
  477. help(testcase) ->
  478. "List of test cases to run";
  479. help(label) ->
  480. "Test label";
  481. help(config) ->
  482. "List of config files";
  483. help(allow_user_terms) ->
  484. "Allow user defined config values in config files";
  485. help(logdir) ->
  486. "Log folder";
  487. help(logopts) ->
  488. "Options for common test logging";
  489. help(verbosity) ->
  490. "Verbosity";
  491. help(cover) ->
  492. "Generate cover data";
  493. help(repeat) ->
  494. "How often to repeat tests";
  495. help(duration) ->
  496. "Max runtime (format: HHMMSS)";
  497. help(until) ->
  498. "Run until (format: HHMMSS)";
  499. help(force_stop) ->
  500. "Force stop on test timeout (true | false | skip_rest)";
  501. help(basic_html) ->
  502. "Show basic HTML";
  503. help(stylesheet) ->
  504. "CSS stylesheet to apply to html output";
  505. help(decrypt_key) ->
  506. "Path to key for decrypting config";
  507. help(decrypt_file) ->
  508. "Path to file containing key for decrypting config";
  509. help(abort_if_missing_suites) ->
  510. "Abort if suites are missing";
  511. help(multiply_timetraps) ->
  512. "Multiply timetraps";
  513. help(scale_timetraps) ->
  514. "Scale timetraps";
  515. help(create_priv_dir) ->
  516. "Create priv dir (auto_per_run | auto_per_tc | manual_per_tc)";
  517. help(readable) ->
  518. "Shows test case names and only displays logs to shell on failures";
  519. help(verbose) ->
  520. "Verbose output";
  521. help(_) ->
  522. "".