mock_tcp.erl 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. %% @doc A module to be used where gen_tcp is expected.
  2. %%
  3. %% A "fake socket" is used in test where we need to mock socket communication.
  4. %% It is a pid maintaining a list of expected send and recv events.
  5. -module(mock_tcp).
  6. %% gen_tcp interface functions.
  7. -export([send/2, recv/2, recv/3]).
  8. %% Functions to setup the mock_tcp.
  9. -export([create/1, close/1]).
  10. %% @doc Creates a mock_tcp process with a buffer of expected recv/2,3 and send/2
  11. %% calls. The pid of the mock_tcp process is returned.
  12. -spec create([{recv, binary()} | {send, binary()}]) -> pid().
  13. create(ExpectedEvents) ->
  14. spawn_link(fun () -> loop(ExpectedEvents) end).
  15. %% @doc Receives NumBytes bytes from mock_tcp Pid. This function can be used
  16. %% as a replacement for gen_tcp:recv/2 in unit tests. If there not enough data
  17. %% in the mock_tcp's buffer, an error is raised.
  18. recv(Pid, NumBytes) ->
  19. Pid ! {recv, NumBytes, self()},
  20. receive
  21. {ok, Data} -> {ok, Data};
  22. error -> error({unexpected_recv, NumBytes})
  23. after 100 ->
  24. error(noreply)
  25. end.
  26. recv(Pid, NumBytes, _Timeout) ->
  27. recv(Pid, NumBytes).
  28. %% @doc Sends data to a mock_tcp. This can be used as replacement for
  29. %% gen_tcp:send/2 in unit tests. If the data sent is not what the mock_tcp
  30. %% expected, an error is raised.
  31. send(Pid, Data) ->
  32. Pid ! {send, iolist_to_binary(Data), self()},
  33. receive
  34. ok -> ok;
  35. error -> error({unexpected_send, Data})
  36. after 100 ->
  37. error(noreply)
  38. end.
  39. %% Stops the mock_tcp process. If the mock_tcp's buffer is not empty,
  40. %% an error is raised.
  41. close(Pid) ->
  42. Pid ! {done, self()},
  43. receive
  44. ok -> ok;
  45. {remains, Remains} -> error({unexpected_close, Remains})
  46. after 100 ->
  47. error(noreply)
  48. end.
  49. %% Used by create/1.
  50. loop(AllEvents = [{Func, Data} | Events]) ->
  51. receive
  52. {recv, NumBytes, FromPid} when Func == recv, NumBytes == size(Data) ->
  53. FromPid ! {ok, Data},
  54. loop(Events);
  55. {recv, NumBytes, FromPid} when Func == recv, NumBytes < size(Data) ->
  56. <<Data1:NumBytes/binary, Rest/binary>> = Data,
  57. FromPid ! {ok, Data1},
  58. loop([{recv, Rest} | Events]);
  59. {send, Bytes, FromPid} when Func == send, Bytes == Data ->
  60. FromPid ! ok,
  61. loop(Events);
  62. {send, Bytes, FromPid} when Func == send, size(Bytes) < size(Data) ->
  63. Size = size(Bytes),
  64. case Data of
  65. <<Bytes:Size/binary, Rest/binary>> ->
  66. FromPid ! ok,
  67. loop([{send, Rest} | Events]);
  68. _ ->
  69. FromPid ! error
  70. end;
  71. {_, _, FromPid} ->
  72. FromPid ! error;
  73. {done, FromPid} ->
  74. FromPid ! {remains, AllEvents}
  75. end;
  76. loop([]) ->
  77. receive
  78. {done, FromPid} -> FromPid ! ok;
  79. {_, _, FromPid} -> FromPid ! error
  80. end.
  81. -ifdef(TEST).
  82. -include_lib("eunit/include/eunit.hrl").
  83. %% Tests for the mock_tcp functions.
  84. bad_recv_test() ->
  85. Pid = create([{recv, <<"foobar">>}]),
  86. ?assertError(_, recv(Pid, 10)).
  87. success_test() ->
  88. Pid = create([{recv, <<"foobar">>}, {send, <<"baz">>}]),
  89. %?assertError({unexpected_close, _}, close(Pid)),
  90. ?assertEqual({ok, <<"foo">>}, recv(Pid, 3)),
  91. ?assertEqual({ok, <<"bar">>}, recv(Pid, 3)),
  92. ?assertEqual(ok, send(Pid, <<"baz">>)),
  93. ?assertEqual(ok, close(Pid)),
  94. %% The process will exit after close. Another recv will raise noreply.
  95. ?assertError(noreply, recv(Pid, 3)).
  96. -endif.