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

890 行
36 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. -ifdef(TEST).
  9. %% exported for test purposes
  10. -export([compile/2, prepare_tests/1, translate_paths/2, maybe_write_coverdata/1]).
  11. -endif.
  12. -include("rebar.hrl").
  13. -include_lib("providers/include/providers.hrl").
  14. -define(PROVIDER, ct).
  15. %% we need to modify app_info state before compile
  16. -define(DEPS, [lock]).
  17. %% ===================================================================
  18. %% Public API
  19. %% ===================================================================
  20. -spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
  21. init(State) ->
  22. Provider = providers:create([{name, ?PROVIDER},
  23. {module, ?MODULE},
  24. {deps, ?DEPS},
  25. {bare, true},
  26. {example, "rebar3 ct"},
  27. {short_desc, "Run Common Tests."},
  28. {desc, "Run Common Tests."},
  29. {opts, ct_opts(State)},
  30. {profiles, [test]}]),
  31. {ok, rebar_state:add_provider(State, Provider)}.
  32. -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
  33. do(State) ->
  34. setup_name(State),
  35. Tests = prepare_tests(State),
  36. case compile(State, Tests) of
  37. %% successfully compiled apps
  38. {ok, S} ->
  39. {RawOpts, _} = rebar_state:command_parsed_args(S),
  40. case proplists:get_value(compile_only, RawOpts, false) of
  41. true ->
  42. {ok, S};
  43. false ->
  44. do(S, Tests)
  45. end;
  46. %% this should look like a compiler error, not a ct error
  47. Error -> Error
  48. end.
  49. do(State, Tests) ->
  50. ?INFO("Running Common Test suites...", []),
  51. rebar_paths:set_paths([deps, plugins], State),
  52. %% Run ct provider prehooks
  53. Providers = rebar_state:providers(State),
  54. Cwd = rebar_dir:get_cwd(),
  55. %% Run ct provider pre hooks for all project apps and top level project hooks
  56. rebar_hooks:run_project_and_app_hooks(Cwd, pre, ?PROVIDER, Providers, State),
  57. case Tests of
  58. {ok, T} ->
  59. case run_tests(State, T) of
  60. ok ->
  61. %% Run ct provider post hooks for all project apps and top level project hooks
  62. rebar_hooks:run_project_and_app_hooks(Cwd, post, ?PROVIDER, Providers, State),
  63. rebar_paths:set_paths([plugins, deps], State),
  64. symlink_to_last_ct_logs(State),
  65. {ok, State};
  66. Error ->
  67. rebar_paths:set_paths([plugins, deps], State),
  68. symlink_to_last_ct_logs(State),
  69. Error
  70. end;
  71. Error ->
  72. rebar_paths:set_paths([plugins, deps], State),
  73. Error
  74. end.
  75. run_tests(State, Opts) ->
  76. T = translate_paths(State, Opts),
  77. Opts1 = setup_logdir(State, T),
  78. Opts2 = turn_off_auto_compile(Opts1),
  79. ?DEBUG("ct_opts ~p", [Opts2]),
  80. {RawOpts, _} = rebar_state:command_parsed_args(State),
  81. Result = case proplists:get_value(verbose, RawOpts, false) of
  82. true -> run_test_verbose(Opts2);
  83. false -> run_test_quiet(Opts2)
  84. end,
  85. ok = maybe_write_coverdata(State),
  86. Result.
  87. -spec format_error(any()) -> iolist().
  88. format_error({error, Reason}) ->
  89. io_lib:format("Error running tests:~n ~p", [Reason]);
  90. format_error({error_running_tests, Reason}) ->
  91. format_error({error, Reason});
  92. format_error({failures_running_tests, {Failed, AutoSkipped}}) ->
  93. io_lib:format("Failures occurred running tests: ~b", [Failed+AutoSkipped]);
  94. format_error({badconfig, {Msg, {Value, Key}}}) ->
  95. io_lib:format(Msg, [Value, Key]);
  96. format_error({badconfig, Msg}) ->
  97. io_lib:format(Msg, []);
  98. format_error({multiple_errors, Errors}) ->
  99. io_lib:format(lists:concat(["Error running tests:"] ++
  100. lists:map(fun(Error) -> "~n " ++ Error end, Errors)), []);
  101. format_error({error_reading_testspec, Reason}) ->
  102. io_lib:format("Error reading testspec: ~p", [Reason]).
  103. %% ===================================================================
  104. %% Internal functions
  105. %% ===================================================================
  106. %% @doc Tries to make the symlink `_build/<profile>/logs/last` to the `ct_run` directory
  107. %% of the last common test run.
  108. -spec symlink_to_last_ct_logs(rebar_state:t()) -> ok.
  109. symlink_to_last_ct_logs(State) ->
  110. LogDir = filename:join([rebar_dir:base_dir(State), "logs"]),
  111. {ok, Filenames} = file:list_dir(LogDir),
  112. CtRunDirs = lists:filter(fun(S) -> re:run(S, "ct_run", [unicode]) /= nomatch end, Filenames),
  113. NewestDir = lists:last(lists:sort(CtRunDirs)),
  114. Target = filename:join([LogDir, "last"]),
  115. Existing = filename:join([LogDir, NewestDir]),
  116. case rebar_file_utils:symlink_or_copy(Existing, Target) of
  117. ok -> ok;
  118. exists ->
  119. %% in case the symlink already exists we remove it
  120. %% and make a new updated one
  121. rebar_file_utils:rm_rf(Target),
  122. rebar_file_utils:symlink_or_copy(Existing, Target);
  123. Reason -> ?DEBUG("Warning, couldn't make a symlink to ~ts, reason: ~p.", [Target, Reason])
  124. end.
  125. setup_name(State) ->
  126. {Long, Short, Opts} = rebar_dist_utils:find_options(State),
  127. rebar_dist_utils:either(Long, Short, Opts).
  128. prepare_tests(State) ->
  129. %% command line test options
  130. CmdOpts = cmdopts(State),
  131. %% rebar.config test options
  132. CfgOpts = cfgopts(State),
  133. ProjectApps = rebar_state:project_apps(State),
  134. %% prioritize tests to run first trying any command line specified
  135. %% tests falling back to tests specified in the config file finally
  136. %% running a default set if no other tests are present
  137. select_tests(State, ProjectApps, CmdOpts, CfgOpts).
  138. cmdopts(State) ->
  139. {RawOpts, _} = rebar_state:command_parsed_args(State),
  140. %% filter out opts common_test doesn't know about and convert
  141. %% to ct acceptable forms
  142. transform_retry(transform_opts(RawOpts, []), State).
  143. transform_opts([], Acc) -> lists:reverse(Acc);
  144. transform_opts([{dir, Dirs}|Rest], Acc) ->
  145. transform_opts(Rest, [{dir, split_string(Dirs)}|Acc]);
  146. transform_opts([{suite, Suites}|Rest], Acc) ->
  147. transform_opts(Rest, [{suite, split_string(Suites)}|Acc]);
  148. transform_opts([{group, Groups}|Rest], Acc) ->
  149. transform_opts(Rest, [{group, split_string(Groups)}|Acc]);
  150. transform_opts([{testcase, Cases}|Rest], Acc) ->
  151. transform_opts(Rest, [{testcase, split_string(Cases)}|Acc]);
  152. transform_opts([{config, Configs}|Rest], Acc) ->
  153. transform_opts(Rest, [{config, split_string(Configs)}|Acc]);
  154. transform_opts([{spec, Specs}|Rest], Acc) ->
  155. transform_opts(Rest, [{spec, split_string(Specs)}|Acc]);
  156. transform_opts([{include, Includes}|Rest], Acc) ->
  157. transform_opts(Rest, [{include, split_string(Includes)}|Acc]);
  158. transform_opts([{logopts, LogOpts}|Rest], Acc) ->
  159. transform_opts(Rest, [{logopts, lists:map(fun(P) -> list_to_atom(P) end, split_string(LogOpts))}|Acc]);
  160. transform_opts([{force_stop, "true"}|Rest], Acc) ->
  161. transform_opts(Rest, [{force_stop, true}|Acc]);
  162. transform_opts([{force_stop, "false"}|Rest], Acc) ->
  163. transform_opts(Rest, [{force_stop, false}|Acc]);
  164. transform_opts([{force_stop, "skip_rest"}|Rest], Acc) ->
  165. transform_opts(Rest, [{force_stop, skip_rest}|Acc]);
  166. transform_opts([{create_priv_dir, CreatePrivDir}|Rest], Acc) ->
  167. transform_opts(Rest, [{create_priv_dir, list_to_atom(CreatePrivDir)}|Acc]);
  168. %% drop cover from opts, ct doesn't care about it
  169. transform_opts([{cover, _}|Rest], Acc) ->
  170. transform_opts(Rest, Acc);
  171. %% drop verbose from opts, ct doesn't care about it
  172. transform_opts([{verbose, _}|Rest], Acc) ->
  173. transform_opts(Rest, Acc);
  174. %% drop fail_fast from opts, ct doesn't care about it
  175. transform_opts([{fail_fast, _}|Rest], Acc) ->
  176. transform_opts(Rest, Acc);
  177. %% getopt should handle anything else
  178. transform_opts([Opt|Rest], Acc) ->
  179. transform_opts(Rest, [Opt|Acc]).
  180. %% @private only retry if specified and if no other spec
  181. %% is given.
  182. transform_retry(Opts, State) ->
  183. case proplists:get_value(retry, Opts, false) andalso
  184. not is_any_defined([spec,dir,suite], Opts) of
  185. false ->
  186. Opts;
  187. true ->
  188. Path = filename:join([rebar_dir:base_dir(State), "logs", "retry.spec"]),
  189. filelib:is_file(Path) andalso [{spec, Path}|Opts]
  190. end.
  191. split_string(String) ->
  192. rebar_string:lexemes(String, [$,]).
  193. cfgopts(State) ->
  194. case rebar_state:get(State, ct_opts, []) of
  195. Opts when is_list(Opts) ->
  196. ensure_opts(add_hooks(Opts, State), []);
  197. Wrong ->
  198. %% probably a single non list term
  199. ?PRV_ERROR({badconfig, {"Value `~p' of option `~p' must be a list", {Wrong, ct_opts}}})
  200. end.
  201. ensure_opts([], Acc) -> lists:reverse(Acc);
  202. ensure_opts([{cover, _}|Rest], Acc) ->
  203. ?WARN("Cover specs not supported. See http://www.rebar3.org/docs/running-tests#common-test", []),
  204. ensure_opts(Rest, Acc);
  205. ensure_opts([{auto_compile, _}|Rest], Acc) ->
  206. ?WARN("Auto compile not supported", []),
  207. ensure_opts(Rest, Acc);
  208. ensure_opts([{suite, Suite}|Rest], Acc) when is_integer(hd(Suite)) ->
  209. ensure_opts(Rest, [{suite, Suite}|Acc]);
  210. ensure_opts([{suite, Suite}|Rest], Acc) when is_atom(Suite) ->
  211. ensure_opts(Rest, [{suite, atom_to_list(Suite)}|Acc]);
  212. ensure_opts([{suite, Suites}|Rest], Acc) ->
  213. NewSuites = {suite, lists:map(fun(S) when is_atom(S) -> atom_to_list(S);
  214. (S) when is_list(S) -> S
  215. end,
  216. Suites)},
  217. ensure_opts(Rest, [NewSuites|Acc]);
  218. ensure_opts([{K, V}|Rest], Acc) ->
  219. ensure_opts(Rest, [{K, V}|Acc]);
  220. %% pass through other options, in case of things like config terms
  221. %% in `ct_opts`
  222. ensure_opts([V|Rest], Acc) ->
  223. ensure_opts(Rest, [V|Acc]).
  224. add_hooks(Opts, State) ->
  225. FailFast = case fails_fast(State) of
  226. true -> [cth_fail_fast];
  227. false -> []
  228. end,
  229. case {readable(State), lists:keyfind(ct_hooks, 1, Opts)} of
  230. {false, _} ->
  231. Opts;
  232. {Other, false} ->
  233. [{ct_hooks, [cth_readable_failonly, readable_shell_type(Other),
  234. cth_retry] ++ FailFast} | Opts];
  235. {Other, {ct_hooks, Hooks}} ->
  236. %% Make sure hooks are there once only.
  237. ReadableHooks = [cth_readable_failonly, readable_shell_type(Other),
  238. cth_retry] ++ FailFast,
  239. AllReadableHooks = [cth_readable_failonly, cth_retry, cth_fail_fast,
  240. cth_readable_shell, cth_readable_compact_shell],
  241. NewHooks = (Hooks -- AllReadableHooks) ++ ReadableHooks,
  242. lists:keyreplace(ct_hooks, 1, Opts, {ct_hooks, NewHooks})
  243. end.
  244. readable_shell_type(true) -> cth_readable_shell;
  245. readable_shell_type(compact) -> cth_readable_compact_shell.
  246. select_tests(_, _, _, {error, _} = Error) -> Error;
  247. select_tests(State, ProjectApps, CmdOpts, CfgOpts) ->
  248. %% set application env if sys_config argument is provided
  249. SysConfigs = sys_config_list(CmdOpts, CfgOpts),
  250. Configs = lists:flatmap(fun(Filename) ->
  251. rebar_file_utils:consult_config(State, Filename)
  252. end, SysConfigs),
  253. %% NB: load the applications (from user directories too) to support OTP < 17
  254. %% to our best ability.
  255. rebar_paths:set_paths([deps, plugins], State),
  256. [application:load(Application) || Config <- Configs, {Application, _} <- Config],
  257. rebar_utils:reread_config(Configs, [update_logger]),
  258. Opts = merge_opts(CmdOpts,CfgOpts),
  259. discover_tests(State, ProjectApps, Opts).
  260. %% Merge the option lists from command line and rebar.config:
  261. %%
  262. %% - Options set on the command line will replace the same options if
  263. %% set in rebar.config.
  264. %%
  265. %% - Special care is taken with options that select which tests to
  266. %% run - ANY such option on the command line will replace ALL such
  267. %% options in the config.
  268. %%
  269. %% Note that if 'spec' is given, common_test will ignore all 'dir',
  270. %% 'suite', 'group' and 'case', so there is no need to explicitly
  271. %% remove any options from the command line.
  272. %%
  273. %% All faulty combinations of options are also handled by
  274. %% common_test and are not taken into account here.
  275. merge_opts(CmdOpts0, CfgOpts0) ->
  276. TestSelectOpts = [spec,dir,suite,group,testcase],
  277. CmdOpts = lists:ukeysort(1, CmdOpts0),
  278. CfgOpts1 = lists:ukeysort(1, CfgOpts0),
  279. CfgOpts = case is_any_defined(TestSelectOpts,CmdOpts) of
  280. false ->
  281. CfgOpts1;
  282. true ->
  283. [Opt || Opt={K,_} <- CfgOpts1,
  284. not lists:member(K,TestSelectOpts)]
  285. end,
  286. lists:ukeymerge(1, CmdOpts, CfgOpts).
  287. is_any_defined([Key|Keys],Opts) ->
  288. proplists:is_defined(Key,Opts) orelse is_any_defined(Keys,Opts);
  289. is_any_defined([],_Opts) ->
  290. false.
  291. sys_config_list(CmdOpts, CfgOpts) ->
  292. CmdSysConfigs = split_string(proplists:get_value(sys_config, CmdOpts, "")),
  293. case proplists:get_value(sys_config, CfgOpts, []) of
  294. [H | _]=Configs when is_list(H) ->
  295. Configs ++ CmdSysConfigs;
  296. [] ->
  297. CmdSysConfigs;
  298. Configs ->
  299. [Configs | CmdSysConfigs]
  300. end.
  301. discover_tests(State, ProjectApps, Opts) ->
  302. case is_any_defined([spec,dir,suite],Opts) of
  303. %% no tests defined, try using `$APP/test` and `$ROOT/test` as dirs
  304. false -> {ok, [default_tests(State, ProjectApps)|Opts]};
  305. true -> {ok, Opts}
  306. end.
  307. default_tests(State, ProjectApps) ->
  308. BareTest = filename:join([rebar_state:dir(State), "test"]),
  309. F = fun(App) -> rebar_app_info:dir(App) == rebar_state:dir(State) end,
  310. AppTests = application_dirs(ProjectApps, []),
  311. case filelib:is_dir(BareTest) andalso not lists:any(F, ProjectApps) of
  312. %% `test` dir at root of project is already scheduled to be
  313. %% included or `test` does not exist
  314. false -> {dir, AppTests};
  315. %% need to add `test` dir at root to dirs to be included
  316. true -> {dir, AppTests ++ [BareTest]}
  317. end.
  318. application_dirs([], []) -> [];
  319. application_dirs([], Acc) -> lists:reverse(Acc);
  320. application_dirs([App|Rest], Acc) ->
  321. TestDir = filename:join([rebar_app_info:dir(App), "test"]),
  322. case filelib:is_dir(TestDir) of
  323. true -> application_dirs(Rest, [TestDir|Acc]);
  324. false -> application_dirs(Rest, Acc)
  325. end.
  326. compile(State, {ok, _} = Tests) ->
  327. %% inject `ct_first_files`, `ct_compile_opts` and `include` (from `ct_opts`
  328. %% and command line options) into the applications to be compiled
  329. case inject_ct_state(State, Tests) of
  330. {ok, NewState} -> do_compile(NewState);
  331. Error -> Error
  332. end;
  333. %% maybe compile even in the face of errors?
  334. compile(_State, Error) -> Error.
  335. do_compile(State) ->
  336. {ok, S} = rebar_prv_compile:do(State),
  337. ok = maybe_cover_compile(S),
  338. {ok, S}.
  339. inject_ct_state(State, {ok, Tests}) ->
  340. Apps = rebar_state:project_apps(State),
  341. case inject_ct_state(State, Tests, Apps, []) of
  342. {ok, {NewState, ModdedApps}} ->
  343. test_dirs(NewState, ModdedApps, Tests);
  344. {error, _} = Error -> Error
  345. end.
  346. inject_ct_state(State, Tests, [App|Rest], Acc) ->
  347. case inject(rebar_app_info:opts(App), State, Tests) of
  348. {error, _} = Error -> Error;
  349. NewOpts ->
  350. NewApp = rebar_app_info:opts(App, NewOpts),
  351. inject_ct_state(State, Tests, Rest, [NewApp|Acc])
  352. end;
  353. inject_ct_state(State, Tests, [], Acc) ->
  354. case inject(rebar_state:opts(State), State, Tests) of
  355. {error, _} = Error -> Error;
  356. NewOpts ->
  357. {ok, {rebar_state:opts(State, NewOpts), lists:reverse(Acc)}}
  358. end.
  359. opts(Opts, Key, Default) ->
  360. case rebar_opts:get(Opts, Key, Default) of
  361. Vs when is_list(Vs) -> Vs;
  362. Wrong ->
  363. ?PRV_ERROR({badconfig, {"Value `~p' of option `~p' must be a list", {Wrong, Key}}})
  364. end.
  365. inject(Opts, State, Tests) -> erl_opts(Opts, State, Tests).
  366. erl_opts(Opts, State, Tests) ->
  367. %% append `ct_compile_opts` to app defined `erl_opts`
  368. ErlOpts = opts(Opts, erl_opts, []),
  369. CTOpts = opts(Opts, ct_compile_opts, []),
  370. case add_transforms(append(CTOpts, ErlOpts), State) of
  371. {error, _} = Error -> Error;
  372. NewErlOpts -> first_files(rebar_opts:set(Opts, erl_opts, NewErlOpts), Tests)
  373. end.
  374. first_files(Opts, Tests) ->
  375. %% append `ct_first_files` to app defined `erl_first_files`
  376. FirstFiles = opts(Opts, erl_first_files, []),
  377. CTFirstFiles = opts(Opts, ct_first_files, []),
  378. case append(CTFirstFiles, FirstFiles) of
  379. {error, _} = Error -> Error;
  380. NewFirstFiles -> include_files(rebar_opts:set(Opts, erl_first_files, NewFirstFiles), Tests)
  381. end.
  382. include_files(Opts, Tests) ->
  383. %% append include dirs from command line and `ct_opts` to app defined
  384. %% `erl_opts`
  385. ErlOpts = opts(Opts, erl_opts, []),
  386. Includes = proplists:get_value(include, Tests, []),
  387. Is = lists:map(fun(I) -> {i, I} end, Includes),
  388. case append(Is, ErlOpts) of
  389. {error, _} = Error -> Error;
  390. NewIncludes -> ct_macro(rebar_opts:set(Opts, erl_opts, NewIncludes))
  391. end.
  392. ct_macro(Opts) ->
  393. ErlOpts = opts(Opts, erl_opts, []),
  394. NewOpts = safe_define_ct_macro(ErlOpts),
  395. rebar_opts:set(Opts, erl_opts, NewOpts).
  396. safe_define_ct_macro(Opts) ->
  397. %% defining a compile macro twice results in an exception so
  398. %% make sure 'COMMON_TEST' is only defined once
  399. case test_defined(Opts) of
  400. true -> Opts;
  401. false -> [{d, 'COMMON_TEST'}|Opts]
  402. end.
  403. test_defined([{d, 'COMMON_TEST'}|_]) -> true;
  404. test_defined([{d, 'COMMON_TEST', true}|_]) -> true;
  405. test_defined([_|Rest]) -> test_defined(Rest);
  406. test_defined([]) -> false.
  407. append({error, _} = Error, _) -> Error;
  408. append(_, {error, _} = Error) -> Error;
  409. append(A, B) -> A ++ B.
  410. add_transforms(CTOpts, State) when is_list(CTOpts) ->
  411. case readable(State) of
  412. false ->
  413. CTOpts;
  414. Other when Other == true; Other == compact ->
  415. ReadableTransform = [{parse_transform, cth_readable_transform}],
  416. (CTOpts -- ReadableTransform) ++ ReadableTransform
  417. end;
  418. add_transforms({error, _} = Error, _State) -> Error.
  419. readable(State) ->
  420. {RawOpts, _} = rebar_state:command_parsed_args(State),
  421. case proplists:get_value(readable, RawOpts) of
  422. "true" -> true;
  423. "false" -> false;
  424. "compact" -> compact;
  425. undefined -> rebar_state:get(State, ct_readable, compact)
  426. end.
  427. fails_fast(State) ->
  428. {RawOpts, _} = rebar_state:command_parsed_args(State),
  429. proplists:get_value(fail_fast, RawOpts) == true.
  430. test_dirs(State, Apps, Opts) ->
  431. case proplists:get_value(spec, Opts) of
  432. undefined ->
  433. case {proplists:get_value(suite, Opts), proplists:get_value(dir, Opts)} of
  434. {Suites, undefined} -> set_compile_dirs(State, Apps, {suite, Suites});
  435. {undefined, Dirs} -> set_compile_dirs(State, Apps, {dir, Dirs});
  436. {Suites, Dir} when is_integer(hd(Dir)) ->
  437. set_compile_dirs(State, Apps, join(Suites, Dir));
  438. {Suites, [Dir]} when is_integer(hd(Dir)) ->
  439. set_compile_dirs(State, Apps, join(Suites, Dir));
  440. {_Suites, _Dirs} -> {error, "Only a single directory may be specified when specifying suites"}
  441. end;
  442. Spec when is_integer(hd(Spec)) ->
  443. spec_test_dirs(State, Apps, [Spec]);
  444. Specs ->
  445. spec_test_dirs(State, Apps, Specs)
  446. end.
  447. spec_test_dirs(State, Apps, Specs0) ->
  448. case get_dirs_from_specs(Specs0) of
  449. {ok,{Specs,SuiteDirs}} ->
  450. {State1,Apps1} = set_compile_dirs1(State, Apps, {dir, SuiteDirs}),
  451. {State2,Apps2} = set_compile_dirs1(State1, Apps1, {spec, Specs}),
  452. [maybe_copy_spec(State2,Apps2,S) || S <- Specs],
  453. {ok, rebar_state:project_apps(State2, Apps2)};
  454. Error ->
  455. Error
  456. end.
  457. join(Suite, Dir) when is_integer(hd(Suite)) ->
  458. {suite, [filename:join([Dir, Suite])]};
  459. join(Suites, Dir) ->
  460. {suite, lists:map(fun(S) -> filename:join([Dir, S]) end, Suites)}.
  461. set_compile_dirs(State, Apps, What) ->
  462. {NewState,NewApps} = set_compile_dirs1(State, Apps, What),
  463. {ok, rebar_state:project_apps(NewState, NewApps)}.
  464. set_compile_dirs1(State, Apps, {dir, Dir}) when is_integer(hd(Dir)) ->
  465. %% single directory
  466. %% insert `Dir` into an app if relative, or the base state if not
  467. %% app relative but relative to the root or not at all if outside
  468. %% project scope
  469. maybe_inject_test_dir(State, [], Apps, Dir);
  470. set_compile_dirs1(State, Apps, {dir, Dirs}) ->
  471. %% multiple directories
  472. F = fun(Dir, {S, A}) -> maybe_inject_test_dir(S, [], A, Dir) end,
  473. lists:foldl(F, {State, Apps}, Dirs);
  474. set_compile_dirs1(State, Apps, {Type, Files}) when Type==spec; Type==suite ->
  475. %% specs or suites with dir component
  476. Dirs = find_file_dirs(Files),
  477. F = fun(Dir, {S, A}) -> maybe_inject_test_dir(S, [], A, Dir) end,
  478. lists:foldl(F, {State, Apps}, Dirs).
  479. find_file_dirs(Files) ->
  480. AllDirs = lists:map(fun(F) -> filename:dirname(filename:absname(F)) end, Files),
  481. %% eliminate duplicates
  482. lists:usort(AllDirs).
  483. maybe_inject_test_dir(State, AppAcc, [App|Rest], Dir) ->
  484. case rebar_file_utils:path_from_ancestor(Dir, rebar_app_info:dir(App)) of
  485. {ok, []} ->
  486. %% normal operation involves copying the entire directory a
  487. %% suite exists in but if the suite is in the app root directory
  488. %% the current compiler tries to compile all subdirs including priv
  489. %% instead copy only files ending in `.erl' and directories
  490. %% ending in `_SUITE_data' into the `_build/PROFILE/lib/APP' dir
  491. ok = copy_bare_suites(Dir, rebar_app_info:out_dir(App)),
  492. Opts = inject_test_dir(rebar_state:opts(State), rebar_app_info:out_dir(App)),
  493. {rebar_state:opts(State, Opts), AppAcc ++ [App]};
  494. {ok, Path} ->
  495. Opts = inject_test_dir(rebar_app_info:opts(App), Path),
  496. {State, AppAcc ++ [rebar_app_info:opts(App, Opts)] ++ Rest};
  497. {error, badparent} ->
  498. maybe_inject_test_dir(State, AppAcc ++ [App], Rest, Dir)
  499. end;
  500. maybe_inject_test_dir(State, AppAcc, [], Dir) ->
  501. case rebar_file_utils:path_from_ancestor(Dir, rebar_state:dir(State)) of
  502. {ok, []} ->
  503. %% normal operation involves copying the entire directory a
  504. %% suite exists in but if the suite is in the root directory
  505. %% that results in a loop as we copy `_build' into itself
  506. %% instead copy only files ending in `.erl' and directories
  507. %% ending in `_SUITE_data' in the `_build/PROFILE/extras' dir
  508. ExtrasDir = filename:join([rebar_dir:base_dir(State), "extras"]),
  509. ok = copy_bare_suites(Dir, ExtrasDir),
  510. Opts = inject_test_dir(rebar_state:opts(State), ExtrasDir),
  511. {rebar_state:opts(State, Opts), AppAcc};
  512. {ok, Path} ->
  513. Opts = inject_test_dir(rebar_state:opts(State), Path),
  514. {rebar_state:opts(State, Opts), AppAcc};
  515. {error, badparent} ->
  516. {State, AppAcc}
  517. end.
  518. copy_bare_suites(From, To) ->
  519. filelib:ensure_dir(filename:join([To, "dummy.txt"])),
  520. SrcFiles = rebar_utils:find_files(From, ".*\\.[e|h]rl\$", false),
  521. DataDirs = lists:filter(fun filelib:is_dir/1,
  522. filelib:wildcard(filename:join([From, "*_SUITE_data"]))),
  523. ok = rebar_file_utils:cp_r(SrcFiles, To),
  524. rebar_file_utils:cp_r(DataDirs, To).
  525. maybe_copy_spec(State, [App|Apps], Spec) ->
  526. case rebar_file_utils:path_from_ancestor(filename:dirname(Spec), rebar_app_info:dir(App)) of
  527. {ok, []} ->
  528. ok = rebar_file_utils:cp_r([Spec],rebar_app_info:out_dir(App));
  529. {ok,_} ->
  530. ok;
  531. {error,badparent} ->
  532. maybe_copy_spec(State, Apps, Spec)
  533. end;
  534. maybe_copy_spec(State, [], Spec) ->
  535. case rebar_file_utils:path_from_ancestor(filename:dirname(Spec), rebar_state:dir(State)) of
  536. {ok, []} ->
  537. ExtrasDir = filename:join([rebar_dir:base_dir(State), "extras"]),
  538. ok = rebar_file_utils:cp_r([Spec],ExtrasDir);
  539. _R ->
  540. ok
  541. end.
  542. inject_test_dir(Opts, Dir) ->
  543. %% append specified test targets to app defined `extra_src_dirs`
  544. ExtraSrcDirs = rebar_opts:get(Opts, extra_src_dirs, []),
  545. rebar_opts:set(Opts, extra_src_dirs, ExtraSrcDirs ++ [Dir]).
  546. get_dirs_from_specs(Specs) ->
  547. case get_tests_from_specs(Specs) of
  548. {ok,Tests} ->
  549. {SpecLists,NodeRunSkipLists} = lists:unzip(Tests),
  550. SpecList = lists:append(SpecLists),
  551. NodeRunSkipList = lists:append(NodeRunSkipLists),
  552. RunList = lists:append([R || {_,R,_} <- NodeRunSkipList]),
  553. DirList = [element(1,R) || R <- RunList],
  554. {ok,{SpecList,DirList}};
  555. {error,Reason} ->
  556. {error,{?MODULE,{error_reading_testspec,Reason}}}
  557. end.
  558. get_tests_from_specs(Specs) ->
  559. _ = ct_testspec:module_info(), % make sure ct_testspec is loaded
  560. case erlang:function_exported(ct_testspec,get_tests,1) of
  561. true ->
  562. ct_testspec:get_tests(Specs);
  563. false ->
  564. case ct_testspec:collect_tests_from_file(Specs,true) of
  565. Tests when is_list(Tests) ->
  566. {ok,[{S,ct_testspec:prepare_tests(R)} || {S,R} <- Tests]};
  567. Error ->
  568. Error
  569. end
  570. end.
  571. translate_paths(State, Opts) ->
  572. case proplists:get_value(spec, Opts) of
  573. undefined ->
  574. case {proplists:get_value(suite, Opts), proplists:get_value(dir, Opts)} of
  575. {_Suites, undefined} -> translate_paths(State, suite, Opts, []);
  576. {undefined, _Dirs} -> translate_paths(State, dir, Opts, []);
  577. %% both dirs and suites are defined, only translate dir paths
  578. _ -> translate_paths(State, dir, Opts, [])
  579. end;
  580. _Specs ->
  581. translate_paths(State, spec, Opts, [])
  582. end.
  583. translate_paths(_State, _Type, [], Acc) -> lists:reverse(Acc);
  584. translate_paths(State, Type, [{Type, Val}|Rest], Acc) when is_integer(hd(Val)) ->
  585. %% single file or dir
  586. translate_paths(State, Type, [{Type, [Val]}|Rest], Acc);
  587. translate_paths(State, Type, [{Type, Files}|Rest], Acc) ->
  588. Apps = rebar_state:project_apps(State),
  589. New = {Type, lists:map(fun(File) -> translate(State, Apps, File) end, Files)},
  590. translate_paths(State, Type, Rest, [New|Acc]);
  591. translate_paths(State, Type, [Test|Rest], Acc) ->
  592. translate_paths(State, Type, Rest, [Test|Acc]).
  593. translate(State, [App|Rest], Path) ->
  594. case rebar_file_utils:path_from_ancestor(Path, rebar_app_info:dir(App)) of
  595. {ok, P} -> filename:join([rebar_app_info:out_dir(App), P]);
  596. {error, badparent} -> translate(State, Rest, Path)
  597. end;
  598. translate(State, [], Path) ->
  599. case rebar_file_utils:path_from_ancestor(Path, rebar_state:dir(State)) of
  600. {ok, P} -> filename:join([rebar_dir:base_dir(State), "extras", P]);
  601. %% not relative, leave as is
  602. {error, badparent} -> Path
  603. end.
  604. setup_logdir(State, Opts) ->
  605. Logdir = case proplists:get_value(logdir, Opts) of
  606. undefined -> filename:join([rebar_dir:base_dir(State), "logs"]);
  607. Dir -> Dir
  608. end,
  609. filelib:ensure_dir(filename:join([Logdir, "dummy.beam"])),
  610. [{logdir, Logdir}|lists:keydelete(logdir, 1, Opts)].
  611. turn_off_auto_compile(Opts) ->
  612. [{auto_compile, false}|lists:keydelete(auto_compile, 1, Opts)].
  613. run_test_verbose(Opts) -> handle_results(ct:run_test(Opts)).
  614. run_test_quiet(Opts) ->
  615. Pid = self(),
  616. Ref = erlang:make_ref(),
  617. LogDir = proplists:get_value(logdir, Opts),
  618. {_, Monitor} = erlang:spawn_monitor(fun() ->
  619. {ok, F} = file:open(filename:join([LogDir, "ct.latest.log"]),
  620. [write]),
  621. true = group_leader(F, self()),
  622. Pid ! {Ref, ct:run_test(Opts)}
  623. end),
  624. receive
  625. {Ref, Result} -> handle_quiet_results(Opts, Result);
  626. {'DOWN', Monitor, _, _, Reason} -> handle_results(?PRV_ERROR(Reason))
  627. end.
  628. handle_results(Results) when is_list(Results) ->
  629. Result = lists:foldl(fun sum_results/2, {0, 0, {0,0}}, Results),
  630. handle_results(Result);
  631. handle_results({_, Failed, {_, AutoSkipped}})
  632. when Failed > 0 orelse AutoSkipped > 0 ->
  633. ?PRV_ERROR({failures_running_tests, {Failed, AutoSkipped}});
  634. handle_results({error, Reason}) ->
  635. ?PRV_ERROR({error_running_tests, Reason});
  636. handle_results(_) ->
  637. ok.
  638. sum_results({Passed, Failed, {UserSkipped, AutoSkipped}},
  639. {Passed2, Failed2, {UserSkipped2, AutoSkipped2}}) ->
  640. {Passed+Passed2, Failed+Failed2,
  641. {UserSkipped+UserSkipped2, AutoSkipped+AutoSkipped2}};
  642. sum_results(_, {error, Reason}) ->
  643. {error, Reason};
  644. sum_results(Unknown, _) ->
  645. {error, Unknown}.
  646. handle_quiet_results(_, {error, _} = Result) ->
  647. handle_results(Result);
  648. handle_quiet_results(CTOpts, Results) when is_list(Results) ->
  649. _ = [format_result(Result) || Result <- Results],
  650. case handle_results(Results) of
  651. ?PRV_ERROR({failures_running_tests, _}) = Error ->
  652. LogDir = proplists:get_value(logdir, CTOpts),
  653. Index = filename:join([LogDir, "index.html"]),
  654. ?CONSOLE("Results written to ~p.", [Index]),
  655. Error;
  656. Other ->
  657. Other
  658. end;
  659. handle_quiet_results(CTOpts, Result) ->
  660. handle_quiet_results(CTOpts, [Result]).
  661. format_result({Passed, 0, {0, 0}}) ->
  662. ?CONSOLE("All ~p tests passed.", [Passed]);
  663. format_result({Passed, Failed, Skipped}) ->
  664. Format = [format_failed(Failed), format_skipped(Skipped),
  665. format_passed(Passed)],
  666. ?CONSOLE("~ts", [Format]);
  667. format_result(_Unknown) ->
  668. %% Happens when CT itself encounters a bug
  669. ok.
  670. format_failed(0) ->
  671. [];
  672. format_failed(Failed) ->
  673. io_lib:format("Failed ~p tests. ", [Failed]).
  674. format_passed(Passed) ->
  675. io_lib:format("Passed ~p tests. ", [Passed]).
  676. format_skipped({0, 0}) ->
  677. [];
  678. format_skipped({User, Auto}) ->
  679. io_lib:format("Skipped ~p (~p, ~p) tests. ", [User+Auto, User, Auto]).
  680. maybe_cover_compile(State) ->
  681. {RawOpts, _} = rebar_state:command_parsed_args(State),
  682. State1 = case proplists:get_value(cover, RawOpts, false) of
  683. true -> rebar_state:set(State, cover_enabled, true);
  684. false -> State
  685. end,
  686. rebar_prv_cover:maybe_cover_compile(State1).
  687. maybe_write_coverdata(State) ->
  688. {RawOpts, _} = rebar_state:command_parsed_args(State),
  689. State1 = case proplists:get_value(cover, RawOpts, false) of
  690. true -> rebar_state:set(State, cover_enabled, true);
  691. false -> State
  692. end,
  693. Name = proplists:get_value(cover_export_name, RawOpts, ?PROVIDER),
  694. rebar_prv_cover:maybe_write_coverdata(State1, Name).
  695. ct_opts(_State) ->
  696. [{dir, undefined, "dir", string, help(dir)}, %% comma-separated list
  697. {suite, undefined, "suite", string, help(suite)}, %% comma-separated list
  698. {group, undefined, "group", string, help(group)}, %% comma-separated list
  699. {testcase, undefined, "case", string, help(testcase)}, %% comma-separated list
  700. {label, undefined, "label", string, help(label)}, %% String
  701. {config, undefined, "config", string, help(config)}, %% comma-separated list
  702. {spec, undefined, "spec", string, help(spec)}, %% comma-separated list
  703. {join_specs, undefined, "join_specs", boolean, help(join_specs)},
  704. {allow_user_terms, undefined, "allow_user_terms", boolean, help(allow_user_terms)}, %% Bool
  705. {logdir, undefined, "logdir", string, help(logdir)}, %% dir
  706. {logopts, undefined, "logopts", string, help(logopts)}, %% comma-separated list
  707. {verbosity, undefined, "verbosity", integer, help(verbosity)}, %% Integer
  708. {cover, $c, "cover", {boolean, false}, help(cover)},
  709. {cover_export_name, undefined, "cover_export_name", string, help(cover_export_name)},
  710. {repeat, undefined, "repeat", integer, help(repeat)}, %% integer
  711. {duration, undefined, "duration", string, help(duration)}, % format: HHMMSS
  712. {until, undefined, "until", string, help(until)}, %% format: YYMoMoDD[HHMMSS]
  713. {force_stop, undefined, "force_stop", string, help(force_stop)}, %% String
  714. {basic_html, undefined, "basic_html", boolean, help(basic_html)}, %% Boolean
  715. {stylesheet, undefined, "stylesheet", string, help(stylesheet)}, %% String
  716. {decrypt_key, undefined, "decrypt_key", string, help(decrypt_key)}, %% String
  717. {decrypt_file, undefined, "decrypt_file", string, help(decrypt_file)}, %% String
  718. {abort_if_missing_suites, undefined, "abort_if_missing_suites", {boolean, true}, help(abort_if_missing_suites)}, %% Boolean
  719. {multiply_timetraps, undefined, "multiply_timetraps", integer, help(multiple_timetraps)}, %% Integer
  720. {scale_timetraps, undefined, "scale_timetraps", boolean, help(scale_timetraps)},
  721. {create_priv_dir, undefined, "create_priv_dir", string, help(create_priv_dir)},
  722. {include, undefined, "include", string, help(include)},
  723. {readable, undefined, "readable", string, help(readable)},
  724. {verbose, $v, "verbose", boolean, help(verbose)},
  725. {name, undefined, "name", atom, help(name)},
  726. {sname, undefined, "sname", atom, help(sname)},
  727. {setcookie, undefined, "setcookie", atom, help(setcookie)},
  728. {sys_config, undefined, "sys_config", string, help(sys_config)}, %% comma-separated list
  729. {compile_only, undefined, "compile_only", boolean, help(compile_only)},
  730. {retry, undefined, "retry", boolean, help(retry)},
  731. {fail_fast, undefined, "fail_fast", {boolean, false}, help(fail_fast)}
  732. ].
  733. help(compile_only) ->
  734. "Compile modules in the project with the test configuration but do not run the tests";
  735. help(dir) ->
  736. "List of additional directories containing test suites";
  737. help(suite) ->
  738. "List of test suites to run";
  739. help(group) ->
  740. "List of test groups to run";
  741. help(testcase) ->
  742. "List of test cases to run";
  743. help(label) ->
  744. "Test label";
  745. help(config) ->
  746. "List of config files";
  747. help(spec) ->
  748. "List of test specifications";
  749. help(join_specs) ->
  750. "Merge all test specifications and perform a single test run";
  751. help(sys_config) ->
  752. "List of application config files";
  753. help(allow_user_terms) ->
  754. "Allow user defined config values in config files";
  755. help(logdir) ->
  756. "Log folder";
  757. help(logopts) ->
  758. "Options for common test logging";
  759. help(verbosity) ->
  760. "Verbosity";
  761. help(cover) ->
  762. "Generate cover data";
  763. help(cover_export_name) ->
  764. "Base name of the coverdata file to write";
  765. help(repeat) ->
  766. "How often to repeat tests";
  767. help(duration) ->
  768. "Max runtime (format: HHMMSS)";
  769. help(until) ->
  770. "Run until (format: HHMMSS)";
  771. help(force_stop) ->
  772. "Force stop on test timeout (true | false | skip_rest)";
  773. help(basic_html) ->
  774. "Show basic HTML";
  775. help(stylesheet) ->
  776. "CSS stylesheet to apply to html output";
  777. help(decrypt_key) ->
  778. "Path to key for decrypting config";
  779. help(decrypt_file) ->
  780. "Path to file containing key for decrypting config";
  781. help(abort_if_missing_suites) ->
  782. "Abort if suites are missing";
  783. help(multiply_timetraps) ->
  784. "Multiply timetraps";
  785. help(scale_timetraps) ->
  786. "Scale timetraps";
  787. help(create_priv_dir) ->
  788. "Create priv dir (auto_per_run | auto_per_tc | manual_per_tc)";
  789. help(include) ->
  790. "Directories containing additional include files";
  791. help(readable) ->
  792. "Shows test case names and only displays logs to shell on failures (true | compact | false)";
  793. help(verbose) ->
  794. "Verbose output";
  795. help(name) ->
  796. "Gives a long name to the node";
  797. help(sname) ->
  798. "Gives a short name to the node";
  799. help(setcookie) ->
  800. "Sets the cookie if the node is distributed";
  801. help(retry) ->
  802. "Experimental feature. If any specification for previously failing test is found, runs them.";
  803. help(fail_fast) ->
  804. "Experimental feature. If any test fails, the run is aborted. Since common test does not "
  805. "support this natively, we abort the rebar3 run on a failure. This May break CT's disk logging and "
  806. "other rebar3 features.";
  807. help(_) ->
  808. "".