`rebar.config` or via the `-c\--cover` flag given to the appropriate task) from the `eunit` and `ct` tasks and add a `cover` task to write coverage analysis to diskpull/202/head
@ -1,261 +0,0 @@ | |||||
%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- | |||||
%% ex: ts=4 sw=4 et | |||||
%% ------------------------------------------------------------------- | |||||
%% | |||||
%% rebar: Erlang Build Tools | |||||
%% | |||||
%% Copyright (c) 2009, 2010 Dave Smith (dizzyd@dizzyd.com) | |||||
%% Copyright (c) 2013 Andras Horvath (andras.horvath@erlang-solutions.com) | |||||
%% | |||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
%% of this software and associated documentation files (the "Software"), to deal | |||||
%% in the Software without restriction, including without limitation the rights | |||||
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
%% copies of the Software, and to permit persons to whom the Software is | |||||
%% furnished to do so, subject to the following conditions: | |||||
%% | |||||
%% The above copyright notice and this permission notice shall be included in | |||||
%% all copies or substantial portions of the Software. | |||||
%% | |||||
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
%% THE SOFTWARE. | |||||
%% ------------------------------------------------------------------- | |||||
-module(rebar_cover_utils). | |||||
%% for internal use only | |||||
-export([init/3, | |||||
perform_cover/4, | |||||
close/1, | |||||
exit/0]). | |||||
-include("rebar.hrl"). | |||||
%% ==================================================================== | |||||
%% Internal functions | |||||
%% ==================================================================== | |||||
perform_cover(Config, BeamFiles, SrcModules, TargetDir) -> | |||||
perform_cover(rebar_state:get(Config, cover_enabled, false), | |||||
Config, BeamFiles, SrcModules, TargetDir). | |||||
perform_cover(false, _Config, _BeamFiles, _SrcModules, _TargetDir) -> | |||||
ok; | |||||
perform_cover(true, Config, BeamFiles, SrcModules, TargetDir) -> | |||||
analyze(Config, BeamFiles, SrcModules, TargetDir). | |||||
close(not_enabled) -> | |||||
ok; | |||||
close(F) -> | |||||
ok = file:close(F). | |||||
exit() -> | |||||
cover:stop(). | |||||
init(false, _BeamFiles, _TargetDir) -> | |||||
{ok, not_enabled}; | |||||
init(true, BeamFiles, TargetDir) -> | |||||
%% Attempt to start the cover server, then set its group leader to | |||||
%% TargetDir/cover.log, so all cover log messages will go there instead of | |||||
%% to stdout. If the cover server is already started, we'll kill that | |||||
%% server and start a new one in order not to inherit a polluted | |||||
%% cover_server state. | |||||
{ok, CoverPid} = case whereis(cover_server) of | |||||
undefined -> | |||||
cover:start(); | |||||
_ -> | |||||
cover:stop(), | |||||
cover:start() | |||||
end, | |||||
{ok, F} = OkOpen = file:open( | |||||
filename:join([TargetDir, "cover.log"]), | |||||
[write]), | |||||
group_leader(F, CoverPid), | |||||
?INFO("Cover compiling ~s\n", [rebar_dir:get_cwd()]), | |||||
Compiled = [{Beam, cover:compile_beam(Beam)} || Beam <- BeamFiles], | |||||
case [Module || {_, {ok, Module}} <- Compiled] of | |||||
[] -> | |||||
%% No modules compiled successfully...fail | |||||
?ERROR("Cover failed to compile any modules; aborting.", []), | |||||
?FAIL; | |||||
_ -> | |||||
%% At least one module compiled successfully | |||||
%% It's not an error for cover compilation to fail partially, | |||||
%% but we do want to warn about them | |||||
PrintWarning = | |||||
fun(Beam, Desc) -> | |||||
?CONSOLE("Cover compilation warning for ~p: ~p", | |||||
[Beam, Desc]) | |||||
end, | |||||
_ = [PrintWarning(Beam, Desc) || {Beam, {error, Desc}} <- Compiled], | |||||
OkOpen | |||||
end; | |||||
init(Config, BeamFiles, TargetDir) -> | |||||
init(rebar_state:get(Config, cover_enabled, false), BeamFiles, TargetDir). | |||||
analyze(_Config, [], _SrcModules, _TargetDir) -> | |||||
ok; | |||||
analyze(Config, FilteredModules, SrcModules, TargetDir) -> | |||||
%% Generate coverage info for all the cover-compiled modules | |||||
Coverage = lists:flatten([analyze_mod(M) | |||||
|| M <- FilteredModules, | |||||
cover:is_compiled(M) =/= false]), | |||||
%% Write index of coverage info | |||||
write_index(lists:sort(Coverage), SrcModules, TargetDir), | |||||
%% Write coverage details for each file | |||||
lists:foreach( | |||||
fun({M, _, _}) -> | |||||
{ok, _} = cover:analyze_to_file(M, | |||||
cover_file(M, TargetDir), | |||||
[html]) | |||||
end, Coverage), | |||||
Index = filename:join([rebar_dir:get_cwd(), TargetDir, "index.html"]), | |||||
?CONSOLE("Cover analysis: ~s\n", [Index]), | |||||
%% Export coverage data, if configured | |||||
case rebar_state:get(Config, cover_export_enabled, false) of | |||||
true -> | |||||
export_coverdata(TargetDir); | |||||
false -> | |||||
ok | |||||
end, | |||||
%% Print coverage report, if configured | |||||
case rebar_state:get(Config, cover_print_enabled, false) of | |||||
true -> | |||||
print_coverage(lists:sort(Coverage)); | |||||
false -> | |||||
ok | |||||
end. | |||||
analyze_mod(Module) -> | |||||
case cover:analyze(Module, coverage, module) of | |||||
{ok, {Module, {Covered, NotCovered}}} -> | |||||
%% Modules that include the eunit header get an implicit | |||||
%% test/0 fun, which cover considers a runnable line, but | |||||
%% eunit:test(TestRepresentation) never calls. Decrement | |||||
%% NotCovered in this case. | |||||
[align_notcovered_count(Module, Covered, NotCovered, | |||||
is_eunitized(Module))]; | |||||
{error, Reason} -> | |||||
?ERROR("Cover analyze failed for ~p: ~p ~p\n", | |||||
[Module, Reason, code:which(Module)]), | |||||
[] | |||||
end. | |||||
is_eunitized(Mod) -> | |||||
has_eunit_test_fun(Mod) andalso | |||||
has_header(Mod, "include/eunit.hrl"). | |||||
has_eunit_test_fun(Mod) -> | |||||
[F || {exports, Funs} <- Mod:module_info(), | |||||
{F, 0} <- Funs, F =:= test] =/= []. | |||||
has_header(Mod, Header) -> | |||||
Mod1 = case code:which(Mod) of | |||||
cover_compiled -> | |||||
{file, File} = cover:is_compiled(Mod), | |||||
File; | |||||
non_existing -> Mod; | |||||
preloaded -> Mod; | |||||
L -> L | |||||
end, | |||||
{ok, {_, [{abstract_code, {_, AC}}]}} = | |||||
beam_lib:chunks(Mod1, [abstract_code]), | |||||
[F || {attribute, 1, file, {F, 1}} <- AC, | |||||
string:str(F, Header) =/= 0] =/= []. | |||||
align_notcovered_count(Module, Covered, NotCovered, false) -> | |||||
{Module, Covered, NotCovered}; | |||||
align_notcovered_count(Module, Covered, NotCovered, true) -> | |||||
{Module, Covered, NotCovered - 1}. | |||||
write_index(Coverage, SrcModules, TargetDir) -> | |||||
{ok, F} = file:open(filename:join([TargetDir, "index.html"]), [write]), | |||||
ok = file:write(F, "<!DOCTYPE HTML><html>\n" | |||||
"<head><meta charset=\"utf-8\">" | |||||
"<title>Coverage Summary</title></head>\n" | |||||
"<body>\n"), | |||||
IsSrcCoverage = fun({Mod,_C,_N}) -> lists:member(Mod, SrcModules) end, | |||||
{SrcCoverage, TestCoverage} = lists:partition(IsSrcCoverage, Coverage), | |||||
write_index_section(F, "Source", SrcCoverage), | |||||
write_index_section(F, "Test", TestCoverage), | |||||
ok = file:write(F, "</body></html>"), | |||||
ok = file:close(F). | |||||
write_index_section(_F, _SectionName, []) -> | |||||
ok; | |||||
write_index_section(F, SectionName, 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 = file:write(F, ?FMT("<h1>~s Summary</h1>\n", [SectionName])), | |||||
ok = file:write(F, ?FMT("<h3>Total: ~s</h3>\n", [TotalCoverage])), | |||||
ok = file:write(F, "<table><tr><th>Module</th><th>Coverage %</th></tr>\n"), | |||||
FmtLink = | |||||
fun(Module, Cov, NotCov) -> | |||||
?FMT("<tr><td><a href='~s.COVER.html'>~s</a></td><td>~s</td>\n", | |||||
[Module, Module, percentage(Cov, NotCov)]) | |||||
end, | |||||
lists:foreach(fun({Module, Cov, NotCov}) -> | |||||
ok = file:write(F, FmtLink(Module, Cov, NotCov)) | |||||
end, Coverage), | |||||
ok = file:write(F, "</table>\n"). | |||||
print_coverage(Coverage) -> | |||||
{Covered, NotCovered} = lists:foldl(fun({_Mod, C, N}, {CAcc, NAcc}) -> | |||||
{CAcc + C, NAcc + N} | |||||
end, {0, 0}, Coverage), | |||||
TotalCoverage = percentage(Covered, NotCovered), | |||||
%% Determine the longest module name for right-padding | |||||
Width = lists:foldl(fun({Mod, _, _}, Acc) -> | |||||
case length(atom_to_list(Mod)) of | |||||
N when N > Acc -> | |||||
N; | |||||
_ -> | |||||
Acc | |||||
end | |||||
end, 0, Coverage) * -1, | |||||
%% Print the output the console | |||||
?CONSOLE("~nCode Coverage:", []), | |||||
lists:foreach(fun({Mod, C, N}) -> | |||||
?CONSOLE("~*s : ~4s", | |||||
[Width, Mod, percentage(C, N)]) | |||||
end, Coverage), | |||||
?CONSOLE("~n~*s : ~s", [Width, "Total", TotalCoverage]). | |||||
cover_file(Module, TargetDir) -> | |||||
filename:join([TargetDir, atom_to_list(Module) ++ ".COVER.html"]). | |||||
export_coverdata(TargetDir) -> | |||||
ExportFile = filename:join(TargetDir, "cover.coverdata"), | |||||
case cover:export(ExportFile) of | |||||
ok -> | |||||
?CONSOLE("Coverdata export: ~s", [ExportFile]); | |||||
{error, Reason} -> | |||||
?ERROR("Coverdata export failed: ~p", [Reason]) | |||||
end. | |||||
percentage(0, 0) -> | |||||
"not executed"; | |||||
percentage(Cov, NotCov) -> | |||||
integer_to_list(trunc((Cov / (Cov + NotCov)) * 100)) ++ "%". |
@ -0,0 +1,332 @@ | |||||
%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- | |||||
%% ex: ts=4 sw=4 et | |||||
-module(rebar_prv_cover). | |||||
-behaviour(provider). | |||||
-export([init/1, | |||||
do/1, | |||||
maybe_cover_compile/1, | |||||
maybe_write_coverdata/2, | |||||
format_error/1]). | |||||
-include("rebar.hrl"). | |||||
-define(PROVIDER, cover). | |||||
-define(DEPS, []). | |||||
%% =================================================================== | |||||
%% Public API | |||||
%% =================================================================== | |||||
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}. | |||||
init(State) -> | |||||
State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, | |||||
{module, ?MODULE}, | |||||
{bare, false}, | |||||
{deps, ?DEPS}, | |||||
{example, "rebar cover"}, | |||||
{short_desc, "Perform coverage analysis."}, | |||||
{desc, ""}, | |||||
{opts, cover_opts(State)}])), | |||||
{ok, State1}. | |||||
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. | |||||
do(State) -> | |||||
{Opts, _} = rebar_state:command_parsed_args(State), | |||||
case proplists:get_value(reset, Opts, false) of | |||||
true -> reset(State); | |||||
false -> analyze(State) | |||||
end. | |||||
-spec maybe_cover_compile(rebar_state:t()) -> ok. | |||||
maybe_cover_compile(State) -> | |||||
case rebar_state:get(State, cover_enabled, false) of | |||||
true -> cover_compile(State); | |||||
false -> ok | |||||
end. | |||||
-spec maybe_write_coverdata(rebar_state:t(), atom()) -> ok. | |||||
maybe_write_coverdata(State, Task) -> | |||||
case cover:modules() of | |||||
%% no coverdata collected, skip writing anything out | |||||
[] -> ok; | |||||
_ -> write_coverdata(State, Task) | |||||
end. | |||||
-spec format_error(any()) -> iolist(). | |||||
format_error(Reason) -> | |||||
io_lib:format("~p", [Reason]). | |||||
%% =================================================================== | |||||
%% Internal functions | |||||
%% =================================================================== | |||||
reset(State) -> | |||||
?INFO("Resetting collected cover data...", []), | |||||
CoverDir = cover_dir(State), | |||||
CoverFiles = get_all_coverdata(CoverDir), | |||||
F = fun(File) -> | |||||
case file:delete(File) of | |||||
{error, Reason} -> | |||||
?WARN("Error deleting ~p: ~p", [Reason, File]); | |||||
_ -> ok | |||||
end | |||||
end, | |||||
ok = lists:foreach(F, CoverFiles), | |||||
{ok, State}. | |||||
analyze(State) -> | |||||
?INFO("Performing cover analysis...", []), | |||||
%% figure out what coverdata we have | |||||
CoverDir = cover_dir(State), | |||||
CoverFiles = get_all_coverdata(CoverDir), | |||||
%% start the cover server if necessary | |||||
{ok, CoverPid} = start_cover(), | |||||
%% redirect cover output | |||||
true = redirect_cover_output(State, CoverPid), | |||||
%% analyze! | |||||
ok = case analyze(State, CoverFiles) of | |||||
[] -> ok; | |||||
Analysis -> | |||||
print_analysis(Analysis, verbose(State)), | |||||
write_index(State, Analysis) | |||||
end, | |||||
{ok, State}. | |||||
get_all_coverdata(CoverDir) -> | |||||
ok = filelib:ensure_dir(filename:join([CoverDir, "dummy.log"])), | |||||
{ok, Files} = file:list_dir(CoverDir), | |||||
rebar_utils:filtermap(fun(FileName) -> | |||||
case filename:extension(FileName) == ".coverdata" of | |||||
true -> {true, filename:join([CoverDir, FileName])}; | |||||
false -> false | |||||
end | |||||
end, Files). | |||||
analyze(_State, []) -> | |||||
?WARN("No coverdata found", []), | |||||
[]; | |||||
analyze(State, CoverFiles) -> | |||||
%% reset any existing cover data | |||||
ok = cover:reset(), | |||||
%% import all coverdata files | |||||
ok = lists:foreach(fun(M) -> import(M) end, CoverFiles), | |||||
[{"aggregate", CoverFiles, analysis(State, "aggregate")}] ++ | |||||
analyze(State, CoverFiles, []). | |||||
analyze(_State, [], Acc) -> lists:reverse(Acc); | |||||
analyze(State, [F|Rest], Acc) -> | |||||
%% reset any existing cover data | |||||
ok = cover:reset(), | |||||
%% extract taskname from the CoverData file | |||||
Task = filename:basename(F, ".coverdata"), | |||||
%% import task cover data and process it | |||||
ok = import(F), | |||||
analyze(State, Rest, [{Task, [F], analysis(State, Task)}] ++ Acc). | |||||
import(CoverData) -> | |||||
case cover:import(CoverData) of | |||||
{error, {cant_open_file, F, _Reason}} -> | |||||
?WARN("Can't import cover data from ~ts.", [F]), | |||||
error; | |||||
ok -> ok | |||||
end. | |||||
analysis(State, Task) -> | |||||
Mods = cover:imported_modules(), | |||||
lists:map( | |||||
fun(Mod) -> | |||||
{ok, Answer} = cover:analyze(Mod, coverage, line), | |||||
{ok, File} = analyze_to_file(Mod, State, Task), | |||||
{Mod, process(Answer), File} | |||||
end, | |||||
Mods). | |||||
analyze_to_file(Mod, State, Task) -> | |||||
CoverDir = cover_dir(State), | |||||
TaskDir = filename:join([CoverDir, Task]), | |||||
ok = filelib:ensure_dir(filename:join([TaskDir, "dummy.html"])), | |||||
case code:ensure_loaded(Mod) of | |||||
{module, _} -> | |||||
cover:analyze_to_file(Mod, mod_to_filename(TaskDir, Mod), [html]); | |||||
{error, _} -> | |||||
?WARN("Can't load module ~ts.", [Mod]), | |||||
{ok, []} | |||||
end. | |||||
mod_to_filename(TaskDir, M) -> | |||||
filename:join([TaskDir, atom_to_list(M) ++ ".html"]). | |||||
process(Coverage) -> process(Coverage, {0, 0}). | |||||
process([], {0, 0}) -> | |||||
"0.0%"; | |||||
process([], {Cov, Not}) -> | |||||
float_to_list((Cov / (Cov + Not)) * 100, [{decimals, 1}]) ++ "%"; | |||||
%% line 0 is a line added by eunit and never executed so ignore it | |||||
process([{{_, 0}, _}|Rest], Acc) -> process(Rest, Acc); | |||||
process([{_, {Cov, Not}}|Rest], {Covered, NotCovered}) -> | |||||
process(Rest, {Covered + Cov, NotCovered + Not}). | |||||
print_analysis(_, false) -> ok; | |||||
print_analysis(Analysis, true) -> | |||||
{_, CoverFiles, Stats} = lists:keyfind("aggregate", 1, Analysis), | |||||
ConsoleStats = [ {atom_to_list(M), C} || {M, C, _} <- Stats ], | |||||
Table = format_table(ConsoleStats, CoverFiles), | |||||
io:format("~ts", [Table]). | |||||
format_table(Stats, CoverFiles) -> | |||||
MaxLength = max(lists:foldl(fun max_length/2, 0, Stats), 20), | |||||
Header = header(MaxLength), | |||||
Seperator = seperator(MaxLength), | |||||
[io_lib:format("~ts~n~ts~n~ts~n", [Seperator, Header, Seperator]), | |||||
lists:map(fun({Mod, Coverage}) -> | |||||
Name = format(Mod, MaxLength), | |||||
Cov = format(Coverage, 8), | |||||
io_lib:format(" | ~ts | ~ts |~n", [Name, Cov]) | |||||
end, Stats), | |||||
io_lib:format("~ts~n", [Seperator]), | |||||
io_lib:format(" coverage calculated from:~n", []), | |||||
lists:map(fun(File) -> | |||||
io_lib:format(" ~ts~n", [File]) | |||||
end, CoverFiles)]. | |||||
max_length({ModName, _}, Min) -> | |||||
Length = length(lists:flatten(ModName)), | |||||
case Length > Min of | |||||
true -> Length; | |||||
false -> Min | |||||
end. | |||||
header(Width) -> | |||||
[" | ", format("module", Width), " | ", format("coverage", 8), " |"]. | |||||
seperator(Width) -> | |||||
[" |--", io_lib:format("~*c", [Width, $-]), "--|------------|"]. | |||||
format(String, Width) -> io_lib:format("~*.ts", [Width, String]). | |||||
write_index(State, Coverage) -> | |||||
CoverDir = cover_dir(State), | |||||
FileName = filename:join([CoverDir, "index.html"]), | |||||
{ok, F} = file:open(FileName, [write]), | |||||
ok = file:write(F, "<!DOCTYPE HTML><html>\n" | |||||
"<head><meta charset=\"utf-8\">" | |||||
"<title>Coverage Summary</title></head>\n" | |||||
"<body>\n"), | |||||
{Aggregate, Rest} = lists:partition(fun({"aggregate", _, _}) -> true; (_) -> false end, | |||||
Coverage), | |||||
ok = write_index_section(F, Aggregate), | |||||
ok = write_index_section(F, Rest), | |||||
ok = file:write(F, "</body></html>"), | |||||
ok = file:close(F), | |||||
io:format(" cover summary written to: ~ts~n", [filename:absname(FileName)]). | |||||
write_index_section(_F, []) -> ok; | |||||
write_index_section(F, [{Section, DataFile, Mods}|Rest]) -> | |||||
%% Write the report | |||||
ok = file:write(F, ?FMT("<h1>~s summary</h1>\n", [Section])), | |||||
ok = file:write(F, "coverage calculated from:\n<ul>"), | |||||
ok = lists:foreach(fun(D) -> ok = file:write(F, io_lib:format("<li>~ts</li>", [D])) end, | |||||
DataFile), | |||||
ok = file:write(F, "</ul>\n"), | |||||
ok = file:write(F, "<table><tr><th>module</th><th>coverage %</th></tr>\n"), | |||||
FmtLink = | |||||
fun({Mod, Cov, Report}) -> | |||||
?FMT("<tr><td><a href='~ts'>~ts</a></td><td>~ts</td>\n", | |||||
[strip_coverdir(Report), Mod, Cov]) | |||||
end, | |||||
lists:foreach(fun(M) -> ok = file:write(F, FmtLink(M)) end, Mods), | |||||
ok = file:write(F, "</table>\n"), | |||||
write_index_section(F, Rest). | |||||
strip_coverdir(File) -> | |||||
filename:join(lists:reverse(lists:sublist(lists:reverse(filename:split(File)), | |||||
2))). | |||||
cover_compile(State) -> | |||||
%% start the cover server if necessary | |||||
{ok, CoverPid} = start_cover(), | |||||
%% redirect cover output | |||||
true = redirect_cover_output(State, CoverPid), | |||||
%% cover compile the modules we just compiled | |||||
Apps = filter_checkouts(rebar_state:project_apps(State)), | |||||
CompileResult = compile_beam_directories(Apps, []) ++ | |||||
compile_bare_test_directory(State), | |||||
%% print any warnings about modules that failed to cover compile | |||||
lists:foreach(fun print_cover_warnings/1, CompileResult). | |||||
filter_checkouts(Apps) -> filter_checkouts(Apps, []). | |||||
filter_checkouts([], Acc) -> lists:reverse(Acc); | |||||
filter_checkouts([App|Rest], Acc) -> | |||||
AppDir = filename:absname(rebar_app_info:dir(App)), | |||||
CheckoutsDir = filename:absname("_checkouts"), | |||||
case lists:prefix(CheckoutsDir, AppDir) of | |||||
true -> filter_checkouts(Rest, Acc); | |||||
false -> filter_checkouts(Rest, [App|Acc]) | |||||
end. | |||||
compile_beam_directories([], Acc) -> Acc; | |||||
compile_beam_directories([App|Rest], Acc) -> | |||||
Result = cover:compile_beam_directory(filename:join([rebar_app_info:out_dir(App), | |||||
"ebin"])), | |||||
compile_beam_directories(Rest, Acc ++ Result). | |||||
compile_bare_test_directory(State) -> | |||||
case cover:compile_beam_directory(filename:join([rebar_dir:base_dir(State), | |||||
"ebin"])) of | |||||
%% if directory doesn't exist just return empty result set | |||||
{error, enoent} -> []; | |||||
Result -> Result | |||||
end. | |||||
start_cover() -> | |||||
case cover:start() of | |||||
{ok, Pid} -> {ok, Pid}; | |||||
{error, {already_started, Pid}} -> {ok, Pid} | |||||
end. | |||||
redirect_cover_output(State, CoverPid) -> | |||||
%% redirect cover console output to file | |||||
DataDir = cover_dir(State), | |||||
ok = filelib:ensure_dir(filename:join([DataDir, "dummy.log"])), | |||||
{ok, F} = file:open(filename:join([DataDir, "cover.log"]), | |||||
[append]), | |||||
group_leader(F, CoverPid). | |||||
print_cover_warnings({ok, _}) -> ok; | |||||
print_cover_warnings({error, File}) -> | |||||
?WARN("Cover compilation of ~p failed, module is not included in cover data.", | |||||
[File]). | |||||
write_coverdata(State, Task) -> | |||||
DataDir = cover_dir(State), | |||||
ok = filelib:ensure_dir(filename:join([DataDir, "dummy.log"])), | |||||
ExportFile = filename:join([DataDir, atom_to_list(Task) ++ ".coverdata"]), | |||||
case cover:export(ExportFile) of | |||||
ok -> | |||||
?DEBUG("Cover data written to ~p.", [ExportFile]), | |||||
ok; | |||||
{error, Reason} -> | |||||
?WARN("Cover data export failed: ~p", [Reason]) | |||||
end. | |||||
verbose(State) -> | |||||
{Opts, _} = rebar_state:command_parsed_args(State), | |||||
case proplists:get_value(verbose, Opts, missing) of | |||||
missing -> rebar_state:get(State, cover_print_enabled, false); | |||||
Else -> Else | |||||
end. | |||||
cover_dir(State) -> | |||||
rebar_state:get(State, cover_data_dir, "_cover"). | |||||
cover_opts(_State) -> | |||||
[{reset, $r, "reset", boolean, help(reset)}, | |||||
{verbose, $v, "verbose", boolean, help(verbose)}]. | |||||
help(reset) -> "Reset all coverdata."; | |||||
help(verbose) -> "Print coverage analysis.". |
@ -0,0 +1,127 @@ | |||||
-module(rebar_cover_SUITE). | |||||
-export([suite/0, | |||||
init_per_suite/1, | |||||
end_per_suite/1, | |||||
init_per_testcase/2, | |||||
all/0, | |||||
flag_coverdata_written/1, | |||||
config_coverdata_written/1, | |||||
index_written/1, | |||||
config_alt_coverdir/1, | |||||
flag_verbose/1, | |||||
config_verbose/1]). | |||||
-include_lib("common_test/include/ct.hrl"). | |||||
-include_lib("eunit/include/eunit.hrl"). | |||||
-include_lib("kernel/include/file.hrl"). | |||||
suite() -> | |||||
[]. | |||||
init_per_suite(Config) -> | |||||
Config. | |||||
end_per_suite(_Config) -> | |||||
ok. | |||||
init_per_testcase(_, Config) -> | |||||
rebar_test_utils:init_rebar_state(Config, "cover_"). | |||||
all() -> | |||||
[flag_coverdata_written, config_coverdata_written, | |||||
index_written, | |||||
config_alt_coverdir, | |||||
flag_verbose, config_verbose]. | |||||
flag_coverdata_written(Config) -> | |||||
AppDir = ?config(apps, Config), | |||||
Name = rebar_test_utils:create_random_name("cover_"), | |||||
Vsn = rebar_test_utils:create_random_vsn(), | |||||
rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]), | |||||
RebarConfig = [{erl_opts, [{d, some_define}]}], | |||||
rebar_test_utils:run_and_check(Config, | |||||
RebarConfig, | |||||
["eunit", "--cover"], | |||||
{ok, [{app, Name}]}), | |||||
true = filelib:is_file(filename:join(["_cover", "eunit.coverdata"])). | |||||
config_coverdata_written(Config) -> | |||||
AppDir = ?config(apps, Config), | |||||
Name = rebar_test_utils:create_random_name("cover_"), | |||||
Vsn = rebar_test_utils:create_random_vsn(), | |||||
rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]), | |||||
RebarConfig = [{erl_opts, [{d, some_define}]}, {cover_enabled, true}], | |||||
rebar_test_utils:run_and_check(Config, | |||||
RebarConfig, | |||||
["eunit"], | |||||
{ok, [{app, Name}]}), | |||||
true = filelib:is_file(filename:join(["_cover", "eunit.coverdata"])). | |||||
index_written(Config) -> | |||||
AppDir = ?config(apps, Config), | |||||
Name = rebar_test_utils:create_random_name("cover_"), | |||||
Vsn = rebar_test_utils:create_random_vsn(), | |||||
rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]), | |||||
RebarConfig = [{erl_opts, [{d, some_define}]}], | |||||
rebar_test_utils:run_and_check(Config, | |||||
RebarConfig, | |||||
["do", "eunit", "--cover", ",", "cover"], | |||||
{ok, [{app, Name}]}), | |||||
true = filelib:is_file(filename:join(["_cover", "index.html"])). | |||||
config_alt_coverdir(Config) -> | |||||
AppDir = ?config(apps, Config), | |||||
Name = rebar_test_utils:create_random_name("cover_"), | |||||
Vsn = rebar_test_utils:create_random_vsn(), | |||||
rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]), | |||||
CoverDir = filename:join(["coverage", "goes", "here"]), | |||||
RebarConfig = [{erl_opts, [{d, some_define}]}, {cover_data_dir, CoverDir}], | |||||
rebar_test_utils:run_and_check(Config, | |||||
RebarConfig, | |||||
["do", "eunit", "--cover", ",", "cover"], | |||||
{ok, [{app, Name}]}), | |||||
true = filelib:is_file(filename:join([CoverDir, "index.html"])). | |||||
flag_verbose(Config) -> | |||||
AppDir = ?config(apps, Config), | |||||
Name = rebar_test_utils:create_random_name("cover_"), | |||||
Vsn = rebar_test_utils:create_random_vsn(), | |||||
rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]), | |||||
RebarConfig = [{erl_opts, [{d, some_define}]}], | |||||
rebar_test_utils:run_and_check(Config, | |||||
RebarConfig, | |||||
["do", "eunit", "--cover", ",", "cover", "--verbose"], | |||||
{ok, [{app, Name}]}), | |||||
true = filelib:is_file(filename:join(["_cover", "index.html"])). | |||||
config_verbose(Config) -> | |||||
AppDir = ?config(apps, Config), | |||||
Name = rebar_test_utils:create_random_name("cover_"), | |||||
Vsn = rebar_test_utils:create_random_vsn(), | |||||
rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]), | |||||
RebarConfig = [{erl_opts, [{d, some_define}]}, {cover_print_enabled, true}], | |||||
rebar_test_utils:run_and_check(Config, | |||||
RebarConfig, | |||||
["do", "eunit", "--cover", ",", "cover"], | |||||
{ok, [{app, Name}]}), | |||||
true = filelib:is_file(filename:join(["_cover", "index.html"])). |