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.

807 lines
32 KiB

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