【问题标题】:Erlang, eunit and gen_server: context cleanup failedErlang、eunit 和 gen_server:上下文清理失败
【发布时间】:2011-09-23 08:08:51
【问题描述】:

我在我的 gen_server 上写了一些 eunit 测试:

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

好像测试通过了,但是context cleanup有en错误,不对吧?)

我该如何解决这个问题?

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

【问题讨论】:

  • 感谢大家在 erlang 中进行这么好的潜水)

标签: unit-testing erlang eunit


【解决方案1】:

可能是测试在gen_server 进程有时间关闭之前完成。 gen_server 链接到测试进程(因为它以gen_server:start_link/4 启动),当测试完成时,该进程仍在运行并被 EUnit 杀死。

即使您按照 Roberto 的建议使用 {stop, normal, ok, State} 返回 okgen_server 执行 terminate/2 的速度可能比测试清理的速度要慢。

我通常在我的测试或拆解中使用这样的函数来等待进程:

wait_for_exit(Pid) ->
    MRef = erlang:monitor(process, Pid),
    receive {'DOWN', MRef, _, _, _} -> ok end.

EUnit 有一个默认的 5000 毫秒的超时时间,如果这个函数阻塞的时间过长就会被触发。

【讨论】:

  • 嗯,我想终止函数被称为“之前”,将回复返回给客户端。 EUnit 不应终止仍在执行的进程(Dimitry 正在使用同步调用发出停止请求)。此外,在这种情况下,终止函数看起来并没有那么复杂,所以我怀疑它可能需要那么长时间。无论如何,我现在很好奇:)
  • 来自文档:If the function returns {stop,Reason,Reply,NewState}, Reply will be given back to From. [...] The gen_server will then call Module:terminate(Reason,NewState) and terminate. 此外,终止是否简单并不重要,总有可能存在竞争条件。当测试代码完成执行时,EUnit 将终止测试进程。它不关心是否有一个链接的进程在某个地方仍然存在。
  • 来自 gen_server 代码(如果我正在寻找正确的位置,我应该有更多时间):[...] {stop, Reason, Reply, NState} -> {' EXIT', R} = (catch terminate(Reason, Name, Msg, Mod, NState, [])), reply(From, Reply), exit(R) [...]
  • @roberto:你是对的,在调用terminate/2 之后它确实会回复。但是,在回复期间(以及之后运行的一些内部gen_server 调试代码),测试过程可能仍会退出。所以竞争条件的可能性仍然存在。
  • 此外,我认为监视应该在测试期间终止的进程始终是一种好习惯。对于这样的测试用例,这是最好的断言类型。
【解决方案2】:

handle_call/2 函数中,您正在返回:

{stop, normal, State}

而不是类似:

{stop, normal, ok, State}

换句话说,您没有回复电话。客户端然后看到服务器终止(正常原因)并且它哭了。

没试过,但这是我的第一个猜测。

【讨论】:

  • 哇,有帮助。但我没明白。手册说“gen_server 调用 handle_call(Request, From, State) 预计会返回一个元组 {reply, Reply, State1}。”反馈中有 3 个原子,而不是 4 个
  • 但是这里你使用的是stop元组,它可以有3个或4个元素。
猜你喜欢
  • 2017-10-14
  • 2011-08-09
  • 2014-11-09
  • 2012-09-05
  • 2015-04-05
  • 2011-07-04
  • 2013-04-12
  • 2011-10-08
  • 2016-08-09
相关资源
最近更新 更多