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.

229 line
8.1 KiB

  1. %% -------------------------------------------------------------------
  2. %%
  3. %% rebar: Erlang Build Tools
  4. %%
  5. %% Copyright (c) 2009 Dave Smith (dizzyd@dizzyd.com)
  6. %%
  7. %% Permission is hereby granted, free of charge, to any person obtaining a copy
  8. %% of this software and associated documentation files (the "Software"), to deal
  9. %% in the Software without restriction, including without limitation the rights
  10. %% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. %% copies of the Software, and to permit persons to whom the Software is
  12. %% furnished to do so, subject to the following conditions:
  13. %%
  14. %% The above copyright notice and this permission notice shall be included in
  15. %% all copies or substantial portions of the Software.
  16. %%
  17. %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. %% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23. %% THE SOFTWARE.
  24. %% -------------------------------------------------------------------
  25. %%
  26. %% Targets:
  27. %% eunit - runs eunit tests
  28. %% clean - remove .eunit directory
  29. %%
  30. %% Global options:
  31. %% verbose=1 - show extra output from the eunit test
  32. %% suite="foo"" - runs test/foo_tests.erl
  33. %% -------------------------------------------------------------------
  34. -module(rebar_eunit).
  35. -export([eunit/2]).
  36. -compile([export_all]).
  37. -include("rebar.hrl").
  38. -define(EUNIT_DIR, ".eunit").
  39. %% ===================================================================
  40. %% Public API
  41. %% ===================================================================
  42. eunit(Config, File) ->
  43. %% Make sure ?EUNIT_DIR/ directory exists (tack on dummy module)
  44. ok = filelib:ensure_dir(?EUNIT_DIR ++ "/foo"),
  45. %% Compile all erlang from src/ into ?EUNIT_DIR
  46. rebar_erlc_compiler:do_compile(Config, "src/*.erl", ?EUNIT_DIR, ".erl", ".beam",
  47. fun compile_erl/2,
  48. rebar_config:get_list(Config, erl_first_files, [])),
  49. %% Build a list of all the .beams in ?EUNIT_DIR -- use this for cover
  50. %% and eunit testing. Normally you can just tell cover and/or eunit to
  51. %% scan the directory for you, but eunit does a code:purge in conjunction
  52. %% with that scan and causes any cover compilation info to be lost. So,
  53. %% we do it by hand. :(
  54. Modules = [list_to_atom(filename:basename(N, ".beam")) ||
  55. N <- filelib:wildcard("*.beam", "ebin")],
  56. %% TODO: If there are other wildcards specified in eunit_sources, compile them
  57. %% Save current code path and then prefix ?EUNIT_DIR on it so that our modules
  58. %% are found there
  59. InitCodePath = code:get_path(),
  60. true = code:add_patha(?EUNIT_DIR),
  61. %% Enable verbose in eunit if so requested..
  62. case rebar_config:is_verbose() of
  63. true ->
  64. BaseOpts = [verbose];
  65. false ->
  66. BaseOpts = []
  67. end,
  68. %% If cover support is requested, set it up
  69. case rebar_config:get(Config, cover_enabled, false) of
  70. true ->
  71. cover_init(Config);
  72. _ ->
  73. ok
  74. end,
  75. %% Run eunit
  76. EunitOpts = BaseOpts ++ rebar_config:get_list(Config, eunit_opts, []),
  77. EunitResult = (catch eunit:test(Modules, EunitOpts)),
  78. %% Analyze cover modules
  79. cover_analyze(Config, cover:modules()),
  80. case EunitResult of
  81. ok ->
  82. ok;
  83. _ ->
  84. ?CONSOLE("One or more eunit tests failed.\n", []),
  85. ?FAIL
  86. end,
  87. %% Restore code path
  88. true = code:set_path(InitCodePath),
  89. ok.
  90. clean(Config, File) ->
  91. rebar_file_utils:rm_rf(?EUNIT_DIR).
  92. %% ===================================================================
  93. %% Internal functions
  94. %% ===================================================================
  95. compile_erl(Source, Config) ->
  96. case is_quickcheck_avail() of
  97. true ->
  98. EqcOpts = [{d, 'EQC'}];
  99. false ->
  100. EqcOpts = []
  101. end,
  102. ErlOpts = rebar_config:get_list(Config, erl_opts, []),
  103. EunitOpts = rebar_config:get_list(Config, eunit_compile_opts, []),
  104. Opts = [{i, "include"}, {outdir, ?EUNIT_DIR}, {d, 'TEST'}, debug_info, report] ++
  105. ErlOpts ++ EunitOpts ++ EqcOpts,
  106. case compile:file(Source, Opts) of
  107. {ok, _} ->
  108. ok;
  109. error ->
  110. ?FAIL
  111. end.
  112. is_quickcheck_avail() ->
  113. case erlang:get(is_quickcheck_avail) of
  114. undefined ->
  115. case code:lib_dir(eqc, include) of
  116. {error, bad_name} ->
  117. IsAvail = false;
  118. Dir ->
  119. IsAvail = filelib:is_file(filename:join(Dir, "eqc.hrl"))
  120. end,
  121. erlang:put(is_quickcheck_avail, IsAvail),
  122. ?DEBUG("Quickcheck availability: ~p\n", [IsAvail]),
  123. IsAvail;
  124. IsAvail ->
  125. IsAvail
  126. end.
  127. cover_init(Config) ->
  128. %% Make sure any previous runs of cover don't unduly influence
  129. cover:reset(),
  130. ?INFO("Cover compiling ~s\n", [rebar_utils:get_cwd()]),
  131. case cover:compile_beam_directory(?EUNIT_DIR) of
  132. {error, Reason2} ->
  133. ?ERROR("Cover compilation failed: ~p\n", [Reason2]),
  134. ?FAIL;
  135. Modules ->
  136. %% It's not an error for cover compilation to fail partially, but we do want
  137. %% to warn about them
  138. [?CONSOLE("Cover compilation warning: ~p", [Desc]) || {error, Desc} <- Modules],
  139. %% Identify the modules that were compiled successfully
  140. case [ M || {ok, M} <- Modules] of
  141. [] ->
  142. %% No modules compiled successfully...fail
  143. ?ERROR("Cover failed to compile any modules; aborting.\n", []),
  144. ?FAIL;
  145. _ ->
  146. %% At least one module compiled successfully
  147. ok
  148. end
  149. end.
  150. cover_analyze(Config, []) ->
  151. ok;
  152. cover_analyze(Config, Modules) ->
  153. %% Generate coverage info for all the cover-compiled modules
  154. Coverage = [cover_analyze_mod(M) || M <- Modules],
  155. %% Write index of coverage info
  156. cover_write_index(lists:sort(Coverage)),
  157. %% Write coverage details for each file
  158. [{ok, _} = cover:analyze_to_file(M, cover_file(M), [html]) || {M, _, _} <- Coverage],
  159. Index = filename:join([rebar_utils:get_cwd(), ?EUNIT_DIR, "index.html"]),
  160. ?CONSOLE("Cover analysis: ~s\n", [Index]).
  161. cover_analyze_mod(Module) ->
  162. case cover:analyze(Module, coverage, module) of
  163. {ok, {Module, {Covered, NotCovered}}} ->
  164. {Module, Covered, NotCovered};
  165. {error, Reason} ->
  166. ?ERROR("Cover analyze failed for ~p: ~p ~p\n",
  167. [Module, Reason, code:which(Module)]),
  168. {0,0}
  169. end.
  170. cover_write_index(Coverage) ->
  171. %% Calculate total coverage %
  172. {Covered, NotCovered} = lists:foldl(fun({Mod, C, N}, {CAcc, NAcc}) ->
  173. {CAcc + C, NAcc + N}
  174. end, {0, 0}, Coverage),
  175. TotalCoverage = percentage(Covered, NotCovered),
  176. %% Write the report
  177. {ok, F} = file:open(filename:join([?EUNIT_DIR, "index.html"]), [write]),
  178. ok = file:write(F, "<html><head><title>Coverage Summary</title></head>\n"
  179. "<body><h1>Coverage Summary</h1>\n"),
  180. ok = file:write(F, ?FMT("<h3>Total: ~w%</h3>\n", [TotalCoverage])),
  181. ok = file:write(F, "<table><tr><th>Module</th><th>Coverage %</th></tr>\n"),
  182. [ok = file:write(F, ?FMT("<tr><td><a href='~s.COVER.html'>~s</a></td><td>~w%</td>\n",
  183. [Module, Module, percentage(Cov, NotCov)])) ||
  184. {Module, Cov, NotCov} <- Coverage],
  185. ok = file:write(F, "</table></body></html>"),
  186. file:close(F).
  187. cover_file(Module) ->
  188. filename:join([?EUNIT_DIR, atom_to_list(Module) ++ ".COVER.html"]).
  189. percentage(Cov, NotCov) ->
  190. trunc((Cov / (Cov + NotCov)) * 100).