mysql_protocol_tests.erl 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. %% @doc Eunit test cases for the mysql_protocol module.
  2. -module(mysql_protocol_tests).
  3. -include_lib("eunit/include/eunit.hrl").
  4. -include("protocol.hrl").
  5. -include("records.hrl").
  6. resultset_test() ->
  7. %% A query that returns a result set in the text protocol.
  8. Query = <<"SELECT @@version_comment">>,
  9. ExpectedReq = <<(size(Query) + 1):24/little, 0, ?COM_QUERY, Query/binary>>,
  10. ExpectedResponse = hexdump_to_bin(
  11. "01 00 00 01 01|27 00 00 02 03 64 65 66 00 00 00 .....'....def..."
  12. "11 40 40 76 65 72 73 69 6f 6e 5f 63 6f 6d 6d 65 .@@version_comme"
  13. "6e 74 00 0c 08 00 1c 00 00 00 fd 00 00 1f 00 00| nt.............."
  14. "05 00 00 03 fe 00 00 02 00|1d 00 00 04 1c 4d 79 ..............My"
  15. "53 51 4c 20 43 6f 6d 6d 75 6e 69 74 79 20 53 65 SQL Community Se"
  16. "72 76 65 72 20 28 47 50 4c 29|05 00 00 05 fe 00 rver (GPL)......"
  17. "00 02 00 ..."),
  18. ExpectedCommunication = [{send, ExpectedReq},
  19. {recv, ExpectedResponse}],
  20. FakeSock = fakesocket_create(ExpectedCommunication),
  21. SendFun = fun (Data) -> fakesocket_send(FakeSock, Data) end,
  22. RecvFun = fun (Size) -> fakesocket_recv(FakeSock, Size) end,
  23. ResultSet = mysql_protocol:query(Query, SendFun, RecvFun),
  24. fakesocket_close(FakeSock),
  25. ?assertMatch(#text_resultset{column_definitions =
  26. [#column_definition{
  27. name = <<"@@version_comment">>}],
  28. rows = [[<<"MySQL Community Server (GPL)">>]]},
  29. ResultSet),
  30. ok.
  31. resultset_error_test() ->
  32. %% A query that returns a response starting as a result set but then
  33. %% interupts itself and decides that it is an error.
  34. Query = <<"EXPLAIN SELECT * FROM dual;">>,
  35. ExpectedReq = <<(size(Query) + 1):24/little, 0, ?COM_QUERY, Query/binary>>,
  36. ExpectedResponse = hexdump_to_bin(
  37. "01 00 00 01 0a 18 00 00 02 03 64 65 66 00 00 00 ..........def..."
  38. "02 69 64 00 0c 3f 00 03 00 00 00 08 a1 00 00 00 .id..?.........."
  39. "00 21 00 00 03 03 64 65 66 00 00 00 0b 73 65 6c .!....def....sel"
  40. "65 63 74 5f 74 79 70 65 00 0c 08 00 13 00 00 00 ect_type........"
  41. "fd 01 00 1f 00 00 1b 00 00 04 03 64 65 66 00 00 ...........def.."
  42. "00 05 74 61 62 6c 65 00 0c 08 00 40 00 00 00 fd ..table....@...."
  43. "00 00 1f 00 00 1a 00 00 05 03 64 65 66 00 00 00 ..........def..."
  44. "04 74 79 70 65 00 0c 08 00 0a 00 00 00 fd 00 00 .type..........."
  45. "1f 00 00 23 00 00 06 03 64 65 66 00 00 00 0d 70 ...#....def....p"
  46. "6f 73 73 69 62 6c 65 5f 6b 65 79 73 00 0c 08 00 ossible_keys...."
  47. "00 10 00 00 fd 00 00 1f 00 00 19 00 00 07 03 64 ...............d"
  48. "65 66 00 00 00 03 6b 65 79 00 0c 08 00 40 00 00 ef....key....@.."
  49. "00 fd 00 00 1f 00 00 1d 00 00 08 03 64 65 66 00 ............def."
  50. "00 00 07 6b 65 79 5f 6c 65 6e 00 0c 08 00 00 10 ...key_len......"
  51. "00 00 fd 00 00 1f 00 00 19 00 00 09 03 64 65 66 .............def"
  52. "00 00 00 03 72 65 66 00 0c 08 00 00 04 00 00 fd ....ref........."
  53. "00 00 1f 00 00 1a 00 00 0a 03 64 65 66 00 00 00 ..........def..."
  54. "04 72 6f 77 73 00 0c 3f 00 0a 00 00 00 08 a0 00 .rows..?........"
  55. "00 00 00 1b 00 00 0b 03 64 65 66 00 00 00 05 45 ........def....E"
  56. "78 74 72 61 00 0c 08 00 ff 00 00 00 fd 01 00 1f xtra............"
  57. "00 00 05 00 00 0c fe 00 00 02 00 17 00 00 0d ff ................"
  58. "48 04 23 48 59 30 30 30 4e 6f 20 74 61 62 6c 65 H.#HY000No table"
  59. "73 20 75 73 65 64 s used"),
  60. Sock = fakesocket_create([{send, ExpectedReq}, {recv, ExpectedResponse}]),
  61. SendFun = fun (Data) -> fakesocket_send(Sock, Data) end,
  62. RecvFun = fun (Size) -> fakesocket_recv(Sock, Size) end,
  63. Result = mysql_protocol:query(Query, SendFun, RecvFun),
  64. ?assertMatch(#error{}, Result),
  65. fakesocket_close(Sock),
  66. ok.
  67. prepare_test() ->
  68. %% Prepared statement. The example from "14.7.4 COM_STMT_PREPARE" in the
  69. %% "MySQL Internals" guide.
  70. Query = <<"SELECT CONCAT(?, ?) AS col1">>,
  71. ExpectedReq = hexdump_to_bin(
  72. "1c 00 00 00 16 53 45 4c 45 43 54 20 43 4f 4e 43 .....SELECT CONC"
  73. "41 54 28 3f 2c 20 3f 29 20 41 53 20 63 6f 6c 31 AT(?, ?) AS col1"
  74. ),
  75. ExpectedResp = hexdump_to_bin(
  76. "0c 00 00 01 00 01 00 00 00 01 00 02 00 00 00 00| ................"
  77. "17 00 00 02 03 64 65 66 00 00 00 01 3f 00 0c 3f .....def....?..?"
  78. "00 00 00 00 00 fd 80 00 00 00 00|17 00 00 03 03 ................"
  79. "64 65 66 00 00 00 01 3f 00 0c 3f 00 00 00 00 00 def....?..?....."
  80. "fd 80 00 00 00 00|05 00 00 04 fe 00 00 02 00|1a ................"
  81. "00 00 05 03 64 65 66 00 00 00 04 63 6f 6c 31 00 ....def....col1."
  82. "0c 3f 00 00 00 00 00 fd 80 00 1f 00 00|05 00 00 .?.............."
  83. "06 fe 00 00 02 00 ......"),
  84. Sock = fakesocket_create([{send, ExpectedReq}, {recv, ExpectedResp}]),
  85. SendFun = fun (Data) -> fakesocket_send(Sock, Data) end,
  86. RecvFun = fun (Size) -> fakesocket_recv(Sock, Size) end,
  87. Result = mysql_protocol:prepare(Query, SendFun, RecvFun),
  88. fakesocket_close(Sock),
  89. ?assertMatch(#prepared{statement_id = StmtId,
  90. params = [#column_definition{name = <<"?">>},
  91. #column_definition{name = <<"?">>}],
  92. columns = [#column_definition{name = <<"col1">>}],
  93. warning_count = 0} when is_integer(StmtId),
  94. Result),
  95. ok.
  96. %% --- Helper functions for the above tests ---
  97. %% Convert hex dumps to binaries. This is a helper function for the tests.
  98. %% This function is also tested below.
  99. hexdump_to_bin(HexDump) ->
  100. hexdump_to_bin(iolist_to_binary(HexDump), <<>>).
  101. hexdump_to_bin(<<Line:50/binary, _Junk:20/binary, Rest/binary>>, Acc) ->
  102. hexdump_to_bin(Line, Rest, Acc);
  103. hexdump_to_bin(<<Line:50/binary, _Junk/binary>>, Acc) ->
  104. %% last line (shorter than 70)
  105. hexdump_to_bin(Line, <<>>, Acc);
  106. hexdump_to_bin(<<>>, Acc) ->
  107. Acc.
  108. hexdump_to_bin(Line, Rest, Acc) ->
  109. HexNums = re:split(Line, <<"[ |]+">>, [{return, list}, trim]),
  110. Acc1 = lists:foldl(fun (HexNum, Acc0) ->
  111. {ok, [Byte], []} = io_lib:fread("~16u", HexNum),
  112. <<Acc0/binary, Byte:8>>
  113. end,
  114. Acc,
  115. HexNums),
  116. hexdump_to_bin(Rest, Acc1).
  117. hexdump_to_bin_test() ->
  118. HexDump =
  119. "0e 00 00 00 03 73 65 6c 65 63 74 20 55 53 45 52 .....select USER"
  120. "28 29 ()",
  121. Expect = <<16#0e, 16#00, 16#00, 16#00, 16#03, 16#73, 16#65, 16#6c,
  122. 16#65, 16#63, 16#74, 16#20, 16#55, 16#53, 16#45, 16#52,
  123. 16#28, 16#29>>,
  124. ?assertEqual(Expect, hexdump_to_bin(HexDump)).
  125. %% --- Fake socket ---
  126. %%
  127. %% A "fake socket" is used in test where we need to mock socket communication.
  128. %% It is a pid maintaining a list of expected send and recv events.
  129. %% @doc Creates a fakesocket process with a buffer of expected recv and send
  130. %% calls. The pid of the fakesocket process is returned.
  131. -spec fakesocket_create([{recv, binary()} | {send, binary()}]) -> pid().
  132. fakesocket_create(ExpectedEvents) ->
  133. spawn_link(fun () -> fakesocket_loop(ExpectedEvents) end).
  134. %% @doc Receives NumBytes bytes from fakesocket Pid. This function can be used
  135. %% as a replacement for gen_tcp:recv/2 in unit tests. If there not enough data
  136. %% in the fakesocket's buffer, an error is raised.
  137. fakesocket_recv(Pid, NumBytes) ->
  138. Pid ! {recv, NumBytes, self()},
  139. receive
  140. {ok, Data} -> {ok, Data};
  141. error -> error({unexpected_recv, NumBytes})
  142. after 100 ->
  143. error(noreply)
  144. end.
  145. %% @doc Sends data to fa fakesocket. This can be used as replacement for
  146. %% gen_tcp:send/2 in unit tests. If the data sent is not what the fakesocket
  147. %% expected, an error is raised.
  148. fakesocket_send(Pid, Data) ->
  149. Pid ! {send, iolist_to_binary(Data), self()},
  150. receive
  151. ok -> ok;
  152. error -> error({unexpected_send, Data})
  153. after 100 ->
  154. error(noreply)
  155. end.
  156. %% Stops the fakesocket process. If the fakesocket's buffer is not empty,
  157. %% an error is raised.
  158. fakesocket_close(Pid) ->
  159. Pid ! {done, self()},
  160. receive
  161. ok -> ok;
  162. {remains, Remains} -> error({unexpected_close, Remains})
  163. after 100 ->
  164. error(noreply)
  165. end.
  166. %% Used by fakesocket_create/1.
  167. fakesocket_loop(AllEvents = [{Func, Data} | Events]) ->
  168. receive
  169. {recv, NumBytes, FromPid} when Func == recv, NumBytes == size(Data) ->
  170. FromPid ! {ok, Data},
  171. fakesocket_loop(Events);
  172. {recv, NumBytes, FromPid} when Func == recv, NumBytes < size(Data) ->
  173. <<Data1:NumBytes/binary, Rest/binary>> = Data,
  174. FromPid ! {ok, Data1},
  175. fakesocket_loop([{recv, Rest} | Events]);
  176. {send, Bytes, FromPid} when Func == send, Bytes == Data ->
  177. FromPid ! ok,
  178. fakesocket_loop(Events);
  179. {send, Bytes, FromPid} when Func == send, size(Bytes) < size(Data) ->
  180. Size = size(Bytes),
  181. case Data of
  182. <<Bytes:Size/binary, Rest/binary>> ->
  183. FromPid ! ok,
  184. fakesocket_loop([{send, Rest} | Events]);
  185. _ ->
  186. FromPid ! error
  187. end;
  188. {_, _, FromPid} ->
  189. FromPid ! error;
  190. {done, FromPid} ->
  191. FromPid ! {remains, AllEvents}
  192. end;
  193. fakesocket_loop([]) ->
  194. receive
  195. {done, FromPid} -> FromPid ! ok;
  196. {_, _, FromPid} -> FromPid ! error
  197. end.
  198. %% Tests for the fakesocket functions.
  199. fakesocket_bad_recv_test() ->
  200. Pid = fakesocket_create([{recv, <<"foobar">>}]),
  201. ?assertError(_, fakesocket_recv(Pid, 10)).
  202. fakesocket_success_test() ->
  203. Pid = fakesocket_create([{recv, <<"foobar">>}, {send, <<"baz">>}]),
  204. %?assertError({unexpected_close, _}, fakesocket_close(Pid)),
  205. ?assertEqual({ok, <<"foo">>}, fakesocket_recv(Pid, 3)),
  206. ?assertEqual({ok, <<"bar">>}, fakesocket_recv(Pid, 3)),
  207. ?assertEqual(ok, fakesocket_send(Pid, <<"baz">>)),
  208. ?assertEqual(ok, fakesocket_close(Pid)),
  209. %% The process will exit after close. Another recv will raise noreply.
  210. ?assertError(noreply, fakesocket_recv(Pid, 3)).