summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/rebar_prv_common_test.erl132
-rw-r--r--src/rebar_prv_eunit.erl196
2 files changed, 205 insertions, 123 deletions
diff --git a/src/rebar_prv_common_test.erl b/src/rebar_prv_common_test.erl
index 7a65dd5..05a1dc6 100644
--- a/src/rebar_prv_common_test.erl
+++ b/src/rebar_prv_common_test.erl
@@ -91,6 +91,10 @@ format_error({error_running_tests, Reason}) ->
format_error({error, Reason});
format_error({failures_running_tests, {Failed, AutoSkipped}}) ->
io_lib:format("Failures occured running tests: ~b", [Failed+AutoSkipped]);
+format_error({badconfig, {Msg, {Value, Key}}}) ->
+ io_lib:format(Msg, [Value, Key]);
+format_error({badconfig, Msg}) ->
+ io_lib:format(Msg, []);
format_error({multiple_errors, Errors}) ->
io_lib:format(lists:concat(["Error running tests:"] ++
lists:map(fun(Error) -> "~n " ++ Error end, Errors)), []).
@@ -151,24 +155,33 @@ split_string(String) ->
string:tokens(String, [$,]).
cfgopts(State) ->
- Opts = rebar_state:get(State, ct_opts, []),
- add_hooks(rebar_utils:filtermap(fun filter_opts/1, Opts), State).
-
-filter_opts({test_spec, _}) ->
- ?WARN("Test specs not supported", []),
- false;
-filter_opts({auto_compile, _}) ->
- ?WARN("Auto compile not supported", []),
- false;
-filter_opts({suite, Suite}) when is_integer(hd(Suite)) -> true;
-filter_opts({suite, Suite}) when is_atom(Suite) ->
- {true, {suite, atom_to_list(Suite)}};
-filter_opts({suite, Suites}) ->
- {true, {suite, lists:map(fun(S) when is_atom(S) -> atom_to_list(S);
- (S) when is_list(S) -> S
- end,
- Suites)}};
-filter_opts(_) -> true.
+ case rebar_state:get(State, ct_opts, []) of
+ Opts when is_list(Opts) ->
+ ensure_opts(add_hooks(Opts, State), []);
+ Wrong ->
+ %% probably a single non list term
+ ?PRV_ERROR({badconfig, {"Value `~p' of option `~p' must be a list", {Wrong, ct_opts}}})
+ end.
+
+ensure_opts([], Acc) -> lists:reverse(Acc);
+ensure_opts([{test_spec, _}|_Rest], _Acc) ->
+ ?PRV_ERROR({badconfig, "Test specs not supported"});
+ensure_opts([{auto_compile, _}|_Rest], _Acc) ->
+ ?PRV_ERROR({badconfig, "Auto compile not supported"});
+ensure_opts([{suite, Suite}|Rest], Acc) when is_integer(hd(Suite)) ->
+ ensure_opts(Rest, [{suite, Suite}|Acc]);
+ensure_opts([{suite, Suite}|Rest], Acc) when is_atom(Suite) ->
+ ensure_opts(Rest, [{suite, atom_to_list(Suite)}|Acc]);
+ensure_opts([{suite, Suites}|Rest], Acc) ->
+ NewSuites = {suite, lists:map(fun(S) when is_atom(S) -> atom_to_list(S);
+ (S) when is_list(S) -> S
+ end,
+ Suites)},
+ ensure_opts(Rest, [NewSuites|Acc]);
+ensure_opts([{K, V}|Rest], Acc) ->
+ ensure_opts(Rest, [{K, V}|Acc]);
+ensure_opts([V|_Rest], _Acc) ->
+ ?PRV_ERROR({badconfig, {"Member `~p' of option `~p' must be a 2-tuple", {V, ct_opts}}}).
add_hooks(Opts, State) ->
case {readable(State), lists:keyfind(ct_hooks, 1, Opts)} of
@@ -183,11 +196,12 @@ add_hooks(Opts, State) ->
lists:keyreplace(ct_hooks, 1, Opts, {ct_hooks, NewHooks})
end.
+select_tests(_, _, {error, _} = Error, _) -> Error;
+select_tests(_, _, _, {error, _} = Error) -> Error;
select_tests(State, ProjectApps, CmdOpts, CfgOpts) ->
- FixedOpts = lists:filter(fun({_, _}) -> true; (V) -> ?WARN("`~p` is not a valid option for `ct_opts`", [V]) end, CfgOpts),
Merged = lists:ukeymerge(1,
lists:ukeysort(1, CmdOpts),
- lists:ukeysort(1, FixedOpts)),
+ lists:ukeysort(1, CfgOpts)),
%% make sure `dir` and/or `suite` from command line go in as
%% a pair overriding both `dir` and `suite` from config if
%% they exist
@@ -228,7 +242,7 @@ application_dirs([App|Rest], Acc) ->
false -> application_dirs(Rest, Acc)
end.
-compile(State, {ok, Tests}) ->
+compile(State, {ok, _} = Tests) ->
%% inject `ct_first_files` and `ct_compile_opts` into the applications
%% to be compiled
case inject_ct_state(State, Tests) of
@@ -248,38 +262,68 @@ do_compile(State) ->
Error -> Error
end.
-inject_ct_state(State, Tests) ->
+inject_ct_state(State, {ok, Tests}) ->
Apps = rebar_state:project_apps(State),
- ModdedApps = lists:map(fun(App) ->
- NewOpts = inject(rebar_app_info:opts(App), State),
- rebar_app_info:opts(App, NewOpts)
- end, Apps),
- NewOpts = inject(rebar_state:opts(State), State),
- NewState = rebar_state:opts(State, NewOpts),
- test_dirs(NewState, ModdedApps, Tests).
-
-inject(Opts, State) ->
+ case inject_ct_state(State, Apps, []) of
+ {ok, {NewState, ModdedApps}} ->
+ test_dirs(NewState, ModdedApps, Tests);
+ {error, _} = Error -> Error
+ end;
+inject_ct_state(_State, Error) -> Error.
+
+inject_ct_state(State, [App|Rest], Acc) ->
+ case inject(rebar_app_info:opts(App), State) of
+ {error, _} = Error -> Error;
+ NewOpts ->
+ NewApp = rebar_app_info:opts(App, NewOpts),
+ inject_ct_state(State, Rest, [NewApp|Acc])
+ end;
+inject_ct_state(State, [], Acc) ->
+ case inject(rebar_state:opts(State), State) of
+ {error, _} = Error -> Error;
+ NewOpts -> {ok, {rebar_state:opts(State, NewOpts), lists:reverse(Acc)}}
+ end.
+
+opts(Opts, Key, Default) ->
+ case rebar_opts:get(Opts, Key, Default) of
+ Vs when is_list(Vs) -> Vs;
+ Wrong ->
+ ?PRV_ERROR({badconfig, {"Value `~p' of option `~p' must be a list", {Wrong, Key}}})
+ end.
+
+inject(Opts, State) -> erl_opts(Opts, State).
+
+erl_opts(Opts, State) ->
%% append `ct_compile_opts` to app defined `erl_opts`
- ErlOpts = rebar_opts:get(Opts, erl_opts, []),
- CTOpts = rebar_state:get(State, ct_compile_opts, []),
- NewErlOpts = add_transforms(CTOpts, State) ++ ErlOpts,
+ ErlOpts = opts(Opts, erl_opts, []),
+ CTOpts = opts(Opts, ct_compile_opts, []),
+ case add_transforms(append(CTOpts, ErlOpts), State) of
+ {error, Error} -> {error, Error};
+ NewErlOpts -> first_files(rebar_opts:set(Opts, erl_opts, NewErlOpts))
+ end.
+
+first_files(Opts) ->
%% append `ct_first_files` to app defined `erl_first_files`
- FirstFiles = rebar_opts:get(Opts, erl_first_files, []),
- CTFirstFiles = rebar_state:get(State, ct_first_files, []),
- NewFirstFiles = CTFirstFiles ++ FirstFiles,
- %% insert the new keys into the opts
- lists:foldl(fun({K, V}, NewOpts) -> rebar_opts:set(NewOpts, K, V) end,
- Opts,
- [{erl_opts, NewErlOpts}, {erl_first_files, NewFirstFiles}]).
-
-add_transforms(CTOpts, State) ->
+ FirstFiles = opts(Opts, erl_first_files, []),
+ CTFirstFiles = opts(Opts, ct_first_files, []),
+ case append(CTFirstFiles, FirstFiles) of
+ {error, _} = Error -> Error;
+ NewFirstFiles -> rebar_opts:set(Opts, erl_first_files, NewFirstFiles)
+ end.
+
+append({error, _} = Error, _) -> Error;
+append(_, {error, _} = Error) -> Error;
+append(A, B) -> A ++ B.
+
+add_transforms(CTOpts, State) when is_list(CTOpts) ->
case readable(State) of
true ->
ReadableTransform = [{parse_transform, cth_readable_transform}],
(CTOpts -- ReadableTransform) ++ ReadableTransform;
false ->
CTOpts
- end.
+ end;
+add_transforms({error, _} = Error, _State) -> Error.
readable(State) ->
{RawOpts, _} = rebar_state:command_parsed_args(State),
diff --git a/src/rebar_prv_eunit.erl b/src/rebar_prv_eunit.erl
index 1884f02..9af2965 100644
--- a/src/rebar_prv_eunit.erl
+++ b/src/rebar_prv_eunit.erl
@@ -9,7 +9,7 @@
do/1,
format_error/1]).
%% exported solely for tests
--export([compile/2, prepare_tests/1, eunit_opts/1, validate_tests/2]).
+-export([prepare_tests/1, eunit_opts/1, validate_tests/2]).
-include("rebar.hrl").
-include_lib("providers/include/providers.hrl").
@@ -39,10 +39,12 @@ init(State) ->
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
Tests = prepare_tests(State),
- case compile(State, Tests) of
+ %% inject `eunit_first_files`, `eunit_compile_opts` and any
+ %% directories required by tests into the applications
+ NewState = inject_eunit_state(State, Tests),
+ case compile(NewState) of
%% successfully compiled apps
{ok, S} -> do(S, Tests);
- %% this should look like a compiler error, not an eunit error
Error -> Error
end.
@@ -95,6 +97,8 @@ format_error({error_running_tests, Reason}) ->
format_error({eunit_test_errors, Errors}) ->
io_lib:format(lists:concat(["Error Running EUnit Tests:"] ++
lists:map(fun(Error) -> "~n " ++ Error end, Errors)), []);
+format_error({badconfig, {Msg, {Value, Key}}}) ->
+ io_lib:format(Msg, [Value, Key]);
format_error({error, Error}) ->
format_error({error_running_tests, Error}).
@@ -102,85 +106,10 @@ format_error({error, Error}) ->
%% Internal functions
%% ===================================================================
-compile(State, {ok, Tests}) ->
- %% inject `eunit_first_files`, `eunit_compile_opts` and any
- %% directories required by tests into the applications
- NewState = inject_eunit_state(State, Tests),
-
- case rebar_prv_compile:do(NewState) of
- %% successfully compiled apps
- {ok, S} ->
- ok = maybe_cover_compile(S),
- {ok, S};
- %% this should look like a compiler error, not an eunit error
- Error -> Error
- end;
-%% maybe compile even in the face of errors?
-compile(_State, Error) -> Error.
-
-inject_eunit_state(State, Tests) ->
- Apps = rebar_state:project_apps(State),
- ModdedApps = lists:map(fun(App) ->
- NewOpts = inject(rebar_app_info:opts(App), State),
- rebar_app_info:opts(App, NewOpts)
- end, Apps),
- NewOpts = inject(rebar_state:opts(State), State),
- NewState = rebar_state:opts(State, NewOpts),
- test_dirs(NewState, ModdedApps, Tests).
-
-inject(Opts, State) ->
- %% append `eunit_compile_opts` to app defined `erl_opts`
- ErlOpts = rebar_opts:get(Opts, erl_opts, []),
- EUnitOpts = rebar_state:get(State, eunit_compile_opts, []),
- NewErlOpts = EUnitOpts ++ ErlOpts,
- %% append `eunit_first_files` to app defined `erl_first_files`
- FirstFiles = rebar_opts:get(Opts, erl_first_files, []),
- EUnitFirstFiles = rebar_state:get(State, eunit_first_files, []),
- NewFirstFiles = EUnitFirstFiles ++ FirstFiles,
- %% insert the new keys into the opts
- lists:foldl(fun({K, V}, NewOpts) -> rebar_opts:set(NewOpts, K, V) end,
- Opts,
- [{erl_opts, NewErlOpts}, {erl_first_files, NewFirstFiles}]).
-
-test_dirs(State, Apps, []) -> rebar_state:project_apps(State, Apps);
-test_dirs(State, Apps, [{dir, Dir}|Rest]) ->
- %% insert `Dir` into an app if relative, or the base state if not
- %% app relative but relative to the root or not at all if outside
- %% project scope
- {NewState, NewApps} = maybe_inject_test_dir(State, [], Apps, Dir),
- test_dirs(NewState, NewApps, Rest);
-test_dirs(State, Apps, [{file, File}|Rest]) ->
- Dir = filename:dirname(File),
- {NewState, NewApps} = maybe_inject_test_dir(State, [], Apps, Dir),
- test_dirs(NewState, NewApps, Rest);
-test_dirs(State, Apps, [_|Rest]) -> test_dirs(State, Apps, Rest).
-
-maybe_inject_test_dir(State, AppAcc, [App|Rest], Dir) ->
- case rebar_file_utils:path_from_ancestor(Dir, rebar_app_info:dir(App)) of
- {ok, Path} ->
- Opts = inject_test_dir(rebar_app_info:opts(App), Path),
- {State, AppAcc ++ [rebar_app_info:opts(App, Opts)] ++ Rest};
- {error, badparent} ->
- maybe_inject_test_dir(State, AppAcc ++ [App], Rest, Dir)
- end;
-maybe_inject_test_dir(State, AppAcc, [], Dir) ->
- case rebar_file_utils:path_from_ancestor(Dir, rebar_state:dir(State)) of
- {ok, Path} ->
- Opts = inject_test_dir(rebar_state:opts(State), Path),
- {rebar_state:opts(State, Opts), AppAcc};
- {error, badparent} ->
- {State, AppAcc}
- end.
-
-inject_test_dir(Opts, Dir) ->
- %% append specified test targets to app defined `extra_src_dirs`
- ExtraSrcDirs = rebar_opts:get(Opts, extra_src_dirs, []),
- rebar_opts:set(Opts, extra_src_dirs, ExtraSrcDirs ++ [Dir]).
-
prepare_tests(State) ->
%% parse and translate command line tests
CmdTests = resolve_tests(State),
- CfgTests = rebar_state:get(State, eunit_tests, []),
+ CfgTests = cfg_tests(State),
ProjectApps = rebar_state:project_apps(State),
%% prioritize tests to run first trying any command line specified
%% tests falling back to tests specified in the config file finally
@@ -208,6 +137,16 @@ resolve(Flag, EUnitKey, RawOpts) ->
normalize(Key, Value) when Key == dir; Key == file -> {Key, Value};
normalize(Key, Value) -> {Key, list_to_atom(Value)}.
+cfg_tests(State) ->
+ case rebar_state:get(State, eunit_tests, []) of
+ Tests when is_list(Tests) -> Tests;
+ Wrong ->
+ %% probably a single non list term
+ ?PRV_ERROR({badconfig, {"Value `~p' of option `~p' must be a list", {Wrong, eunit_tests}}})
+ end.
+
+select_tests(_State, _ProjectApps, {error, _} = Error, _) -> Error;
+select_tests(_State, _ProjectApps, _, {error, _} = Error) -> Error;
select_tests(State, ProjectApps, [], []) -> {ok, default_tests(State, ProjectApps)};
select_tests(_State, _ProjectApps, [], Tests) -> {ok, Tests};
select_tests(_State, _ProjectApps, Tests, _) -> {ok, Tests}.
@@ -229,6 +168,105 @@ set_apps([App|Rest], Acc) ->
AppName = list_to_atom(binary_to_list(rebar_app_info:name(App))),
set_apps(Rest, [{application, AppName}|Acc]).
+inject_eunit_state(State, {ok, Tests}) ->
+ Apps = rebar_state:project_apps(State),
+ case inject_eunit_state(State, Apps, []) of
+ {ok, {NewState, ModdedApps}} ->
+ test_dirs(NewState, ModdedApps, Tests);
+ {error, _} = Error -> Error
+ end;
+inject_eunit_state(_State, Error) -> Error.
+
+inject_eunit_state(State, [App|Rest], Acc) ->
+ case inject(rebar_app_info:opts(App)) of
+ {error, _} = Error -> Error;
+ NewOpts ->
+ NewApp = rebar_app_info:opts(App, NewOpts),
+ inject_eunit_state(State, Rest, [NewApp|Acc])
+ end;
+inject_eunit_state(State, [], Acc) ->
+ case inject(rebar_state:opts(State)) of
+ {error, _} = Error -> Error;
+ NewOpts -> {ok, {rebar_state:opts(State, NewOpts), lists:reverse(Acc)}}
+ end.
+
+opts(Opts, Key, Default) ->
+ case rebar_opts:get(Opts, Key, Default) of
+ Vs when is_list(Vs) -> Vs;
+ Wrong ->
+ ?PRV_ERROR({badconfig, {"Value `~p' of option `~p' must be a list", {Wrong, Key}}})
+ end.
+
+inject(Opts) -> erl_opts(Opts).
+
+erl_opts(Opts) ->
+ %% append `eunit_compile_opts` to app defined `erl_opts`
+ ErlOpts = opts(Opts, erl_opts, []),
+ EUnitOpts = opts(Opts, eunit_compile_opts, []),
+ case append(EUnitOpts, ErlOpts) of
+ {error, _} = Error -> Error;
+ NewErlOpts -> first_files(rebar_opts:set(Opts, erl_opts, NewErlOpts))
+ end.
+
+first_files(Opts) ->
+ %% append `eunit_first_files` to app defined `erl_first_files`
+ FirstFiles = opts(Opts, erl_first_files, []),
+ EUnitFirstFiles = opts(Opts, eunit_first_files, []),
+ case append(EUnitFirstFiles, FirstFiles) of
+ {error, _} = Error -> Error;
+ NewFirstFiles -> rebar_opts:set(Opts, erl_first_files, NewFirstFiles)
+ end.
+
+append({error, _} = Error, _) -> Error;
+append(_, {error, _} = Error) -> Error;
+append(A, B) -> A ++ B.
+
+test_dirs(State, Apps, []) -> rebar_state:project_apps(State, Apps);
+test_dirs(State, Apps, [{dir, Dir}|Rest]) ->
+ %% insert `Dir` into an app if relative, or the base state if not
+ %% app relative but relative to the root or not at all if outside
+ %% project scope
+ {NewState, NewApps} = maybe_inject_test_dir(State, [], Apps, Dir),
+ test_dirs(NewState, NewApps, Rest);
+test_dirs(State, Apps, [{file, File}|Rest]) ->
+ Dir = filename:dirname(File),
+ {NewState, NewApps} = maybe_inject_test_dir(State, [], Apps, Dir),
+ test_dirs(NewState, NewApps, Rest);
+test_dirs(State, Apps, [_|Rest]) -> test_dirs(State, Apps, Rest).
+
+maybe_inject_test_dir(State, AppAcc, [App|Rest], Dir) ->
+ case rebar_file_utils:path_from_ancestor(Dir, rebar_app_info:dir(App)) of
+ {ok, Path} ->
+ Opts = inject_test_dir(rebar_app_info:opts(App), Path),
+ {State, AppAcc ++ [rebar_app_info:opts(App, Opts)] ++ Rest};
+ {error, badparent} ->
+ maybe_inject_test_dir(State, AppAcc ++ [App], Rest, Dir)
+ end;
+maybe_inject_test_dir(State, AppAcc, [], Dir) ->
+ case rebar_file_utils:path_from_ancestor(Dir, rebar_state:dir(State)) of
+ {ok, Path} ->
+ Opts = inject_test_dir(rebar_state:opts(State), Path),
+ {rebar_state:opts(State, Opts), AppAcc};
+ {error, badparent} ->
+ {State, AppAcc}
+ end.
+
+inject_test_dir(Opts, Dir) ->
+ %% append specified test targets to app defined `extra_src_dirs`
+ ExtraSrcDirs = rebar_dir:extra_src_dirs(Opts),
+ rebar_opts:set(Opts, extra_src_dirs, ExtraSrcDirs ++ [Dir]).
+
+compile({error, _} = Error) -> Error;
+compile(State) ->
+ case rebar_prv_compile:do(State) of
+ %% successfully compiled apps
+ {ok, S} ->
+ ok = maybe_cover_compile(S),
+ {ok, S};
+ %% this should look like a compiler error, not an eunit error
+ Error -> Error
+ end.
+
validate_tests(State, {ok, Tests}) ->
gather_tests(fun(Elem) -> validate(State, Elem) end, Tests, []);
validate_tests(_State, Error) -> Error.