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