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.

364 lines
13 KiB

14 years ago
14 years ago
  1. %% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
  2. %% ex: ts=4 sw=4 et
  3. %% -------------------------------------------------------------------
  4. %%
  5. %% rebar: Erlang Build Tools
  6. %%
  7. %% Copyright (c) 2009 Dave Smith (dizzyd@dizzyd.com)
  8. %%
  9. %% Permission is hereby granted, free of charge, to any person obtaining a copy
  10. %% of this software and associated documentation files (the "Software"), to deal
  11. %% in the Software without restriction, including without limitation the rights
  12. %% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. %% copies of the Software, and to permit persons to whom the Software is
  14. %% furnished to do so, subject to the following conditions:
  15. %%
  16. %% The above copyright notice and this permission notice shall be included in
  17. %% all copies or substantial portions of the Software.
  18. %%
  19. %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. %% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. %% THE SOFTWARE.
  26. %% -------------------------------------------------------------------
  27. %%
  28. %% Targets:
  29. %% test - run common test suites in ./test
  30. %% int_test - run suites in ./int_test
  31. %% perf_test - run suites inm ./perf_test
  32. %%
  33. %% Global options:
  34. %% verbose=1 - show output from the common_test run as it goes
  35. %% suites="foo,bar" - run <test>/foo_SUITE and <test>/bar_SUITE
  36. %% case="mycase" - run individual test case foo_SUITE:mycase
  37. %% -------------------------------------------------------------------
  38. -module(rebar_ct).
  39. -export([ct/2]).
  40. %% for internal use only
  41. -export([info/2]).
  42. -include("rebar.hrl").
  43. %% ===================================================================
  44. %% Public API
  45. %% ===================================================================
  46. ct(Config, File) ->
  47. TestDir = rebar_config:get_local(Config, ct_dir, "test"),
  48. LogDir = rebar_config:get_local(Config, ct_log_dir, "logs"),
  49. run_test_if_present(TestDir, LogDir, Config, File).
  50. %% ===================================================================
  51. %% Internal functions
  52. %% ===================================================================
  53. info(help, ct) ->
  54. ?CONSOLE(
  55. "Run common_test suites.~n"
  56. "~n"
  57. "Valid rebar.config options:~n"
  58. " ~p~n"
  59. " ~p~n"
  60. " ~p~n"
  61. " ~p~n"
  62. "Valid command line options:~n"
  63. " suites=foo,bar - run <test>/foo_SUITE and <test>/bar_SUITE~n"
  64. " case=\"mycase\" - run individual test case foo_SUITE:mycase~n",
  65. [
  66. {ct_dir, "itest"},
  67. {ct_log_dir, "test/logs"},
  68. {ct_extra_params, "-boot start_sasl -s myapp"},
  69. {ct_use_short_names, true}
  70. ]).
  71. run_test_if_present(TestDir, LogDir, Config, File) ->
  72. case filelib:is_dir(TestDir) of
  73. false ->
  74. ?WARN("~s directory not present - skipping\n", [TestDir]),
  75. ok;
  76. true ->
  77. case filelib:wildcard(TestDir ++ "/*_SUITE.{beam,erl}") of
  78. [] ->
  79. ?WARN("~s directory present, but no common_test"
  80. ++ " SUITES - skipping\n", [TestDir]),
  81. ok;
  82. _ ->
  83. try
  84. run_test(TestDir, LogDir, Config, File)
  85. catch
  86. throw:skip ->
  87. ok
  88. end
  89. end
  90. end.
  91. run_test(TestDir, LogDir, Config, _File) ->
  92. {Cmd, RawLog} = make_cmd(TestDir, LogDir, Config),
  93. ?DEBUG("ct_run cmd:~n~p~n", [Cmd]),
  94. clear_log(LogDir, RawLog),
  95. Output = case rebar_log:is_verbose(Config) of
  96. false ->
  97. " >> " ++ RawLog ++ " 2>&1";
  98. true ->
  99. " 2>&1 | tee -a " ++ RawLog
  100. end,
  101. ShOpts = [{env,[{"TESTDIR", TestDir}]}, return_on_error],
  102. case rebar_utils:sh(Cmd ++ Output, ShOpts) of
  103. {ok,_} ->
  104. %% in older versions of ct_run, this could have been a failure
  105. %% that returned a non-0 code. Check for that!
  106. check_success_log(Config, RawLog);
  107. {error,Res} ->
  108. %% In newer ct_run versions, this may be a sign of a good compile
  109. %% that failed cases. In older version, it's a worse error.
  110. check_fail_log(Config, RawLog, Cmd ++ Output, Res)
  111. end.
  112. clear_log(LogDir, RawLog) ->
  113. case filelib:ensure_dir(filename:join(LogDir, "index.html")) of
  114. ok ->
  115. NowStr = rebar_utils:now_str(),
  116. LogHeader = "--- Test run on " ++ NowStr ++ " ---\n",
  117. ok = file:write_file(RawLog, LogHeader);
  118. {error, Reason} ->
  119. ?ERROR("Could not create log dir - ~p\n", [Reason]),
  120. ?FAIL
  121. end.
  122. %% calling ct with erl does not return non-zero on failure - have to check
  123. %% log results
  124. check_success_log(Config, RawLog) ->
  125. check_log(Config, RawLog, fun(Msg) -> ?CONSOLE("DONE.\n~s\n", [Msg]) end).
  126. -type err_handler() :: fun((string()) -> no_return()).
  127. -spec failure_logger(string(), {integer(), string()}) -> err_handler().
  128. failure_logger(Command, {Rc, Output}) ->
  129. fun(_Msg) ->
  130. ?ABORT("~s failed with error: ~w and output:~n~s~n",
  131. [Command, Rc, Output])
  132. end.
  133. check_fail_log(Config, RawLog, Command, Result) ->
  134. check_log(Config, RawLog, failure_logger(Command, Result)).
  135. check_log(Config,RawLog,Fun) ->
  136. {ok, Msg} =
  137. rebar_utils:sh("grep -e \"TEST COMPLETE\" -e \"{error,make_failed}\" "
  138. ++ RawLog, [{use_stdout, false}]),
  139. MakeFailed = string:str(Msg, "{error,make_failed}") =/= 0,
  140. RunFailed = string:str(Msg, ", 0 failed") =:= 0,
  141. if
  142. MakeFailed ->
  143. show_log(Config, RawLog),
  144. ?ERROR("Building tests failed\n",[]),
  145. ?FAIL;
  146. RunFailed ->
  147. show_log(Config, RawLog),
  148. ?ERROR("One or more tests failed\n",[]),
  149. ?FAIL;
  150. true ->
  151. Fun(Msg)
  152. end.
  153. %% Show the log if it hasn't already been shown because verbose was on
  154. show_log(Config, RawLog) ->
  155. ?CONSOLE("Showing log\n", []),
  156. case rebar_log:is_verbose(Config) of
  157. false ->
  158. {ok, Contents} = file:read_file(RawLog),
  159. ?CONSOLE("~s", [Contents]);
  160. true ->
  161. ok
  162. end.
  163. make_cmd(TestDir, RawLogDir, Config) ->
  164. Cwd = rebar_utils:get_cwd(),
  165. LogDir = filename:join(Cwd, RawLogDir),
  166. EbinDir = filename:absname(filename:join(Cwd, "ebin")),
  167. IncludeDir = filename:join(Cwd, "include"),
  168. Include = case filelib:is_dir(IncludeDir) of
  169. true ->
  170. " -include \"" ++ IncludeDir ++ "\"";
  171. false ->
  172. ""
  173. end,
  174. %% Check for the availability of ct_run; if we can't find it, generate a
  175. %% warning and use the old school, less reliable approach to running CT.
  176. BaseCmd = case os:find_executable("ct_run") of
  177. false ->
  178. "erl -noshell -s ct_run script_start -s erlang halt";
  179. _ ->
  180. "ct_run -noshell"
  181. end,
  182. %% Add the code path of the rebar process to the code path. This
  183. %% includes the dependencies in the code path. The directories
  184. %% that are part of the root Erlang install are filtered out to
  185. %% avoid duplication
  186. R = code:root_dir(),
  187. NonLibCodeDirs = [P || P <- code:get_path(), not lists:prefix(R, P)],
  188. CodeDirs = [io_lib:format("\"~s\"", [Dir]) ||
  189. Dir <- [EbinDir|NonLibCodeDirs]],
  190. CodePathString = string:join(CodeDirs, " "),
  191. Cmd = case get_ct_specs(Cwd) of
  192. undefined ->
  193. ?FMT("~s"
  194. " -pa ~s"
  195. " ~s"
  196. " ~s"
  197. " -logdir \"~s\""
  198. " -env TEST_DIR \"~s\""
  199. " ~s",
  200. [BaseCmd,
  201. CodePathString,
  202. Include,
  203. build_name(Config),
  204. LogDir,
  205. filename:join(Cwd, TestDir),
  206. get_extra_params(Config)]) ++
  207. get_cover_config(Config, Cwd) ++
  208. get_ct_config_file(TestDir) ++
  209. get_config_file(TestDir) ++
  210. get_suites(Config, TestDir) ++
  211. get_case(Config);
  212. SpecFlags ->
  213. ?FMT("~s"
  214. " -pa ~s"
  215. " ~s"
  216. " ~s"
  217. " -logdir \"~s\""
  218. " -env TEST_DIR \"~s\""
  219. " ~s",
  220. [BaseCmd,
  221. CodePathString,
  222. Include,
  223. build_name(Config),
  224. LogDir,
  225. filename:join(Cwd, TestDir),
  226. get_extra_params(Config)]) ++
  227. SpecFlags ++ get_cover_config(Config, Cwd)
  228. end,
  229. RawLog = filename:join(LogDir, "raw.log"),
  230. {Cmd, RawLog}.
  231. build_name(Config) ->
  232. case rebar_config:get_local(Config, ct_use_short_names, false) of
  233. true -> "-sname test";
  234. false -> " -name test@" ++ net_adm:localhost()
  235. end.
  236. get_extra_params(Config) ->
  237. rebar_config:get_local(Config, ct_extra_params, "").
  238. get_ct_specs(Cwd) ->
  239. case collect_glob(Cwd, ".*\.test\.spec\$") of
  240. [] -> undefined;
  241. [Spec] ->
  242. " -spec " ++ Spec;
  243. Specs ->
  244. " -spec " ++
  245. lists:flatten([io_lib:format("~s ", [Spec]) || Spec <- Specs])
  246. end.
  247. get_cover_config(Config, Cwd) ->
  248. case rebar_config:get_local(Config, cover_enabled, false) of
  249. false ->
  250. "";
  251. true ->
  252. case collect_glob(Cwd, ".*cover\.spec\$") of
  253. [] ->
  254. ?DEBUG("No cover spec found: ~s~n", [Cwd]),
  255. "";
  256. [Spec] ->
  257. ?DEBUG("Found cover file ~s~n", [Spec]),
  258. " -cover " ++ Spec;
  259. Specs ->
  260. ?ABORT("Multiple cover specs found: ~p~n", [Specs])
  261. end
  262. end.
  263. collect_glob(Cwd, Glob) ->
  264. CwdParts = filename:split(Cwd),
  265. filelib:fold_files(Cwd, Glob, true, fun(F, Acc) ->
  266. %% Ignore any specs under the deps/ directory. Do this pulling
  267. %% the dirname off the the F and then splitting it into a list.
  268. Parts = filename:split(filename:dirname(F)),
  269. Parts2 = remove_common_prefix(Parts, CwdParts),
  270. case lists:member("deps", Parts2) of
  271. true ->
  272. Acc; % There is a directory named "deps" in path
  273. false ->
  274. [F | Acc] % No "deps" directory in path
  275. end
  276. end, []).
  277. remove_common_prefix([H1|T1], [H1|T2]) ->
  278. remove_common_prefix(T1, T2);
  279. remove_common_prefix(L1, _) ->
  280. L1.
  281. get_ct_config_file(TestDir) ->
  282. Config = filename:join(TestDir, "test.config"),
  283. case filelib:is_regular(Config) of
  284. false ->
  285. " ";
  286. true ->
  287. " -ct_config " ++ Config
  288. end.
  289. get_config_file(TestDir) ->
  290. Config = filename:join(TestDir, "app.config"),
  291. case filelib:is_regular(Config) of
  292. false ->
  293. " ";
  294. true ->
  295. " -config " ++ Config
  296. end.
  297. get_suites(Config, TestDir) ->
  298. case get_suites(Config) of
  299. undefined ->
  300. " -dir " ++ TestDir;
  301. Suites ->
  302. Suites1 = string:tokens(Suites, ","),
  303. Suites2 = [find_suite_path(Suite, TestDir) || Suite <- Suites1],
  304. string:join([" -suite"] ++ Suites2, " ")
  305. end.
  306. get_suites(Config) ->
  307. case rebar_config:get_global(Config, suites, undefined) of
  308. undefined ->
  309. rebar_config:get_global(Config, suite, undefined);
  310. Suites ->
  311. Suites
  312. end.
  313. find_suite_path(Suite, TestDir) ->
  314. Path = filename:join(TestDir, Suite ++ "_SUITE.erl"),
  315. case filelib:is_regular(Path) of
  316. false ->
  317. ?WARN("Suite ~s not found\n", [Suite]),
  318. %% Note - this throw is caught in run_test_if_present/3;
  319. %% this solution was easier than refactoring the entire module.
  320. throw(skip);
  321. true ->
  322. Path
  323. end.
  324. get_case(Config) ->
  325. case rebar_config:get_global(Config, 'case', undefined) of
  326. undefined ->
  327. "";
  328. Case ->
  329. " -case " ++ Case
  330. end.