diff options
author | Scott Lystig Fritchie <slfritchie@snookles.com> | 2011-08-24 23:19:01 -0500 |
---|---|---|
committer | Scott Lystig Fritchie <slfritchie@snookles.com> | 2011-08-24 23:19:01 -0500 |
commit | 9832a5c5c637804a6420869d58a278e28cd51f09 (patch) | |
tree | 8d8f405f0ca48ba6a5002228e47855b4c442fdf7 | |
parent | 1dcf0d15dcb0b6720a462424f7466220500606d0 (diff) | |
parent | 9c8b5e6c82fc055dd13a8d0a056add7658cbc3e2 (diff) |
Merge slf-eunit-process-isolation2 branch
-rw-r--r-- | src/rebar_eunit.erl | 148 |
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. |