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

846 lines
34 KiB

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