|
|
@ -37,13 +37,14 @@ init(State) -> |
|
|
|
-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), |
|
|
|
case prepare_tests(State) of |
|
|
|
{ok, Tests} -> do_tests(State, Tests); |
|
|
|
Error -> Error |
|
|
|
end. |
|
|
|
|
|
|
|
do_tests(State, Tests) -> |
|
|
|
EUnitOpts = resolve_eunit_opts(State), |
|
|
|
Result = eunit:test(Tests, EUnitOpts), |
|
|
|
ok = rebar_prv_cover:maybe_write_coverdata(State, ?PROVIDER), |
|
|
|
case handle_results(Result) of |
|
|
|
{error, Reason} -> |
|
|
@ -58,45 +59,9 @@ format_error(unknown_error) -> |
|
|
|
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]). |
|
|
|
%% =================================================================== |
|
|
|
%% Internal functions |
|
|
|
%% =================================================================== |
|
|
|
|
|
|
|
test_state(State) -> |
|
|
|
ErlOpts = rebar_state:get(State, eunit_compile_opts, []), |
|
|
@ -120,14 +85,33 @@ 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 |
|
|
|
prepare_tests(State) -> |
|
|
|
{RawOpts, _} = rebar_state:command_parsed_args(State), |
|
|
|
resolve_apps(State, RawOpts). |
|
|
|
|
|
|
|
resolve_apps(State, RawOpts) -> |
|
|
|
case proplists:get_value(app, RawOpts) of |
|
|
|
undefined -> resolve_suites(State, project_apps(State), RawOpts); |
|
|
|
%% convert app name strings to `rebar_app_info` objects |
|
|
|
Apps -> AppNames = string:tokens(Apps, [$,]), |
|
|
|
ProjectApps = project_apps(State), |
|
|
|
case filter_apps_by_name(AppNames, ProjectApps) of |
|
|
|
{ok, TestApps} -> resolve_suites(State, TestApps, RawOpts); |
|
|
|
Error -> Error |
|
|
|
end |
|
|
|
end. |
|
|
|
|
|
|
|
compile_tests(State, TestApps) -> |
|
|
|
resolve_suites(State, Apps, RawOpts) -> |
|
|
|
case proplists:get_value(suite, RawOpts) of |
|
|
|
undefined -> compile_tests(State, Apps, all, RawOpts); |
|
|
|
Suites -> SuiteNames = string:tokens(Suites, [$,]), |
|
|
|
case filter_suites_by_apps(SuiteNames, Apps) of |
|
|
|
{ok, S} -> compile_tests(State, Apps, S, RawOpts); |
|
|
|
Error -> Error |
|
|
|
end |
|
|
|
end. |
|
|
|
|
|
|
|
compile_tests(State, TestApps, Suites, RawOpts) -> |
|
|
|
F = fun(AppInfo) -> |
|
|
|
AppDir = rebar_app_info:dir(AppInfo), |
|
|
|
S = case rebar_app_info:state(AppInfo) of |
|
|
@ -141,24 +125,82 @@ compile_tests(State, TestApps) -> |
|
|
|
ec_cnv:to_list(rebar_app_info:out_dir(AppInfo))) |
|
|
|
end, |
|
|
|
lists:foreach(F, TestApps), |
|
|
|
case filelib:is_dir(filename:join([rebar_dir:get_cwd(), "test"])) of |
|
|
|
true -> compile_bare_tests(State, TestApps); |
|
|
|
false -> ok |
|
|
|
ok = maybe_cover_compile(State, RawOpts), |
|
|
|
{ok, test_set(TestApps, Suites)}. |
|
|
|
|
|
|
|
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). |
|
|
|
|
|
|
|
project_apps(State) -> |
|
|
|
filter_checkouts(rebar_state:project_apps(State)). |
|
|
|
|
|
|
|
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. |
|
|
|
|
|
|
|
%% make sure applications specified actually exist |
|
|
|
filter_apps_by_name(AppNames, ProjectApps) -> |
|
|
|
filter_apps_by_name(AppNames, ProjectApps, []). |
|
|
|
|
|
|
|
filter_apps_by_name([], _ProjectApps, Acc) -> {ok, lists:reverse(Acc)}; |
|
|
|
filter_apps_by_name([Name|Rest], ProjectApps, Acc) -> |
|
|
|
case find_app_by_name(Name, ProjectApps) of |
|
|
|
{error, app_not_found} -> |
|
|
|
?PRV_ERROR({error_running_tests, |
|
|
|
"Application `" ++ Name ++ "' not found in project."}); |
|
|
|
App -> |
|
|
|
filter_apps_by_name(Rest, ProjectApps, [App|Acc]) |
|
|
|
end. |
|
|
|
|
|
|
|
find_app_by_name(_, []) -> {error, app_not_found}; |
|
|
|
find_app_by_name(Name, [App|Rest]) -> |
|
|
|
case Name == binary_to_list(rebar_app_info:name(App)) of |
|
|
|
true -> App; |
|
|
|
false -> find_app_by_name(Name, Rest) |
|
|
|
end. |
|
|
|
|
|
|
|
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 and link just the `test` directory of the base dir |
|
|
|
[] -> |
|
|
|
Source = filename:join([rebar_dir:get_cwd(), "test"]), |
|
|
|
Target = filename:join([rebar_dir:base_dir(State), "test"]), |
|
|
|
ok = rebar_file_utils:symlink_or_copy(Source, Target), |
|
|
|
rebar_erlc_compiler:compile(replace_src_dirs(State), |
|
|
|
rebar_dir:base_dir(State), |
|
|
|
filename:join([rebar_dir:base_dir(State), "ebin"])); |
|
|
|
%% already compiled `./test` so do nothing |
|
|
|
_ -> ok |
|
|
|
%% ensure specified suites are in the applications included |
|
|
|
filter_suites_by_apps(Suites, ProjectApps) -> |
|
|
|
filter_suites_by_apps(Suites, ProjectApps, []). |
|
|
|
|
|
|
|
filter_suites_by_apps([], _ProjectApps, Acc) -> {ok, lists:reverse(Acc)}; |
|
|
|
filter_suites_by_apps([Suite|Rest], Apps, Acc) -> |
|
|
|
Modules = app_modules([binary_to_atom(rebar_app_info:name(A), unicode) || A <- Apps], []), |
|
|
|
case lists:member(list_to_atom(Suite), Modules) of |
|
|
|
false -> |
|
|
|
?PRV_ERROR({error_running_tests, |
|
|
|
"Module `" ++ Suite ++ "' not found in applications."}); |
|
|
|
true -> |
|
|
|
filter_suites_by_apps(Rest, Apps, [Suite|Acc]) |
|
|
|
end. |
|
|
|
|
|
|
|
app_modules([], Acc) -> Acc; |
|
|
|
app_modules([App|Rest], Acc) -> |
|
|
|
Unload = case application:load(App) of |
|
|
|
ok -> true; |
|
|
|
{error, {already_loaded, _}} -> false |
|
|
|
end, |
|
|
|
NewAcc = case application:get_key(App, modules) of |
|
|
|
{ok, Modules} -> Modules ++ Acc; |
|
|
|
undefined -> Acc |
|
|
|
end, |
|
|
|
case Unload of |
|
|
|
true -> |
|
|
|
application:unload(App), |
|
|
|
app_modules(Rest, NewAcc); |
|
|
|
false -> |
|
|
|
app_modules(Rest, NewAcc) |
|
|
|
end. |
|
|
|
|
|
|
|
replace_src_dirs(State) -> |
|
|
@ -167,15 +209,44 @@ replace_src_dirs(State) -> |
|
|
|
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). |
|
|
|
test_set(Apps, Suites) -> test_set(Apps, Suites, []). |
|
|
|
|
|
|
|
test_set([], all, Acc) -> lists:reverse(Acc); |
|
|
|
test_set(_, [], Acc) -> lists:reverse(Acc); |
|
|
|
test_set([App|Rest], all, Acc) -> |
|
|
|
AppName = list_to_atom(binary_to_list(rebar_app_info:name(App))), |
|
|
|
test_set(Rest, all, [{application, AppName}|Acc]); |
|
|
|
test_set(Apps, [Suite|Rest], Acc) -> |
|
|
|
test_set(Apps, Rest, [{module, list_to_atom(Suite)}|Acc]). |
|
|
|
|
|
|
|
resolve_eunit_opts(State) -> |
|
|
|
{Opts, _} = rebar_state:command_parsed_args(State), |
|
|
|
EUnitOpts = rebar_state:get(State, eunit_opts, []), |
|
|
|
case proplists:get_value(verbose, Opts, false) of |
|
|
|
true -> set_verbose(EUnitOpts); |
|
|
|
false -> EUnitOpts |
|
|
|
end. |
|
|
|
|
|
|
|
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. |
|
|
|
|
|
|
|
handle_results(ok) -> ok; |
|
|
|
handle_results(error) -> |
|
|
|
{error, unknown_error}; |
|
|
|
handle_results({error, Reason}) -> |
|
|
|
{error, {error_running_tests, Reason}}. |
|
|
|
|
|
|
|
eunit_opts(_State) -> |
|
|
|
[{app, undefined, "app", string, help(app)}, |
|
|
|
{cover, $c, "cover", boolean, help(cover)}, |
|
|
|
{suite, undefined, "suite", string, help(suite)}, |
|
|
|
{verbose, $v, "verbose", boolean, help(verbose)}]. |
|
|
|
|
|
|
|
help(app) -> "List of application test suites to run"; |
|
|
|
help(cover) -> "Generate cover data"; |
|
|
|
help(suite) -> "List of test suites to run"; |
|
|
|
help(verbose) -> "Verbose output". |