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.

763 line
28 KiB

14 年之前
14 年之前
12 年之前
14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
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, 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.