Browse Source

Adds missing test helper module

Viktor Söderqvist 10 years ago
parent
commit
1594422260
1 changed files with 107 additions and 0 deletions
  1. 107 0
      test/mock_tcp.erl

+ 107 - 0
test/mock_tcp.erl

@@ -0,0 +1,107 @@
+%% @doc A module to be used where gen_tcp is expected.
+%%
+%% A "fake socket" is used in test where we need to mock socket communication.
+%% It is a pid maintaining a list of expected send and recv events.
+-module(mock_tcp).
+
+%% gen_tcp interface functions.
+-export([send/2, recv/2, recv/3]).
+
+%% Functions to setup the mock_tcp.
+-export([create/1, close/1]).
+
+%% @doc Creates a mock_tcp process with a buffer of expected recv/2,3 and send/2
+%% calls. The pid of the mock_tcp process is returned.
+-spec create([{recv, binary()} | {send, binary()}]) -> pid().
+create(ExpectedEvents) ->
+    spawn_link(fun () -> loop(ExpectedEvents) end).
+
+%% @doc Receives NumBytes bytes from mock_tcp Pid. This function can be used
+%% as a replacement for gen_tcp:recv/2 in unit tests. If there not enough data
+%% in the mock_tcp's buffer, an error is raised.
+recv(Pid, NumBytes) ->
+    Pid ! {recv, NumBytes, self()},
+    receive
+        {ok, Data} -> {ok, Data};
+        error -> error({unexpected_recv, NumBytes})
+    after 100 ->
+        error(noreply)
+    end.
+
+recv(Pid, NumBytes, _Timeout) ->
+    recv(Pid, NumBytes).
+
+%% @doc Sends data to a mock_tcp. This can be used as replacement for
+%% gen_tcp:send/2 in unit tests. If the data sent is not what the mock_tcp
+%% expected, an error is raised.
+send(Pid, Data) ->
+    Pid ! {send, iolist_to_binary(Data), self()},
+    receive
+        ok -> ok;
+        error -> error({unexpected_send, Data})
+    after 100 ->
+        error(noreply)
+    end.
+
+%% Stops the mock_tcp process. If the mock_tcp's buffer is not empty,
+%% an error is raised.
+close(Pid) ->
+    Pid ! {done, self()},
+    receive
+        ok -> ok;
+        {remains, Remains} -> error({unexpected_close, Remains})
+    after 100 ->
+        error(noreply)
+    end.
+
+%% Used by create/1.
+loop(AllEvents = [{Func, Data} | Events]) ->
+    receive
+        {recv, NumBytes, FromPid} when Func == recv, NumBytes == size(Data) ->
+            FromPid ! {ok, Data},
+            loop(Events);
+        {recv, NumBytes, FromPid} when Func == recv, NumBytes < size(Data) ->
+            <<Data1:NumBytes/binary, Rest/binary>> = Data,
+            FromPid ! {ok, Data1},
+            loop([{recv, Rest} | Events]);
+        {send, Bytes, FromPid} when Func == send, Bytes == Data ->
+            FromPid ! ok,
+            loop(Events);
+        {send, Bytes, FromPid} when Func == send, size(Bytes) < size(Data) ->
+            Size = size(Bytes),
+            case Data of
+                <<Bytes:Size/binary, Rest/binary>> ->
+                    FromPid ! ok,
+                    loop([{send, Rest} | Events]);
+                _ ->
+                    FromPid ! error
+            end;
+        {_, _, FromPid} ->
+            FromPid ! error;
+        {done, FromPid} ->
+            FromPid ! {remains, AllEvents}
+    end;
+loop([]) ->
+    receive
+        {done, FromPid} -> FromPid ! ok;
+        {_, _, FromPid} -> FromPid ! error
+    end.
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+%% Tests for the mock_tcp functions.
+bad_recv_test() ->
+    Pid = create([{recv, <<"foobar">>}]),
+    ?assertError(_, recv(Pid, 10)).
+
+success_test() ->
+    Pid = create([{recv, <<"foobar">>}, {send, <<"baz">>}]),
+    %?assertError({unexpected_close, _}, close(Pid)),
+    ?assertEqual({ok, <<"foo">>}, recv(Pid, 3)),
+    ?assertEqual({ok, <<"bar">>}, recv(Pid, 3)),
+    ?assertEqual(ok, send(Pid, <<"baz">>)),
+    ?assertEqual(ok, close(Pid)),
+    %% The process will exit after close. Another recv will raise noreply.
+    ?assertError(noreply, recv(Pid, 3)).
+-endif.