Merge slf-eunit-process-isolation2 branch

This commit is contained in:
Scott Lystig Fritchie 2011-08-24 23:19:01 -05:00
commit 9832a5c5c6

View file

@ -29,6 +29,15 @@
%% <ul> %% <ul>
%% <li>eunit - runs eunit tests</li> %% <li>eunit - runs eunit tests</li>
%% <li>clean - remove .eunit directory</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> %% </ul>
%% The following Global options are supported: %% The following Global options are supported:
%% <ul> %% <ul>
@ -130,11 +139,20 @@ eunit(Config, AppFile) ->
{ok, CoverLog} = cover_init(Config, BeamFiles), {ok, CoverLog} = cover_init(Config, BeamFiles),
StatusBefore = status_before_eunit(),
EunitResult = perform_eunit(Config, Modules), EunitResult = perform_eunit(Config, Modules),
perform_cover(Config, Modules, SrcModules), perform_cover(Config, Modules, SrcModules),
cover_close(CoverLog), 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 case EunitResult of
ok -> ok ->
ok; ok;
@ -439,3 +457,133 @@ percentage(0, 0) ->
"not executed"; "not executed";
percentage(Cov, NotCov) -> percentage(Cov, NotCov) ->
integer_to_list(trunc((Cov / (Cov + NotCov)) * 100)) ++ "%". 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.