Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

738 Zeilen
30 KiB

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