Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

364 строки
13 KiB

15 лет назад
15 лет назад
14 лет назад
15 лет назад
14 лет назад
14 лет назад
14 лет назад
15 лет назад
13 лет назад
14 лет назад
13 лет назад
14 лет назад
14 лет назад
15 лет назад
14 лет назад
14 лет назад
  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.