Erlang, eunit и gen_server: очистка контекста не удалась
Я написал тест eunit на моем сервере gen_server:
-module(st_db_tests).
-include_lib("eunit/include/eunit.hrl").
main_test_() ->
{foreach,
fun setup/0,
fun cleanup/1,
[
fun db_server_up/1
]}.
setup() ->
{ok,Pid} = st_db:start_link(), Pid.
cleanup(Pid) ->
gen_server:call(Pid, stop).
db_server_up(Pid) ->
?_assertMatch({[{<<"couchdb">>,<<"Welcome">>},{<<"version">>, _}]},
gen_server:call(Pid, test)).
Когда я делаю тест, у меня есть это:
./rebar eunit suite=st_db_tests skip_deps=true
==> site_stater (eunit)
Compiled test/st_db_tests.erl
... loading stuff ...
=PROGRESS REPORT==== 27-Jun-2011::12:33:21 ===
supervisor: {local,kernel_safe_sup}
started: [{pid,<0.127.0>},
{name,inet_gethost_native_sup},
{mfargs,{inet_gethost_native,start_link,[]}},
{restart_type,temporary},
{shutdown,1000},
{child_type,worker}]
module 'st_db_tests'
*** context cleanup failed ***
::exit:{normal,{gen_server,call,[<0.99.0>,stop]}}
in function gen_server:call/2
=======================================================
Failed: 0. Skipped: 0. Passed: 1.
Похоже, тест пройден, но в очистке контекста есть ошибка, что не так, верно?)
Как я могу это исправить?
PS: мой gen_server
-module(st_db).
-behaviour(gen_server).
%% --------------------------------------------------------------------
%% Include files
%% --------------------------------------------------------------------
%% --------------------------------------------------------------------
%% External exports
-export([start_link/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-record(state, {db_pid, couch_server_pid}).
%% ====================================================================
%% External functions
%% ====================================================================
%%--------------------------------------------------------------------
%% @doc Starts the server.
%%
%% @spec start_link() -> {ok, Pid}
%% where
%% Pid = pid()
%% @end
%%--------------------------------------------------------------------
start_link() ->
gen_server:start_link({global, st_db}, ?MODULE, ["localhost", 5984, "site_stater"], []).
%% ====================================================================
%% Server internal functions
%% ====================================================================
%% --------------------------------------------------------------------
%% Function: init/1
%% Description: Initiates the server
%% Returns: {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% --------------------------------------------------------------------
init([Server, Port, DB]) ->
couchbeam:start(),
CouchServer = couchbeam:server_connection(Server, Port, "", []),
{ok, CouchDB} = couchbeam:open_or_create_db(CouchServer, DB, []),
{ok, #state{db_pid=CouchDB, couch_server_pid=CouchServer}}.
%% ====================================================================
%% DB manipulation functions
%% ====================================================================
%% --------------------------------------------------------------------
%% Function: handle_call/3
%% Description: Handling call messages
%% Returns: {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} | (terminate/2 is called)
%% {stop, Reason, State} (terminate/2 is called)
%% --------------------------------------------------------------------
handle_call (test, _From, #state{couch_server_pid = Couch_server_pid} = State) ->
{ok, Version} = couchbeam:server_info(Couch_server_pid),
{reply, Version, State};
handle_call(stop, _From, State) ->
{stop, normal, State}.
%% --------------------------------------------------------------------
%% Function: handle_cast/2
%% Description: Handling cast messages
%% Returns: {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State} (terminate/2 is called)
%% --------------------------------------------------------------------
handle_cast(_Msg, State) ->
{noreply, State}.
%% --------------------------------------------------------------------
%% Function: handle_info/2
%% Description: Handling all non call/cast messages
%% Returns: {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State} (terminate/2 is called)
%% --------------------------------------------------------------------
handle_info(_Info, State) ->
{noreply, State}.
%% --------------------------------------------------------------------
%% Function: terminate/2
%% Description: Shutdown the server
%% Returns: any (ignored by gen_server)
%% --------------------------------------------------------------------
terminate(_Reason, _State) ->
ok.
%% --------------------------------------------------------------------
%% Func: code_change/3
%% Purpose: Convert process state when code is changed
%% Returns: {ok, NewState}
%% --------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
2 ответа
В handle_call/2
функция, вы возвращаетесь:
{stop, normal, State}
а не что-то вроде:
{stop, normal, ok, State}
Другими словами, вы не отвечаете на звонок. Затем клиент видит, что сервер завершает работу (по обычной причине), и он плачет.
Не пробовал, но это будет мое первое предположение.
Может быть, что тест заканчивается до gen_server
Процесс успел завершиться. gen_server
связан с процессом тестирования (потому что он начинается с gen_server:start_link/4
) и когда тест завершается, процесс все еще выполняется и завершается EUnit.
Даже если ты вернешься ok
с помощью {stop, normal, ok, State}
как предложено Роберто, gen_server
может быть медленнее terminate/2
чем тест занимает очистка.
Обычно я использую такую функцию в своих тестах или разборках для ожидания процессов:
wait_for_exit(Pid) ->
MRef = erlang:monitor(process, Pid),
receive {'DOWN', MRef, _, _, _} -> ok end.
По умолчанию EUnit имеет тайм-аут 5000 мс, который срабатывает, если эта функция блокируется слишком долго.