%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1998-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. %% %% %CopyrightEnd% %% -module(gen_tcp_api_SUITE). %% Tests the documented API for the gen_tcp functions. The "normal" cases %% are not tested here, because they are tested indirectly in this and %% and other test suites. -include_lib("test_server/include/test_server.hrl"). -include_lib("kernel/include/inet.hrl"). -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, init_per_testcase/2, end_per_testcase/2, t_connect_timeout/1, t_accept_timeout/1, t_connect_bad/1, t_recv_timeout/1, t_recv_eof/1, t_shutdown_write/1, t_shutdown_both/1, t_shutdown_error/1, t_fdopen/1, t_implicit_inet6/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [{group, t_accept}, {group, t_connect}, {group, t_recv}, t_shutdown_write, t_shutdown_both, t_shutdown_error, t_fdopen, t_implicit_inet6]. groups() -> [{t_accept, [], [t_accept_timeout]}, {t_connect, [], [t_connect_timeout, t_connect_bad]}, {t_recv, [], [t_recv_timeout, t_recv_eof]}, {t_sendfile, [], [sendfile]}]. init_per_suite(Config) -> Config. end_per_suite(_Config) -> ok. init_per_group(_GroupName, Config) -> Config. end_per_group(_GroupName, Config) -> Config. init_per_testcase(_Func, Config) -> Dog = test_server:timetrap(test_server:seconds(60)), [{watchdog, Dog}|Config]. end_per_testcase(_Func, Config) -> Dog = ?config(watchdog, Config), test_server:timetrap_cancel(Dog). %%% gen_tcp:accept/1,2 t_accept_timeout(doc) -> "Test that gen_tcp:accept/2 (with timeout) works."; t_accept_timeout(suite) -> []; t_accept_timeout(Config) when is_list(Config) -> ?line {ok, L} = gen_tcp:listen(0, []), ?line timeout({gen_tcp, accept, [L, 200]}, 0.2, 1.0). %%% gen_tcp:connect/X t_connect_timeout(doc) -> "Test that gen_tcp:connect/4 (with timeout) works."; t_connect_timeout(Config) when is_list(Config) -> %%?line BadAddr = {134,138,177,16}, %%?line TcpPort = 80, ?line {ok, BadAddr} = unused_ip(), ?line TcpPort = 45638, ?line ok = io:format("Connecting to ~p, port ~p", [BadAddr, TcpPort]), ?line connect_timeout({gen_tcp,connect,[BadAddr,TcpPort,[],200]}, 0.2, 5.0). t_connect_bad(doc) -> ["Test that gen_tcp:connect/3 handles non-existings hosts, and other ", "invalid things."]; t_connect_bad(suite) -> []; t_connect_bad(Config) when is_list(Config) -> ?line NonExistingPort = 45638, % Not in use, I hope. ?line {error, Reason1} = gen_tcp:connect(localhost, NonExistingPort, []), ?line io:format("Error for connection attempt to port not in use: ~p", [Reason1]), ?line {error, Reason2} = gen_tcp:connect("non-existing-host-xxx", 7, []), ?line io:format("Error for connection attempt to non-existing host: ~p", [Reason2]), ok. %%% gen_tcp:recv/X t_recv_timeout(doc) -> "Test that gen_tcp:recv/3 (with timeout works)."; t_recv_timeout(suite) -> []; t_recv_timeout(Config) when is_list(Config) -> ?line {ok, L} = gen_tcp:listen(0, []), ?line {ok, Port} = inet:port(L), ?line {ok, Client} = gen_tcp:connect(localhost, Port, [{active, false}]), ?line {ok, _A} = gen_tcp:accept(L), ?line timeout({gen_tcp, recv, [Client, 0, 200]}, 0.2, 5.0). t_recv_eof(doc) -> "Test that end of file on a socket is reported correctly."; t_recv_eof(suite) -> []; t_recv_eof(Config) when is_list(Config) -> ?line {ok, L} = gen_tcp:listen(0, []), ?line {ok, Port} = inet:port(L), ?line {ok, Client} = gen_tcp:connect(localhost, Port, [{active, false}]), ?line {ok, A} = gen_tcp:accept(L), ?line ok = gen_tcp:close(A), ?line {error, closed} = gen_tcp:recv(Client, 0), ok. %%% gen_tcp:shutdown/2 t_shutdown_write(Config) when is_list(Config) -> ?line {ok, L} = gen_tcp:listen(0, []), ?line {ok, Port} = inet:port(L), ?line {ok, Client} = gen_tcp:connect(localhost, Port, [{active, false}]), ?line {ok, A} = gen_tcp:accept(L), ?line ok = gen_tcp:shutdown(A, write), ?line {error, closed} = gen_tcp:recv(Client, 0), ok. t_shutdown_both(Config) when is_list(Config) -> ?line {ok, L} = gen_tcp:listen(0, []), ?line {ok, Port} = inet:port(L), ?line {ok, Client} = gen_tcp:connect(localhost, Port, [{active, false}]), ?line {ok, A} = gen_tcp:accept(L), ?line ok = gen_tcp:shutdown(A, read_write), ?line {error, closed} = gen_tcp:recv(Client, 0), ok. t_shutdown_error(Config) when is_list(Config) -> ?line {ok, L} = gen_tcp:listen(0, []), ?line {error, enotconn} = gen_tcp:shutdown(L, read_write), ?line ok = gen_tcp:close(L), ?line {error, closed} = gen_tcp:shutdown(L, read_write), ok. %%% gen_tcp:fdopen/2 t_fdopen(Config) when is_list(Config) -> ?line Question = "Aaaa... Long time ago in a small town in Germany,", ?line Question1 = list_to_binary(Question), ?line Question2 = [<<"Aaaa">>, "... ", $L, <<>>, $o, "ng time ago ", ["in ", [], <<"a small town">>, [" in Germany,", <<>>]]], ?line Question1 = iolist_to_binary(Question2), ?line Answer = "there was a shoemaker, Schumacher was his name.", ?line {ok, L} = gen_tcp:listen(0, [{active, false}]), ?line {ok, Port} = inet:port(L), ?line {ok, Client} = gen_tcp:connect(localhost, Port, [{active, false}]), ?line {ok, A} = gen_tcp:accept(L), ?line {ok, FD} = prim_inet:getfd(A), ?line {ok, Server} = gen_tcp:fdopen(FD, []), ?line ok = gen_tcp:send(Client, Question), ?line {ok, Question} = gen_tcp:recv(Server, length(Question), 2000), ?line ok = gen_tcp:send(Client, Question1), ?line {ok, Question} = gen_tcp:recv(Server, length(Question), 2000), ?line ok = gen_tcp:send(Client, Question2), ?line {ok, Question} = gen_tcp:recv(Server, length(Question), 2000), ?line ok = gen_tcp:send(Server, Answer), ?line {ok, Answer} = gen_tcp:recv(Client, length(Answer), 2000), ?line ok = gen_tcp:close(Client), ?line {error,closed} = gen_tcp:recv(A, 1, 2000), ?line ok = gen_tcp:close(Server), ?line ok = gen_tcp:close(A), ?line ok = gen_tcp:close(L), ok. %%% implicit inet6 option to api functions t_implicit_inet6(Config) when is_list(Config) -> ?line Host = ok(inet:gethostname()), ?line case inet:getaddr(Host, inet6) of {ok,Addr} -> ?line t_implicit_inet6(Host, Addr); {error,Reason} -> {skip, "Can not look up IPv6 address: " ++atom_to_list(Reason)} end. t_implicit_inet6(Host, Addr) -> ?line case gen_tcp:listen(0, [inet6]) of {ok,S1} -> ?line Loopback = {0,0,0,0,0,0,0,1}, ?line io:format("~s ~p~n", ["::1",Loopback]), ?line implicit_inet6(S1, Loopback), ?line ok = gen_tcp:close(S1), %% ?line Localhost = "localhost", ?line Localaddr = ok(inet:getaddr(Localhost, inet6)), ?line io:format("~s ~p~n", [Localhost,Localaddr]), ?line S2 = ok(gen_tcp:listen(0, [{ip,Localaddr}])), ?line implicit_inet6(S2, Localaddr), ?line ok = gen_tcp:close(S2), %% ?line io:format("~s ~p~n", [Host,Addr]), ?line S3 = ok(gen_tcp:listen(0, [{ifaddr,Addr}])), ?line implicit_inet6(S3, Addr), ?line ok = gen_tcp:close(S3); {error,_} -> {skip,"IPv6 not supported"} end. implicit_inet6(S, Addr) -> ?line P = ok(inet:port(S)), ?line S2 = ok(gen_tcp:connect(Addr, P, [])), ?line P2 = ok(inet:port(S2)), ?line S1 = ok(gen_tcp:accept(S)), ?line P1 = P = ok(inet:port(S1)), ?line {Addr,P2} = ok(inet:peername(S1)), ?line {Addr,P1} = ok(inet:peername(S2)), ?line {Addr,P1} = ok(inet:sockname(S1)), ?line {Addr,P2} = ok(inet:sockname(S2)), ?line ok = gen_tcp:close(S2), ?line ok = gen_tcp:close(S1). sendfile() -> [{timetrap, {seconds, 5}}]. sendfile_supported({unix,linux}) -> true; sendfile_supported({unix,sunos}) -> true; sendfile_supported({unix,freebsd}) -> true; sendfile_supported({unix,dragonfly}) -> true; sendfile_supported({unix,darwin}) -> true; %% TODO: enable win32 once TransmitFile based implemenation written properly %% sendfile_supported({win32,_}) -> true; sendfile_supported(_) -> false. sendfile(Config) when is_list(Config) -> case sendfile_supported(os:type()) of true -> ?line Data = ?config(data_dir, Config), ?line Real = filename:join(Data, "realmen.html"), Host = "localhost", %% TODO: find another way to test for {error, posix_error()}? %% Disabled because with driver_select I cannot test for %% invalid out_fd %% ?line {error, Error} = file:sendfile(Real, -1), %% ?line test_server:format("sendfile error = ~p", [Error]), %% %% Unix ebadf, Windows eio %% ?line true = Error =:= ebadf orelse Error =:= eio, ?line ok = sendfile_send(Host, Real); false -> {skip, "sendfile not supported on this platform"} end. %% TODO: consolidate tests and reduce code sendfile_send(Host, File) -> {Size, _Md5} = FileInfo = sendfile_file_info(File), spawn_link(?MODULE, sendfile_server, [self()]), receive {server, Port} -> ?line {ok, Sock} = gen_tcp:connect(Host, Port, [binary,{packet,0}]), ?line {ok, Size} = file:sendfile(File, Sock), ?line ok = gen_tcp:close(Sock), receive {ok, Bin} -> ?line FileInfo = sendfile_bin_info(Bin), ok end end. sendfile_server(ClientPid) -> ?line {ok, LSock} = gen_tcp:listen(0, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]), ?line {ok, Port} = inet:port(LSock), ClientPid ! {server, Port}, ?line {ok, Sock} = gen_tcp:accept(LSock), ?line {ok, Bin} = sendfile_do_recv(Sock, []), ?line ok = gen_tcp:close(Sock), ClientPid ! {ok, Bin}. -define(SENDFILE_TIMEOUT, 5000). sendfile_do_recv(Sock, Bs) -> case gen_tcp:recv(Sock, 0, ?SENDFILE_TIMEOUT) of {ok, B} -> sendfile_do_recv(Sock, [B|Bs]); {error, closed} -> {ok, lists:reverse(Bs)} end. sendfile_file_info(File) -> {ok, #file_info{size = Size}} = file:read_file_info(File), {ok, Data} = file:read_file(File), Md5 = erlang:md5(Data), {Size, Md5}. sendfile_bin_info(Data) -> Size = lists:foldl(fun(E,Sum) -> size(E) + Sum end, 0, Data), Md5 = erlang:md5(Data), {Size, Md5}. %%% Utilities %% Calls M:F/length(A), which should return a timeout error, and complete %% within the given time. timeout({M,F,A}, Lower, Upper) -> case test_server:timecall(M, F, A) of {Time, Result} when Time < Lower -> test_server:fail({too_short_time, Time, Result}); {Time, Result} when Time > Upper -> test_server:fail({too_long_time, Time, Result}); {_, {error, timeout}} -> ok; {_, Result} -> test_server:fail({unexpected_result, Result}) end. connect_timeout({M,F,A}, Lower, Upper) -> case test_server:timecall(M, F, A) of {Time, Result} when Time < Lower -> case Result of {error,econnrefused=E} -> {comment,"Not tested -- got error "++atom_to_list(E)}; {error,enetunreach=E} -> {comment,"Not tested -- got error "++atom_to_list(E)}; {ok,Socket} -> % What the... Pinfo = erlang:port_info(Socket), Db = inet_db:lookup_socket(Socket), Peer = inet:peername(Socket), test_server:fail({too_short_time, Time, [Result,Pinfo,Db,Peer]}); _ -> test_server:fail({too_short_time, Time, Result}) end; {Time, Result} when Time > Upper -> test_server:fail({too_long_time, Time, Result}); {_, {error, timeout}} -> ok; {_, Result} -> test_server:fail({unexpected_result, Result}) end. %% Try to obtain an unused IP address in the local network. unused_ip() -> ?line {ok, Host} = inet:gethostname(), ?line {ok, Hent} = inet:gethostbyname(Host), ?line #hostent{h_addr_list=[{A, B, C, _D}|_]} = Hent, %% Note: In our net, addresses below 16 are reserved for routers and %% other strange creatures. ?line IP = unused_ip(A, B, C, 16), io:format("we = ~p, unused_ip = ~p~n", [Hent, IP]), IP. unused_ip(_, _, _, 255) -> error; unused_ip(A, B, C, D) -> case inet:gethostbyaddr({A, B, C, D}) of {ok, _} -> unused_ip(A, B, C, D+1); {error, _} -> {ok, {A, B, C, D}} end. ok({ok,V}) -> V.