From 588337e78bfd093a2003929c2bbc1d824a08fc76 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 9 Dec 2009 22:00:28 -0700 Subject: [PATCH] Add support for coverage generation --- src/rebar_eunit.erl | 105 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 2 deletions(-) diff --git a/src/rebar_eunit.erl b/src/rebar_eunit.erl index 69f8a0a..a75a8a0 100644 --- a/src/rebar_eunit.erl +++ b/src/rebar_eunit.erl @@ -50,9 +50,18 @@ eunit(Config, File) -> ok = filelib:ensure_dir(?EUNIT_DIR ++ "/foo"), %% Compile all erlang from src/ into ?EUNIT_DIR - rebar_erlc_compiler:do_compile(Config, "src/*.erl", ?EUNIT_DIR, ".erl", ".beam", fun compile_erl/2, + rebar_erlc_compiler:do_compile(Config, "src/*.erl", ?EUNIT_DIR, ".erl", ".beam", + fun compile_erl/2, rebar_config:get_list(Config, erl_first_files, [])), + %% Build a list of all the .beams in ?EUNIT_DIR -- use this for cover + %% and eunit testing. Normally you can just tell cover and/or eunit to + %% scan the directory for you, but eunit does a code:purge in conjunction + %% with that scan and causes any cover compilation info to be lost. So, + %% we do it by hand. :( + Modules = [list_to_atom(filename:basename(N, ".beam")) || + N <- filelib:wildcard("*.beam", "ebin")], + %% TODO: If there are other wildcards specified in eunit_sources, compile them %% Save current code path and then prefix ?EUNIT_DIR on it so that our modules @@ -68,9 +77,22 @@ eunit(Config, File) -> BaseOpts = [] end, + %% If cover support is requested, set it up + case rebar_config:get(Config, cover_enabled, false) of + true -> + cover_init(Config); + _ -> + ok + end, + %% Run eunit EunitOpts = BaseOpts ++ rebar_config:get_list(Config, eunit_opts, []), - case catch(eunit:test({dir, ?EUNIT_DIR}, EunitOpts)) of + EunitResult = (catch eunit:test(Modules, EunitOpts)), + + %% Analyze cover modules + cover_analyze(Config, cover:modules()), + + case EunitResult of ok -> ok; _ -> @@ -78,6 +100,7 @@ eunit(Config, File) -> ?FAIL end, + %% Restore code path true = code:set_path(InitCodePath), ok. @@ -125,4 +148,82 @@ is_quickcheck_avail() -> IsAvail end. +cover_init(Config) -> + %% Make sure any previous runs of cover don't unduly influence + cover:reset(), + ?INFO("Cover compiling ~s\n", [rebar_utils:get_cwd()]), + + case cover:compile_beam_directory(?EUNIT_DIR) of + {error, Reason2} -> + ?ERROR("Cover compilation failed: ~p\n", [Reason2]), + ?FAIL; + Modules -> + %% It's not an error for cover compilation to fail partially, but we do want + %% to warn about them + [?CONSOLE("Cover compilation warning: ~p", [Desc]) || {error, Desc} <- Modules], + + %% Identify the modules that were compiled successfully + case [ M || {ok, M} <- Modules] of + [] -> + %% No modules compiled successfully...fail + ?ERROR("Cover failed to compile any modules; aborting.\n", []), + ?FAIL; + _ -> + %% At least one module compiled successfully + ok + end + end. + +cover_analyze(Config, []) -> + ok; +cover_analyze(Config, Modules) -> + %% Generate coverage info for all the cover-compiled modules + Coverage = [cover_analyze_mod(M) || M <- Modules], + + %% Write index of coverage info + cover_write_index(lists:sort(Coverage)), + + %% Write coverage details for each file + [{ok, _} = cover:analyze_to_file(M, cover_file(M), [html]) || {M, _, _} <- Coverage], + + Index = filename:join([rebar_utils:get_cwd(), ?EUNIT_DIR, "index.html"]), + ?CONSOLE("Cover analysis: ~s\n", [Index]). + + +cover_analyze_mod(Module) -> + case cover:analyze(Module, coverage, module) of + {ok, {Module, {Covered, NotCovered}}} -> + {Module, Covered, NotCovered}; + {error, Reason} -> + ?ERROR("Cover analyze failed for ~p: ~p ~p\n", + [Module, Reason, code:which(Module)]), + {0,0} + end. + +cover_write_index(Coverage) -> + %% Calculate total coverage % + {Covered, NotCovered} = lists:foldl(fun({Mod, C, N}, {CAcc, NAcc}) -> + {CAcc + C, NAcc + N} + end, {0, 0}, Coverage), + TotalCoverage = percentage(Covered, NotCovered), + + %% Write the report + {ok, F} = file:open(filename:join([?EUNIT_DIR, "index.html"]), [write]), + ok = file:write(F, "Coverage Summary\n" + "

Coverage Summary

\n"), + ok = file:write(F, ?FMT("

Total: ~w%

\n", [TotalCoverage])), + ok = file:write(F, "\n"), + + [ok = file:write(F, ?FMT("\n", + [Module, Module, percentage(Cov, NotCov)])) || + {Module, Cov, NotCov} <- Coverage], + ok = file:write(F, "
ModuleCoverage %
~s~w%
"), + file:close(F). + +cover_file(Module) -> + filename:join([?EUNIT_DIR, atom_to_list(Module) ++ ".COVER.html"]). + + +percentage(Cov, NotCov) -> + trunc((Cov / (Cov + NotCov)) * 100).