summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorScott Lystig Fritchie <slfritchie@snookles.com>2011-08-24 23:19:01 -0500
committerScott Lystig Fritchie <slfritchie@snookles.com>2011-08-24 23:19:01 -0500
commit9832a5c5c637804a6420869d58a278e28cd51f09 (patch)
tree8d8f405f0ca48ba6a5002228e47855b4c442fdf7 /src
parent1dcf0d15dcb0b6720a462424f7466220500606d0 (diff)
parent9c8b5e6c82fc055dd13a8d0a056add7658cbc3e2 (diff)
Merge slf-eunit-process-isolation2 branch
Diffstat (limited to 'src')
-rw-r--r--src/rebar_eunit.erl148
1 files changed, 148 insertions, 0 deletions
diff --git a/src/rebar_eunit.erl b/src/rebar_eunit.erl
index c15e934..040e739 100644
--- a/src/rebar_eunit.erl
+++ b/src/rebar_eunit.erl
@@ -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.