|
|
@ -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. |