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

261 строка
10 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. %% Copyright (c) 2013 Andras Horvath (andras.horvath@erlang-solutions.com)
  9. %%
  10. %% Permission is hereby granted, free of charge, to any person obtaining a copy
  11. %% of this software and associated documentation files (the "Software"), to deal
  12. %% in the Software without restriction, including without limitation the rights
  13. %% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  14. %% copies of the Software, and to permit persons to whom the Software is
  15. %% furnished to do so, subject to the following conditions:
  16. %%
  17. %% The above copyright notice and this permission notice shall be included in
  18. %% all copies or substantial portions of the Software.
  19. %%
  20. %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  21. %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  22. %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  23. %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  24. %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  25. %% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  26. %% THE SOFTWARE.
  27. %% -------------------------------------------------------------------
  28. -module(rebar_cover_utils).
  29. %% for internal use only
  30. -export([init/3,
  31. perform_cover/4,
  32. close/1,
  33. exit/0]).
  34. -include("rebar.hrl").
  35. %% ====================================================================
  36. %% Internal functions
  37. %% ====================================================================
  38. perform_cover(Config, BeamFiles, SrcModules, TargetDir) ->
  39. perform_cover(rebar_config:get(Config, cover_enabled, false),
  40. Config, BeamFiles, SrcModules, TargetDir).
  41. perform_cover(false, _Config, _BeamFiles, _SrcModules, _TargetDir) ->
  42. ok;
  43. perform_cover(true, Config, BeamFiles, SrcModules, TargetDir) ->
  44. analyze(Config, BeamFiles, SrcModules, TargetDir).
  45. close(not_enabled) ->
  46. ok;
  47. close(F) ->
  48. ok = file:close(F).
  49. exit() ->
  50. cover:stop().
  51. init(false, _BeamFiles, _TargetDir) ->
  52. {ok, not_enabled};
  53. init(true, BeamFiles, TargetDir) ->
  54. %% Attempt to start the cover server, then set its group leader to
  55. %% TargetDir/cover.log, so all cover log messages will go there instead of
  56. %% to stdout. If the cover server is already started, we'll kill that
  57. %% server and start a new one in order not to inherit a polluted
  58. %% cover_server state.
  59. {ok, CoverPid} = case whereis(cover_server) of
  60. undefined ->
  61. cover:start();
  62. _ ->
  63. cover:stop(),
  64. cover:start()
  65. end,
  66. {ok, F} = OkOpen = file:open(
  67. filename:join([TargetDir, "cover.log"]),
  68. [write]),
  69. group_leader(F, CoverPid),
  70. ?INFO("Cover compiling ~s\n", [rebar_utils:get_cwd()]),
  71. Compiled = [{Beam, cover:compile_beam(Beam)} || Beam <- BeamFiles],
  72. case [Module || {_, {ok, Module}} <- Compiled] of
  73. [] ->
  74. %% No modules compiled successfully...fail
  75. ?ERROR("Cover failed to compile any modules; aborting.~n", []),
  76. ?FAIL;
  77. _ ->
  78. %% At least one module compiled successfully
  79. %% It's not an error for cover compilation to fail partially,
  80. %% but we do want to warn about them
  81. PrintWarning =
  82. fun(Beam, Desc) ->
  83. ?CONSOLE("Cover compilation warning for ~p: ~p",
  84. [Beam, Desc])
  85. end,
  86. _ = [PrintWarning(Beam, Desc) || {Beam, {error, Desc}} <- Compiled],
  87. OkOpen
  88. end;
  89. init(Config, BeamFiles, TargetDir) ->
  90. init(rebar_config:get(Config, cover_enabled, false), BeamFiles, TargetDir).
  91. analyze(_Config, [], _SrcModules, _TargetDir) ->
  92. ok;
  93. analyze(Config, FilteredModules, SrcModules, TargetDir) ->
  94. %% Generate coverage info for all the cover-compiled modules
  95. Coverage = lists:flatten([analyze_mod(M)
  96. || M <- FilteredModules,
  97. cover:is_compiled(M) =/= false]),
  98. %% Write index of coverage info
  99. write_index(lists:sort(Coverage), SrcModules, TargetDir),
  100. %% Write coverage details for each file
  101. lists:foreach(
  102. fun({M, _, _}) ->
  103. {ok, _} = cover:analyze_to_file(M,
  104. cover_file(M, TargetDir),
  105. [html])
  106. end, Coverage),
  107. Index = filename:join([rebar_utils:get_cwd(), TargetDir, "index.html"]),
  108. ?CONSOLE("Cover analysis: ~s\n", [Index]),
  109. %% Export coverage data, if configured
  110. case rebar_config:get(Config, cover_export_enabled, false) of
  111. true ->
  112. export_coverdata(TargetDir);
  113. false ->
  114. ok
  115. end,
  116. %% Print coverage report, if configured
  117. case rebar_config:get(Config, cover_print_enabled, false) of
  118. true ->
  119. print_coverage(lists:sort(Coverage));
  120. false ->
  121. ok
  122. end.
  123. analyze_mod(Module) ->
  124. case cover:analyze(Module, coverage, module) of
  125. {ok, {Module, {Covered, NotCovered}}} ->
  126. %% Modules that include the eunit header get an implicit
  127. %% test/0 fun, which cover considers a runnable line, but
  128. %% eunit:test(TestRepresentation) never calls. Decrement
  129. %% NotCovered in this case.
  130. [align_notcovered_count(Module, Covered, NotCovered,
  131. is_eunitized(Module))];
  132. {error, Reason} ->
  133. ?ERROR("Cover analyze failed for ~p: ~p ~p\n",
  134. [Module, Reason, code:which(Module)]),
  135. []
  136. end.
  137. is_eunitized(Mod) ->
  138. has_eunit_test_fun(Mod) andalso
  139. has_header(Mod, "include/eunit.hrl").
  140. has_eunit_test_fun(Mod) ->
  141. [F || {exports, Funs} <- Mod:module_info(),
  142. {F, 0} <- Funs, F =:= test] =/= [].
  143. has_header(Mod, Header) ->
  144. Mod1 = case code:which(Mod) of
  145. cover_compiled ->
  146. {file, File} = cover:is_compiled(Mod),
  147. File;
  148. non_existing -> Mod;
  149. preloaded -> Mod;
  150. L -> L
  151. end,
  152. {ok, {_, [{abstract_code, {_, AC}}]}} =
  153. beam_lib:chunks(Mod1, [abstract_code]),
  154. [F || {attribute, 1, file, {F, 1}} <- AC,
  155. string:str(F, Header) =/= 0] =/= [].
  156. align_notcovered_count(Module, Covered, NotCovered, false) ->
  157. {Module, Covered, NotCovered};
  158. align_notcovered_count(Module, Covered, NotCovered, true) ->
  159. {Module, Covered, NotCovered - 1}.
  160. write_index(Coverage, SrcModules, TargetDir) ->
  161. {ok, F} = file:open(filename:join([TargetDir, "index.html"]), [write]),
  162. ok = file:write(F, "<!DOCTYPE HTML><html>\n"
  163. "<head><meta charset=\"utf-8\">"
  164. "<title>Coverage Summary</title></head>\n"
  165. "<body>\n"),
  166. IsSrcCoverage = fun({Mod,_C,_N}) -> lists:member(Mod, SrcModules) end,
  167. {SrcCoverage, TestCoverage} = lists:partition(IsSrcCoverage, Coverage),
  168. write_index_section(F, "Source", SrcCoverage),
  169. write_index_section(F, "Test", TestCoverage),
  170. ok = file:write(F, "</body></html>"),
  171. ok = file:close(F).
  172. write_index_section(_F, _SectionName, []) ->
  173. ok;
  174. write_index_section(F, SectionName, Coverage) ->
  175. %% Calculate total coverage
  176. {Covered, NotCovered} = lists:foldl(fun({_Mod, C, N}, {CAcc, NAcc}) ->
  177. {CAcc + C, NAcc + N}
  178. end, {0, 0}, Coverage),
  179. TotalCoverage = percentage(Covered, NotCovered),
  180. %% Write the report
  181. ok = file:write(F, ?FMT("<h1>~s Summary</h1>\n", [SectionName])),
  182. ok = file:write(F, ?FMT("<h3>Total: ~s</h3>\n", [TotalCoverage])),
  183. ok = file:write(F, "<table><tr><th>Module</th><th>Coverage %</th></tr>\n"),
  184. FmtLink =
  185. fun(Module, Cov, NotCov) ->
  186. ?FMT("<tr><td><a href='~s.COVER.html'>~s</a></td><td>~s</td>\n",
  187. [Module, Module, percentage(Cov, NotCov)])
  188. end,
  189. lists:foreach(fun({Module, Cov, NotCov}) ->
  190. ok = file:write(F, FmtLink(Module, Cov, NotCov))
  191. end, Coverage),
  192. ok = file:write(F, "</table>\n").
  193. print_coverage(Coverage) ->
  194. {Covered, NotCovered} = lists:foldl(fun({_Mod, C, N}, {CAcc, NAcc}) ->
  195. {CAcc + C, NAcc + N}
  196. end, {0, 0}, Coverage),
  197. TotalCoverage = percentage(Covered, NotCovered),
  198. %% Determine the longest module name for right-padding
  199. Width = lists:foldl(fun({Mod, _, _}, Acc) ->
  200. case length(atom_to_list(Mod)) of
  201. N when N > Acc ->
  202. N;
  203. _ ->
  204. Acc
  205. end
  206. end, 0, Coverage) * -1,
  207. %% Print the output the console
  208. ?CONSOLE("~nCode Coverage:~n", []),
  209. lists:foreach(fun({Mod, C, N}) ->
  210. ?CONSOLE("~*s : ~3s~n",
  211. [Width, Mod, percentage(C, N)])
  212. end, Coverage),
  213. ?CONSOLE("~n~*s : ~s~n", [Width, "Total", TotalCoverage]).
  214. cover_file(Module, TargetDir) ->
  215. filename:join([TargetDir, atom_to_list(Module) ++ ".COVER.html"]).
  216. export_coverdata(TargetDir) ->
  217. ExportFile = filename:join(TargetDir, "cover.coverdata"),
  218. case cover:export(ExportFile) of
  219. ok ->
  220. ?CONSOLE("Coverdata export: ~s~n", [ExportFile]);
  221. {error, Reason} ->
  222. ?ERROR("Coverdata export failed: ~p~n", [Reason])
  223. end.
  224. percentage(0, 0) ->
  225. "not executed";
  226. percentage(Cov, NotCov) ->
  227. integer_to_list(trunc((Cov / (Cov + NotCov)) * 100)) ++ "%".