`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"])). |