You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

698 rivejä
28 KiB

10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
9 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
  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. %% Run ct provider pre hooks for all project apps and top level project hooks
  46. rebar_hooks:run_project_and_app_hooks(Cwd, pre, ?PROVIDER, Providers, State),
  47. case Tests of
  48. {ok, T} ->
  49. case run_tests(State, T) of
  50. ok ->
  51. %% Run ct provider post hooks for all project apps and top level project hooks
  52. rebar_hooks:run_project_and_app_hooks(Cwd, post, ?PROVIDER, Providers, State),
  53. rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)),
  54. {ok, State};
  55. Error ->
  56. rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)),
  57. Error
  58. end;
  59. Error ->
  60. rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)),
  61. Error
  62. end.
  63. run_tests(State, Opts) ->
  64. T = translate_paths(State, Opts),
  65. Opts1 = setup_logdir(State, T),
  66. Opts2 = turn_off_auto_compile(Opts1),
  67. ?DEBUG("ct_opts ~p", [Opts2]),
  68. {RawOpts, _} = rebar_state:command_parsed_args(State),
  69. Result = case proplists:get_value(verbose, RawOpts, false) of
  70. true -> run_test_verbose(Opts2);
  71. false -> run_test_quiet(Opts2)
  72. end,
  73. ok = maybe_write_coverdata(State),
  74. Result.
  75. -spec format_error(any()) -> iolist().
  76. format_error({error, Reason}) ->
  77. io_lib:format("Error running tests:~n ~p", [Reason]);
  78. format_error({error_running_tests, Reason}) ->
  79. format_error({error, Reason});
  80. format_error({failures_running_tests, {Failed, AutoSkipped}}) ->
  81. io_lib:format("Failures occured running tests: ~b", [Failed+AutoSkipped]);
  82. format_error({badconfig, {Msg, {Value, Key}}}) ->
  83. io_lib:format(Msg, [Value, Key]);
  84. format_error({badconfig, Msg}) ->
  85. io_lib:format(Msg, []);
  86. format_error({multiple_errors, Errors}) ->
  87. io_lib:format(lists:concat(["Error running tests:"] ++
  88. lists:map(fun(Error) -> "~n " ++ Error end, Errors)), []).
  89. %% ===================================================================
  90. %% Internal functions
  91. %% ===================================================================
  92. prepare_tests(State) ->
  93. %% command line test options
  94. CmdOpts = cmdopts(State),
  95. %% rebar.config test options
  96. CfgOpts = cfgopts(State),
  97. ProjectApps = rebar_state:project_apps(State),
  98. %% prioritize tests to run first trying any command line specified
  99. %% tests falling back to tests specified in the config file finally
  100. %% running a default set if no other tests are present
  101. select_tests(State, ProjectApps, CmdOpts, CfgOpts).
  102. cmdopts(State) ->
  103. {RawOpts, _} = rebar_state:command_parsed_args(State),
  104. %% filter out opts common_test doesn't know about and convert
  105. %% to ct acceptable forms
  106. transform_opts(RawOpts, []).
  107. transform_opts([], Acc) -> lists:reverse(Acc);
  108. transform_opts([{dir, Dirs}|Rest], Acc) ->
  109. transform_opts(Rest, [{dir, split_string(Dirs)}|Acc]);
  110. transform_opts([{suite, Suites}|Rest], Acc) ->
  111. transform_opts(Rest, [{suite, split_string(Suites)}|Acc]);
  112. transform_opts([{group, Groups}|Rest], Acc) ->
  113. transform_opts(Rest, [{group, split_string(Groups)}|Acc]);
  114. transform_opts([{testcase, Cases}|Rest], Acc) ->
  115. transform_opts(Rest, [{testcase, split_string(Cases)}|Acc]);
  116. transform_opts([{config, Configs}|Rest], Acc) ->
  117. transform_opts(Rest, [{config, split_string(Configs)}|Acc]);
  118. transform_opts([{include, Includes}|Rest], Acc) ->
  119. transform_opts(Rest, [{include, split_string(Includes)}|Acc]);
  120. transform_opts([{logopts, LogOpts}|Rest], Acc) ->
  121. transform_opts(Rest, [{logopts, lists:map(fun(P) -> list_to_atom(P) end, split_string(LogOpts))}|Acc]);
  122. transform_opts([{force_stop, "true"}|Rest], Acc) ->
  123. transform_opts(Rest, [{force_stop, true}|Acc]);
  124. transform_opts([{force_stop, "false"}|Rest], Acc) ->
  125. transform_opts(Rest, [{force_stop, false}|Acc]);
  126. transform_opts([{force_stop, "skip_rest"}|Rest], Acc) ->
  127. transform_opts(Rest, [{force_stop, skip_rest}|Acc]);
  128. transform_opts([{create_priv_dir, CreatePrivDir}|Rest], Acc) ->
  129. transform_opts(Rest, [{create_priv_dir, list_to_atom(CreatePrivDir)}|Acc]);
  130. %% drop cover from opts, ct doesn't care about it
  131. transform_opts([{cover, _}|Rest], Acc) ->
  132. transform_opts(Rest, Acc);
  133. %% drop verbose from opts, ct doesn't care about it
  134. transform_opts([{verbose, _}|Rest], Acc) ->
  135. transform_opts(Rest, Acc);
  136. %% getopt should handle anything else
  137. transform_opts([Opt|Rest], Acc) ->
  138. transform_opts(Rest, [Opt|Acc]).
  139. split_string(String) ->
  140. string:tokens(String, [$,]).
  141. cfgopts(State) ->
  142. case rebar_state:get(State, ct_opts, []) of
  143. Opts when is_list(Opts) ->
  144. ensure_opts(add_hooks(Opts, State), []);
  145. Wrong ->
  146. %% probably a single non list term
  147. ?PRV_ERROR({badconfig, {"Value `~p' of option `~p' must be a list", {Wrong, ct_opts}}})
  148. end.
  149. ensure_opts([], Acc) -> lists:reverse(Acc);
  150. ensure_opts([{test_spec, _}|Rest], Acc) ->
  151. ?WARN("Test specs not supported. See http://www.rebar3.org/docs/running-tests#common-test", []),
  152. ensure_opts(Rest, Acc);
  153. ensure_opts([{cover, _}|Rest], Acc) ->
  154. ?WARN("Cover specs not supported. See http://www.rebar3.org/docs/running-tests#common-test", []),
  155. ensure_opts(Rest, Acc);
  156. ensure_opts([{auto_compile, _}|Rest], Acc) ->
  157. ?WARN("Auto compile not supported", []),
  158. ensure_opts(Rest, Acc);
  159. ensure_opts([{suite, Suite}|Rest], Acc) when is_integer(hd(Suite)) ->
  160. ensure_opts(Rest, [{suite, Suite}|Acc]);
  161. ensure_opts([{suite, Suite}|Rest], Acc) when is_atom(Suite) ->
  162. ensure_opts(Rest, [{suite, atom_to_list(Suite)}|Acc]);
  163. ensure_opts([{suite, Suites}|Rest], Acc) ->
  164. NewSuites = {suite, lists:map(fun(S) when is_atom(S) -> atom_to_list(S);
  165. (S) when is_list(S) -> S
  166. end,
  167. Suites)},
  168. ensure_opts(Rest, [NewSuites|Acc]);
  169. ensure_opts([{K, V}|Rest], Acc) ->
  170. ensure_opts(Rest, [{K, V}|Acc]);
  171. %% pass through other options, in case of things like config terms
  172. %% in `ct_opts`
  173. ensure_opts([V|Rest], Acc) ->
  174. ensure_opts(Rest, [V|Acc]).
  175. add_hooks(Opts, State) ->
  176. case {readable(State), lists:keyfind(ct_hooks, 1, Opts)} of
  177. {false, _} ->
  178. Opts;
  179. {true, false} ->
  180. [{ct_hooks, [cth_readable_failonly, cth_readable_shell]} | Opts];
  181. {true, {ct_hooks, Hooks}} ->
  182. %% Make sure hooks are there once only.
  183. ReadableHooks = [cth_readable_failonly, cth_readable_shell],
  184. NewHooks = (Hooks -- ReadableHooks) ++ ReadableHooks,
  185. lists:keyreplace(ct_hooks, 1, Opts, {ct_hooks, NewHooks})
  186. end.
  187. select_tests(_, _, {error, _} = Error, _) -> Error;
  188. select_tests(_, _, _, {error, _} = Error) -> Error;
  189. select_tests(State, ProjectApps, CmdOpts, CfgOpts) ->
  190. Merged = lists:ukeymerge(1,
  191. lists:ukeysort(1, CmdOpts),
  192. lists:ukeysort(1, CfgOpts)),
  193. %% make sure `dir` and/or `suite` from command line go in as
  194. %% a pair overriding both `dir` and `suite` from config if
  195. %% they exist
  196. Opts = case {proplists:get_value(suite, CmdOpts), proplists:get_value(dir, CmdOpts)} of
  197. {undefined, undefined} -> Merged;
  198. {_Suite, undefined} -> lists:keydelete(dir, 1, Merged);
  199. {undefined, _Dir} -> lists:keydelete(suite, 1, Merged);
  200. {_Suite, _Dir} -> Merged
  201. end,
  202. discover_tests(State, ProjectApps, Opts).
  203. discover_tests(State, ProjectApps, Opts) ->
  204. case {proplists:get_value(suite, Opts), proplists:get_value(dir, Opts)} of
  205. %% no dirs or suites defined, try using `$APP/test` and `$ROOT/test`
  206. %% as suites
  207. {undefined, undefined} -> {ok, [default_tests(State, ProjectApps)|Opts]};
  208. {_, _} -> {ok, Opts}
  209. end.
  210. default_tests(State, ProjectApps) ->
  211. BareTest = filename:join([rebar_state:dir(State), "test"]),
  212. F = fun(App) -> rebar_app_info:dir(App) == rebar_state:dir(State) end,
  213. AppTests = application_dirs(ProjectApps, []),
  214. case filelib:is_dir(BareTest) andalso not lists:any(F, ProjectApps) of
  215. %% `test` dir at root of project is already scheduled to be
  216. %% included or `test` does not exist
  217. false -> {dir, AppTests};
  218. %% need to add `test` dir at root to dirs to be included
  219. true -> {dir, AppTests ++ [BareTest]}
  220. end.
  221. application_dirs([], []) -> [];
  222. application_dirs([], Acc) -> lists:reverse(Acc);
  223. application_dirs([App|Rest], Acc) ->
  224. TestDir = filename:join([rebar_app_info:dir(App), "test"]),
  225. case filelib:is_dir(TestDir) of
  226. true -> application_dirs(Rest, [TestDir|Acc]);
  227. false -> application_dirs(Rest, Acc)
  228. end.
  229. compile(State, {ok, _} = Tests) ->
  230. %% inject `ct_first_files`, `ct_compile_opts` and `include` (from `ct_opts`
  231. %% and command line options) into the applications to be compiled
  232. case inject_ct_state(State, Tests) of
  233. {ok, NewState} -> do_compile(NewState);
  234. Error -> Error
  235. end;
  236. %% maybe compile even in the face of errors?
  237. compile(_State, Error) -> Error.
  238. do_compile(State) ->
  239. case rebar_prv_compile:do(State) of
  240. %% successfully compiled apps
  241. {ok, S} ->
  242. ok = maybe_cover_compile(S),
  243. {ok, S};
  244. %% this should look like a compiler error, not an eunit error
  245. Error -> Error
  246. end.
  247. inject_ct_state(State, {ok, Tests}) ->
  248. Apps = rebar_state:project_apps(State),
  249. case inject_ct_state(State, Tests, Apps, []) of
  250. {ok, {NewState, ModdedApps}} ->
  251. test_dirs(NewState, ModdedApps, Tests);
  252. {error, _} = Error -> Error
  253. end;
  254. inject_ct_state(_State, Error) -> Error.
  255. inject_ct_state(State, Tests, [App|Rest], Acc) ->
  256. case inject(rebar_app_info:opts(App), State, Tests) of
  257. {error, _} = Error -> Error;
  258. NewOpts ->
  259. NewApp = rebar_app_info:opts(App, NewOpts),
  260. inject_ct_state(State, Tests, Rest, [NewApp|Acc])
  261. end;
  262. inject_ct_state(State, Tests, [], Acc) ->
  263. case inject(rebar_state:opts(State), State, Tests) of
  264. {error, _} = Error -> Error;
  265. NewOpts ->
  266. {ok, {rebar_state:opts(State, NewOpts), lists:reverse(Acc)}}
  267. end.
  268. opts(Opts, Key, Default) ->
  269. case rebar_opts:get(Opts, Key, Default) of
  270. Vs when is_list(Vs) -> Vs;
  271. Wrong ->
  272. ?PRV_ERROR({badconfig, {"Value `~p' of option `~p' must be a list", {Wrong, Key}}})
  273. end.
  274. inject(Opts, State, Tests) -> erl_opts(Opts, State, Tests).
  275. erl_opts(Opts, State, Tests) ->
  276. %% append `ct_compile_opts` to app defined `erl_opts`
  277. ErlOpts = opts(Opts, erl_opts, []),
  278. CTOpts = opts(Opts, ct_compile_opts, []),
  279. case add_transforms(append(CTOpts, ErlOpts), State) of
  280. {error, _} = Error -> Error;
  281. NewErlOpts -> first_files(rebar_opts:set(Opts, erl_opts, NewErlOpts), Tests)
  282. end.
  283. first_files(Opts, Tests) ->
  284. %% append `ct_first_files` to app defined `erl_first_files`
  285. FirstFiles = opts(Opts, erl_first_files, []),
  286. CTFirstFiles = opts(Opts, ct_first_files, []),
  287. case append(CTFirstFiles, FirstFiles) of
  288. {error, _} = Error -> Error;
  289. NewFirstFiles -> include_files(rebar_opts:set(Opts, erl_first_files, NewFirstFiles), Tests)
  290. end.
  291. include_files(Opts, Tests) ->
  292. %% append include dirs from command line and `ct_opts` to app defined
  293. %% `erl_opts`
  294. ErlOpts = opts(Opts, erl_opts, []),
  295. Includes = proplists:get_value(include, Tests, []),
  296. Is = lists:map(fun(I) -> {i, I} end, Includes),
  297. case append(Is, ErlOpts) of
  298. {error, _} = Error -> Error;
  299. NewIncludes -> ct_macro(rebar_opts:set(Opts, erl_opts, NewIncludes))
  300. end.
  301. ct_macro(Opts) ->
  302. ErlOpts = opts(Opts, erl_opts, []),
  303. NewOpts = safe_define_ct_macro(ErlOpts),
  304. rebar_opts:set(Opts, erl_opts, NewOpts).
  305. safe_define_ct_macro(Opts) ->
  306. %% defining a compile macro twice results in an exception so
  307. %% make sure 'COMMON_TEST' is only defined once
  308. case test_defined(Opts) of
  309. true -> Opts;
  310. false -> [{d, 'COMMON_TEST'}|Opts]
  311. end.
  312. test_defined([{d, 'COMMON_TEST'}|_]) -> true;
  313. test_defined([{d, 'COMMON_TEST', true}|_]) -> true;
  314. test_defined([_|Rest]) -> test_defined(Rest);
  315. test_defined([]) -> false.
  316. append({error, _} = Error, _) -> Error;
  317. append(_, {error, _} = Error) -> Error;
  318. append(A, B) -> A ++ B.
  319. add_transforms(CTOpts, State) when is_list(CTOpts) ->
  320. case readable(State) of
  321. true ->
  322. ReadableTransform = [{parse_transform, cth_readable_transform}],
  323. (CTOpts -- ReadableTransform) ++ ReadableTransform;
  324. false ->
  325. CTOpts
  326. end;
  327. add_transforms({error, _} = Error, _State) -> Error.
  328. readable(State) ->
  329. {RawOpts, _} = rebar_state:command_parsed_args(State),
  330. case proplists:get_value(readable, RawOpts) of
  331. true -> true;
  332. false -> false;
  333. undefined -> rebar_state:get(State, ct_readable, true)
  334. end.
  335. test_dirs(State, Apps, Opts) ->
  336. case {proplists:get_value(suite, Opts), proplists:get_value(dir, Opts)} of
  337. {Suites, undefined} -> set_compile_dirs(State, Apps, {suite, Suites});
  338. {undefined, Dirs} -> set_compile_dirs(State, Apps, {dir, Dirs});
  339. {Suites, Dir} when is_integer(hd(Dir)) ->
  340. set_compile_dirs(State, Apps, join(Suites, Dir));
  341. {Suites, [Dir]} when is_integer(hd(Dir)) ->
  342. set_compile_dirs(State, Apps, join(Suites, Dir));
  343. {_Suites, _Dirs} -> {error, "Only a single directory may be specified when specifying suites"}
  344. end.
  345. join(Suite, Dir) when is_integer(hd(Suite)) ->
  346. {suite, [filename:join([Dir, Suite])]};
  347. join(Suites, Dir) ->
  348. {suite, lists:map(fun(S) -> filename:join([Dir, S]) end, Suites)}.
  349. set_compile_dirs(State, Apps, {dir, Dir}) when is_integer(hd(Dir)) ->
  350. %% single directory
  351. %% insert `Dir` into an app if relative, or the base state if not
  352. %% app relative but relative to the root or not at all if outside
  353. %% project scope
  354. {NewState, NewApps} = maybe_inject_test_dir(State, [], Apps, Dir),
  355. {ok, rebar_state:project_apps(NewState, NewApps)};
  356. set_compile_dirs(State, Apps, {dir, Dirs}) ->
  357. %% multiple directories
  358. F = fun(Dir, {S, A}) -> maybe_inject_test_dir(S, [], A, Dir) end,
  359. {NewState, NewApps} = lists:foldl(F, {State, Apps}, Dirs),
  360. {ok, rebar_state:project_apps(NewState, NewApps)};
  361. set_compile_dirs(State, Apps, {suite, Suites}) ->
  362. %% suites with dir component
  363. Dirs = find_suite_dirs(Suites),
  364. F = fun(Dir, {S, A}) -> maybe_inject_test_dir(S, [], A, Dir) end,
  365. {NewState, NewApps} = lists:foldl(F, {State, Apps}, Dirs),
  366. {ok, rebar_state:project_apps(NewState, NewApps)}.
  367. find_suite_dirs(Suites) ->
  368. AllDirs = lists:map(fun(S) -> filename:dirname(filename:absname(S)) end, Suites),
  369. %% eliminate duplicates
  370. lists:usort(AllDirs).
  371. maybe_inject_test_dir(State, AppAcc, [App|Rest], Dir) ->
  372. case rebar_file_utils:path_from_ancestor(Dir, rebar_app_info:dir(App)) of
  373. {ok, []} ->
  374. %% normal operation involves copying the entire directory a
  375. %% suite exists in but if the suite is in the app root directory
  376. %% the current compiler tries to compile all subdirs including priv
  377. %% instead copy only files ending in `.erl' and directories
  378. %% ending in `_SUITE_data' into the `_build/PROFILE/lib/APP' dir
  379. ok = copy_bare_suites(Dir, rebar_app_info:out_dir(App)),
  380. Opts = inject_test_dir(rebar_state:opts(State), rebar_app_info:out_dir(App)),
  381. {rebar_state:opts(State, Opts), AppAcc ++ [App]};
  382. {ok, Path} ->
  383. Opts = inject_test_dir(rebar_app_info:opts(App), Path),
  384. {State, AppAcc ++ [rebar_app_info:opts(App, Opts)] ++ Rest};
  385. {error, badparent} ->
  386. maybe_inject_test_dir(State, AppAcc ++ [App], Rest, Dir)
  387. end;
  388. maybe_inject_test_dir(State, AppAcc, [], Dir) ->
  389. case rebar_file_utils:path_from_ancestor(Dir, rebar_state:dir(State)) of
  390. {ok, []} ->
  391. %% normal operation involves copying the entire directory a
  392. %% suite exists in but if the suite is in the root directory
  393. %% that results in a loop as we copy `_build' into itself
  394. %% instead copy only files ending in `.erl' and directories
  395. %% ending in `_SUITE_data' in the `_build/PROFILE/extras' dir
  396. ExtrasDir = filename:join([rebar_dir:base_dir(State), "extras"]),
  397. ok = copy_bare_suites(Dir, ExtrasDir),
  398. Opts = inject_test_dir(rebar_state:opts(State), ExtrasDir),
  399. {rebar_state:opts(State, Opts), AppAcc};
  400. {ok, Path} ->
  401. Opts = inject_test_dir(rebar_state:opts(State), Path),
  402. {rebar_state:opts(State, Opts), AppAcc};
  403. {error, badparent} ->
  404. {State, AppAcc}
  405. end.
  406. copy_bare_suites(From, To) ->
  407. filelib:ensure_dir(filename:join([To, "dummy.txt"])),
  408. SrcFiles = rebar_utils:find_files(From, ".*\\.[e|h]rl\$", false),
  409. DataDirs = lists:filter(fun filelib:is_dir/1,
  410. filelib:wildcard(filename:join([From, "*_SUITE_data"]))),
  411. ok = rebar_file_utils:cp_r(SrcFiles, To),
  412. rebar_file_utils:cp_r(DataDirs, To).
  413. inject_test_dir(Opts, Dir) ->
  414. %% append specified test targets to app defined `extra_src_dirs`
  415. ExtraSrcDirs = rebar_opts:get(Opts, extra_src_dirs, []),
  416. rebar_opts:set(Opts, extra_src_dirs, ExtraSrcDirs ++ [Dir]).
  417. translate_paths(State, Opts) ->
  418. case {proplists:get_value(suite, Opts), proplists:get_value(dir, Opts)} of
  419. {_Suites, undefined} -> translate_suites(State, Opts, []);
  420. {undefined, _Dirs} -> translate_dirs(State, Opts, []);
  421. %% both dirs and suites are defined, only translate dir paths
  422. _ -> translate_dirs(State, Opts, [])
  423. end.
  424. translate_dirs(_State, [], Acc) -> lists:reverse(Acc);
  425. translate_dirs(State, [{dir, Dir}|Rest], Acc) when is_integer(hd(Dir)) ->
  426. %% single dir
  427. Apps = rebar_state:project_apps(State),
  428. translate_dirs(State, Rest, [{dir, translate(State, Apps, Dir)}|Acc]);
  429. translate_dirs(State, [{dir, Dirs}|Rest], Acc) ->
  430. %% multiple dirs
  431. Apps = rebar_state:project_apps(State),
  432. NewDirs = {dir, lists:map(fun(Dir) -> translate(State, Apps, Dir) end, Dirs)},
  433. translate_dirs(State, Rest, [NewDirs|Acc]);
  434. translate_dirs(State, [Test|Rest], Acc) ->
  435. translate_dirs(State, Rest, [Test|Acc]).
  436. translate_suites(_State, [], Acc) -> lists:reverse(Acc);
  437. translate_suites(State, [{suite, Suite}|Rest], Acc) when is_integer(hd(Suite)) ->
  438. %% single suite
  439. Apps = rebar_state:project_apps(State),
  440. translate_suites(State, Rest, [{suite, translate_suite(State, Apps, Suite)}|Acc]);
  441. translate_suites(State, [{suite, Suites}|Rest], Acc) ->
  442. %% multiple suites
  443. Apps = rebar_state:project_apps(State),
  444. NewSuites = {suite, lists:map(fun(Suite) -> translate_suite(State, Apps, Suite) end, Suites)},
  445. translate_suites(State, Rest, [NewSuites|Acc]);
  446. translate_suites(State, [Test|Rest], Acc) ->
  447. translate_suites(State, Rest, [Test|Acc]).
  448. translate_suite(State, Apps, Suite) ->
  449. Dirname = filename:dirname(Suite),
  450. Basename = filename:basename(Suite),
  451. case Dirname of
  452. "." -> Suite;
  453. _ -> filename:join([translate(State, Apps, Dirname), Basename])
  454. end.
  455. translate(State, [App|Rest], Path) ->
  456. case rebar_file_utils:path_from_ancestor(Path, rebar_app_info:dir(App)) of
  457. {ok, P} -> filename:join([rebar_app_info:out_dir(App), P]);
  458. {error, badparent} -> translate(State, Rest, Path)
  459. end;
  460. translate(State, [], Path) ->
  461. case rebar_file_utils:path_from_ancestor(Path, rebar_state:dir(State)) of
  462. {ok, P} -> filename:join([rebar_dir:base_dir(State), "extras", P]);
  463. %% not relative, leave as is
  464. {error, badparent} -> Path
  465. end.
  466. setup_logdir(State, Opts) ->
  467. Logdir = case proplists:get_value(logdir, Opts) of
  468. undefined -> filename:join([rebar_dir:base_dir(State), "logs"]);
  469. Dir -> Dir
  470. end,
  471. filelib:ensure_dir(filename:join([Logdir, "dummy.beam"])),
  472. [{logdir, Logdir}|lists:keydelete(logdir, 1, Opts)].
  473. turn_off_auto_compile(Opts) ->
  474. [{auto_compile, false}|lists:keydelete(auto_compile, 1, Opts)].
  475. run_test_verbose(Opts) -> handle_results(ct:run_test(Opts)).
  476. run_test_quiet(Opts) ->
  477. Pid = self(),
  478. Ref = erlang:make_ref(),
  479. LogDir = proplists:get_value(logdir, Opts),
  480. {_, Monitor} = erlang:spawn_monitor(fun() ->
  481. {ok, F} = file:open(filename:join([LogDir, "ct.latest.log"]),
  482. [write]),
  483. true = group_leader(F, self()),
  484. Pid ! {Ref, ct:run_test(Opts)}
  485. end),
  486. receive
  487. {Ref, Result} -> handle_quiet_results(Opts, Result);
  488. {'DOWN', Monitor, _, _, Reason} -> handle_results(?PRV_ERROR(Reason))
  489. end.
  490. handle_results(Results) when is_list(Results) ->
  491. Result = lists:foldl(fun sum_results/2, {0, 0, {0,0}}, Results),
  492. handle_results(Result);
  493. handle_results({_, Failed, {_, AutoSkipped}})
  494. when Failed > 0 orelse AutoSkipped > 0 ->
  495. ?PRV_ERROR({failures_running_tests, {Failed, AutoSkipped}});
  496. handle_results({error, Reason}) ->
  497. ?PRV_ERROR({error_running_tests, Reason});
  498. handle_results(_) ->
  499. ok.
  500. sum_results({Passed, Failed, {UserSkipped, AutoSkipped}},
  501. {Passed2, Failed2, {UserSkipped2, AutoSkipped2}}) ->
  502. {Passed+Passed2, Failed+Failed2,
  503. {UserSkipped+UserSkipped2, AutoSkipped+AutoSkipped2}}.
  504. handle_quiet_results(_, {error, _} = Result) ->
  505. handle_results(Result);
  506. handle_quiet_results(CTOpts, Results) when is_list(Results) ->
  507. _ = [format_result(Result) || Result <- Results],
  508. case handle_results(Results) of
  509. ?PRV_ERROR({failures_running_tests, _}) = Error ->
  510. LogDir = proplists:get_value(logdir, CTOpts),
  511. Index = filename:join([LogDir, "index.html"]),
  512. ?CONSOLE("Results written to ~p.", [Index]),
  513. Error;
  514. Other ->
  515. Other
  516. end;
  517. handle_quiet_results(CTOpts, Result) ->
  518. handle_quiet_results(CTOpts, [Result]).
  519. format_result({Passed, 0, {0, 0}}) ->
  520. ?CONSOLE("All ~p tests passed.", [Passed]);
  521. format_result({Passed, Failed, Skipped}) ->
  522. Format = [format_failed(Failed), format_skipped(Skipped),
  523. format_passed(Passed)],
  524. ?CONSOLE("~s", [Format]).
  525. format_failed(0) ->
  526. [];
  527. format_failed(Failed) ->
  528. io_lib:format("Failed ~p tests. ", [Failed]).
  529. format_passed(Passed) ->
  530. io_lib:format("Passed ~p tests. ", [Passed]).
  531. format_skipped({0, 0}) ->
  532. [];
  533. format_skipped({User, Auto}) ->
  534. io_lib:format("Skipped ~p (~p, ~p) tests. ", [User+Auto, User, Auto]).
  535. maybe_cover_compile(State) ->
  536. {RawOpts, _} = rebar_state:command_parsed_args(State),
  537. State1 = case proplists:get_value(cover, RawOpts, false) of
  538. true -> rebar_state:set(State, cover_enabled, true);
  539. false -> State
  540. end,
  541. rebar_prv_cover:maybe_cover_compile(State1).
  542. maybe_write_coverdata(State) ->
  543. {RawOpts, _} = rebar_state:command_parsed_args(State),
  544. State1 = case proplists:get_value(cover, RawOpts, false) of
  545. true -> rebar_state:set(State, cover_enabled, true);
  546. false -> State
  547. end,
  548. rebar_prv_cover:maybe_write_coverdata(State1, ?PROVIDER).
  549. ct_opts(_State) ->
  550. [{dir, undefined, "dir", string, help(dir)}, %% comma-seperated list
  551. {suite, undefined, "suite", string, help(suite)}, %% comma-seperated list
  552. {group, undefined, "group", string, help(group)}, %% comma-seperated list
  553. {testcase, undefined, "case", string, help(testcase)}, %% comma-seperated list
  554. {label, undefined, "label", string, help(label)}, %% String
  555. {config, undefined, "config", string, help(config)}, %% comma-seperated list
  556. {allow_user_terms, undefined, "allow_user_terms", boolean, help(allow_user_terms)}, %% Bool
  557. {logdir, undefined, "logdir", string, help(logdir)}, %% dir
  558. {logopts, undefined, "logopts", string, help(logopts)}, %% comma seperated list
  559. {verbosity, undefined, "verbosity", integer, help(verbosity)}, %% Integer
  560. {cover, $c, "cover", {boolean, false}, help(cover)},
  561. {repeat, undefined, "repeat", integer, help(repeat)}, %% integer
  562. {duration, undefined, "duration", string, help(duration)}, % format: HHMMSS
  563. {until, undefined, "until", string, help(until)}, %% format: YYMoMoDD[HHMMSS]
  564. {force_stop, undefined, "force_stop", string, help(force_stop)}, %% String
  565. {basic_html, undefined, "basic_html", boolean, help(basic_html)}, %% Boolean
  566. {stylesheet, undefined, "stylesheet", string, help(stylesheet)}, %% String
  567. {decrypt_key, undefined, "decrypt_key", string, help(decrypt_key)}, %% String
  568. {decrypt_file, undefined, "decrypt_file", string, help(decrypt_file)}, %% String
  569. {abort_if_missing_suites, undefined, "abort_if_missing_suites", {boolean, true}, help(abort_if_missing_suites)}, %% Boolean
  570. {multiply_timetraps, undefined, "multiply_timetraps", integer, help(multiple_timetraps)}, %% Integer
  571. {scale_timetraps, undefined, "scale_timetraps", boolean, help(scale_timetraps)},
  572. {create_priv_dir, undefined, "create_priv_dir", string, help(create_priv_dir)},
  573. {include, undefined, "include", string, help(include)},
  574. {readable, undefined, "readable", boolean, help(readable)},
  575. {verbose, $v, "verbose", boolean, help(verbose)}
  576. ].
  577. help(dir) ->
  578. "List of additional directories containing test suites";
  579. help(suite) ->
  580. "List of test suites to run";
  581. help(group) ->
  582. "List of test groups to run";
  583. help(testcase) ->
  584. "List of test cases to run";
  585. help(label) ->
  586. "Test label";
  587. help(config) ->
  588. "List of config files";
  589. help(allow_user_terms) ->
  590. "Allow user defined config values in config files";
  591. help(logdir) ->
  592. "Log folder";
  593. help(logopts) ->
  594. "Options for common test logging";
  595. help(verbosity) ->
  596. "Verbosity";
  597. help(cover) ->
  598. "Generate cover data";
  599. help(repeat) ->
  600. "How often to repeat tests";
  601. help(duration) ->
  602. "Max runtime (format: HHMMSS)";
  603. help(until) ->
  604. "Run until (format: HHMMSS)";
  605. help(force_stop) ->
  606. "Force stop on test timeout (true | false | skip_rest)";
  607. help(basic_html) ->
  608. "Show basic HTML";
  609. help(stylesheet) ->
  610. "CSS stylesheet to apply to html output";
  611. help(decrypt_key) ->
  612. "Path to key for decrypting config";
  613. help(decrypt_file) ->
  614. "Path to file containing key for decrypting config";
  615. help(abort_if_missing_suites) ->
  616. "Abort if suites are missing";
  617. help(multiply_timetraps) ->
  618. "Multiply timetraps";
  619. help(scale_timetraps) ->
  620. "Scale timetraps";
  621. help(create_priv_dir) ->
  622. "Create priv dir (auto_per_run | auto_per_tc | manual_per_tc)";
  623. help(include) ->
  624. "Directories containing additional include files";
  625. help(readable) ->
  626. "Shows test case names and only displays logs to shell on failures";
  627. help(verbose) ->
  628. "Verbose output";
  629. help(_) ->
  630. "".