mirror of
https://github.com/correl/rebar.git
synced 2024-12-18 03:00:17 +00:00
Merge slf-eunit-process-isolation2 branch
This commit is contained in:
commit
9832a5c5c6
1 changed files with 148 additions and 0 deletions
|
@ -29,6 +29,15 @@
|
|||
%% <ul>
|
||||
%% <li>eunit - runs eunit tests</li>
|
||||
%% <li>clean - remove .eunit directory</li>
|
||||
%% <li>reset_after_eunit::boolean() - default = true.
|
||||
%% If true, try to "reset" VM state to approximate state prior to
|
||||
%% running the EUnit tests:
|
||||
%% <ul>
|
||||
%% <li> Stop net_kernel if it was started </li>
|
||||
%% <li> Stop OTP applications not running before EUnit tests were run </li>
|
||||
%% <li> Kill processes not running before EUnit tests were run </li>
|
||||
%% <li> Reset OTP application environment variables </li>
|
||||
%% </ul> </li>
|
||||
%% </ul>
|
||||
%% The following Global options are supported:
|
||||
%% <ul>
|
||||
|
@ -130,11 +139,20 @@ eunit(Config, AppFile) ->
|
|||
|
||||
{ok, CoverLog} = cover_init(Config, BeamFiles),
|
||||
|
||||
StatusBefore = status_before_eunit(),
|
||||
EunitResult = perform_eunit(Config, Modules),
|
||||
perform_cover(Config, Modules, SrcModules),
|
||||
|
||||
cover_close(CoverLog),
|
||||
|
||||
case proplists:get_value(reset_after_eunit, get_eunit_opts(Config),
|
||||
true) of
|
||||
true ->
|
||||
reset_after_eunit(StatusBefore);
|
||||
false ->
|
||||
ok
|
||||
end,
|
||||
|
||||
case EunitResult of
|
||||
ok ->
|
||||
ok;
|
||||
|
@ -439,3 +457,133 @@ percentage(0, 0) ->
|
|||
"not executed";
|
||||
percentage(Cov, NotCov) ->
|
||||
integer_to_list(trunc((Cov / (Cov + NotCov)) * 100)) ++ "%".
|
||||
|
||||
get_app_names() ->
|
||||
[AppName || {AppName, _, _} <- application:loaded_applications()].
|
||||
|
||||
status_before_eunit() ->
|
||||
Apps = get_app_names(),
|
||||
AppEnvs = [{App, application:get_all_env(App)} || App <- Apps],
|
||||
{erlang:processes(), erlang:is_alive(), AppEnvs, ets:tab2list(ac_tab)}.
|
||||
|
||||
reset_after_eunit({OldProcesses, WasAlive, OldAppEnvs, _OldACs}) ->
|
||||
IsAlive = erlang:is_alive(),
|
||||
if not WasAlive andalso IsAlive ->
|
||||
?DEBUG("Stopping net kernel....\n", []),
|
||||
erl_epmd:stop(),
|
||||
net_kernel:stop(),
|
||||
pause_until_net_kernel_stopped();
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
|
||||
Processes = erlang:processes(),
|
||||
kill_extras(Processes -- OldProcesses),
|
||||
|
||||
OldApps = [App || {App, _} <- OldAppEnvs],
|
||||
Apps = get_app_names(),
|
||||
[begin
|
||||
case lists:member(App, OldApps) of
|
||||
true -> ok;
|
||||
false -> application:stop(App)
|
||||
end,
|
||||
application:unset_env(App, K)
|
||||
end || App <- Apps, App /= rebar,
|
||||
{K, _V} <- application:get_all_env(App)],
|
||||
|
||||
reconstruct_app_env_vars(Apps),
|
||||
ok.
|
||||
|
||||
kill_extras(Pids) ->
|
||||
%% Killing any of the procs below will either:
|
||||
%% 1. Interfere with stuff that we don't want interfered with, or
|
||||
%% 2. May/will force the 'kernel' app to shutdown, which *will*
|
||||
%% interfere with rebar's ability To Do Useful Stuff(tm).
|
||||
%% This list may require changes as OTP versions and/or
|
||||
%% rebar use cases change.
|
||||
KeepProcs = [cover_server, eunit_server,
|
||||
eqc, eqc_license, eqc_locked],
|
||||
Killed = [begin
|
||||
Info = case erlang:process_info(Pid) of
|
||||
undefined -> [];
|
||||
Else -> Else
|
||||
end,
|
||||
Keep1 = case proplists:get_value(registered_name, Info) of
|
||||
undefined ->
|
||||
false;
|
||||
Name ->
|
||||
lists:member(Name, KeepProcs)
|
||||
end,
|
||||
Keep2 = case proplists:get_value(dictionary, Info) of
|
||||
undefined ->
|
||||
false;
|
||||
Ds ->
|
||||
case proplists:get_value('$ancestors', Ds) of
|
||||
undefined ->
|
||||
false;
|
||||
As ->
|
||||
lists:member(kernel_sup, As)
|
||||
end
|
||||
end,
|
||||
if Keep1 orelse Keep2 ->
|
||||
ok;
|
||||
true ->
|
||||
?DEBUG("Kill ~p ~p\n", [Pid, Info]),
|
||||
exit(Pid, kill),
|
||||
Pid
|
||||
end
|
||||
end || Pid <- Pids],
|
||||
case lists:usort(Killed) -- [ok] of
|
||||
[] ->
|
||||
?DEBUG("No processes to kill\n", []),
|
||||
[];
|
||||
Else ->
|
||||
[wait_until_dead(Pid) || Pid <- Else],
|
||||
Else
|
||||
end.
|
||||
|
||||
reconstruct_app_env_vars([App|Apps]) ->
|
||||
CmdLine0 = proplists:get_value(App, init:get_arguments(), []),
|
||||
CmdVars = [{list_to_atom(K), list_to_atom(V)} || {K, V} <- CmdLine0],
|
||||
AppFile = (catch filename:join([code:lib_dir(App),
|
||||
"ebin",
|
||||
atom_to_list(App) ++ ".app"])),
|
||||
AppVars = case file:consult(AppFile) of
|
||||
{ok, [{application, App, Ps}]} ->
|
||||
proplists:get_value(env, Ps, []);
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
AllVars = CmdVars ++ AppVars,
|
||||
?DEBUG("Reconstruct ~p ~p\n", [App, AllVars]),
|
||||
[application:set_env(App, K, V) || {K, V} <- AllVars],
|
||||
reconstruct_app_env_vars(Apps);
|
||||
reconstruct_app_env_vars([]) ->
|
||||
ok.
|
||||
|
||||
wait_until_dead(Pid) when is_pid(Pid) ->
|
||||
Ref = monitor(process, Pid),
|
||||
receive
|
||||
{'DOWN', Ref, process, _Obj, Info} ->
|
||||
Info
|
||||
after 10*1000 ->
|
||||
exit({timeout_waiting_for, Pid})
|
||||
end;
|
||||
wait_until_dead(_) ->
|
||||
ok.
|
||||
|
||||
pause_until_net_kernel_stopped() ->
|
||||
pause_until_net_kernel_stopped(10).
|
||||
|
||||
pause_until_net_kernel_stopped(0) ->
|
||||
exit(net_kernel_stop_failed);
|
||||
pause_until_net_kernel_stopped(N) ->
|
||||
try
|
||||
_ = net_kernel:i(),
|
||||
timer:sleep(100),
|
||||
pause_until_net_kernel_stopped(N - 1)
|
||||
catch
|
||||
error:badarg ->
|
||||
?DEBUG("Stopped net kernel.\n", []),
|
||||
ok
|
||||
end.
|
||||
|
|
Loading…
Reference in a new issue