Erlang测试超时错误

时间:2018-04-03 12:43:39

标签: testing erlang timeout

我正在尝试在data_actor模块上实现一个类似Erlang Twitter的应用程序。它允许用户注册,推特,获取他们的所有推文,订阅其他用户的推文,并获得他们的时间表,其中包括他们的推文和他们订阅的用户的推文。该应用程序有一个data_actor循环作为主服务器和一个分布式服务器的worker_process循环。在用户注册时,data_actor循环(主服务器)为每个用户分配一个工作服务器(使用worker_process循环)。

另外,我有一个服务器模块,它只是应用程序的接口。

我编写了7个测试用例,但最后一个测试(subscription_test)失败,出现以下错误。我已经看了一些针对此超时测试的建议解决方案。有人建议在超时时跳​​过测试。首先,我没有为这些测试添加超时。其次,我希望所有的测试都能成功。看来我有一个逻辑错误。作为Erlang的新手,我几乎无法指出什么是错误。

 Eshell V9.0.4
(Erlang_Project_twitter@GAKUO)1> data_actor:test().
data_actor: subscription_test...*timed out*
undefined
=======================================================
  Failed: 0.  Skipped: 0.  Passed: 6.
One or more tests were cancelled.
error
(Erlang_Project_twitter@GAKUO)2> 

下面是带有应用程序实现逻辑和7个测试的data_actor模块:

    %% This is a simple implementation of the project, using one centralized server.
%%
%% It will create one "server" actor that contains all internal state (users,
%% their subscriptions, and their tweets).
%%
%% This implementation is provided with unit tests, however, these tests are
%% neither complete nor implementation independent, so be careful when reusing
%% them.
-module(data_actor).

-include_lib("eunit/include/eunit.hrl").

%%
%% Exported Functions
%%
-export([initialize/0,
         % internal actors
         data_actor/1,worker_process/1]).

%%
%% API Functions
%%

% Start server.
% This returns the pid of the server, but you can also use the name "data_actor"
% to refer to it.
initialize() ->
    ServerPid = spawn_link(?MODULE, data_actor, [[]]),

    register(data_actor, ServerPid),
    ServerPid.

% The data actor works like a small database and encapsulates all state of this
% simple implementation.
data_actor(Data) ->
    receive
        {Sender, register_user} ->
            {NewData, NewUserId} = add_new_user(Data),
            Sender ! {self(), registered_user, NewUserId},
            data_actor(NewData);
       {Sender, get_timeline, UserId, Page} ->
            {user, UserId, Worker_pid, _Tweets, _Subscriptions} = lists:nth(UserId + 1, Data),
            Worker_pid !  {Sender, get_timeline, UserId, Page},

            data_actor(Data);

        {Sender, get_tweets, UserId, Page} ->
            {user, UserId, Worker_pid, _Tweets, _Subscriptions} = lists:nth(UserId + 1, Data),
            Worker_pid !  {Sender, get_tweets, UserId, Page},
            data_actor(Data);

        {Sender, tweet, UserId, Tweet} ->
            {user, UserId, Worker_pid, _Tweets, _Subscriptions} = lists:nth(UserId + 1, Data),
            Worker_pid ! {Sender, tweet, UserId, Tweet},

            data_actor(Data);


        {Sender, subscribe, UserId, UserIdToSubscribeTo} ->
            {user, UserId, Worker_pid, _Tweets, _Subscriptions} = lists:nth(UserId + 1, Data),
            {user, UserId, SubscribedToWorker_pid, _Tweets, _Subscriptions} = lists:nth(UserId + 1, Data),
            Worker_pid ! {Sender,  subscribe, UserId, UserIdToSubscribeTo,SubscribedToWorker_pid},          
            data_actor(Data)
    end.

%%% data actor internal processes 

add_new_user(Data) ->
    %create new user user and assign user id    
    NewUserId = length(Data),
    % start a worker process for the newly registred user
    Worker_pid = spawn_link(?MODULE,worker_process,[[{user, NewUserId, [], sets:new()}]]),
    %add the worker_pid to the data list   
    NewData = Data ++ [{user, NewUserId,Worker_pid, [], sets:new()}],
    {NewData, NewUserId}.

%%% worker actor
worker_process(Data)->
    receive

        {Sender, get_timeline, UserId, Page} ->
            Sender ! {self(), timeline, UserId, Page, timeline(Data, UserId, Page)},
            worker_process(Data);

        {Sender, get_tweets, UserId, Page} ->
            Sender ! {self(), tweets, UserId, Page, tweets(Data, UserId, Page)},
            worker_process(Data);

        {Sender, tweet, UserId, Tweet} ->
            {NewData, Timestamp} = tweet(Data, UserId, Tweet),
            Sender ! {self(), tweet_accepted, UserId, Timestamp},
           worker_process(NewData);

        {Sender,  subscribe, UserId, UserIdToSubscribeTo, SubscribedToWorker_pid} ->
            NewData = subscribe_to_user(Data, UserId, UserIdToSubscribeTo,SubscribedToWorker_pid),
            Sender ! {self(), subscribed, UserId, UserIdToSubscribeTo},

          worker_process(NewData)
    end.

%%
%% worker actor internal Functions
%%


timeline(Data, UserId, Page) ->
   [ {user, UserId, Tweets, Subscriptions}] = Data, 

    UnsortedTweetsForTimeLine =
        lists:foldl(fun(Worker_pid, AllTweets) ->
                        Worker_pid !  {self(), get_tweets, UserId, Page},
                        receive
                        {_ResponsePid, tweets, UserId, Page, Subscribed_Tweets} ->Subscribed_Tweets
                        end,
                        AllTweets ++ Subscribed_Tweets
                    end,
                    Tweets,
                    sets:to_list(Subscriptions)),
    SortedTweets = lists:reverse(lists:keysort(3, UnsortedTweetsForTimeLine)),
    lists:sublist(SortedTweets, 10).

tweets(Data,_UserId, _Page) ->
    [{user, _UserId, Tweets, _Subscriptions}]= Data,    
    Tweets.

tweet(Data, UserId, Tweet) ->
      [ {user, UserId, Tweets, Subscriptions}] = Data, 

    Timestamp = erlang:timestamp(),
    NewUser = [{user, UserId, Tweets ++ [{tweet, UserId, Timestamp, Tweet}], Subscriptions}],
   { NewUser, Timestamp}.

subscribe_to_user(Data, UserId, _UserIdToSubscribeTo,SubscribedToWorker_pid) ->
    [{user, UserId,Tweets, Subscriptions}] = Data,
    NewUser = [{user, UserId, Tweets, sets:add_element(SubscribedToWorker_pid, Subscriptions)}],
    NewUser.




%%
%% Test Functions
%% 
%% These tests are for this specific implementation. They are a partial
%% definition of the semantics of the provided interface but also make certain
%% assumptions of its implementation. Thus, they need to be reused with care.
%%

initialization_test() ->
    catch unregister(data_actor),
    initialize().

register_user_test() ->
    ServerPid = initialization_test(),

    % We assume here that everything is sequential, and we have simple
    % incremental ids
    ?assertMatch({0, _Pid1}, server:register_user(ServerPid)),
    ?assertMatch({1, _Pid2}, server:register_user(ServerPid)),
    ?assertMatch({2, _Pid3}, server:register_user(ServerPid)),
    ?assertMatch({3, _Pid4}, server:register_user(ServerPid)).

init_for_test() ->
    ServerPid = initialization_test(),
    {0, Pid1} = server:register_user(ServerPid),
    {1, Pid2} = server:register_user(ServerPid),
    {2, Pid3} = server:register_user(ServerPid),
    {3, Pid4} = server:register_user(ServerPid),
    [Pid1, Pid2, Pid3, Pid4].

timeline_test() ->
    Pids = init_for_test(),
    [Pid1, Pid2 | _ ] = Pids,

    ?assertMatch([], server:get_timeline(Pid1, 1, 0)),
    ?assertMatch([], server:get_timeline(Pid2, 2, 0)).

users_tweets_test() ->
    Pids = init_for_test(),
    [Pid1 | _ ] = Pids,

    ?assertMatch([], server:get_tweets(Pid1, 1, 0)),
    ?assertMatch([], server:get_tweets(Pid1, 2, 0)).

tweet_test() ->
    Pids = init_for_test(),
    [Pid1, Pid2 | _ ] = Pids,

    ?assertMatch([], server:get_timeline(Pid1, 1, 0)),
    ?assertMatch([], server:get_timeline(Pid2, 2, 0)),

    ?assertMatch({_, _Secs, _MicroSecs}, server:tweet(Pid1, 1, "Tweet no. 1")),

    ?assertMatch([{tweet, _, _, "Tweet no. 1"}], server:get_tweets(Pid1, 1, 0)),
    ?assertMatch([], server:get_tweets(Pid1, 2, 0)),

    ?assertMatch([{tweet, _, _, "Tweet no. 1"}], server:get_timeline(Pid1, 1, 0)), % own tweets included in timeline
    ?assertMatch([], server:get_timeline(Pid2, 2, 0)),

    Pids. % no subscription

subscription_test() ->
    [_Pid1, Pid2 | _ ] = tweet_test(),

    ?assertMatch(ok, server:subscribe(Pid2, 2, 1)),

    ?assertMatch([{tweet, _, _, "Tweet no. 1"}], server:get_timeline(Pid2, 2, 0)), % now there is a subscription

    ?assertMatch({_, _Secs, _MicroSecs}, server:tweet(Pid2, 2, "Tweet no. 2")),
    ?assertMatch([{tweet, _, _, "Tweet no. 2"},
                  {tweet, _, _, "Tweet no. 1"}],
                 server:get_timeline(Pid2, 2, 0)),
    done. 

以下是服务器模块,它是我的应用程序的接口:

%% This module provides the protocol that is used to interact with an
%% implementation of a microblogging service.
%%
%% The interface is design to be synchrounous: it waits for the reply of the
%% system.
%%
%% This module defines the public API that is supposed to be used for
%% experiments. The semantics of the API here should remain unchanged.
-module(server).

-export([register_user/1,
         subscribe/3,
         get_timeline/3,
         get_tweets/3,
         tweet/3]).

%%
%% Server API
%%

% Register a new user. Returns its id and a pid that should be used for
% subsequent requests by this client.
-spec register_user(pid()) -> {integer(), pid()}.
register_user(ServerPid) ->
    ServerPid ! {self(), register_user},
    receive
        {ResponsePid, registered_user, UserId} -> {UserId, ResponsePid}
    end.

% Subscribe/follow another user.
-spec subscribe(pid(), integer(), integer()) -> ok.
subscribe(ServerPid, UserId, UserIdToSubscribeTo) ->
    ServerPid ! {self(), subscribe, UserId, UserIdToSubscribeTo},
    receive
        {_ResponsePid, subscribed, UserId, UserIdToSubscribeTo} -> ok
    end.

% Request a page of the timeline of a particular user.
% Request results can be 'paginated' to reduce the amount of data to be sent in
% a single response. This is up to the server.
-spec get_timeline(pid(), integer(), integer()) -> [{tweet, integer(), erlang:timestamp(), string()}].
get_timeline(ServerPid, UserId, Page) ->
    ServerPid ! {self(), get_timeline, UserId, Page},
    receive
        {_ResponsePid, timeline, UserId, Page, Timeline} ->
            Timeline
    end.

% Request a page of tweets of a particular user.
% Request results can be 'paginated' to reduce the amount of data to be sent in
% a single response. This is up to the server.
-spec get_tweets(pid(), integer(), integer()) -> [{tweet, integer(), erlang:timestamp(), string()}].
get_tweets(ServerPid, UserId, Page) ->
    ServerPid ! {self(), get_tweets, UserId, Page},
    receive
        {_ResponsePid, tweets, UserId, Page, Tweets} ->
            Tweets
    end.

% Submit a tweet for a user.
% (Authorization/security are not regarded in any way.)
-spec tweet(pid(), integer(), string()) -> erlang:timestamp(). 
tweet(ServerPid, UserId, Tweet) ->
    ServerPid ! {self(), tweet, UserId, Tweet},
    receive
        {_ResponsePid, tweet_accepted, UserId, Timestamp} ->
            Timestamp
    end.

1 个答案:

答案 0 :(得分:0)

当您的工作进程处理get_timeline请求时,它会调用函数时间轴(...)。但是这会尝试将get_tweets请求发送到Subscriptions中的进程,其中包含工作进程本身。由于仍然忙着处理get_timeline,它现在无法处理get_tweets消息,因此您永远无法得到答案,并且您的进程已陷入僵局。

提示:进行较小的单元测试并在添加功能时逐步添加它们,这样您就可以更轻松地确定最新更改何时破坏了测试。您甚至可以在实现实际功能之前添加测试。