Add a simple 'smoke test' tool to run eunit/dialyzer on riak source trees

This commit is contained in:
Andrew Thompson 2014-01-22 12:21:22 -05:00
parent d983476f59
commit 1ec8bda21c
4 changed files with 224 additions and 0 deletions

1
.gitignore vendored
View File

@ -2,6 +2,7 @@ deps
ebin
log
riak_test
smoke_test
.eunit
.DS_Store
out

View File

@ -7,6 +7,7 @@ PLT = $(HOME)/.riak-test_dialyzer_plt
all: deps compile
./rebar skip_deps=true escriptize
SMOKE_TEST=1 ./rebar skip_deps=true escriptize
deps:
./rebar get-deps

8
rebar.config.script Normal file
View File

@ -0,0 +1,8 @@
case os:getenv("SMOKE_TEST") of
false -> CONFIG;
[] -> CONFIG;
_ ->
C1 = lists:keystore(escript_emu_args, 1, CONFIG,
{escript_emu_args, "%%! -escript main smoke_test_escript +K true +P 10000 -env ERL_MAX_PORTS 10000\n"}),
lists:keystore(escript_name, 1, C1, {escript_name, smoke_test})
end.

214
src/smoke_test_escript.erl Executable file
View File

@ -0,0 +1,214 @@
-module(smoke_test_escript).
-include_lib("kernel/include/file.hrl").
-export([main/1, get_version/0, worker/3]).
get_version() ->
list_to_binary(string:strip(os:cmd("git describe"), right, $\n)).
cli_options() ->
%% Option Name, Short Code, Long Code, Argument Spec, Help Message
[
{project, $p, "project", string, "specifices which project"},
{debug, $v, "debug", undefined, "debug?"},
{directory, $d, "directory", string, "source tree directory"},
{jobs, $j, "jobs", integer, "jobs?"}
].
main(Args) ->
{ok, {Parsed, _Other}} = getopt:parse(cli_options(), Args),
application:start(ibrowse),
lager:start(),
rt_config:load("default", filename:join([os:getenv("HOME"), ".smoke_test.config"])),
case lists:keyfind(project, 1, Parsed) of
false ->
lager:error("Must specify project!"),
application:stop(lager),
halt(1);
{project, Project} ->
rt_config:set(rt_project, Project)
end,
case lists:keyfind(directory, 1, Parsed) of
false ->
%% run in current working directory
ok;
{directory, Dir} ->
lager:info("Changing working dir to ~s", [Dir]),
ok = file:set_cwd(filename:absname(Dir))
end,
case lists:member(debug, Parsed) of
true ->
lager:set_loglevel(lager_console_backend, debug);
_ ->
ok
end,
rt_config:set(rt_harness, ?MODULE),
lager:debug("ParsedArgs ~p", [Parsed]),
Suites = giddyup:get_suite(rt_config:get(platform)),
Jobs = case lists:keyfind(jobs, 1, Parsed) of
false ->
1;
{jobs, J} ->
J
end,
{ok, PWD} = file:get_cwd(),
Rebar = filename:join(PWD, "rebar"),
setup_deps(Rebar, PWD, [filename:join([PWD, "deps", F])
|| F <- element(2, file:list_dir(filename:join(PWD, "deps"))),
filelib:is_dir(filename:join([PWD, "deps", F]))]),
case Jobs > 1 of
true ->
%% partiton the suite list by the number of jobs
SplitSuites = dict:to_list(element(2, lists:foldl(fun(S, {Counter, Dict}) ->
{Counter + 1, dict:append(Counter rem Jobs, S, Dict)}
end, {0, dict:new()}, Suites))),
lager:debug("Split into ~p lists", [length(SplitSuites)]),
Workers = [spawn_monitor(?MODULE, worker, [Rebar, PWD, SS]) || {_, SS} <- SplitSuites],
wait_for_workers(Workers);
_ ->
worker(Rebar, PWD, Suites)
end.
worker(Rebar, PWD, Suites) ->
lists:foreach(fun({Suite, Config}) ->
lager:info("Suite ~p config ~p", [Suite, Config]),
[Dep, Task] = string:tokens(atom_to_list(Suite), ":"),
FDep = filename:join([PWD, deps, Dep]),
case filelib:is_dir(FDep) of
true ->
case Task of
"eunit" ->
%% set up a symlink so that each dep has deps
P = erlang:open_port({spawn_executable, Rebar},
[{args, ["eunit", "skip_deps=true"]},
{cd, FDep}, exit_status,
{line, 1024}, stderr_to_stdout, binary]),
{Res, Log} = accumulate(P, []),
CleanedLog = cleanup_logs(Log),
giddyup:post_result([{test, Suite}, {status, get_status(Res)},
{log, CleanedLog} | Config]),
Res;
"dialyzer" ->
P = erlang:open_port({spawn_executable, "/usr/bin/make"},
[{args, ["dialyzer"]},
{cd, FDep}, exit_status,
{line, 1024}, stderr_to_stdout, binary]),
{Res, Log} = accumulate(P, []),
%% TODO split the logs so that the PLT stuff is elided
CleanedLog = cleanup_logs(Log),
giddyup:post_result([{test, Suite}, {status, get_status(Res)},
{log, CleanedLog} | Config]),
Res;
_ ->
ok
end;
false ->
lager:debug("Not a dep: ~p", [FDep])
end
end, Suites).
setup_deps(_, _, []) -> ok;
setup_deps(Rebar, PWD, [Dep|Deps]) ->
%% clean up an old deps dir, if present
remove_deps_dir(Dep),
%% symlink ALL the deps in
file:make_symlink(filename:join(PWD, "deps"), filename:join(Dep, "deps")),
lager:debug("ln -sf ~s ~s", [filename:join(PWD, "deps"),
filename:join(Dep, "deps")]),
%% run rebar list deps, to find out which ones to keep
P = erlang:open_port({spawn_executable, Rebar},
[{args, ["list-deps"]},
{cd, Dep}, exit_status,
{line, 1024}, stderr_to_stdout, binary]),
{0, Log} = accumulate(P, []),
%% find all the deps, amongst the noise
case re:run(Log, "([a-zA-Z0-9_]+) (?:BRANCH|TAG|REV)",
[global, {capture, all_but_first, list}]) of
{match, Matches} ->
lager:info("Deps for ~p are ~p", [Dep, Matches]),
ok = file:delete(filename:join(Dep, "deps")),
ok = filelib:ensure_dir(filename:join(Dep, "deps")++"/"),
[file:make_symlink(filename:join([PWD, "deps", M]),
filename:join([Dep, "deps", M]))
|| M <- Matches];
nomatch ->
%% remove the symlink
file:delete(filename:join(Dep, "deps")),
lager:info("~p has no deps", [Dep])
end,
setup_deps(Rebar, PWD, Deps).
remove_deps_dir(Dep) ->
case filelib:is_dir(filename:join(Dep, "deps")) of
true ->
%% there should ONLY be a deps dir leftover from a previous run,
%% so it should be a directory filled with symlinks
{ok, Files} = file:list_dir(filename:join(Dep, "deps")),
lists:foreach(fun(F) ->
File = filename:join([Dep, "deps", F]),
{ok, FI} = file:read_link_info(File),
case FI#file_info.type of
symlink ->
ok = file:delete(File);
_ ->
ok
end
end, Files),
%% this will fail if the directory is not now empty
ok = file:del_dir(filename:join(Dep, "deps")),
ok;
false ->
ok
end.
wait_for_workers([]) ->
ok;
wait_for_workers(Workers) ->
receive
{'DOWN', _, _, Pid, normal} ->
lager:info("Worker exited normally"),
wait_for_workers(Workers -- [Pid]);
{'DOWN', _, _, Pid, Reason} ->
lager:info("Worker exited abnormally: ~p", [Reason]),
wait_for_workers(Workers -- [Pid])
end.
cleanup_logs(Logs) ->
case unicode:characters_to_binary(Logs, latin1, unicode) of
{error, Bin, Rest} ->
lager:error("Bad binary ~p", [Rest]),
Bin;
{incomplete, Bin, Rest} ->
lager:error("Bad binary ~p", [Rest]),
Bin;
Bin ->
Bin
end.
maybe_eol(eol) ->
"\n";
maybe_eol(noeol) ->
"".
get_status(0) ->
pass;
get_status(_) ->
fail.
accumulate(P, Acc) ->
receive
{P, {data, {EOL, Data}}} ->
accumulate(P, [[Data,maybe_eol(EOL)]|Acc]);
{P, {exit_status, Status}} ->
lager:debug("Exited with status ~b", [Status]),
{Status, list_to_binary(lists:reverse(Acc))};
{P, Other} ->
lager:warning("Unexpected return from port: ~p", [Other]),
accumulate(P, Acc)
end.