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

738 行
30 KiB

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