Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

858 rader
32 KiB

14 år sedan
11 år sedan
14 år sedan
12 år sedan
14 år sedan
14 år sedan
14 år sedan
14 år sedan
14 år sedan
14 år sedan
14 år sedan
14 år sedan
14 år sedan
14 år sedan
  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.