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

579 行
23 KiB

10 年前
10 年前
10 年前
10 年前
10 年前
10 年前
10 年前
10 年前
10 年前
10 年前
10 年前
10 年前
10 年前
  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. "".