選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

763 行
28 KiB

  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, 2010 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. %% @author Dave Smith <dizzyd@dizzyd.com>
  28. %% @doc rebar_eunit supports the following commands:
  29. %% <ul>
  30. %% <li>eunit - runs eunit tests</li>
  31. %% <li>clean - remove ?EUNIT_DIR directory</li>
  32. %% <li>reset_after_eunit::boolean() - default = true.
  33. %% If true, try to "reset" VM state to approximate state prior to
  34. %% running the EUnit tests:
  35. %% <ul>
  36. %% <li>Stop net_kernel if it was started</li>
  37. %% <li>Stop OTP applications not running before EUnit tests were run</li>
  38. %% <li>Kill processes not running before EUnit tests were run</li>
  39. %% <li>Reset OTP application environment variables</li>
  40. %% </ul>
  41. %% </li>
  42. %% </ul>
  43. %% The following Global options are supported:
  44. %% <ul>
  45. %% <li>verbose=1 - show extra output from the eunit test</li>
  46. %% <li>
  47. %% suites="foo,bar" - runs tests in foo.erl, test/foo_tests.erl and
  48. %% tests in bar.erl, test/bar_tests.erl
  49. %% </li>
  50. %% <li>
  51. %% suites="foo,bar" tests="baz"- runs first test with name starting
  52. %% with 'baz' in foo.erl, test/foo_tests.erl and tests in bar.erl,
  53. %% test/bar_tests.erl
  54. %% </li>
  55. %% <li>
  56. %% tests="baz"- For every existing suite, run the first test whose
  57. %% name starts with bar and, if no such test exists, run the test
  58. %% whose name starts with bar in the suite's _tests module
  59. %% </li>
  60. %% </ul>
  61. %% Additionally, for projects that have separate folders for the core
  62. %% implementation, and for the unit tests, then the following
  63. %% <code>rebar.config</code> option can be provided:
  64. %% <code>{test_compile_opts, [{src_dirs, ["dir"]}]}.</code>.
  65. %% @copyright 2009, 2010 Dave Smith
  66. %% -------------------------------------------------------------------
  67. -module(rebar_eunit).
  68. -export([eunit/2,
  69. clean/2]).
  70. -include("rebar.hrl").
  71. -define(EUNIT_DIR, ".eunit").
  72. %% ===================================================================
  73. %% Public API
  74. %% ===================================================================
  75. eunit(Config, _AppFile) ->
  76. ok = ensure_dirs(),
  77. %% Save code path
  78. CodePath = setup_code_path(),
  79. CompileOnly = rebar_utils:get_experimental_global(Config, compile_only,
  80. false),
  81. {ok, SrcErls} = rebar_erlc_compiler:test_compile(Config, "eunit",
  82. ?EUNIT_DIR),
  83. case CompileOnly of
  84. "true" ->
  85. true = code:set_path(CodePath),
  86. ?CONSOLE("Compiled modules for eunit~n", []);
  87. false ->
  88. run_eunit(Config, CodePath, SrcErls)
  89. end.
  90. clean(_Config, _File) ->
  91. rebar_file_utils:rm_rf(?EUNIT_DIR).
  92. %% ===================================================================
  93. %% Internal functions
  94. %% ===================================================================
  95. run_eunit(Config, CodePath, SrcErls) ->
  96. %% Build a list of all the .beams in ?EUNIT_DIR -- use this for
  97. %% cover and eunit testing. Normally you can just tell cover
  98. %% and/or eunit to scan the directory for you, but eunit does a
  99. %% code:purge in conjunction with that scan and causes any cover
  100. %% compilation info to be lost.
  101. AllBeamFiles = rebar_utils:beams(?EUNIT_DIR),
  102. {BeamFiles, TestBeamFiles} =
  103. lists:partition(fun(N) -> string:str(N, "_tests.beam") =:= 0 end,
  104. AllBeamFiles),
  105. OtherBeamFiles = TestBeamFiles --
  106. [filename:rootname(N) ++ "_tests.beam" || N <- AllBeamFiles],
  107. ModuleBeamFiles = BeamFiles ++ OtherBeamFiles,
  108. %% Get modules to be run in eunit
  109. AllModules = [rebar_utils:beam_to_mod(?EUNIT_DIR, N) || N <- AllBeamFiles],
  110. {SuitesProvided, FilteredModules} = filter_suites(Config, AllModules),
  111. %% Get matching tests
  112. Tests = get_tests(Config, SuitesProvided, ModuleBeamFiles, FilteredModules),
  113. SrcModules = [rebar_utils:erl_to_mod(M) || M <- SrcErls],
  114. {ok, CoverLog} = cover_init(Config, ModuleBeamFiles),
  115. StatusBefore = status_before_eunit(),
  116. EunitResult = perform_eunit(Config, Tests),
  117. perform_cover(Config, FilteredModules, SrcModules),
  118. cover_close(CoverLog),
  119. case proplists:get_value(reset_after_eunit, get_eunit_opts(Config),
  120. true) of
  121. true ->
  122. reset_after_eunit(StatusBefore);
  123. false ->
  124. ok
  125. end,
  126. %% Stop cover to clean the cover_server state. This is important if we want
  127. %% eunit+cover to not slow down when analyzing many Erlang modules.
  128. ok = cover:stop(),
  129. case EunitResult of
  130. ok ->
  131. ok;
  132. _ ->
  133. ?ABORT("One or more eunit tests failed.~n", [])
  134. end,
  135. %% Restore code path
  136. true = code:set_path(CodePath),
  137. ok.
  138. ensure_dirs() ->
  139. %% Make sure ?EUNIT_DIR/ and ebin/ directory exists (append dummy module)
  140. ok = filelib:ensure_dir(filename:join(eunit_dir(), "dummy")),
  141. ok = filelib:ensure_dir(filename:join(rebar_utils:ebin_dir(), "dummy")).
  142. eunit_dir() ->
  143. filename:join(rebar_utils:get_cwd(), ?EUNIT_DIR).
  144. setup_code_path() ->
  145. %% Setup code path prior to compilation so that parse_transforms
  146. %% and the like work properly. Also, be sure to add ebin_dir()
  147. %% to the END of the code path so that we don't have to jump
  148. %% through hoops to access the .app file
  149. CodePath = code:get_path(),
  150. true = code:add_patha(eunit_dir()),
  151. true = code:add_pathz(rebar_utils:ebin_dir()),
  152. CodePath.
  153. %%
  154. %% == filter suites ==
  155. %%
  156. filter_suites(Config, Modules) ->
  157. RawSuites = rebar_config:get_global(Config, suites, ""),
  158. SuitesProvided = RawSuites =/= "",
  159. Suites = [list_to_atom(Suite) || Suite <- string:tokens(RawSuites, ",")],
  160. {SuitesProvided, filter_suites1(Modules, Suites)}.
  161. filter_suites1(Modules, []) ->
  162. Modules;
  163. filter_suites1(Modules, Suites) ->
  164. [M || M <- Modules, lists:member(M, Suites)].
  165. %%
  166. %% == get matching tests ==
  167. %%
  168. get_tests(Config, SuitesProvided, ModuleBeamFiles, FilteredModules) ->
  169. Modules = case SuitesProvided of
  170. false ->
  171. %% No specific suites have been provided, use
  172. %% ModuleBeamFiles which filters out "*_tests" modules
  173. %% so eunit won't doubly run them and cover only
  174. %% calculates coverage on production code. However,
  175. %% keep "*_tests" modules that are not automatically
  176. %% included by eunit.
  177. %%
  178. %% From 'Primitives' in the EUnit User's Guide
  179. %% http://www.erlang.org/doc/apps/eunit/chapter.html
  180. %% "In addition, EUnit will also look for another
  181. %% module whose name is ModuleName plus the suffix
  182. %% _tests, and if it exists, all the tests from that
  183. %% module will also be added. (If ModuleName already
  184. %% contains the suffix _tests, this is not done.) E.g.,
  185. %% the specification {module, mymodule} will run all
  186. %% tests in the modules mymodule and mymodule_tests.
  187. %% Typically, the _tests module should only contain
  188. %% test cases that use the public interface of the main
  189. %% module (and no other code)."
  190. [rebar_utils:beam_to_mod(?EUNIT_DIR, N) ||
  191. N <- ModuleBeamFiles];
  192. true ->
  193. %% Specific suites have been provided, return the
  194. %% filtered modules
  195. FilteredModules
  196. end,
  197. get_matching_tests(Config, Modules).
  198. get_matching_tests(Config, Modules) ->
  199. RawFunctions = rebar_utils:get_experimental_global(Config, tests, ""),
  200. Tests = [list_to_atom(F1) || F1 <- string:tokens(RawFunctions, ",")],
  201. case Tests of
  202. [] ->
  203. Modules;
  204. Functions ->
  205. case get_matching_tests1(Modules, Functions, []) of
  206. [] ->
  207. [];
  208. RawTests ->
  209. make_test_wrappers(RawTests)
  210. end
  211. end.
  212. get_matching_tests1([], _Functions, TestFunctions) ->
  213. TestFunctions;
  214. get_matching_tests1([Module|TModules], Functions, TestFunctions) ->
  215. %% Get module exports
  216. ModuleStr = atom_to_list(Module),
  217. ModuleExports = get_beam_test_exports(ModuleStr),
  218. %% Get module _tests exports
  219. TestModuleStr = string:concat(ModuleStr, "_tests"),
  220. TestModuleExports = get_beam_test_exports(TestModuleStr),
  221. %% Build tests {M, F} list
  222. Tests = get_matching_tests2(Functions, {Module, ModuleExports},
  223. {list_to_atom(TestModuleStr),
  224. TestModuleExports}),
  225. get_matching_tests1(TModules, Functions,
  226. lists:merge([TestFunctions, Tests])).
  227. get_matching_tests2(Functions, {Mod, ModExports}, {TestMod, TestModExports}) ->
  228. %% Look for matching functions into ModExports
  229. ModExportsStr = [atom_to_list(E1) || E1 <- ModExports],
  230. TestModExportsStr = [atom_to_list(E2) || E2 <- TestModExports],
  231. get_matching_exports(Functions, {Mod, ModExportsStr},
  232. {TestMod, TestModExportsStr}, []).
  233. get_matching_exports([], _, _, Matched) ->
  234. Matched;
  235. get_matching_exports([Function|TFunctions], {Mod, ModExportsStr},
  236. {TestMod, TestModExportsStr}, Matched) ->
  237. FunctionStr = atom_to_list(Function),
  238. %% Get matching Function in module, otherwise look in _tests module
  239. NewMatch = case get_matching_export(FunctionStr, ModExportsStr) of
  240. [] ->
  241. {TestMod, get_matching_export(FunctionStr,
  242. TestModExportsStr)};
  243. MatchingExport ->
  244. {Mod, MatchingExport}
  245. end,
  246. case NewMatch of
  247. {_, []} ->
  248. get_matching_exports(TFunctions, {Mod, ModExportsStr},
  249. {TestMod, TestModExportsStr}, Matched);
  250. _ ->
  251. get_matching_exports(TFunctions, {Mod, ModExportsStr},
  252. {TestMod, TestModExportsStr},
  253. [NewMatch|Matched])
  254. end.
  255. get_matching_export(_FunctionStr, []) ->
  256. [];
  257. get_matching_export(FunctionStr, [ExportStr|TExportsStr]) ->
  258. case string:str(ExportStr, FunctionStr) of
  259. 1 ->
  260. list_to_atom(ExportStr);
  261. _ ->
  262. get_matching_export(FunctionStr, TExportsStr)
  263. end.
  264. get_beam_test_exports(ModuleStr) ->
  265. FilePath = filename:join(eunit_dir(),
  266. string:concat(ModuleStr, ".beam")),
  267. case filelib:is_regular(FilePath) of
  268. true ->
  269. {beam_file, _, Exports0, _, _, _} = beam_disasm:file(FilePath),
  270. Exports1 = [FunName || {FunName, FunArity, _} <- Exports0,
  271. FunArity =:= 0],
  272. F = fun(FName) ->
  273. FNameStr = atom_to_list(FName),
  274. re:run(FNameStr, "_test(_)?") =/= nomatch
  275. end,
  276. lists:filter(F, Exports1);
  277. _ ->
  278. []
  279. end.
  280. make_test_wrappers(RawTests) ->
  281. %% eunit_test:function_wrapper/2 was renamed to mf_wrapper/2 in R15B02
  282. {module, eunit_test} = code:ensure_loaded(eunit_test),
  283. WrapperFun = case erlang:function_exported(eunit_test, mf_wrapper, 2) of
  284. true -> fun eunit_test:mf_wrapper/2;
  285. false -> fun eunit_test:function_wrapper/2
  286. end,
  287. ?CONSOLE(" Running test function(s):~n", []),
  288. F = fun({M, F2}, Acc) ->
  289. ?CONSOLE(" ~p:~p/0~n", [M, F2]),
  290. FNameStr = atom_to_list(F2),
  291. NewFunction =
  292. case re:run(FNameStr, "_test_") of
  293. nomatch ->
  294. %% Normal test
  295. eunit_test(WrapperFun, M, F2);
  296. _ ->
  297. %% Generator
  298. eunit_generator(WrapperFun, M, F2)
  299. end,
  300. [NewFunction|Acc]
  301. end,
  302. lists:foldl(F, [], RawTests).
  303. eunit_test(WrapperFun, M, F) ->
  304. WrapperFun(M, F).
  305. eunit_generator(WrapperFun, M, F) ->
  306. {generator, WrapperFun(M, F)}.
  307. %%
  308. %% == run tests ==
  309. %%
  310. perform_eunit(Config, Tests) ->
  311. EunitOpts = get_eunit_opts(Config),
  312. %% Move down into ?EUNIT_DIR while we run tests so any generated files
  313. %% are created there (versus in the source dir)
  314. Cwd = rebar_utils:get_cwd(),
  315. ok = file:set_cwd(?EUNIT_DIR),
  316. EunitResult = (catch eunit:test(Tests, EunitOpts)),
  317. %% Return to original working dir
  318. ok = file:set_cwd(Cwd),
  319. EunitResult.
  320. get_eunit_opts(Config) ->
  321. %% Enable verbose in eunit if so requested..
  322. BaseOpts = case rebar_config:is_verbose(Config) of
  323. true ->
  324. [verbose];
  325. false ->
  326. []
  327. end,
  328. BaseOpts ++ rebar_config:get_list(Config, eunit_opts, []).
  329. %%
  330. %% == code coverage ==
  331. %%
  332. perform_cover(Config, BeamFiles, SrcModules) ->
  333. perform_cover(rebar_config:get(Config, cover_enabled, false),
  334. Config, BeamFiles, SrcModules).
  335. perform_cover(false, _Config, _BeamFiles, _SrcModules) ->
  336. ok;
  337. perform_cover(true, Config, BeamFiles, SrcModules) ->
  338. cover_analyze(Config, BeamFiles, SrcModules).
  339. cover_analyze(_Config, [], _SrcModules) ->
  340. ok;
  341. cover_analyze(Config, FilteredModules, SrcModules) ->
  342. %% Generate coverage info for all the cover-compiled modules
  343. Coverage = lists:flatten([cover_analyze_mod(M) || M <- FilteredModules]),
  344. %% Write index of coverage info
  345. cover_write_index(lists:sort(Coverage), SrcModules),
  346. %% Write coverage details for each file
  347. lists:foreach(fun({M, _, _}) ->
  348. {ok, _} = cover:analyze_to_file(M, cover_file(M),
  349. [html])
  350. end, Coverage),
  351. Index = filename:join([rebar_utils:get_cwd(), ?EUNIT_DIR, "index.html"]),
  352. ?CONSOLE("Cover analysis: ~s\n", [Index]),
  353. %% Export coverage data, if configured
  354. case rebar_config:get(Config, cover_export_enabled, false) of
  355. true ->
  356. cover_export_coverdata();
  357. false ->
  358. ok
  359. end,
  360. %% Print coverage report, if configured
  361. case rebar_config:get(Config, cover_print_enabled, false) of
  362. true ->
  363. cover_print_coverage(lists:sort(Coverage));
  364. false ->
  365. ok
  366. end.
  367. cover_close(not_enabled) ->
  368. ok;
  369. cover_close(F) ->
  370. ok = file:close(F).
  371. cover_init(false, _BeamFiles) ->
  372. {ok, not_enabled};
  373. cover_init(true, BeamFiles) ->
  374. %% Attempt to start the cover server, then set its group leader to
  375. %% .eunit/cover.log, so all cover log messages will go there instead of
  376. %% to stdout. If the cover server is already started, we'll kill that
  377. %% server and start a new one in order not to inherit a polluted
  378. %% cover_server state.
  379. {ok, CoverPid} = case whereis(cover_server) of
  380. undefined ->
  381. cover:start();
  382. _ ->
  383. cover:stop(),
  384. cover:start()
  385. end,
  386. {ok, F} = OkOpen = file:open(
  387. filename:join([?EUNIT_DIR, "cover.log"]),
  388. [write]),
  389. group_leader(F, CoverPid),
  390. ?INFO("Cover compiling ~s\n", [rebar_utils:get_cwd()]),
  391. Compiled = [{Beam, cover:compile_beam(Beam)} || Beam <- BeamFiles],
  392. case [Module || {_, {ok, Module}} <- Compiled] of
  393. [] ->
  394. %% No modules compiled successfully...fail
  395. ?ERROR("Cover failed to compile any modules; aborting.~n", []),
  396. ?FAIL;
  397. _ ->
  398. %% At least one module compiled successfully
  399. %% It's not an error for cover compilation to fail partially,
  400. %% but we do want to warn about them
  401. PrintWarning =
  402. fun(Beam, Desc) ->
  403. ?CONSOLE("Cover compilation warning for ~p: ~p",
  404. [Beam, Desc])
  405. end,
  406. _ = [PrintWarning(Beam, Desc) || {Beam, {error, Desc}} <- Compiled],
  407. OkOpen
  408. end;
  409. cover_init(Config, BeamFiles) ->
  410. cover_init(rebar_config:get(Config, cover_enabled, false), BeamFiles).
  411. cover_analyze_mod(Module) ->
  412. case cover:analyze(Module, coverage, module) of
  413. {ok, {Module, {Covered, NotCovered}}} ->
  414. %% Modules that include the eunit header get an implicit
  415. %% test/0 fun, which cover considers a runnable line, but
  416. %% eunit:test(TestRepresentation) never calls. Decrement
  417. %% NotCovered in this case.
  418. [align_notcovered_count(Module, Covered, NotCovered,
  419. is_eunitized(Module))];
  420. {error, Reason} ->
  421. ?ERROR("Cover analyze failed for ~p: ~p ~p\n",
  422. [Module, Reason, code:which(Module)]),
  423. []
  424. end.
  425. is_eunitized(Mod) ->
  426. has_eunit_test_fun(Mod) andalso
  427. has_header(Mod, "include/eunit.hrl").
  428. has_eunit_test_fun(Mod) ->
  429. [F || {exports, Funs} <- Mod:module_info(),
  430. {F, 0} <- Funs, F =:= test] =/= [].
  431. has_header(Mod, Header) ->
  432. Mod1 = case code:which(Mod) of
  433. cover_compiled ->
  434. {file, File} = cover:is_compiled(Mod),
  435. File;
  436. non_existing -> Mod;
  437. preloaded -> Mod;
  438. L -> L
  439. end,
  440. {ok, {_, [{abstract_code, {_, AC}}]}} = beam_lib:chunks(Mod1,
  441. [abstract_code]),
  442. [F || {attribute, 1, file, {F, 1}} <- AC,
  443. string:str(F, Header) =/= 0] =/= [].
  444. align_notcovered_count(Module, Covered, NotCovered, false) ->
  445. {Module, Covered, NotCovered};
  446. align_notcovered_count(Module, Covered, NotCovered, true) ->
  447. {Module, Covered, NotCovered - 1}.
  448. cover_write_index(Coverage, SrcModules) ->
  449. {ok, F} = file:open(filename:join([?EUNIT_DIR, "index.html"]), [write]),
  450. ok = file:write(F, "<html><head><title>Coverage Summary</title></head>\n"),
  451. IsSrcCoverage = fun({Mod,_C,_N}) -> lists:member(Mod, SrcModules) end,
  452. {SrcCoverage, TestCoverage} = lists:partition(IsSrcCoverage, Coverage),
  453. cover_write_index_section(F, "Source", SrcCoverage),
  454. cover_write_index_section(F, "Test", TestCoverage),
  455. ok = file:write(F, "</body></html>"),
  456. ok = file:close(F).
  457. cover_write_index_section(_F, _SectionName, []) ->
  458. ok;
  459. cover_write_index_section(F, SectionName, Coverage) ->
  460. %% Calculate total coverage
  461. {Covered, NotCovered} = lists:foldl(fun({_Mod, C, N}, {CAcc, NAcc}) ->
  462. {CAcc + C, NAcc + N}
  463. end, {0, 0}, Coverage),
  464. TotalCoverage = percentage(Covered, NotCovered),
  465. %% Write the report
  466. ok = file:write(F, ?FMT("<body><h1>~s Summary</h1>\n", [SectionName])),
  467. ok = file:write(F, ?FMT("<h3>Total: ~s</h3>\n", [TotalCoverage])),
  468. ok = file:write(F, "<table><tr><th>Module</th><th>Coverage %</th></tr>\n"),
  469. FmtLink =
  470. fun(Module, Cov, NotCov) ->
  471. ?FMT("<tr><td><a href='~s.COVER.html'>~s</a></td><td>~s</td>\n",
  472. [Module, Module, percentage(Cov, NotCov)])
  473. end,
  474. lists:foreach(fun({Module, Cov, NotCov}) ->
  475. ok = file:write(F, FmtLink(Module, Cov, NotCov))
  476. end, Coverage),
  477. ok = file:write(F, "</table>\n").
  478. cover_print_coverage(Coverage) ->
  479. {Covered, NotCovered} = lists:foldl(fun({_Mod, C, N}, {CAcc, NAcc}) ->
  480. {CAcc + C, NAcc + N}
  481. end, {0, 0}, Coverage),
  482. TotalCoverage = percentage(Covered, NotCovered),
  483. %% Determine the longest module name for right-padding
  484. Width = lists:foldl(fun({Mod, _, _}, Acc) ->
  485. case length(atom_to_list(Mod)) of
  486. N when N > Acc ->
  487. N;
  488. _ ->
  489. Acc
  490. end
  491. end, 0, Coverage) * -1,
  492. %% Print the output the console
  493. ?CONSOLE("~nCode Coverage:~n", []),
  494. lists:foreach(fun({Mod, C, N}) ->
  495. ?CONSOLE("~*s : ~3s~n",
  496. [Width, Mod, percentage(C, N)])
  497. end, Coverage),
  498. ?CONSOLE("~n~*s : ~s~n", [Width, "Total", TotalCoverage]).
  499. cover_file(Module) ->
  500. filename:join([?EUNIT_DIR, atom_to_list(Module) ++ ".COVER.html"]).
  501. cover_export_coverdata() ->
  502. ExportFile = filename:join(eunit_dir(), "eunit.coverdata"),
  503. case cover:export(ExportFile) of
  504. ok ->
  505. ?CONSOLE("Coverdata export: ~s~n", [ExportFile]);
  506. {error, Reason} ->
  507. ?ERROR("Coverdata export failed: ~p~n", [Reason])
  508. end.
  509. percentage(0, 0) ->
  510. "not executed";
  511. percentage(Cov, NotCov) ->
  512. integer_to_list(trunc((Cov / (Cov + NotCov)) * 100)) ++ "%".
  513. %%
  514. %% == reset_after_eunit ==
  515. %%
  516. status_before_eunit() ->
  517. Apps = get_app_names(),
  518. AppEnvs = [{App, application:get_all_env(App)} || App <- Apps],
  519. {erlang:processes(), erlang:is_alive(), AppEnvs, ets:tab2list(ac_tab)}.
  520. get_app_names() ->
  521. [AppName || {AppName, _, _} <- application:loaded_applications()].
  522. reset_after_eunit({OldProcesses, WasAlive, OldAppEnvs, _OldACs}) ->
  523. IsAlive = erlang:is_alive(),
  524. if not WasAlive andalso IsAlive ->
  525. ?DEBUG("Stopping net kernel....\n", []),
  526. erl_epmd:stop(),
  527. _ = net_kernel:stop(),
  528. pause_until_net_kernel_stopped();
  529. true ->
  530. ok
  531. end,
  532. OldApps = [App || {App, _} <- OldAppEnvs],
  533. Apps = get_app_names(),
  534. _ = [begin
  535. _ = case lists:member(App, OldApps) of
  536. true -> ok;
  537. false -> application:stop(App)
  538. end,
  539. ok = application:unset_env(App, K)
  540. end || App <- Apps, App /= rebar,
  541. {K, _V} <- application:get_all_env(App)],
  542. reconstruct_app_env_vars(Apps),
  543. Processes = erlang:processes(),
  544. _ = kill_extras(Processes -- OldProcesses),
  545. ok.
  546. kill_extras(Pids) ->
  547. %% Killing any of the procs below will either:
  548. %% 1. Interfere with stuff that we don't want interfered with, or
  549. %% 2. May/will force the 'kernel' app to shutdown, which *will*
  550. %% interfere with rebar's ability To Do Useful Stuff(tm).
  551. %% This list may require changes as OTP versions and/or
  552. %% rebar use cases change.
  553. KeepProcs = [cover_server, eunit_server,
  554. eqc, eqc_license, eqc_locked,
  555. %% inet_gethost_native is started on demand, when
  556. %% doing name lookups. It is under kernel_sup, under
  557. %% a supervisor_bridge.
  558. inet_gethost_native],
  559. Killed = [begin
  560. Info = case erlang:process_info(Pid) of
  561. undefined -> [];
  562. Else -> Else
  563. end,
  564. Keep1 = case proplists:get_value(registered_name, Info) of
  565. undefined ->
  566. false;
  567. Name ->
  568. lists:member(Name, KeepProcs)
  569. end,
  570. Keep2 = case proplists:get_value(dictionary, Info) of
  571. undefined ->
  572. false;
  573. Ds ->
  574. case proplists:get_value('$ancestors', Ds) of
  575. undefined ->
  576. false;
  577. As ->
  578. lists:member(kernel_sup, As)
  579. end
  580. end,
  581. if Keep1 orelse Keep2 ->
  582. ok;
  583. true ->
  584. ?DEBUG("Kill ~p ~p\n", [Pid, Info]),
  585. exit(Pid, kill),
  586. Pid
  587. end
  588. end || Pid <- Pids],
  589. case lists:usort(Killed) -- [ok] of
  590. [] ->
  591. ?DEBUG("No processes to kill\n", []),
  592. [];
  593. Else ->
  594. lists:foreach(fun(Pid) -> wait_until_dead(Pid) end, Else),
  595. Else
  596. end.
  597. reconstruct_app_env_vars([App|Apps]) ->
  598. CmdLine0 = proplists:get_value(App, init:get_arguments(), []),
  599. CmdVars = [{list_to_atom(K), list_to_atom(V)} || {K, V} <- CmdLine0],
  600. AppFile = (catch filename:join([code:lib_dir(App),
  601. "ebin",
  602. atom_to_list(App) ++ ".app"])),
  603. AppVars = case file:consult(AppFile) of
  604. {ok, [{application, App, Ps}]} ->
  605. proplists:get_value(env, Ps, []);
  606. _ ->
  607. []
  608. end,
  609. %% App vars specified in config files override those in the .app file.
  610. %% Config files later in the args list override earlier ones.
  611. AppVars1 = case init:get_argument(config) of
  612. {ok, ConfigFiles} ->
  613. {App, MergedAppVars} = lists:foldl(fun merge_app_vars/2,
  614. {App, AppVars},
  615. ConfigFiles),
  616. MergedAppVars;
  617. error ->
  618. AppVars
  619. end,
  620. AllVars = CmdVars ++ AppVars1,
  621. ?DEBUG("Reconstruct ~p ~p\n", [App, AllVars]),
  622. lists:foreach(fun({K, V}) -> application:set_env(App, K, V) end, AllVars),
  623. reconstruct_app_env_vars(Apps);
  624. reconstruct_app_env_vars([]) ->
  625. ok.
  626. merge_app_vars(ConfigFile, {App, AppVars}) ->
  627. File = ensure_config_extension(ConfigFile),
  628. FileAppVars = app_vars_from_config_file(File, App),
  629. Dict1 = dict:from_list(AppVars),
  630. Dict2 = dict:from_list(FileAppVars),
  631. Dict3 = dict:merge(fun(_Key, _Value1, Value2) -> Value2 end, Dict1, Dict2),
  632. {App, dict:to_list(Dict3)}.
  633. ensure_config_extension(File) ->
  634. %% config files must end with .config on disk but when specifying them
  635. %% via the -config option the extension is optional
  636. BaseFileName = filename:basename(File, ".config"),
  637. DirName = filename:dirname(File),
  638. filename:join(DirName, BaseFileName ++ ".config").
  639. app_vars_from_config_file(File, App) ->
  640. case file:consult(File) of
  641. {ok, [Env]} ->
  642. proplists:get_value(App, Env, []);
  643. _ ->
  644. []
  645. end.
  646. wait_until_dead(Pid) when is_pid(Pid) ->
  647. Ref = erlang:monitor(process, Pid),
  648. receive
  649. {'DOWN', Ref, process, _Obj, Info} ->
  650. Info
  651. after 10*1000 ->
  652. exit({timeout_waiting_for, Pid})
  653. end;
  654. wait_until_dead(_) ->
  655. ok.
  656. pause_until_net_kernel_stopped() ->
  657. pause_until_net_kernel_stopped(10).
  658. pause_until_net_kernel_stopped(0) ->
  659. exit(net_kernel_stop_failed);
  660. pause_until_net_kernel_stopped(N) ->
  661. try
  662. _ = net_kernel:i(),
  663. timer:sleep(100),
  664. pause_until_net_kernel_stopped(N - 1)
  665. catch
  666. error:badarg ->
  667. ?DEBUG("Stopped net kernel.\n", []),
  668. ok
  669. end.