Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 

276 Zeilen
9.6 KiB

%% -*- 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").
-include_lib("providers/include/providers.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, true},
{example, "rebar3 eunit"},
{short_desc, "Run EUnit Tests."},
{desc, "Run EUnit Tests."},
{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...", []),
rebar_utils:update_code(rebar_state:code_paths(State, all_deps)),
%% Run eunit provider prehooks
Providers = rebar_state:providers(State),
Cwd = rebar_dir:get_cwd(),
rebar_hooks:run_all_hooks(Cwd, pre, ?PROVIDER, Providers, State),
case prepare_tests(State) of
{ok, Tests} ->
case do_tests(State, Tests) of
{ok, State1} ->
%% Run eunit provider posthooks
rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, State1),
rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)),
{ok, State1};
Error ->
rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)),
Error
end;
Error ->
rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)),
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} ->
?PRV_ERROR(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]).
%% ===================================================================
%% Internal functions
%% ===================================================================
test_state(State) ->
ErlOpts = rebar_state:get(State, eunit_compile_opts, []),
TestOpts = safe_define_test_macro(ErlOpts),
TestDir = [{extra_src_dirs, ["test"]}],
first_files(State) ++ [{erl_opts, TestOpts ++ TestDir}].
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}].
prepare_tests(State) ->
{RawOpts, _} = rebar_state:command_parsed_args(State),
ok = maybe_cover_compile(State, RawOpts),
ProjectApps = project_apps(State),
resolve_tests(ProjectApps, RawOpts).
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).
resolve_tests(ProjectApps, RawOpts) ->
case proplists:get_value(file, RawOpts) of
undefined -> resolve_apps(ProjectApps, RawOpts);
Files -> resolve_files(ProjectApps, Files, RawOpts)
end.
resolve_files(ProjectApps, Files, RawOpts) ->
case {proplists:get_value(app, RawOpts), proplists:get_value(suite, RawOpts)} of
{undefined, undefined} -> resolve_files(Files, []);
_ ->
case resolve_apps(ProjectApps, RawOpts) of
{ok, TestSet} -> resolve_files(Files, TestSet);
Error -> Error
end
end.
resolve_files(Files, TestSet) ->
FileNames = string:tokens(Files, [$,]),
{ok, TestSet ++ set_files(FileNames, [])}.
resolve_apps(ProjectApps, RawOpts) ->
case proplists:get_value(app, RawOpts) of
undefined -> resolve_suites(ProjectApps, RawOpts);
%% convert app name strings to `rebar_app_info` objects
Apps -> AppNames = string:tokens(Apps, [$,]),
case filter_apps_by_name(AppNames, ProjectApps) of
{ok, TestApps} -> resolve_suites(TestApps, RawOpts);
Error -> Error
end
end.
resolve_suites(Apps, RawOpts) ->
case proplists:get_value(suite, RawOpts) of
undefined -> test_set(Apps, all);
Suites -> SuiteNames = string:tokens(Suites, [$,]),
case filter_suites_by_apps(SuiteNames, Apps) of
{ok, S} -> test_set(Apps, S);
Error -> Error
end
end.
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) ->
case rebar_app_info:is_checkout(App) 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.
%% 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.
test_set(Apps, all) -> {ok, set_apps(Apps, [])};
test_set(_Apps, Suites) -> {ok, set_suites(Suites, [])}.
set_apps([], Acc) -> lists:reverse(Acc);
set_apps([App|Rest], Acc) ->
AppName = list_to_atom(binary_to_list(rebar_app_info:name(App))),
set_apps(Rest, [{application, AppName}|Acc]).
set_suites([], Acc) -> lists:reverse(Acc);
set_suites([Suite|Rest], Acc) ->
set_suites(Rest, [{module, list_to_atom(Suite)}|Acc]).
set_files([], Acc) -> lists:reverse(Acc);
set_files([File|Rest], Acc) ->
set_files(Rest, [{file, File}|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)},
{file, $f, "file", string, help(file)},
{suite, undefined, "suite", string, help(suite)},
{verbose, $v, "verbose", boolean, help(verbose)}].
help(app) -> "Comma seperated list of application test suites to run";
help(cover) -> "Generate cover data";
help(file) -> "Comma seperated list of modules to run";
help(suite) -> "Comma seperated list of test suites to run";
help(verbose) -> "Verbose output".