diff --git a/README.md b/README.md index adb1a16..1985277 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,8 @@ def project do end ``` +If you need, then you can add `summary: true` to print summary on the standard output. Useful in CI tools that can extract total coverage from there. + ## Screenshots ![Screenshot1](screenshots/shot1.png) diff --git a/include/covertool.hrl b/include/covertool.hrl index c82a059..50ee5c6 100644 --- a/include/covertool.hrl +++ b/include/covertool.hrl @@ -2,5 +2,6 @@ prefix_len = 0, cover_data = no_import, output = "coverage.xml", + summary = false, sources = ["src/"], beams = ["ebin/"]}). diff --git a/src/covertool.erl b/src/covertool.erl index 12597bc..2b30479 100644 --- a/src/covertool.erl +++ b/src/covertool.erl @@ -18,8 +18,6 @@ -include("covertool.hrl"). -include_lib("xmerl/include/xmerl.hrl"). --define(EUNIT_DIR, ".eunit"). - -record(result, {line = {0, 0}, branches = {0, 0}, data = []}). @@ -137,6 +135,15 @@ generate_report(Config, Modules) -> {packages, Result#result.data}]}, Report = xmerl:export_simple([Root], xmerl_xml, [{prolog, Prolog}]), write_output(Report, Output), + if + Config#config.summary -> + io:format("Line total: ~s~nBranch total: ~s~n", [ + percentage(Result#result.line), + percentage(Result#result.branches) + ]); + true -> + ok + end, ok. generate_packages(AppName, PrefixLen, Modules) -> @@ -202,7 +209,7 @@ generate_classes(Modules) -> Class = generate_class(Module), {Class#result.data, sum(Result, Class)} end, - + % Skip modules without sources Filter = fun(Module) -> case lookup_source(Module) of @@ -224,10 +231,11 @@ generate_class(Module) -> []}, {Data, Result#result{line = LineCoverage}} end, - {ok, Lines} = cover:analyse(Module, calls, line), + {ok, Lines0} = cover:analyse(Module, calls, line), + Lines = dedup(Lines0), - % XXX: ignore zero-indexed line, for some reason it is always present and always not hit - Filter = fun({{_Module, 0}, 0}) -> false; + % ignore zero-indexed lines, these are lines for generated code + Filter = fun({{_Module, 0}, _}) -> false; (_Other) -> true end, Lines2 = lists:filter(Filter, Lines), @@ -289,6 +297,10 @@ sum({Covered1, Valid1}, {Covered2, Valid2}) -> rate({_Covered, 0}) -> "0.0"; rate({Covered, Valid}) -> float_to_list(Covered / Valid, [{decimals, 3}, compact]). +percentage({_Covered, 0}) -> "100.0%"; +percentage({Covered, Valid}) -> + float_to_list(100 * Covered / Valid, [{decimals, 1}, compact]) ++ "%". + % lookup source in source directory lookup_source(Module) -> lookup_source(get(ebin), Module). @@ -356,3 +368,10 @@ function_lines(MFA, LinesData) -> Line = proplists:get_value(number, element(2, LineData)), Line > Start andalso Line =< End end, LinesData). + +dedup(List) -> dedup(lists:sort(List), []). + +dedup([], Agg) -> lists:reverse(Agg); +dedup([{Pos, C1}, {Pos, C2} | Rest], Agg) -> + dedup([{Pos, C1 + C2} | Rest], Agg); +dedup([Entry | Rest], Agg) -> dedup(Rest, [Entry | Agg]). diff --git a/src/mix_covertool.erl b/src/mix_covertool.erl index c32137d..5cb4626 100644 --- a/src/mix_covertool.erl +++ b/src/mix_covertool.erl @@ -8,7 +8,7 @@ %% =================================================================== %% Mix plugin callbacks %% =================================================================== -start( CompilePath, _Opts ) -> +start( CompilePath, Opts ) -> _ = cover:start(), case cover:compile_beam_directory(binary:bin_to_list(CompilePath)) of @@ -21,7 +21,9 @@ start( CompilePath, _Opts ) -> AppName = proplists:get_value(app, mix_project(config)), {ok, SrcDir} = file:get_cwd(), BeamDir = binary:bin_to_list(mix_project(compile_path)), - Config = #config{appname = AppName, sources = [SrcDir], beams = [BeamDir]}, + Summary = proplists:get_bool(summary, Opts), + Config = #config{appname = AppName, sources = [SrcDir], beams = [BeamDir], + summary = Summary}, fun() -> covertool:generate_report(Config, cover:modules()) diff --git a/src/rebar3_covertool_gen.erl b/src/rebar3_covertool_gen.erl index 4b59f74..2b25b53 100644 --- a/src/rebar3_covertool_gen.erl +++ b/src/rebar3_covertool_gen.erl @@ -25,7 +25,7 @@ init( State ) -> NewState = rebar_state:add_provider(State, providers:create(Options)), {ok, NewState}. - + do(State) -> OutputFiles = output_files(State), InputFiles = input_files(State), @@ -98,7 +98,7 @@ get_apps(State) -> ProjectApps = [app_to_atom(rebar_app_info:name(PA)) || PA <- rebar_state:project_apps(State)], IncludeApps = include_apps(State), - lists:usort(ProjectApps ++ IncludeApps). + lists:usort(ProjectApps ++ IncludeApps). generate_apps( State, Apps, LogFile ) -> Result = lists:foldl( fun(App, Result) -> generate_app(State, App, Result) end, ok, Apps ), @@ -167,7 +167,8 @@ coverdata_files(State) -> covertool_opts(_State) -> [{include_apps, $a, "include_apps", string, help(include_apps)}, - {prefix_len, $p, "prefix_len", integer, help(prefix_len)}]. + {prefix_len, $p, "prefix_len", integer, help(prefix_len)}, + {summary, $s, "summary", boolean, help(summary)}]. help(include_apps) -> "A CSV of OTP app dependencies to include in covertool output. " @@ -177,7 +178,10 @@ help(prefix_len) -> "[Optional] include the first N sections of the '_'-delimited module name in " "the package name. For example, with a prefix_len of 2 and a module named " "'app0_worker_srv_sup', the term 'app0.worker' would be added to the end of " - "the package name. Default: 0". + "the package name. Default: 0"; +help(summary) -> + "Print summary of the results to the standard output. " + "Useful for CI jobs. Default: false". app_to_atom(A) when is_atom(A) -> A; app_to_atom(S) when is_list(S) -> list_to_atom(S); diff --git a/src/rebar_covertool.erl b/src/rebar_covertool.erl index f7d7e5c..6bf7ca3 100644 --- a/src/rebar_covertool.erl +++ b/src/rebar_covertool.erl @@ -58,8 +58,10 @@ rebar2_generate(Config, AppFile, ConfigKey) -> {ok, CoverLog} -> cover:import(From), PrefixLen = rebar_config:get_local(Config, covertool_prefix_len, 0), + Summary = rebar_config:get_local(Config, covertool_summary, false), CoverConfig = #config{appname = AppName, prefix_len = PrefixLen, + summary = Summary, output = To}, covertool:generate_report(CoverConfig, cover:imported_modules()),