Browse Source

Merge pull request #202 from talentdeficit/cover

`cover` task
pull/203/head
Tristan Sloughter 10 years ago
parent
commit
358046b095
12 changed files with 712 additions and 436 deletions
  1. +1
    -0
      README.md
  2. +1
    -0
      src/rebar.app.src
  3. +0
    -261
      src/rebar_cover_utils.erl
  4. +2
    -4
      src/rebar_erlc_compiler.erl
  5. +26
    -4
      src/rebar_otp_app.erl
  6. +92
    -82
      src/rebar_prv_common_test.erl
  7. +343
    -0
      src/rebar_prv_cover.erl
  8. +74
    -79
      src/rebar_prv_eunit.erl
  9. +8
    -2
      src/rebar_state.erl
  10. +13
    -2
      test/rebar_compile_SUITE.erl
  11. +127
    -0
      test/rebar_cover_SUITE.erl
  12. +25
    -2
      test/rebar_profiles_SUITE.erl

+ 1
- 0
README.md View File

@ -32,6 +32,7 @@ limit scope.
| as | Higher-order provider to run multiple tasks in sequence as certain profiles |
| compile | Build project |
| clean | Remove project apps beam files |
| cover | Generate coverage info from data compiled by `eunit --cover` or `ct --cover` |
| ct | Run Common Test suites |
| do | Higher-order provider to run multiple tasks in sequence |
| dialyzer | Run the Dialyzer analyzer on the project |

+ 1
- 0
src/rebar.app.src View File

@ -25,6 +25,7 @@
{providers, [rebar_prv_as,
rebar_prv_clean,
rebar_prv_cover,
rebar_prv_deps,
rebar_prv_dialyzer,
rebar_prv_do,

+ 0
- 261
src/rebar_cover_utils.erl View File

@ -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)) ++ "%".

+ 2
- 4
src/rebar_erlc_compiler.erl View File

@ -260,10 +260,10 @@ opts_changed(Opts, Target) ->
Basename = filename:basename(Target, ".beam"),
Dirname = filename:dirname(Target),
ObjectFile = filename:join([Dirname, Basename]),
_ = purge(list_to_atom(Basename)),
case code:load_abs(ObjectFile) of
{module, Mod} ->
Compile = Mod:module_info(compile),
_ = purge(Mod),
lists:sort(Opts) =/= lists:sort(proplists:get_value(options,
Compile));
{error, nofile} -> false
@ -273,9 +273,7 @@ purge(Mod) ->
%% remove old code if necessary
_ = code:purge(Mod),
%% move current code to old
true = code:delete(Mod),
%% remove new old code
_ = code:purge(Mod).
_ = code:delete(Mod).
check_erlcinfo(_Config, #erlcinfo{vsn=?ERLCINFO_VSN}) ->
ok;

+ 26
- 4
src/rebar_otp_app.erl View File

@ -100,7 +100,7 @@ preprocess(State, AppInfo, AppSrcFile) ->
%% substitute. Note that we include the list of modules available in
%% ebin/ and update the app data accordingly.
OutDir = rebar_app_info:out_dir(AppInfo),
AppVars = load_app_vars(State) ++ [{modules, ebin_modules(OutDir)}],
AppVars = load_app_vars(State) ++ [{modules, ebin_modules(AppInfo, OutDir)}],
A1 = apply_app_vars(AppVars, AppData),
%% AppSrcFile may contain instructions for generating a vsn number
@ -157,9 +157,31 @@ validate_name(AppName, File) ->
?PRV_ERROR({invalid_name, File, AppName})
end.
ebin_modules(Dir) ->
lists:sort([rebar_utils:beam_to_mod(N) ||
N <- rebar_utils:beams(filename:join(Dir, "ebin"))]).
ebin_modules(App, Dir) ->
Beams = lists:sort(rebar_utils:beams(filename:join(Dir, "ebin"))),
F = fun(Beam) -> not lists:prefix(filename:join([rebar_app_info:dir(App), "test"]),
beam_src(Beam))
end,
Filtered = lists:filter(F, Beams),
[rebar_utils:beam_to_mod(N) || N <- Filtered].
beam_src(Beam) ->
try
Mod = list_to_atom(filename:basename(Beam, ".beam")),
_ = purge(Mod),
{module, Mod} = code:load_abs(filename:rootname(Beam, ".beam")),
Compile = Mod:module_info(compile),
proplists:get_value(source, Compile, [])
catch
error:undef -> [];
error:nofile -> []
end.
purge(Mod) ->
%% remove old code if necessary
_ = code:purge(Mod),
%% move current code to old
_ = code:delete(Mod).
ensure_registered(AppData) ->
case lists:keyfind(registered, 1, AppData) of

+ 92
- 82
src/rebar_prv_common_test.erl View File

@ -30,35 +30,24 @@ init(State) ->
{opts, ct_opts(State)},
{profiles, [test]}]),
State1 = rebar_state:add_provider(State, Provider),
{ok, State1}.
State2 = rebar_state:add_to_profile(State1, test, test_state(State1)),
{ok, State2}.
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
?INFO("Running Common Test suites...", []),
{RawOpts, _} = rebar_state:command_parsed_args(State),
{InDirs, OutDir} = split_ct_dirs(State, RawOpts),
Opts = transform_opts(RawOpts),
TestApps = filter_checkouts(rebar_state:project_apps(State)),
ok = create_dirs(Opts),
?DEBUG("Compiling Common Test suites in: ~p", [OutDir]),
lists:foreach(fun(App) ->
AppDir = rebar_app_info:dir(App),
AppOutDir = rebar_app_info:out_dir(App),
C = rebar_config:consult(AppDir),
S = rebar_state:new(State, C, AppDir),
%% combine `erl_first_files` and `common_test_first_files` and
%% adjust compile opts to include `common_test_compile_opts`
%% and `{src_dirs, "test"}`
TestState = test_state(S, InDirs, OutDir),
ok = rebar_erlc_compiler:compile(TestState, AppDir, AppOutDir)
end, TestApps),
ok = maybe_compile_extra_tests(TestApps, State, InDirs, OutDir),
Path = code:get_path(),
true = code:add_patha(OutDir),
CTOpts = resolve_ct_opts(State, Opts, OutDir),
Verbose = proplists:get_value(verbose, Opts, false),
Result = run_test(CTOpts, Verbose),
true = code:set_path(Path),
InDirs = in_dirs(State, RawOpts),
ok = compile_tests(State, TestApps, InDirs),
ok = maybe_cover_compile(State, RawOpts),
CTOpts = resolve_ct_opts(State, Opts),
Verbose = proplists:get_value(verbose, RawOpts, false),
TestDirs = test_dirs(State, TestApps),
Result = run_test([{dir, TestDirs}|CTOpts], Verbose),
ok = rebar_prv_cover:maybe_write_coverdata(State, ?PROVIDER),
case Result of
{error, Reason} ->
{error, {?MODULE, Reason}};
@ -86,9 +75,8 @@ run_test(CTOpts, false) ->
receive Result -> handle_quiet_results(CTOpts, Result) end.
ct_opts(State) ->
DefaultLogsDir = filename:join([rebar_state:dir(State), "logs"]),
DefaultLogsDir = filename:join([rebar_state:dir(State), "_logs"]),
[{dir, undefined, "dir", string, help(dir)}, %% comma-seperated list
{outdir, undefined, "outdir", string, help(outdir)}, %% string
{suite, undefined, "suite", string, help(suite)}, %% comma-seperated list
{group, undefined, "group", string, help(group)}, %% comma-seperated list
{testcase, undefined, "case", string, help(testcase)}, %% comma-seperated list
@ -104,7 +92,8 @@ ct_opts(State) ->
{silent_connections, undefined, "silent_connections", string,
help(silent_connections)}, % all OR %% comma-seperated list
{stylesheet, undefined, "stylesheet", string, help(stylesheet)}, %% file
{cover, undefined, "cover", string, help(cover)}, %% file
{cover, $c, "cover", boolean, help(cover)},
{cover_spec, undefined, "cover_spec", string, help(cover_spec)}, %% file
{cover_stop, undefined, "cover_stop", boolean, help(cover_stop)}, %% Boolean
{event_handler, undefined, "event_handler", string, help(event_handler)}, %% EH | [EH] WHERE EH atom() | {atom(), InitArgs} | {[atom()], InitArgs}
{include, undefined, "include", string, help(include)}, % comma-seperated list
@ -123,8 +112,6 @@ ct_opts(State) ->
{verbose, $v, "verbose", boolean, help(verbose)}
].
help(outdir) ->
"Output directory for compiled modules";
help(dir) ->
"List of additional directories containing test suites";
help(suite) ->
@ -154,6 +141,8 @@ help(silent_connections) ->
help(stylesheet) ->
"Stylesheet to use for test results";
help(cover) ->
"Generate cover data";
help(cover_spec) ->
"Cover file to use";
help(cover_stop) ->
""; %% ??
@ -186,33 +175,14 @@ help(userconfig) ->
help(verbose) ->
"Verbose output".
split_ct_dirs(State, RawOpts) ->
%% preserve the override nature of command line opts by only checking
%% `rebar.config` defined additional test dirs if none are defined via
%% command line flag
InDirs = case proplists:get_value(dir, RawOpts) of
undefined ->
CTOpts = rebar_state:get(State, common_test_opts, []),
proplists:get_value(dir, CTOpts, []);
Dirs -> split_string(Dirs)
end,
OutDir = proplists:get_value(outdir, RawOpts, default_test_dir(State)),
{InDirs, OutDir}.
default_test_dir(State) ->
Tmp = rebar_file_utils:system_tmpdir(),
Root = filename:join([rebar_state:dir(State), Tmp]),
Project = filename:basename(rebar_state:dir(State)),
OutDir = filename:join([Root, Project ++ "_rebar3_ct"]),
ok = rebar_file_utils:reset_dir(OutDir),
OutDir.
transform_opts(Opts) ->
transform_opts(Opts, []).
transform_opts([], Acc) -> Acc;
%% drop `outdir` so it's not passed to common_test
transform_opts([{outdir, _}|Rest], Acc) ->
%% drop `cover` and `verbose` so they're not passed as an option to common_test
transform_opts([{cover, _}|Rest], Acc) ->
transform_opts(Rest, Acc);
transform_opts([{verbose, _}|Rest], Acc) ->
transform_opts(Rest, Acc);
transform_opts([{ct_hooks, CtHooks}|Rest], Acc) ->
transform_opts(Rest, [{ct_hooks, parse_term(CtHooks)}|Acc]);
@ -302,52 +272,92 @@ ensure_dir([Dir|Rest]) ->
end,
ensure_dir(Rest).
test_state(State, InDirs, OutDir) ->
ErlOpts = rebar_state:get(State, common_test_compile_opts, []) ++
rebar_utils:erl_opts(State),
TestOpts = [{outdir, OutDir}] ++
add_test_dir(ErlOpts, InDirs),
first_files(rebar_state:set(State, erl_opts, TestOpts)).
add_test_dir(Opts, InDirs) ->
%% if no src_dirs are set we have to specify `src` or it won't
%% be built
case proplists:append_values(src_dirs, Opts) of
[] -> [{src_dirs, ["src", "test" | InDirs]} | Opts];
_ -> [{src_dirs, ["test" | InDirs]} | Opts]
in_dirs(State, Opts) ->
%% preserve the override nature of command line opts by only checking
%% `rebar.config` defined additional test dirs if none are defined via
%% command line flag
case proplists:get_value(dir, Opts) of
undefined ->
CTOpts = rebar_state:get(State, ct_opts, []),
proplists:get_value(dir, CTOpts, []);
Dirs -> split_string(Dirs)
end.
test_dirs(State, TestApps) ->
%% we need to add "./ebin" if it exists but only if it's not already
%% due to be added
F = fun(App) -> rebar_app_info:dir(App) =/= rebar_dir:get_cwd() end,
BareEbin = filename:join([rebar_dir:base_dir(State), "ebin"]),
case lists:any(F, TestApps) andalso filelib:is_dir(BareEbin) of
false -> application_dirs(TestApps, []);
true -> [BareEbin|application_dirs(TestApps, [])]
end.
application_dirs([], Acc) -> lists:reverse(Acc);
application_dirs([App|Rest], Acc) ->
application_dirs(Rest, [rebar_app_info:ebin_dir(App)|Acc]).
test_state(State) ->
TestOpts = case rebar_state:get(State, ct_compile_opts, []) of
[] -> [];
Opts -> [{erl_opts, Opts}]
end,
[first_files(State)|TestOpts].
first_files(State) ->
BaseFirst = rebar_state:get(State, erl_first_files, []),
CTFirst = rebar_state:get(State, common_test_first_files, []),
rebar_state:set(State, erl_first_files, BaseFirst ++ CTFirst).
CTFirst = rebar_state:get(State, ct_first_files, []),
{erl_first_files, CTFirst}.
resolve_ct_opts(State, CmdLineOpts, OutDir) ->
CTOpts = rebar_state:get(State, common_test_opts, []),
resolve_ct_opts(State, CmdLineOpts) ->
CTOpts = rebar_state:get(State, ct_opts, []),
Opts = lists:ukeymerge(1,
lists:ukeysort(1, CmdLineOpts),
lists:ukeysort(1, CTOpts)),
%% rebar has seperate input and output directories whereas `common_test`
%% uses only a single directory so set `dir` to our precompiled `OutDir`
%% and disable `auto_compile`
[{auto_compile, false}, {dir, OutDir}] ++ lists:keydelete(dir, 1, Opts).
%% disable `auto_compile` and remove `dir` from the opts
[{auto_compile, false}|lists:keydelete(dir, 1, Opts)].
compile_tests(State, TestApps, InDirs) ->
State1 = replace_src_dirs(State, InDirs),
F = fun(AppInfo) ->
AppDir = rebar_app_info:dir(AppInfo),
S = case rebar_app_info:state(AppInfo) of
undefined ->
C = rebar_config:consult(AppDir),
rebar_state:new(State1, C, AppDir);
AppState ->
AppState
end,
ok = rebar_erlc_compiler:compile(S,
ec_cnv:to_list(rebar_app_info:dir(AppInfo)),
ec_cnv:to_list(rebar_app_info:out_dir(AppInfo)))
end,
lists:foreach(F, TestApps),
compile_bare_tests(State1, TestApps).
maybe_compile_extra_tests(TestApps, State, InDirs, OutDir) ->
compile_bare_tests(State, TestApps) ->
F = fun(App) -> rebar_app_info:dir(App) == rebar_dir:get_cwd() end,
case lists:filter(F, TestApps) of
%% compile just the `test` and extra test directories of the base dir
[] ->
ErlOpts = rebar_state:get(State, common_test_compile_opts, []) ++
rebar_utils:erl_opts(State),
TestOpts = [{outdir, OutDir}] ++
[{src_dirs, ["test"|InDirs]}] ++
lists:keydelete(src_dirs, 1, ErlOpts),
TestState = first_files(rebar_state:set(State, erl_opts, TestOpts)),
rebar_erlc_compiler:compile(TestState, rebar_dir:get_cwd(), rebar_dir:get_cwd());
%% compile just the `test` directory of the base dir
[] -> rebar_erlc_compiler:compile(State,
rebar_dir:get_cwd(),
rebar_dir:base_dir(State));
%% already compiled `./test` so do nothing
_ -> ok
_ -> ok
end.
replace_src_dirs(State, InDirs) ->
%% replace any `src_dirs` with just the `test` dir and any `InDirs`
ErlOpts = rebar_state:get(State, erl_opts, []),
StrippedOpts = lists:keydelete(src_dirs, 1, ErlOpts),
rebar_state:set(State, erl_opts, [{src_dirs, ["test"|InDirs]}|StrippedOpts]).
maybe_cover_compile(State, Opts) ->
State1 = case proplists:get_value(cover, Opts, false) of
true -> rebar_state:set(State, cover_enabled, true);
false -> State
end,
rebar_prv_cover:maybe_cover_compile(State1).
handle_results([Result]) ->
handle_results(Result);
handle_results([Result|Results]) when is_list(Results) ->

+ 343
- 0
src/rebar_prv_cover.erl View File

@ -0,0 +1,343 @@
%% -*- 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, _} ->
write_file(Mod, mod_to_filename(TaskDir, Mod));
{error, _} ->
?WARN("Can't load module ~ts.", [Mod]),
{ok, []}
end.
write_file(Mod, FileName) ->
case cover:analyze_to_file(Mod, FileName, [html]) of
{ok, File} -> {ok, File};
{error, Reason} ->
?WARN("Couldn't write annotated file for module ~p for reason ~p", [Mod, Reason]),
{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%";
process([], {Cov, Not}) ->
integer_to_list(trunc((Cov / (Cov + Not)) * 100)) ++ "%";
%% 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).
%% fix for r15b which doesn't put the correct path in the `source` section
%% of `module_info(compile)`
strip_coverdir([]) -> "";
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.".

+ 74
- 79
src/rebar_prv_eunit.erl View File

@ -30,35 +30,20 @@ init(State) ->
{opts, eunit_opts(State)},
{profiles, [test]}]),
State1 = rebar_state:add_provider(State, Provider),
{ok, State1}.
State2 = rebar_state:add_to_profile(State1, test, test_state(State1)),
{ok, State2}.
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
?INFO("Performing EUnit tests...", []),
{RawOpts, _} = rebar_state:command_parsed_args(State),
Opts = transform_opts(RawOpts, State),
TestApps = filter_checkouts(rebar_state:project_apps(State)),
OutDir = proplists:get_value(outdir, Opts, default_test_dir(State)),
?DEBUG("Compiling EUnit instrumented modules in: ~p", [OutDir]),
lists:foreach(fun(App) ->
AppDir = rebar_app_info:dir(App),
AppOutDir = rebar_app_info:out_dir(App),
C = rebar_config:consult(AppDir),
S = rebar_state:new(State, C, AppDir),
%% combine `erl_first_files` and `eunit_first_files` and adjust
%% compile opts to include `eunit_compile_opts`, `{d, 'TEST'}`
%% and `{src_dirs, "test"}`
TestState = first_files(test_state(S, OutDir)),
ok = rebar_erlc_compiler:compile(TestState, AppDir, AppOutDir)
end, TestApps),
ok = maybe_compile_extra_tests(TestApps, State, OutDir),
Path = code:get_path(),
true = code:add_patha(OutDir),
{Opts, _} = rebar_state:command_parsed_args(State),
EUnitOpts = resolve_eunit_opts(State, Opts),
AppsToTest = [{application, erlang:binary_to_atom(rebar_app_info:name(App), unicode)}
|| App <- TestApps],
TestApps = filter_checkouts(rebar_state:project_apps(State)),
ok = compile_tests(State, TestApps),
ok = maybe_cover_compile(State, Opts),
AppsToTest = test_dirs(State, TestApps),
Result = eunit:test(AppsToTest, EUnitOpts),
true = code:set_path(Path),
ok = rebar_prv_cover:maybe_write_coverdata(State, ?PROVIDER),
case handle_results(Result) of
{error, Reason} ->
{error, {?MODULE, Reason}};
@ -73,24 +58,12 @@ format_error({error_running_tests, Reason}) ->
io_lib:format("Error running tests: ~p", [Reason]).
eunit_opts(_State) ->
[{outdir, $o, "outdir", string, help(outdir)},
[{cover, $c, "cover", boolean, help(cover)},
{verbose, $v, "verbose", boolean, help(verbose)}].
help(outdir) -> "Output directory for EUnit compiled modules";
help(cover) -> "Generate cover data";
help(verbose) -> "Verbose output".
transform_opts(Opts, State) -> transform_opts(Opts, State, []).
transform_opts([], _State, Acc) -> Acc;
transform_opts([{outdir, Path}|Rest], State, Acc) ->
NewAcc = case filename:pathtype(Path) of
absolute -> [{outdir, Path}] ++ Acc;
_ -> [{outdir, filename:join([rebar_state:dir(State), Path])}] ++ Acc
end,
transform_opts(Rest, State, NewAcc);
transform_opts([{Key, Val}|Rest], State, Acc) ->
transform_opts(Rest, State, [{Key, Val}|Acc]).
filter_checkouts(Apps) -> filter_checkouts(Apps, []).
filter_checkouts([], Acc) -> lists:reverse(Acc);
@ -102,30 +75,33 @@ filter_checkouts([App|Rest], Acc) ->
false -> filter_checkouts(Rest, [App|Acc])
end.
default_test_dir(State) ->
Tmp = rebar_file_utils:system_tmpdir(),
Root = filename:join([rebar_state:dir(State), Tmp]),
Project = filename:basename(rebar_state:dir(State)),
OutDir = filename:join([Root, Project ++ "_rebar3_eunit"]),
ok = rebar_file_utils:reset_dir(OutDir),
OutDir.
test_state(State, TmpDir) ->
ErlOpts = rebar_state:get(State, eunit_compile_opts, []) ++
rebar_utils:erl_opts(State),
ErlOpts1 = [{outdir, TmpDir}] ++
add_test_dir(ErlOpts),
TestOpts = safe_define_test_macro(ErlOpts1),
rebar_state:set(State, erl_opts, TestOpts).
add_test_dir(Opts) ->
%% if no src_dirs are set we have to specify `src` or it won't
%% be built
case proplists:append_values(src_dirs, Opts) of
[] -> [{src_dirs, ["src", "test"]} | Opts];
_ -> [{src_dirs, ["test"]} | Opts]
resolve_eunit_opts(State, Opts) ->
EUnitOpts = rebar_state:get(State, eunit_opts, []),
case proplists:get_value(verbose, Opts, false) of
true -> set_verbose(EUnitOpts);
false -> EUnitOpts
end.
test_dirs(State, TestApps) ->
%% we need to add "./ebin" if it exists but only if it's not already
%% due to be added
F = fun(App) -> rebar_app_info:dir(App) =/= rebar_dir:get_cwd() end,
BareEbin = filename:join([rebar_dir:base_dir(State), "ebin"]),
case lists:any(F, TestApps) andalso filelib:is_dir(BareEbin) of
false -> application_dirs(TestApps, []);
true -> [{dir, BareEbin}|application_dirs(TestApps, [])]
end.
application_dirs([], Acc) -> lists:reverse(Acc);
application_dirs([App|Rest], Acc) ->
AppName = list_to_atom(binary_to_list(rebar_app_info:name(App))),
application_dirs(Rest, [{application, AppName}|Acc]).
test_state(State) ->
ErlOpts = rebar_state:get(State, eunit_compile_opts, []),
TestOpts = safe_define_test_macro(ErlOpts),
first_files(State) ++ [{erl_opts, TestOpts}].
safe_define_test_macro(Opts) ->
%% defining a compile macro twice results in an exception so
%% make sure 'TEST' is only defined once
@ -140,39 +116,58 @@ test_defined([_|Rest]) -> test_defined(Rest);
test_defined([]) -> false.
first_files(State) ->
BaseFirst = rebar_state:get(State, erl_first_files, []),
EUnitFirst = rebar_state:get(State, eunit_first_files, []),
rebar_state:set(State, erl_first_files, BaseFirst ++ EUnitFirst).
resolve_eunit_opts(State, Opts) ->
EUnitOpts = rebar_state:get(State, eunit_opts, []),
case lists:member({verbose, true}, Opts) of
true -> set_verbose(EUnitOpts);
false -> EUnitOpts
end.
[{erl_first_files, EUnitFirst}].
set_verbose(Opts) ->
%% if `verbose` is already set don't set it again
case lists:member(verbose, Opts) of
true -> Opts;
false -> [verbose] ++ Opts
end.
maybe_compile_extra_tests(TestApps, State, OutDir) ->
compile_tests(State, TestApps) ->
State1 = replace_src_dirs(State),
F = fun(AppInfo) ->
AppDir = rebar_app_info:dir(AppInfo),
S = case rebar_app_info:state(AppInfo) of
undefined ->
C = rebar_config:consult(AppDir),
rebar_state:new(State1, C, AppDir);
AppState ->
AppState
end,
ok = rebar_erlc_compiler:compile(S,
ec_cnv:to_list(rebar_app_info:dir(AppInfo)),
ec_cnv:to_list(rebar_app_info:out_dir(AppInfo)))
end,
lists:foreach(F, TestApps),
compile_bare_tests(State1, TestApps).
compile_bare_tests(State, TestApps) ->
F = fun(App) -> rebar_app_info:dir(App) == rebar_dir:get_cwd() end,
case lists:filter(F, TestApps) of
%% compile just the `test` and extra test directories of the base dir
[] ->
ErlOpts = rebar_state:get(State, common_test_compile_opts, []) ++
rebar_utils:erl_opts(State),
TestOpts = [{outdir, OutDir}] ++
[{src_dirs, ["test"]}] ++
safe_define_test_macro(lists:keydelete(src_dirs, 1, ErlOpts)),
TestState = first_files(rebar_state:set(State, erl_opts, TestOpts)),
rebar_erlc_compiler:compile(TestState, rebar_dir:get_cwd(), rebar_dir:get_cwd());
%% compile just the `test` directory of the base dir
[] -> rebar_erlc_compiler:compile(State,
rebar_dir:get_cwd(),
rebar_dir:base_dir(State));
%% already compiled `./test` so do nothing
_ -> ok
_ -> ok
end.
replace_src_dirs(State) ->
%% replace any `src_dirs` with just the `test` dir
ErlOpts = rebar_state:get(State, erl_opts, []),
StrippedOpts = lists:keydelete(src_dirs, 1, ErlOpts),
rebar_state:set(State, erl_opts, [{src_dirs, ["test"]}|StrippedOpts]).
maybe_cover_compile(State, Opts) ->
State1 = case proplists:get_value(cover, Opts, false) of
true -> rebar_state:set(State, cover_enabled, true);
false -> State
end,
rebar_prv_cover:maybe_cover_compile(State1).
handle_results(ok) -> ok;
handle_results(error) ->
{error, unknown_error};

+ 8
- 2
src/rebar_state.erl View File

@ -15,7 +15,7 @@
command_args/1, command_args/2,
command_parsed_args/1, command_parsed_args/2,
apply_profiles/2,
add_to_profile/3, apply_profiles/2,
dir/1, dir/2,
create_logic_providers/2,
@ -97,7 +97,6 @@ new(ParentState, Config, Dir) ->
D = proplists:get_value(deps, Config, []),
dict:from_list([{{deps, default}, D} | Config])
end,
NewOpts = dict:merge(fun(_Key, Value1, _Value2) ->
Value1
end, LocalOpts, Opts),
@ -188,6 +187,13 @@ apply_overrides(State=#state_t{overrides=Overrides}, AppName) ->
StateAcc
end, State2, Overrides).
add_to_profile(State, Profile, KVs) when is_atom(Profile), is_list(KVs) ->
Profiles = rebar_state:get(State, profiles, []),
ProfileOpts = dict:from_list(proplists:get_value(Profile, Profiles, [])),
NewOpts = merge_opts(Profile, dict:from_list(KVs), ProfileOpts),
NewProfiles = [{Profile, dict:to_list(NewOpts)}|lists:keydelete(Profile, 1, Profiles)],
rebar_state:set(State, profiles, NewProfiles).
apply_profiles(State, Profile) when not is_list(Profile) ->
apply_profiles(State, [Profile]);
apply_profiles(State, [default]) ->

+ 13
- 2
test/rebar_compile_SUITE.erl View File

@ -11,7 +11,8 @@
build_checkout_deps/1,
recompile_when_opts_change/1,
dont_recompile_when_opts_dont_change/1,
dont_recompile_yrl_or_xrl/1]).
dont_recompile_yrl_or_xrl/1,
purge_before_load/1]).
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
@ -33,7 +34,7 @@ all() ->
[build_basic_app, build_release_apps,
build_checkout_apps, build_checkout_deps,
recompile_when_opts_change, dont_recompile_when_opts_dont_change,
dont_recompile_yrl_or_xrl].
dont_recompile_yrl_or_xrl, purge_before_load].
build_basic_app(Config) ->
AppDir = ?config(apps, Config),
@ -183,3 +184,13 @@ dont_recompile_yrl_or_xrl(Config) ->
NewModTime = filelib:last_modified(XrlBeam),
?assert(ModTime == NewModTime).
purge_before_load(Config) ->
AppDir = ?config(apps, Config),
Name = rebar_test_utils:create_random_name("app1_"),
Vsn = rebar_test_utils:create_random_vsn(),
rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]),
Tasks = ["do", "compile,compile,compile"],
rebar_test_utils:run_and_check(Config, [], Tasks, {ok, [{app, Name}]}).

+ 127
- 0
test/rebar_cover_SUITE.erl View File

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

+ 25
- 2
test/rebar_profiles_SUITE.erl View File

@ -7,14 +7,17 @@
all/0,
profile_new_key/1,
profile_merge_keys/1,
profile_merges/1]).
profile_merges/1,
add_to_profile/1,
add_to_existing_profile/1]).
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
-include_lib("kernel/include/file.hrl").
all() ->
[profile_new_key, profile_merge_keys, profile_merges].
[profile_new_key, profile_merge_keys, profile_merges,
add_to_profile, add_to_existing_profile].
init_per_suite(Config) ->
application:start(meck),
@ -106,3 +109,23 @@ profile_merges(_Config) ->
%% Check that a newvalue of []/"" doesn't override non-string oldvalues
[key3] = rebar_state:get(State1, test3),
[] = rebar_state:get(State1, test4).
add_to_profile(_Config) ->
RebarConfig = [{foo, true}, {bar, false}],
State = rebar_state:new(RebarConfig),
State1 = rebar_state:add_to_profile(State, test, [{foo, false}]),
State2 = rebar_state:apply_profiles(State1, test),
Opts = rebar_state:opts(State2),
lists:map(fun(K) -> false = dict:fetch(K, Opts) end, [foo, bar]).
add_to_existing_profile(_Config) ->
RebarConfig = [{foo, true}, {bar, false}, {profiles, [
{test, [{foo, false}]}
]}],
State = rebar_state:new(RebarConfig),
State1 = rebar_state:add_to_profile(State, test, [{baz, false}]),
State2 = rebar_state:apply_profiles(State1, test),
Opts = rebar_state:opts(State2),
lists:map(fun(K) -> false = dict:fetch(K, Opts) end, [foo, bar, baz]).

Loading…
Cancel
Save