您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

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