%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
|
|
%% ex: ts=4 sw=4 et
|
|
|
|
-module(rebar_prv_eunit).
|
|
|
|
-behaviour(provider).
|
|
|
|
-export([init/1,
|
|
do/1,
|
|
format_error/1]).
|
|
|
|
-include("rebar.hrl").
|
|
|
|
-define(PROVIDER, eunit).
|
|
-define(DEPS, [compile]).
|
|
|
|
%% ===================================================================
|
|
%% Public API
|
|
%% ===================================================================
|
|
|
|
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
|
|
init(State) ->
|
|
Provider = providers:create([{name, ?PROVIDER},
|
|
{module, ?MODULE},
|
|
{deps, ?DEPS},
|
|
{bare, false},
|
|
{example, "rebar eunit"},
|
|
{short_desc, "Run EUnit Tests."},
|
|
{desc, ""},
|
|
{opts, eunit_opts(State)},
|
|
{profiles, [test]}]),
|
|
State1 = rebar_state:add_provider(State, Provider),
|
|
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...", []),
|
|
{Opts, _} = rebar_state:command_parsed_args(State),
|
|
EUnitOpts = resolve_eunit_opts(State, Opts),
|
|
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),
|
|
ok = rebar_prv_cover:maybe_write_coverdata(State, ?PROVIDER),
|
|
case handle_results(Result) of
|
|
{error, Reason} ->
|
|
{error, {?MODULE, Reason}};
|
|
ok ->
|
|
{ok, State}
|
|
end.
|
|
|
|
-spec format_error(any()) -> iolist().
|
|
format_error(unknown_error) ->
|
|
io_lib:format("Error running tests", []);
|
|
format_error({error_running_tests, Reason}) ->
|
|
io_lib:format("Error running tests: ~p", [Reason]).
|
|
|
|
eunit_opts(_State) ->
|
|
[{cover, $c, "cover", boolean, help(cover)},
|
|
{verbose, $v, "verbose", boolean, help(verbose)}].
|
|
|
|
help(cover) -> "Generate cover data";
|
|
help(verbose) -> "Verbose output".
|
|
|
|
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.
|
|
|
|
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
|
|
case test_defined(Opts) of
|
|
true -> Opts;
|
|
false -> [{d, 'TEST'}] ++ Opts
|
|
end.
|
|
|
|
test_defined([{d, 'TEST'}|_]) -> true;
|
|
test_defined([{d, 'TEST', true}|_]) -> true;
|
|
test_defined([_|Rest]) -> test_defined(Rest);
|
|
test_defined([]) -> false.
|
|
|
|
first_files(State) ->
|
|
EUnitFirst = rebar_state:get(State, eunit_first_files, []),
|
|
[{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.
|
|
|
|
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` 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
|
|
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};
|
|
handle_results({error, Reason}) ->
|
|
{error, {error_running_tests, Reason}}.
|