summaryrefslogtreecommitdiff
path: root/src/rebar_prv_eunit.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/rebar_prv_eunit.erl')
-rw-r--r--src/rebar_prv_eunit.erl231
1 files changed, 124 insertions, 107 deletions
diff --git a/src/rebar_prv_eunit.erl b/src/rebar_prv_eunit.erl
index d744204..18a8993 100644
--- a/src/rebar_prv_eunit.erl
+++ b/src/rebar_prv_eunit.erl
@@ -8,6 +8,8 @@
-export([init/1,
do/1,
format_error/1]).
+%% exported solely for tests
+-export([compile/1, prepare_tests/1, eunit_opts/1]).
-include("rebar.hrl").
-include_lib("providers/include/providers.hrl").
@@ -37,11 +39,8 @@ init(State) ->
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
- %% inject the `TEST` macro, `eunit_first_files` and `eunit_compile_opts`
- %% into the applications to be compiled
- NewState = inject_eunit_state(State),
-
- case rebar_prv_compile:do(NewState) of
+ case compile(State) of
+ %% successfully compiled apps
{ok, S} -> do_tests(S);
%% this should look like a compiler error, not an eunit error
Error -> Error
@@ -49,9 +48,10 @@ do(State) ->
do_tests(State) ->
?INFO("Performing EUnit tests...", []),
+
rebar_utils:update_code(rebar_state:code_paths(State, all_deps)),
- %% Run compile provider prehooks
+ %% Run eunit provider prehooks
Providers = rebar_state:providers(State),
Cwd = rebar_dir:get_cwd(),
rebar_hooks:run_all_hooks(Cwd, pre, ?PROVIDER, Providers, State),
@@ -101,8 +101,32 @@ format_error({error, Error}) ->
%% Internal functions
%% ===================================================================
-%% currently only add the `extra_drc_dirs` on provider init
-test_state(_State) -> [{extra_src_dirs, ["test"]}].
+test_state(State) ->
+ ErlOpts = rebar_state:get(State, erl_opts, []),
+ TestOpts = safe_define_test_macro(ErlOpts),
+ [{extra_src_dirs, ["test"]}, {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.
+
+compile(State) ->
+ %% inject `eunit_first_files` and `eunit_compile_opts` into the applications to be compiled
+ NewState = inject_eunit_state(State),
+
+ case rebar_prv_compile:do(NewState) of
+ %% successfully compiled apps
+ {ok, S} ->
+ _ = maybe_cover_compile(S),
+ _ = maybe_compile_bare_testdir(S),
+ {ok, S};
+ %% this should look like a compiler error, not an eunit error
+ Error -> Error
+ end.
inject_eunit_state(State) ->
Apps = project_apps(State),
@@ -110,11 +134,10 @@ inject_eunit_state(State) ->
rebar_state:project_apps(State, ModdedApps).
inject(State, App) ->
- %% append `eunit_compile_opts` to app defined `erl_opts` and define
- %% the `TEST` macro if not already compiled
+ %% append `eunit_compile_opts` to app defined `erl_opts`
ErlOpts = rebar_app_info:get(App, erl_opts, []),
EUnitOpts = rebar_state:get(State, eunit_compile_opts, []),
- NewOpts = safe_define_test_macro(EUnitOpts ++ ErlOpts),
+ NewOpts = EUnitOpts ++ ErlOpts,
%% append `eunit_first_files` to app defined `erl_first_files`
FirstFiles = rebar_app_info:get(App, erl_first_files, []),
EUnitFirstFiles = rebar_state:get(State, eunit_first_files, []),
@@ -124,29 +147,27 @@ inject(State, App) ->
App,
[{erl_opts, NewOpts}, {erl_first_files, NewFirstFiles}]).
-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.
prepare_tests(State) ->
- {RawOpts, _} = rebar_state:command_parsed_args(State),
- ok = maybe_cover_compile(State, RawOpts),
- CmdTests = resolve_tests(RawOpts),
+ %% parse and translate command line tests
+ CmdTests = resolve_tests(State),
CfgTests = rebar_state:get(State, eunit_tests, []),
ProjectApps = project_apps(State),
- Tests = select_tests(ProjectApps, CmdTests, CfgTests),
+ %% prioritize tests to run first trying any command line specified
+ %% tests falling back to tests specified in the config file finally
+ %% running a default set if no other tests are present
+ Tests = select_tests(State, ProjectApps, CmdTests, CfgTests),
+ %% check applications for existence in project, modules for existence
+ %% in applications, files and dirs for existence on disk and allow
+ %% any unrecognized tests through for eunit to handle
validate_tests(State, ProjectApps, Tests).
-resolve_tests(RawOpts) ->
+resolve_tests(State) ->
+ {RawOpts, _} = rebar_state:command_parsed_args(State),
Apps = resolve(app, application, RawOpts),
Applications = resolve(application, RawOpts),
Dirs = resolve(dir, RawOpts),
@@ -166,8 +187,38 @@ resolve(Flag, EUnitKey, RawOpts) ->
normalize(Key, Value) when Key == dir; Key == file -> {Key, Value};
normalize(Key, Value) -> {Key, list_to_atom(Value)}.
-select_tests(ProjectApps, [], []) -> default_tests(ProjectApps);
-select_tests(_ProjectApps, A, B) -> A ++ B.
+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.
+
+select_tests(State, ProjectApps, [], []) -> default_tests(State, ProjectApps);
+select_tests(_State, _ProjectApps, [], Tests) -> Tests;
+select_tests(_State, _ProjectApps, Tests, _) -> Tests.
+
+default_tests(State, Apps) ->
+ Tests = set_apps(Apps, []),
+ BareTest = filename:join([rebar_state:dir(State), "test"]),
+ F = fun(App) -> rebar_app_info:dir(App) == rebar_state:dir(State) end,
+ case filelib:is_dir(BareTest) andalso not lists:any(F, Apps) of
+ %% `test` dir at root of project is already scheduled to be
+ %% included or `test` does not exist
+ false -> lists:reverse(Tests);
+ %% need to add `test` dir at root to dirs to be included
+ true -> lists:reverse([{dir, filename:join([rebar_dir:base_dir(State), "test"])}|Tests])
+ end.
+
+set_apps([], Acc) -> Acc;
+set_apps([App|Rest], Acc) ->
+ AppName = list_to_atom(binary_to_list(rebar_app_info:name(App))),
+ set_apps(Rest, [{application, AppName}|Acc]).
validate_tests(State, ProjectApps, Tests) ->
gather_tests(fun(Elem) -> validate(State, ProjectApps, Elem) end, Tests, []).
@@ -175,34 +226,30 @@ validate_tests(State, ProjectApps, Tests) ->
gather_tests(_F, [], Acc) -> {ok, lists:reverse(Acc)};
gather_tests(F, [Test|Rest], Acc) ->
case F(Test) of
- true -> gather_tests(F, Rest, [Test|Acc]);
- false -> gather_tests(F, Rest, Acc);
+ ok -> gather_tests(F, Rest, [Test|Acc]);
%% failure mode, switch to gathering errors
- Error -> gather_errors(F, Rest, [Error])
+ {error, Error} -> gather_errors(F, Rest, [Error])
end.
gather_errors(_F, [], Acc) -> ?PRV_ERROR({eunit_test_errors, lists:reverse(Acc)});
gather_errors(F, [Test|Rest], Acc) ->
case F(Test) of
- true -> gather_errors(F, Rest, Acc);
- false -> gather_errors(F, Rest, Acc);
- Error -> gather_errors(F, Rest, [Error|Acc])
+ ok -> gather_errors(F, Rest, Acc);
+ {error, Error} -> gather_errors(F, Rest, [Error|Acc])
end.
validate(State, ProjectApps, {application, App}) ->
validate_app(State, ProjectApps, App);
validate(State, _ProjectApps, {dir, Dir}) ->
- ok = maybe_compile_dir(State, Dir),
validate_dir(State, Dir);
validate(State, _ProjectApps, {file, File}) ->
- ok = maybe_compile_file(State, File),
validate_file(State, File);
-validate(State, ProjectApps, {module, Module}) ->
- validate_module(State, ProjectApps, Module);
-validate(State, ProjectApps, {suite, Module}) ->
- validate_module(State, ProjectApps, Module);
-validate(State, ProjectApps, Module) when is_atom(Module) ->
- validate_module(State, ProjectApps, Module);
+validate(State, _ProjectApps, {module, Module}) ->
+ validate_module(State, Module);
+validate(State, _ProjectApps, {suite, Module}) ->
+ validate_module(State, Module);
+validate(State, _ProjectApps, Module) when is_atom(Module) ->
+ validate_module(State, Module);
validate(State, ProjectApps, Path) when is_list(Path) ->
case ec_file:is_dir(Path) of
true -> validate(State, ProjectApps, {dir, Path});
@@ -211,80 +258,35 @@ validate(State, ProjectApps, Path) when is_list(Path) ->
%% unrecognized tests should be included. if they're invalid eunit will error
%% and rebar.config may contain arbitrarily complex tests that are effectively
%% unvalidatable
-validate(_State, _ProjectApps, _Test) -> true.
+validate(_State, _ProjectApps, _Test) -> ok.
-validate_app(State, [], AppName) ->
- warn_or_error(State, lists:concat(["Application `", AppName, "' not found in project."]));
+validate_app(_State, [], AppName) ->
+ {error, lists:concat(["Application `", AppName, "' not found in project."])};
validate_app(State, [App|Rest], AppName) ->
case AppName == binary_to_atom(rebar_app_info:name(App), unicode) of
- true -> true;
+ true -> ok;
false -> validate_app(State, Rest, AppName)
end.
-validate_dir(State, Dir) ->
+validate_dir(_State, Dir) ->
case ec_file:is_dir(Dir) of
- true -> true;
- false -> warn_or_error(State, lists:concat(["Directory `", Dir, "' not found."]))
+ true -> ok;
+ false -> {error, lists:concat(["Directory `", Dir, "' not found."])}
end.
-validate_file(State, File) ->
+validate_file(_State, File) ->
case ec_file:exists(File) of
- true -> true;
- false -> warn_or_error(State, lists:concat(["File `", File, "' not found."]))
- end.
-
-validate_module(State, Apps, Module) ->
- AppModules = app_modules([binary_to_atom(rebar_app_info:name(A), unicode) || A <- Apps], []),
- case lists:member(Module, AppModules) of
- true -> true;
- false -> warn_or_error(State, lists:concat(["Module `", Module, "' not found in applications."]))
- end.
-
-warn_or_error(State, Msg) ->
- {RawOpts, _} = rebar_state:command_parsed_args(State),
- Error = proplists:get_value(error_on_warning, RawOpts, false),
- case Error of
- true -> Msg;
- false -> ?WARN(Msg, []), false
- 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])
+ true -> ok;
+ false -> {error, lists:concat(["File `", File, "' not found."])}
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)
+validate_module(_State, Module) ->
+ Path = code:which(Module),
+ case beam_lib:chunks(Path, [exports]) of
+ {ok, _} -> ok;
+ {error, beam_lib, _} -> {error, lists:concat(["Module `", Module, "' not found in project."])}
end.
-default_tests(Apps) -> set_apps(Apps, []).
-
-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]).
-
resolve_eunit_opts(State) ->
{Opts, _} = rebar_state:command_parsed_args(State),
EUnitOpts = rebar_state:get(State, eunit_opts, []),
@@ -300,16 +302,33 @@ set_verbose(Opts) ->
false -> [verbose] ++ Opts
end.
-maybe_compile_dir(_, _) -> ok.
-maybe_compile_file(_, _) -> ok.
-
-maybe_cover_compile(State, Opts) ->
- State1 = case proplists:get_value(cover, Opts, false) of
+maybe_cover_compile(State) ->
+ State1 = case rebar_state:get(State, cover, false) of
true -> rebar_state:set(State, cover_enabled, true);
false -> State
end,
rebar_prv_cover:maybe_cover_compile(State1).
+maybe_compile_bare_testdir(State) ->
+ case ec_file:is_dir(filename:join([rebar_state:dir(State), "test"])) of
+ true ->
+ ErlOpts = rebar_state:get(State, erl_opts, []),
+ EUnitOpts = rebar_state:get(State, eunit_compile_opts, []),
+ NewErlOpts = safe_define_test_macro(EUnitOpts ++ ErlOpts),
+ %% append `eunit_first_files` to app defined `erl_first_files`
+ FirstFiles = rebar_state:get(State, erl_first_files, []),
+ EUnitFirstFiles = rebar_state:get(State, eunit_first_files, []),
+ NewFirstFiles = EUnitFirstFiles ++ FirstFiles,
+ Opts = rebar_state:opts(State),
+ NewOpts = lists:foldl(fun({K, V}, Dict) -> rebar_opts:set(Dict, K, V) end,
+ Opts,
+ [{erl_opts, NewErlOpts}, {erl_first_files, NewFirstFiles}, {src_dirs, ["test"]}]),
+ OutDir = filename:join([rebar_dir:base_dir(State), "test"]),
+ filelib:ensure_dir(filename:join([OutDir, "dummy.beam"])),
+ rebar_erlc_compiler:compile(NewOpts, rebar_state:dir(State), OutDir);
+ false -> ok
+ end.
+
handle_results(ok) -> ok;
handle_results(error) ->
{error, unknown_error};
@@ -321,7 +340,6 @@ eunit_opts(_State) ->
{application, undefined, "application", string, help(app)},
{cover, $c, "cover", boolean, help(cover)},
{dir, undefined, "dir", string, help(dir)},
- {error_on_warning, $e, "error_on_warning", boolean, help(error)},
{file, undefined, "file", string, help(file)},
{module, undefined, "module", string, help(module)},
{suite, undefined, "suite", string, help(module)},
@@ -330,7 +348,6 @@ eunit_opts(_State) ->
help(app) -> "Comma separated list of application test suites to run. Equivalent to `[{application, App}]`.";
help(cover) -> "Generate cover data. Defaults to false.";
help(dir) -> "Comma separated list of dirs to load tests from. Equivalent to `[{dir, Dir}]`.";
-help(error) -> "Error on invalid test specifications instead of warning.";
help(file) -> "Comma separated list of files to load tests from. Equivalent to `[{file, File}]`.";
help(module) -> "Comma separated list of modules to load tests from. Equivalent to `[{module, Module}]`.";
help(verbose) -> "Verbose output. Defaults to false.".