diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/rebar.app.src | 1 | ||||
| -rw-r--r-- | src/rebar_cover_utils.erl | 261 | ||||
| -rw-r--r-- | src/rebar_erlc_compiler.erl | 6 | ||||
| -rw-r--r-- | src/rebar_otp_app.erl | 30 | ||||
| -rw-r--r-- | src/rebar_prv_common_test.erl | 174 | ||||
| -rw-r--r-- | src/rebar_prv_cover.erl | 343 | ||||
| -rw-r--r-- | src/rebar_prv_eunit.erl | 153 | ||||
| -rw-r--r-- | src/rebar_state.erl | 10 | 
8 files changed, 546 insertions, 432 deletions
| diff --git a/src/rebar.app.src b/src/rebar.app.src index e5f21ea..8c544aa 100644 --- a/src/rebar.app.src +++ b/src/rebar.app.src @@ -25,6 +25,7 @@          {providers, [rebar_prv_as,                       rebar_prv_clean, +                     rebar_prv_cover,                       rebar_prv_deps,                       rebar_prv_dialyzer,                       rebar_prv_do, diff --git a/src/rebar_cover_utils.erl b/src/rebar_cover_utils.erl deleted file mode 100644 index 0439b8a..0000000 --- a/src/rebar_cover_utils.erl +++ /dev/null @@ -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)) ++ "%". diff --git a/src/rebar_erlc_compiler.erl b/src/rebar_erlc_compiler.erl index 9157e2a..c757511 100644 --- a/src/rebar_erlc_compiler.erl +++ b/src/rebar_erlc_compiler.erl @@ -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; diff --git a/src/rebar_otp_app.erl b/src/rebar_otp_app.erl index 278d7e5..e850e58 100644 --- a/src/rebar_otp_app.erl +++ b/src/rebar_otp_app.erl @@ -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 diff --git a/src/rebar_prv_common_test.erl b/src/rebar_prv_common_test.erl index c3f9163..032a8a6 100644 --- a/src/rebar_prv_common_test.erl +++ b/src/rebar_prv_common_test.erl @@ -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) -> diff --git a/src/rebar_prv_cover.erl b/src/rebar_prv_cover.erl new file mode 100644 index 0000000..765e2ac --- /dev/null +++ b/src/rebar_prv_cover.erl @@ -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.".
\ No newline at end of file diff --git a/src/rebar_prv_eunit.erl b/src/rebar_prv_eunit.erl index cd1b0f8..d841f26 100644 --- a/src/rebar_prv_eunit.erl +++ b/src/rebar_prv_eunit.erl @@ -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}; diff --git a/src/rebar_state.erl b/src/rebar_state.erl index 39e0e88..8170b8d 100644 --- a/src/rebar_state.erl +++ b/src/rebar_state.erl @@ -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]) -> | 
