diff options
-rw-r--r-- | THANKS | 1 | ||||
-rw-r--r-- | src/rebar_eunit.erl | 114 |
2 files changed, 115 insertions, 0 deletions
@@ -63,3 +63,4 @@ Mattias Holmlund Tino Breddin David Nonnenmacher Anders Nygren +Scott Lystig Fritchie diff --git a/src/rebar_eunit.erl b/src/rebar_eunit.erl index d42ed1f..bfaaaa4 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,99 @@ 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(), + timer:sleep(100); + 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) -> + KeepProcs = [cover_server, eunit_server, inet_gethost_native_sup, + inet_gethost_native, timer_server], + Killed = [begin + Info = case erlang:process_info(Pid) of + undefined -> []; + Else -> Else + end, + Keep = case proplists:get_value(registered_name, Info) of + undefined -> + false; + Name -> + lists:member(Name, KeepProcs) + end, + if Keep -> + 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 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. |