|
|
@ -37,21 +37,41 @@ |
|
|
|
%% ------------------------------------------------------------------- |
|
|
|
-module(rebar_ct). |
|
|
|
|
|
|
|
-export([ct/2]). |
|
|
|
-behaviour(rebar_provider). |
|
|
|
|
|
|
|
-export([init/1, |
|
|
|
do/1]). |
|
|
|
|
|
|
|
%% for internal use only |
|
|
|
-export([info/2]). |
|
|
|
|
|
|
|
-include("rebar.hrl"). |
|
|
|
|
|
|
|
-define(PROVIDER, ct). |
|
|
|
-define(DEPS, [compile]). |
|
|
|
|
|
|
|
%% =================================================================== |
|
|
|
%% Public API |
|
|
|
%% =================================================================== |
|
|
|
|
|
|
|
ct(Config, File) -> |
|
|
|
TestDir = rebar_config:get_local(Config, ct_dir, "test"), |
|
|
|
LogDir = rebar_config:get_local(Config, ct_log_dir, "logs"), |
|
|
|
run_test_if_present(TestDir, LogDir, Config, File). |
|
|
|
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}. |
|
|
|
init(State) -> |
|
|
|
State1 = rebar_state:add_provider(State, #provider{name = ?PROVIDER, |
|
|
|
provider_impl = ?MODULE, |
|
|
|
bare = false, |
|
|
|
deps = ?DEPS, |
|
|
|
example = "rebar ct", |
|
|
|
short_desc = "", |
|
|
|
desc = "", |
|
|
|
opts = []}), |
|
|
|
{ok, State1}. |
|
|
|
|
|
|
|
-spec do(rebar_state:t()) -> {ok, rebar_state:t()}. |
|
|
|
do(State) -> |
|
|
|
TestDir = rebar_state:get(State, ct_dir, "test"), |
|
|
|
LogDir = rebar_state:get(State, ct_log_dir, "logs"), |
|
|
|
run_test_if_present(TestDir, LogDir, State), |
|
|
|
{ok, State}. |
|
|
|
|
|
|
|
%% =================================================================== |
|
|
|
%% Internal functions |
|
|
@ -76,7 +96,7 @@ info(help, ct) -> |
|
|
|
{ct_use_short_names, true} |
|
|
|
]). |
|
|
|
|
|
|
|
run_test_if_present(TestDir, LogDir, Config, File) -> |
|
|
|
run_test_if_present(TestDir, LogDir, State) -> |
|
|
|
case filelib:is_dir(TestDir) of |
|
|
|
false -> |
|
|
|
?WARN("~s directory not present - skipping\n", [TestDir]), |
|
|
@ -89,7 +109,7 @@ run_test_if_present(TestDir, LogDir, Config, File) -> |
|
|
|
ok; |
|
|
|
_ -> |
|
|
|
try |
|
|
|
run_test(TestDir, LogDir, Config, File) |
|
|
|
run_test(TestDir, LogDir, State) |
|
|
|
catch |
|
|
|
throw:skip -> |
|
|
|
ok |
|
|
@ -97,11 +117,11 @@ run_test_if_present(TestDir, LogDir, Config, File) -> |
|
|
|
end |
|
|
|
end. |
|
|
|
|
|
|
|
run_test(TestDir, LogDir, Config, _File) -> |
|
|
|
{Cmd, RawLog} = make_cmd(TestDir, LogDir, Config), |
|
|
|
run_test(TestDir, LogDir, State) -> |
|
|
|
{Cmd, RawLog} = make_cmd(TestDir, LogDir, State), |
|
|
|
?DEBUG("ct_run cmd:~n~p~n", [Cmd]), |
|
|
|
clear_log(LogDir, RawLog), |
|
|
|
Output = case rebar_log:is_verbose(Config) of |
|
|
|
Output = case rebar_log:is_verbose(State) of |
|
|
|
false -> |
|
|
|
" >> " ++ RawLog ++ " 2>&1"; |
|
|
|
true -> |
|
|
@ -113,11 +133,11 @@ run_test(TestDir, LogDir, Config, _File) -> |
|
|
|
{ok,_} -> |
|
|
|
%% in older versions of ct_run, this could have been a failure |
|
|
|
%% that returned a non-0 code. Check for that! |
|
|
|
check_success_log(Config, RawLog); |
|
|
|
check_success_log(State, RawLog); |
|
|
|
{error,Res} -> |
|
|
|
%% In newer ct_run versions, this may be a sign of a good compile |
|
|
|
%% that failed cases. In older version, it's a worse error. |
|
|
|
check_fail_log(Config, RawLog, Cmd ++ Output, Res) |
|
|
|
check_fail_log(State, RawLog, Cmd ++ Output, Res) |
|
|
|
end. |
|
|
|
|
|
|
|
clear_log(LogDir, RawLog) -> |
|
|
@ -133,8 +153,8 @@ clear_log(LogDir, RawLog) -> |
|
|
|
|
|
|
|
%% calling ct with erl does not return non-zero on failure - have to check |
|
|
|
%% log results |
|
|
|
check_success_log(Config, RawLog) -> |
|
|
|
check_log(Config, RawLog, fun(Msg) -> ?CONSOLE("DONE.\n~s\n", [Msg]) end). |
|
|
|
check_success_log(State, RawLog) -> |
|
|
|
check_log(State, RawLog, fun(Msg) -> ?CONSOLE("DONE.\n~s\n", [Msg]) end). |
|
|
|
|
|
|
|
-type err_handler() :: fun((string()) -> no_return()). |
|
|
|
-spec failure_logger(string(), {integer(), string()}) -> err_handler(). |
|
|
@ -144,10 +164,10 @@ failure_logger(Command, {Rc, Output}) -> |
|
|
|
[Command, Rc, Output]) |
|
|
|
end. |
|
|
|
|
|
|
|
check_fail_log(Config, RawLog, Command, Result) -> |
|
|
|
check_log(Config, RawLog, failure_logger(Command, Result)). |
|
|
|
check_fail_log(State, RawLog, Command, Result) -> |
|
|
|
check_log(State, RawLog, failure_logger(Command, Result)). |
|
|
|
|
|
|
|
check_log(Config,RawLog,Fun) -> |
|
|
|
check_log(State,RawLog,Fun) -> |
|
|
|
{ok, Msg} = |
|
|
|
rebar_utils:sh("grep -e \"TEST COMPLETE\" -e \"{error,make_failed}\" " |
|
|
|
++ RawLog, [{use_stdout, false}]), |
|
|
@ -155,12 +175,12 @@ check_log(Config,RawLog,Fun) -> |
|
|
|
RunFailed = string:str(Msg, ", 0 failed") =:= 0, |
|
|
|
if |
|
|
|
MakeFailed -> |
|
|
|
show_log(Config, RawLog), |
|
|
|
show_log(State, RawLog), |
|
|
|
?ERROR("Building tests failed\n",[]), |
|
|
|
?FAIL; |
|
|
|
|
|
|
|
RunFailed -> |
|
|
|
show_log(Config, RawLog), |
|
|
|
show_log(State, RawLog), |
|
|
|
?ERROR("One or more tests failed\n",[]), |
|
|
|
?FAIL; |
|
|
|
|
|
|
@ -170,9 +190,9 @@ check_log(Config,RawLog,Fun) -> |
|
|
|
|
|
|
|
|
|
|
|
%% Show the log if it hasn't already been shown because verbose was on |
|
|
|
show_log(Config, RawLog) -> |
|
|
|
show_log(State, RawLog) -> |
|
|
|
?CONSOLE("Showing log\n", []), |
|
|
|
case rebar_log:is_verbose(Config) of |
|
|
|
case rebar_log:is_verbose(State) of |
|
|
|
false -> |
|
|
|
{ok, Contents} = file:read_file(RawLog), |
|
|
|
?CONSOLE("~s", [Contents]); |
|
|
@ -180,10 +200,9 @@ show_log(Config, RawLog) -> |
|
|
|
ok |
|
|
|
end. |
|
|
|
|
|
|
|
make_cmd(TestDir, RawLogDir, Config) -> |
|
|
|
make_cmd(TestDir, RawLogDir, State) -> |
|
|
|
Cwd = rebar_utils:get_cwd(), |
|
|
|
LogDir = filename:join(Cwd, RawLogDir), |
|
|
|
EbinDir = filename:absname(filename:join(Cwd, "ebin")), |
|
|
|
IncludeDir = filename:join(Cwd, "include"), |
|
|
|
Include = case filelib:is_dir(IncludeDir) of |
|
|
|
true -> |
|
|
@ -205,12 +224,13 @@ make_cmd(TestDir, RawLogDir, Config) -> |
|
|
|
%% includes the dependencies in the code path. The directories |
|
|
|
%% that are part of the root Erlang install are filtered out to |
|
|
|
%% avoid duplication |
|
|
|
R = code:root_dir(), |
|
|
|
NonLibCodeDirs = [P || P <- code:get_path(), not lists:prefix(R, P)], |
|
|
|
CodeDirs = [io_lib:format("\"~s\"", [Dir]) || |
|
|
|
Dir <- [EbinDir|NonLibCodeDirs]], |
|
|
|
Apps = rebar_state:apps_to_build(State), |
|
|
|
DepsDir = rebar_deps:get_deps_dir(State), |
|
|
|
DepsDirEbin = filename:join([DepsDir, "*", "ebin"]), |
|
|
|
AppDirs = [filename:join(rebar_app_info:dir(A), "ebin") || A <- Apps], |
|
|
|
CodeDirs = [io_lib:format("\"~s\"", [Dir]) || Dir <- [DepsDirEbin | AppDirs]], |
|
|
|
CodePathString = string:join(CodeDirs, " "), |
|
|
|
Cmd = case get_ct_specs(Config, Cwd) of |
|
|
|
Cmd = case get_ct_specs(State, Cwd) of |
|
|
|
undefined -> |
|
|
|
?FMT("~s" |
|
|
|
" -pa ~s" |
|
|
@ -221,14 +241,14 @@ make_cmd(TestDir, RawLogDir, Config) -> |
|
|
|
[BaseCmd, |
|
|
|
CodePathString, |
|
|
|
Include, |
|
|
|
build_name(Config), |
|
|
|
build_name(State), |
|
|
|
LogDir, |
|
|
|
filename:join(Cwd, TestDir)]) ++ |
|
|
|
get_cover_config(Config, Cwd) ++ |
|
|
|
get_cover_config(State, Cwd) ++ |
|
|
|
get_ct_config_file(TestDir) ++ |
|
|
|
get_config_file(TestDir) ++ |
|
|
|
get_suites(Config, TestDir) ++ |
|
|
|
get_case(Config); |
|
|
|
get_suites(State, TestDir) ++ |
|
|
|
get_case(State); |
|
|
|
SpecFlags -> |
|
|
|
?FMT("~s" |
|
|
|
" -pa ~s" |
|
|
@ -239,31 +259,32 @@ make_cmd(TestDir, RawLogDir, Config) -> |
|
|
|
[BaseCmd, |
|
|
|
CodePathString, |
|
|
|
Include, |
|
|
|
build_name(Config), |
|
|
|
build_name(State), |
|
|
|
LogDir, |
|
|
|
filename:join(Cwd, TestDir)]) ++ |
|
|
|
SpecFlags ++ get_cover_config(Config, Cwd) |
|
|
|
SpecFlags ++ get_cover_config(State, Cwd) |
|
|
|
end, |
|
|
|
Cmd1 = Cmd ++ get_extra_params(Config), |
|
|
|
io:format("Cmd ~s~n", [Cmd]), |
|
|
|
Cmd1 = Cmd ++ get_extra_params(State), |
|
|
|
RawLog = filename:join(LogDir, "raw.log"), |
|
|
|
{Cmd1, RawLog}. |
|
|
|
|
|
|
|
build_name(Config) -> |
|
|
|
case rebar_config:get_local(Config, ct_use_short_names, false) of |
|
|
|
build_name(State) -> |
|
|
|
case rebar_state:get(State, ct_use_short_names, false) of |
|
|
|
true -> "-sname test"; |
|
|
|
false -> " -name test@" ++ net_adm:localhost() |
|
|
|
end. |
|
|
|
|
|
|
|
get_extra_params(Config) -> |
|
|
|
case rebar_config:get_local(Config, ct_extra_params, undefined) of |
|
|
|
get_extra_params(State) -> |
|
|
|
case rebar_state:get(State, ct_extra_params, undefined) of |
|
|
|
undefined -> |
|
|
|
""; |
|
|
|
Defined -> |
|
|
|
" " ++ Defined |
|
|
|
end. |
|
|
|
|
|
|
|
get_ct_specs(Config, Cwd) -> |
|
|
|
case collect_glob(Config, Cwd, ".*\.test\.spec\$") of |
|
|
|
get_ct_specs(State, Cwd) -> |
|
|
|
case collect_glob(State, Cwd, ".*\.test\.spec\$") of |
|
|
|
[] -> undefined; |
|
|
|
[Spec] -> |
|
|
|
" -spec " ++ Spec; |
|
|
@ -272,12 +293,12 @@ get_ct_specs(Config, Cwd) -> |
|
|
|
lists:flatten([io_lib:format("~s ", [Spec]) || Spec <- Specs]) |
|
|
|
end. |
|
|
|
|
|
|
|
get_cover_config(Config, Cwd) -> |
|
|
|
case rebar_config:get_local(Config, cover_enabled, false) of |
|
|
|
get_cover_config(State, Cwd) -> |
|
|
|
case rebar_state:get(State, cover_enabled, false) of |
|
|
|
false -> |
|
|
|
""; |
|
|
|
true -> |
|
|
|
case collect_glob(Config, Cwd, ".*cover\.spec\$") of |
|
|
|
case collect_glob(State, Cwd, ".*cover\.spec\$") of |
|
|
|
[] -> |
|
|
|
?DEBUG("No cover spec found: ~s~n", [Cwd]), |
|
|
|
""; |
|
|
@ -289,15 +310,15 @@ get_cover_config(Config, Cwd) -> |
|
|
|
end |
|
|
|
end. |
|
|
|
|
|
|
|
collect_glob(Config, Cwd, Glob) -> |
|
|
|
{true, Deps} = rebar_deps:get_deps_dir(Config), |
|
|
|
collect_glob(State, Cwd, Glob) -> |
|
|
|
DepsDir = rebar_deps:get_deps_dir(State), |
|
|
|
CwdParts = filename:split(Cwd), |
|
|
|
filelib:fold_files(Cwd, Glob, true, fun(F, Acc) -> |
|
|
|
%% Ignore any specs under the deps/ directory. Do this pulling |
|
|
|
%% the dirname off the F and then splitting it into a list. |
|
|
|
Parts = filename:split(filename:dirname(F)), |
|
|
|
Parts2 = remove_common_prefix(Parts, CwdParts), |
|
|
|
case lists:member(Deps, Parts2) of |
|
|
|
case lists:member(DepsDir, Parts2) of |
|
|
|
true -> |
|
|
|
Acc; % There is a directory named "deps" in path |
|
|
|
false -> |
|
|
@ -311,25 +332,25 @@ remove_common_prefix(L1, _) -> |
|
|
|
L1. |
|
|
|
|
|
|
|
get_ct_config_file(TestDir) -> |
|
|
|
Config = filename:join(TestDir, "test.config"), |
|
|
|
case filelib:is_regular(Config) of |
|
|
|
State = filename:join(TestDir, "test.config"), |
|
|
|
case filelib:is_regular(State) of |
|
|
|
false -> |
|
|
|
" "; |
|
|
|
true -> |
|
|
|
" -ct_config " ++ Config |
|
|
|
" -ct_config " ++ State |
|
|
|
end. |
|
|
|
|
|
|
|
get_config_file(TestDir) -> |
|
|
|
Config = filename:join(TestDir, "app.config"), |
|
|
|
case filelib:is_regular(Config) of |
|
|
|
State = filename:join(TestDir, "app.config"), |
|
|
|
case filelib:is_regular(State) of |
|
|
|
false -> |
|
|
|
" "; |
|
|
|
true -> |
|
|
|
" -config " ++ Config |
|
|
|
" -config " ++ State |
|
|
|
end. |
|
|
|
|
|
|
|
get_suites(Config, TestDir) -> |
|
|
|
case get_suites(Config) of |
|
|
|
get_suites(State, TestDir) -> |
|
|
|
case get_suites(State) of |
|
|
|
undefined -> |
|
|
|
" -dir " ++ TestDir; |
|
|
|
Suites -> |
|
|
@ -338,10 +359,10 @@ get_suites(Config, TestDir) -> |
|
|
|
string:join([" -suite"] ++ Suites2, " ") |
|
|
|
end. |
|
|
|
|
|
|
|
get_suites(Config) -> |
|
|
|
case rebar_config:get_global(Config, suites, undefined) of |
|
|
|
get_suites(State) -> |
|
|
|
case rebar_state:get(State, suites, undefined) of |
|
|
|
undefined -> |
|
|
|
rebar_config:get_global(Config, suite, undefined); |
|
|
|
rebar_state:get(State, suite, undefined); |
|
|
|
Suites -> |
|
|
|
Suites |
|
|
|
end. |
|
|
@ -358,8 +379,8 @@ find_suite_path(Suite, TestDir) -> |
|
|
|
Path |
|
|
|
end. |
|
|
|
|
|
|
|
get_case(Config) -> |
|
|
|
case rebar_config:get_global(Config, 'case', undefined) of |
|
|
|
get_case(State) -> |
|
|
|
case rebar_state:get(State, 'case', undefined) of |
|
|
|
undefined -> |
|
|
|
""; |
|
|
|
Case -> |
|
|
|