summaryrefslogtreecommitdiff
path: root/src/rebar_prv_common_test.erl
diff options
context:
space:
mode:
authoralisdair sullivan <alisdairsullivan@yahoo.ca>2015-03-10 09:31:04 -0700
committeralisdair sullivan <alisdairsullivan@yahoo.ca>2015-04-03 14:39:49 -0700
commitb77d3e5083e281101a2e7820388bb7aa51143ddf (patch)
tree2aa6da52aa3deaab63f5b97bcbe18c97cbc8417d /src/rebar_prv_common_test.erl
parent9c234dddeb964ee2fa6ec15268814aed8d446afd (diff)
modify `ct` provider to copy selected directories and compile them
alongside their source (ie, if `some_tests` is a directory that contains test suites beams resulting from compiling them will be placed in `some_tests` in the appropriate `_build` directory
Diffstat (limited to 'src/rebar_prv_common_test.erl')
-rw-r--r--src/rebar_prv_common_test.erl602
1 files changed, 339 insertions, 263 deletions
diff --git a/src/rebar_prv_common_test.erl b/src/rebar_prv_common_test.erl
index f4085c5..97f0637 100644
--- a/src/rebar_prv_common_test.erl
+++ b/src/rebar_prv_common_test.erl
@@ -2,12 +2,13 @@
%% ex: ts=4 sw=4 et
-module(rebar_prv_common_test).
-
-behaviour(provider).
-export([init/1,
do/1,
format_error/1]).
+%% exported for test purposes, consider private
+-export([setup_ct/1]).
-include("rebar.hrl").
-include_lib("providers/include/providers.hrl").
@@ -37,54 +38,352 @@ init(State) ->
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
?INFO("Running Common Test suites...", []),
- {RawOpts, _} = rebar_state:command_parsed_args(State),
- Opts = transform_opts(RawOpts),
- TestApps = filter_checkouts(rebar_state:project_apps(State)),
- ok = create_dirs(Opts),
- InDirs = in_dirs(State, RawOpts),
- ok = compile_tests(State, TestApps, InDirs),
- case resolve_ct_opts(State, TestApps, Opts) of
- {ok, CTOpts} ->
- run_test(State, RawOpts, CTOpts);
- {error, Reason} ->
- ?PRV_ERROR(Reason)
- end.
-
-run_test(State, RawOpts, CTOpts) ->
- ok = maybe_cover_compile(State, RawOpts),
- Verbose = proplists:get_value(verbose, RawOpts, false),
- Result = run_test(CTOpts, Verbose),
- ok = rebar_prv_cover:maybe_write_coverdata(State, ?PROVIDER),
- case Result of
- {error, Reason} ->
- {error, {?MODULE, Reason}};
- ok ->
- {ok, State}
+ try
+ case setup_ct(State) of
+ {error, {no_tests_specified, Opts}} ->
+ ?WARN("No tests specified in opts: ~p", [Opts]),
+ {ok, State};
+ Opts ->
+ Opts1 = setup_logdir(State, Opts),
+ ?DEBUG("common test opts: ~p", [Opts1]),
+ run_test(State, Opts1)
+ end
+ catch error:Reason -> ?PRV_ERROR(Reason)
end.
-spec format_error(any()) -> iolist().
+format_error({multiple_dirs_and_suites, Opts}) ->
+ io_lib:format("Multiple dirs declared alongside suite in opts: ~p", [Opts]);
+format_error({bad_dir_or_suite, Opts}) ->
+ io_lib:format("Bad value for dir or suite in opts: ~p", [Opts]);
format_error({failures_running_tests, {Failed, AutoSkipped}}) ->
io_lib:format("Failures occured running tests: ~b", [Failed+AutoSkipped]);
format_error({error_running_tests, Reason}) ->
io_lib:format("Error running tests: ~p", [Reason]);
-format_error({error_processing_options, Reason}) ->
- io_lib:format("Error processing options: ~p", [Reason]).
+format_error({error, Reason}) ->
+ io_lib:format("Unknown error: ~p", [Reason]).
+
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+run_test(State, Opts) ->
+ {RawOpts, _} = rebar_state:command_parsed_args(State),
+ Result = case proplists:get_value(verbose, RawOpts, false) of
+ true -> run_test(Opts);
+ false -> run_test_quiet(Opts)
+ end,
+ ok = rebar_prv_cover:maybe_write_coverdata(State, ?PROVIDER),
+ case Result of
+ ok -> {ok, State};
+ Error -> Error
+ end.
+
+run_test(Opts) -> handle_results(ct:run_test(Opts)).
-run_test(CTOpts, true) ->
- handle_results(ct:run_test(CTOpts));
-run_test(CTOpts, false) ->
+run_test_quiet(Opts) ->
Pid = self(),
- LogDir = proplists:get_value(logdir, CTOpts),
+ LogDir = proplists:get_value(logdir, Opts),
erlang:spawn_monitor(fun() ->
{ok, F} = file:open(filename:join([LogDir, "ct.latest.log"]),
[write]),
true = group_leader(F, self()),
- Pid ! ct:run_test(CTOpts)
+ Pid ! ct:run_test(Opts)
end),
- receive Result -> handle_quiet_results(CTOpts, Result) end.
+ receive Result -> handle_quiet_results(Opts, Result) end.
+
+handle_results(Results) when is_list(Results) ->
+ Result = lists:foldl(fun sum_results/2, {0, 0, {0,0}}, Results),
+ handle_results(Result);
+handle_results({_, Failed, {_, AutoSkipped}})
+ when Failed > 0 orelse AutoSkipped > 0 ->
+ ?PRV_ERROR({failures_running_tests, {Failed, AutoSkipped}});
+handle_results({error, Reason}) ->
+ ?PRV_ERROR({error_running_tests, Reason});
+handle_results(_) ->
+ ok.
+
+sum_results({Passed, Failed, {UserSkipped, AutoSkipped}},
+ {Passed2, Failed2, {UserSkipped2, AutoSkipped2}}) ->
+ {Passed+Passed2, Failed+Failed2,
+ {UserSkipped+UserSkipped2, AutoSkipped+AutoSkipped2}}.
+
+handle_quiet_results(_, {error, _} = Result) ->
+ handle_results(Result);
+handle_quiet_results(_, {'DOWN', _, _, _, Reason}) ->
+ handle_results(?PRV_ERROR(Reason));
+handle_quiet_results(CTOpts, Results) when is_list(Results) ->
+ _ = [format_result(Result) || Result <- Results],
+ case handle_results(Results) of
+ ?PRV_ERROR({failures_running_tests, _}) = Error ->
+ LogDir = proplists:get_value(logdir, CTOpts),
+ Index = filename:join([LogDir, "index.html"]),
+ ?CONSOLE("Results written to ~p.", [Index]),
+ Error;
+ Other ->
+ Other
+ end;
+handle_quiet_results(CTOpts, Result) ->
+ handle_quiet_results(CTOpts, [Result]).
+
+format_result({Passed, 0, {0, 0}}) ->
+ ?CONSOLE("All ~p tests passed.", [Passed]);
+format_result({Passed, Failed, Skipped}) ->
+ Format = [format_failed(Failed), format_skipped(Skipped),
+ format_passed(Passed)],
+ ?CONSOLE("~s", [Format]).
+
+format_failed(0) ->
+ [];
+format_failed(Failed) ->
+ io_lib:format("Failed ~p tests. ", [Failed]).
+
+format_passed(Passed) ->
+ io_lib:format("Passed ~p tests. ", [Passed]).
+
+format_skipped({0, 0}) ->
+ [];
+format_skipped({User, Auto}) ->
+ io_lib:format("Skipped ~p (~p, ~p) tests. ", [User+Auto, User, Auto]).
+
+test_state(State) ->
+ TestOpts = case rebar_state:get(State, ct_compile_opts, []) of
+ [] -> [];
+ Opts -> [{erl_opts, Opts}]
+ end,
+ [first_files(State)|TestOpts].
+
+first_files(State) ->
+ CTFirst = rebar_state:get(State, ct_first_files, []),
+ {erl_first_files, CTFirst}.
+
+setup_ct(State) ->
+ Opts = resolve_ct_opts(State),
+ Opts1 = discover_tests(State, Opts),
+ copy_and_compile_tests(State, Opts1).
+
+resolve_ct_opts(State) ->
+ {RawOpts, _} = rebar_state:command_parsed_args(State),
+ CmdOpts = transform_opts(RawOpts),
+ CfgOpts = rebar_state:get(State, ct_opts, []),
+ Merged = lists:ukeymerge(1,
+ lists:ukeysort(1, CmdOpts),
+ 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
+ case {proplists:get_value(suite, CmdOpts), proplists:get_value(dir, CmdOpts)} of
+ {undefined, undefined} -> Merged;
+ {_Suite, undefined} -> lists:keydelete(dir, 1, Merged);
+ {undefined, _Dir} -> lists:keydelete(suite, 1, Merged);
+ {_Suite, _Dir} -> Merged
+ end.
+
+discover_tests(State, Opts) ->
+ case proplists:get_value(spec, Opts) of
+ undefined -> discover_dirs_and_suites(State, Opts);
+ TestSpec -> discover_testspec(TestSpec, Opts)
+ end.
+
+discover_dirs_and_suites(State, Opts) ->
+ case {proplists:get_value(dir, Opts), proplists:get_value(suite, Opts)} of
+ %% no dirs or suites defined, try using `$APP/test` and `$ROOT/test`
+ %% as suites
+ {undefined, undefined} -> test_dirs(State, Opts);
+ %% no dirs defined
+ {undefined, _} -> Opts;
+ %% no suites defined
+ {_, undefined} -> Opts;
+ %% a single dir defined, this is ok
+ {Dirs, Suites} when is_integer(hd(Dirs)), is_list(Suites) -> Opts;
+ %% still a single dir defined, adjust to make acceptable to ct
+ {[Dir], Suites} when is_integer(hd(Dir)), is_list(Suites) ->
+ [{dir, Dir}|lists:keydelete(dir, 1, Opts)];
+ %% multiple dirs and suites, error now to simplify later steps
+ {_, _} -> erlang:error({multiple_dirs_and_suites, Opts})
+ end.
+
+discover_testspec(_TestSpec, Opts) ->
+ lists:keydelete(auto_compile, 1, Opts).
+
+copy_and_compile_tests(State, Opts) ->
+ %% possibly enable cover
+ {RawOpts, _} = rebar_state:command_parsed_args(State),
+ State1 = case proplists:get_value(cover, RawOpts, false) of
+ true -> rebar_state:set(State, cover_enabled, true);
+ false -> State
+ end,
+ copy_and_compile_test_suites(State1, Opts).
+
+copy_and_compile_test_suites(State, Opts) ->
+ case proplists:get_value(suite, Opts) of
+ %% no suites, try dirs
+ undefined -> copy_and_compile_test_dirs(State, Opts);
+ Suites ->
+ Dir = proplists:get_value(dir, Opts, undefined),
+ AllSuites = join(Dir, Suites),
+ Dirs = find_suite_dirs(AllSuites),
+ lists:foreach(fun(S) ->
+ NewPath = copy(State, S),
+ compile_dir(State, NewPath)
+ end, Dirs),
+ NewSuites = lists:map(fun(S) -> retarget_path(State, S) end, AllSuites),
+ [{suite, NewSuites}|lists:keydelete(suite, 1, Opts)]
+ end.
+
+copy_and_compile_test_dirs(State, Opts) ->
+ case proplists:get_value(dir, Opts) of
+ undefined -> {error, {no_tests_specified, Opts}};
+ %% dir is a single directory
+ Dir when is_list(Dir), is_integer(hd(Dir)) ->
+ NewPath = copy(State, Dir),
+ [{dir, compile_dir(State, NewPath)}|lists:keydelete(dir, 1, Opts)];
+ %% dir is a list of directories
+ Dirs when is_list(Dirs) ->
+ NewDirs = lists:map(fun(Dir) ->
+ NewPath = copy(State, Dir),
+ compile_dir(State, NewPath)
+ end, Dirs),
+ [{dir, NewDirs}|lists:keydelete(dir, 1, Opts)]
+ end.
+
+join(undefined, Suites) -> Suites;
+join(Dir, Suites) when is_list(Dir), is_integer(hd(Dir)) ->
+ lists:map(fun(S) -> filename:join([Dir, S]) end, Suites);
+%% multiple dirs or a bad dir argument, try to continue anyways
+join(_, Suites) -> Suites.
+
+find_suite_dirs(Suites) ->
+ AllDirs = lists:map(fun(S) -> filename:dirname(filename:absname(S)) end, Suites),
+ %% eliminate duplicates
+ lists:usort(AllDirs).
+
+copy(State, Target) ->
+ case retarget_path(State, Target) of
+ %% directory lies outside of our project's file structure so
+ %% don't copy it
+ Target -> Target;
+ NewTarget ->
+ %% unlink the directory if it's a symlink
+ case ec_file:is_symlink(NewTarget) of
+ true -> ok = ec_file:remove(NewTarget);
+ false -> ok
+ end,
+ ok = ec_file:copy(Target, NewTarget, [recursive]),
+ NewTarget
+ end.
+
+compile_dir(State, Dir) ->
+ NewState = replace_src_dirs(State, [Dir]),
+ ok = rebar_erlc_compiler:compile(NewState, rebar_dir:base_dir(State), Dir),
+ ok = maybe_cover_compile(State, Dir),
+ Dir.
+
+retarget_path(State, Path) ->
+ ProjectApps = rebar_state:project_apps(State),
+ retarget_path(State, Path, ProjectApps).
+
+%% not relative to any apps in project, check to see it's relative to
+%% project root
+retarget_path(State, Path, []) ->
+ case relative_path(reduce_path(Path), rebar_state:dir(State)) of
+ {ok, NewPath} -> filename:join([rebar_dir:base_dir(State), NewPath]);
+ %% not relative to project root, don't modify
+ {error, not_relative} -> Path
+ end;
+%% relative to current app, retarget to the same dir relative to
+%% the app's out_dir
+retarget_path(State, Path, [App|Rest]) ->
+ case relative_path(reduce_path(Path), rebar_app_info:dir(App)) of
+ {ok, NewPath} -> filename:join([rebar_app_info:out_dir(App), NewPath]);
+ {error, not_relative} -> retarget_path(State, Path, Rest)
+ end.
+
+relative_path(Target, To) ->
+ relative_path1(filename:split(filename:absname(Target)),
+ filename:split(filename:absname(To))).
+
+relative_path1([Part|Target], [Part|To]) -> relative_path1(Target, To);
+relative_path1([], []) -> {ok, ""};
+relative_path1(Target, []) -> {ok, filename:join(Target)};
+relative_path1(_, _) -> {error, not_relative}.
+
+reduce_path(Dir) -> reduce_path([], filename:split(filename:absname(Dir))).
+
+reduce_path([], []) -> filename:nativename("/");
+reduce_path(Acc, []) -> filename:join(lists:reverse(Acc));
+reduce_path(Acc, ["."|Rest]) -> reduce_path(Acc, Rest);
+reduce_path([_|Acc], [".."|Rest]) -> reduce_path(Acc, Rest);
+reduce_path([], [".."|Rest]) -> reduce_path([], Rest);
+reduce_path(Acc, [Component|Rest]) -> reduce_path([Component|Acc], Rest).
+
+replace_src_dirs(State, Dirs) ->
+ %% replace any `src_dirs` with the test dirs
+ ErlOpts = rebar_state:get(State, erl_opts, []),
+ StrippedOpts = lists:keydelete(src_dirs, 1, ErlOpts),
+ rebar_state:set(State, erl_opts, [{src_dirs, Dirs}|StrippedOpts]).
+
+test_dirs(State, Opts) ->
+ BareTest = filename:join([rebar_state:dir(State), "test"]),
+ F = fun(App) -> rebar_app_info:dir(App) == rebar_state:dir(State) end,
+ TestApps = project_apps(State),
+ case filelib:is_dir(BareTest) andalso not lists:any(F, TestApps) of
+ %% `test` dir at root of project is already scheduled to be
+ %% included or `test` does not exist
+ false -> application_dirs(TestApps, Opts, []);
+ %% need to add `test` dir at root to dirs to be included
+ true -> application_dirs(TestApps, Opts, [BareTest])
+ 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.
+
+application_dirs([], Opts, []) -> Opts;
+application_dirs([], Opts, [Acc]) -> [{dir, Acc}|Opts];
+application_dirs([], Opts, Acc) -> [{dir, lists:reverse(Acc)}|Opts];
+application_dirs([App|Rest], Opts, Acc) ->
+ TestDir = filename:join([rebar_app_info:dir(App), "test"]),
+ case filelib:is_dir(TestDir) of
+ true -> application_dirs(Rest, Opts, [TestDir|Acc]);
+ false -> application_dirs(Rest, Opts, Acc)
+ end.
+
+setup_logdir(State, Opts) ->
+ Logdir = case proplists:get_value(logdir, Opts) of
+ undefined -> filename:join([rebar_dir:base_dir(State), "logs"]);
+ Dir -> Dir
+ end,
+ ensure_dir([Logdir]),
+ [{logdir, Logdir}|lists:keydelete(logdir, 1, Opts)].
+
+ensure_dir([]) -> ok;
+ensure_dir([Dir|Rest]) ->
+ case ec_file:is_dir(Dir) of
+ true ->
+ ok;
+ false ->
+ ec_file:mkdir_path(Dir)
+ end,
+ ensure_dir(Rest).
+
+maybe_cover_compile(State, Dir) ->
+ {Opts, _} = rebar_state:command_parsed_args(State),
+ 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, [Dir]).
ct_opts(_State) ->
- DefaultLogsDir = filename:join([rebar_dir:get_cwd(), "_build", "logs"]),
[{dir, undefined, "dir", string, help(dir)}, %% comma-seperated list
{suite, undefined, "suite", string, help(suite)}, %% comma-seperated list
{group, undefined, "group", string, help(group)}, %% comma-seperated list
@@ -95,13 +394,13 @@ ct_opts(_State) ->
{config, undefined, "config", string, help(config)}, %% comma-seperated list
{userconfig, undefined, "userconfig", string, help(userconfig)}, %% [{CallbackMod, CfgStrings}] | {CallbackMod, CfgStrings}
{allow_user_terms, undefined, "allow_user_terms", boolean, help(allow_user_terms)}, %% Bool
- {logdir, undefined, "logdir", {string, DefaultLogsDir}, help(logdir)}, %% dir
+ {logdir, undefined, "logdir", string, help(logdir)}, %% dir
{logopts, undefined, "logopts", string, help(logopts)}, %% enum, no_nl | no_src
{verbosity, undefined, "verbosity", string, help(verbosity)}, %% Integer OR [{Category, VLevel}]
{silent_connections, undefined, "silent_connections", string,
help(silent_connections)}, % all OR %% comma-seperated list
{stylesheet, undefined, "stylesheet", string, help(stylesheet)}, %% file
- {cover, $c, "cover", boolean, help(cover)},
+ {cover, $c, "cover", {boolean, false}, help(cover)},
{cover_spec, undefined, "cover_spec", string, help(cover_spec)}, %% file
{cover_stop, undefined, "cover_stop", boolean, help(cover_stop)}, %% Boolean
{event_handler, undefined, "event_handler", string, help(event_handler)}, %% EH | [EH] WHERE EH atom() | {atom(), InitArgs} | {[atom()], InitArgs}
@@ -118,6 +417,7 @@ ct_opts(_State) ->
{force_stop, undefined, "force_stop", string, help(force_stop)}, % enum: skip_rest, bool
{basic_html, undefined, "basic_html", boolean, help(basic_html)}, %% Booloean
{ct_hooks, undefined, "ct_hooks", string, help(ct_hooks)}, %% List: [CTHModule | {CTHModule, CTHInitArgs}] where CTHModule is atom CthInitArgs is term
+ {auto_compile, undefined, "auto_compile", {boolean, false}, help(auto_compile)},
{verbose, $v, "verbose", boolean, help(verbose)}
].
@@ -131,42 +431,26 @@ help(testcase) ->
"List of test cases to run";
help(spec) ->
"List of test specs to run";
-help(join_specs) ->
- ""; %% ??
help(label) ->
"Test label";
help(config) ->
"List of config files";
-help(allow_user_terms) ->
- ""; %% ??
help(logdir) ->
"Log folder";
-help(logopts) ->
- ""; %% ??
help(verbosity) ->
"Verbosity";
-help(silent_connections) ->
- ""; %% ??
help(stylesheet) ->
"Stylesheet to use for test results";
help(cover) ->
"Generate cover data";
help(cover_spec) ->
"Cover file to use";
-help(cover_stop) ->
- ""; %% ??
help(event_handler) ->
"Event handlers to attach to the runner";
help(include) ->
"Include folder";
help(abort_if_missing_suites) ->
"Abort if suites are missing";
-help(multiply_timetraps) ->
- ""; %% ??
-help(scale_timetraps) ->
- ""; %% ??
-help(create_priv_dir) ->
- ""; %% ??
help(repeat) ->
"How often to repeat tests";
help(duration) ->
@@ -177,12 +461,10 @@ help(force_stop) ->
"Force stop after time";
help(basic_html) ->
"Show basic HTML";
-help(ct_hooks) ->
- "";
-help(userconfig) ->
- "";
help(verbose) ->
- "Verbose output".
+ "Verbose output";
+help(_) ->
+ "".
transform_opts(Opts) ->
transform_opts(Opts, []).
@@ -255,210 +537,4 @@ parse_term(String) ->
Terms;
Term ->
Term
- end.
-
-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.
-
-create_dirs(Opts) ->
- LogDir = proplists:get_value(logdir, Opts),
- ensure_dir([LogDir]),
- ok.
-
-ensure_dir([]) -> ok;
-ensure_dir([Dir|Rest]) ->
- case ec_file:is_dir(Dir) of
- true ->
- ok;
- false ->
- ec_file:mkdir_path(Dir)
- end,
- ensure_dir(Rest).
-
-in_dirs(State, Opts) ->
- %% preserve the override nature of command line opts by only checking
- %% `rebar.config` defined additional test dirs if none are defined via
- %% command line flag
- case proplists:get_value(dir, Opts) of
- undefined ->
- CTOpts = rebar_state:get(State, ct_opts, []),
- proplists:get_value(dir, CTOpts, []);
- Dirs -> split_string(Dirs)
- 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 -> [BareEbin|application_dirs(TestApps, [])]
- end.
-
-application_dirs([], Acc) -> lists:reverse(Acc);
-application_dirs([App|Rest], Acc) ->
- application_dirs(Rest, [rebar_app_info:ebin_dir(App)|Acc]).
-
-test_state(State) ->
- TestOpts = case rebar_state:get(State, ct_compile_opts, []) of
- [] -> [];
- Opts -> [{erl_opts, Opts}]
- end,
- [first_files(State)|TestOpts].
-
-first_files(State) ->
- CTFirst = rebar_state:get(State, ct_first_files, []),
- {erl_first_files, CTFirst}.
-
-resolve_ct_opts(State, TestApps, CmdLineOpts) ->
- CTOpts = rebar_state:get(State, ct_opts, []),
- Opts = lists:ukeymerge(1,
- lists:ukeysort(1, CmdLineOpts),
- lists:ukeysort(1, CTOpts)),
- TestDirs = test_dirs(State, TestApps),
- try resolve_ct_opts(TestDirs, Opts) of
- Opts2 ->
- %% disable `auto_compile`
- {ok, [{auto_compile, false} | Opts2]}
- catch
- throw:{error, Reason}->
- {error, {error_processing_options, Reason}}
- end.
-
-resolve_ct_opts(Dirs, Opts) ->
- Opts2 = lists:keydelete(dir, 1, Opts),
- case lists:keytake(suite, 1, Opts2) of
- {value, {suite, Suites}, Opts3} ->
- %% Find full path to suites so that test names are consistent with
- %% names when testing all dirs.
- Suites2 = [resolve_suite(Dirs, Suite) || Suite <- Suites],
- [{suite, Suites2} | Opts3];
- false ->
- %% No suites, test all dirs.
- [{dir, Dirs} | Opts2]
- end.
-
-resolve_suite(Dirs, Suite) ->
- File = Suite ++ code:objfile_extension(),
- case [Path || Dir <- Dirs,
- Path <- [filename:join(Dir, File)],
- filelib:is_file(Path)] of
- [Suite2] ->
- Suite2;
- [] ->
- throw({error, {unknown_suite, File}});
- Suites ->
- throw({error, {duplicate_suites, Suites}})
- end.
-
-compile_tests(State, TestApps, InDirs) ->
- F = fun(AppInfo) ->
- AppDir = rebar_app_info:dir(AppInfo),
- S = case rebar_app_info:state(AppInfo) of
- undefined ->
- C = rebar_config:consult(AppDir),
- rebar_state:new(State, C, AppDir);
- AppState ->
- AppState
- end,
- ok = rebar_erlc_compiler:compile(replace_src_dirs(S, ["test"]),
- ec_cnv:to_list(rebar_app_info:out_dir(AppInfo)))
- end,
- lists:foreach(F, TestApps),
- compile_extra_tests(State, TestApps, InDirs).
-
-%% extra directories containing tests can be passed to ct via the `dir` option
-compile_extra_tests(State, TestApps, InDirs) ->
- F = fun(App) -> rebar_app_info:dir(App) == rebar_dir:get_cwd() end,
- TestDirs = case lists:filter(F, TestApps) of
- %% add `test` to indirs if it exists at the root of the project and
- %% it hasn't already been compiled
- [] -> ["test"|InDirs];
- %% already compiled `./test` so do nothing
- _ -> InDirs
- end,
- %% symlink each of the extra dirs
- lists:foreach(fun(Dir) ->
- Source = filename:join([rebar_dir:get_cwd(), Dir]),
- Target = filename:join([rebar_dir:base_dir(State), Dir]),
- ok = rebar_file_utils:symlink_or_copy(Source, Target)
- end, TestDirs),
- rebar_erlc_compiler:compile(replace_src_dirs(State, TestDirs),
- rebar_dir:base_dir(State),
- filename:join([rebar_dir:base_dir(State), "ebin"])).
-
-replace_src_dirs(State, Dirs) ->
- %% replace any `src_dirs` with the test dirs
- ErlOpts = rebar_state:get(State, erl_opts, []),
- StrippedOpts = lists:keydelete(src_dirs, 1, ErlOpts),
- rebar_state:set(State, erl_opts, [{src_dirs, Dirs}|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).
-
-handle_results(Results) when is_list(Results) ->
- Result = lists:foldl(fun sum_results/2, {0, 0, {0,0}}, Results),
- handle_results(Result);
-handle_results({_, Failed, {_, AutoSkipped}})
- when Failed > 0 orelse AutoSkipped > 0 ->
- {error, {failures_running_tests, {Failed, AutoSkipped}}};
-handle_results({error, Reason}) ->
- {error, {error_running_tests, Reason}};
-handle_results(_) ->
- ok.
-
-sum_results({Passed, Failed, {UserSkipped, AutoSkipped}},
- {Passed2, Failed2, {UserSkipped2, AutoSkipped2}}) ->
- {Passed+Passed2, Failed+Failed2,
- {UserSkipped+UserSkipped2, AutoSkipped+AutoSkipped2}}.
-
-handle_quiet_results(_, {error, _} = Result) ->
- handle_results(Result);
-handle_quiet_results(_, {'DOWN', _, _, _, Reason}) ->
- handle_results({error, Reason});
-handle_quiet_results(CTOpts, Results) when is_list(Results) ->
- _ = [format_result(Result) || Result <- Results],
- case handle_results(Results) of
- {error, {failures_running_tests, _}} = Error ->
- LogDir = proplists:get_value(logdir, CTOpts),
- Index = filename:join([LogDir, "index.html"]),
- ?CONSOLE("Results written to ~p.", [Index]),
- Error;
- Other ->
- Other
- end;
-handle_quiet_results(CTOpts, Result) ->
- handle_quiet_results(CTOpts, [Result]).
-
-format_result({Passed, 0, {0, 0}}) ->
- ?CONSOLE("All ~p tests passed.", [Passed]);
-format_result({Passed, Failed, Skipped}) ->
- Format = [format_failed(Failed), format_skipped(Skipped),
- format_passed(Passed)],
- ?CONSOLE("~s", [Format]).
-
-format_failed(0) ->
- [];
-format_failed(Failed) ->
- io_lib:format("Failed ~p tests. ", [Failed]).
-
-format_passed(Passed) ->
- io_lib:format("Passed ~p tests. ", [Passed]).
-
-format_skipped({0, 0}) ->
- [];
-format_skipped({User, Auto}) ->
- io_lib:format("Skipped ~p (~p, ~p) tests. ", [User+Auto, User, Auto]).
+ end. \ No newline at end of file