25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

696 lines
28 KiB

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