Current section

Files

Jump to
rebar3_exunit src rebar3_exunit.erl
Raw

src/rebar3_exunit.erl

%% @doc
%% Add the plugin to your rebar config:
%%
%% ```
%% {plugins, [rebar3_exunit]}.'''
%%
%% Then just call your plugin directly in an existing application to run
%% ExUnit tests (.exs files) from the test/ directory.
%%
%% ```
%% $ rebar3 exunit
%% ===> Fetching rebar3_exunit_plugin
%% ===> Compiling rebar3_exunit_plugin'''
-module(rebar3_exunit).
-behaviour(provider).
-export([init/1, do/1, format_error/1]).
-define(PROVIDER, exunit).
-define(DEPS, [compile]).
-define(DEFAULT_TEST_DIR, "test").
%%%===================================================================
%%% 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 exunit"},
{short_desc, "Run ExUnit tests."},
{desc, ""},
{opts, exunit_opts(State)},
{profiles, [test]}]),
State1 = rebar_state:add_provider(State, Provider),
State2 = rebar_state:add_to_profile(State1, test, []),
elixir_dep_code_path(State2),
{ok, State2}.
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
rebar_api:info("Performing ExUnit tests...", []),
rebar_utils:update_code(rebar_state:code_paths(State, all_deps)),
%% Run exunit provider prehooks
Providers = rebar_state:providers(State),
Cwd = rebar_dir:get_cwd(),
rebar_hooks:run_all_hooks(Cwd, pre, ?PROVIDER, Providers, State),
{ok, Tests} = prepare_tests(State),
case do_tests(State, Tests) of
ok ->
rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)),
{ok, State};
error ->
rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)),
rebar_api:error("Some ExUnit tests failed", []),
{ok, State}
end.
do_tests(_State, ElixirTests) ->
%% Run elixir tests
lists:foldl(
fun(TestFile, Acc) ->
case run_elixir_test(TestFile) of
{ok, 0} ->
Acc;
{ok, _N} ->
rebar_api:error(" Tests failed on ~s", [TestFile]),
error
end
end, ok, ElixirTests).
-spec format_error(any()) -> iolist().
format_error(Reason) ->
io_lib:format("~p", [Reason]).
%%%===================================================================
%%% Internal Functions
%%%===================================================================
%% Add Elixir lib subdirs from dependencies in code path
elixir_dep_code_path(State) ->
PluginDir = rebar_dir:plugins_dir(State),
PluginSubEbinDirs = filelib:wildcard(filename:join(PluginDir, "*/lib/*/ebin")),
rebar_utils:update_code(PluginSubEbinDirs).
prepare_tests(State) ->
{RawOpts, _} = rebar_state:command_parsed_args(State),
resolve_tests(RawOpts).
resolve_tests(RawOpts) ->
case proplists:get_value(file, RawOpts) of
undefined -> resolve_all(RawOpts);
Files -> resolve_files(Files, RawOpts)
end.
resolve_all(_Opts) ->
Files = filelib:fold_files(test_dir(), ".*\.exs", false,
fun(Filename, Acc) ->
[filename:basename(Filename) | Acc] end, []),
{ok, Files}.
resolve_files(Files, _Opts) ->
FileNames = string:tokens(Files, [$,]),
{ok, set_files(FileNames, [])}.
%% TODO I need to
%% - Add "test" path prefix if there is only a file name
%% - Check if the file exist and display a proper error message if the file does not exist
set_files([], Acc) -> lists:reverse(Acc);
set_files([File|Rest], Acc) ->
FileWithExt = case filename:extension(File) of
[] -> File ++ ".exs";
".exs" ->
File
end,
set_files(Rest, [FileWithExt|Acc]).
run_elixir_test(Module) ->
rebar_api:info(" Starting tests in ~s", [Module]),
'Elixir.ExUnit':start([]),
'Elixir.Code':load_file(list_to_binary(filename:join(test_dir(), Module))),
%% I did not use map syntax, so that this file can still be build under R16
ResultMap = 'Elixir.ExUnit':run(),
maps:find(failures, ResultMap).
%% TODO: Make ExUnit test dir name an option
test_dir() ->
?DEFAULT_TEST_DIR.
exunit_opts(_State) ->
[{file, $f, "file", string, help(file)}].
help(file) -> "Comma separated list of files to run.".
%%%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
%% ex: ts=4 sw=4 et