%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2018-2018. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(socket_SUITE).
-include_lib("common_test/include/ct.hrl").
-include_lib("common_test/include/ct_event.hrl").
%% Suite exports
-export([suite/0, all/0, groups/0]).
-export([init_per_suite/1, end_per_suite/1,
init_per_testcase/2, end_per_testcase/2]).
%% Test cases
-export([
%% API Basic
api_b_open_and_close_udp4/1,
api_b_open_and_close_tcp4/1,
api_b_sendto_and_recvfrom_udp4/1,
api_b_sendmsg_and_recvmsg_udp4/1,
api_b_send_and_recv_tcp4/1,
api_b_sendmsg_and_recvmsg_tcp4/1,
%% API Options
api_opt_simple_otp_options/1,
api_opt_simple_otp_controlling_process/1,
%% API Operation Timeout
api_to_connect_tcp4/1,
api_to_connect_tcp6/1,
api_to_accept_tcp4/1,
api_to_accept_tcp6/1,
api_to_send_tcp4/1,
api_to_send_tcp6/1,
api_to_sendapi_to_udp4/1,
api_to_sendapi_to_udp6/1,
api_to_sendmsg_tcp4/1,
api_to_sendmsg_tcp6/1,
api_to_recv_udp4/1,
api_to_recv_udp6/1,
api_to_recv_tcp4/1,
api_to_recv_tcp6/1,
api_to_recvfrom_udp4/1,
api_to_recvfrom_udp6/1,
api_to_recvmsg_udp4/1,
api_to_recvmsg_udp6/1,
api_to_recvmsg_tcp4/1,
api_to_recvmsg_tcp6/1
%% Tickets
]).
%% Internal exports
%% -export([]).
-type initial_evaluator_state() :: map().
-type evaluator_state() :: term().
-type command_fun() ::
fun((State :: evaluator_state()) -> ok) |
fun((State :: evaluator_state()) -> {ok, evaluator_state()}) |
fun((State :: evaluator_state()) -> {error, term()}).
-type command() :: #{desc := string(),
cmd := command_fun()}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-define(BASIC_REQ, <<"hejsan">>).
-define(BASIC_REP, <<"hoppsan">>).
-define(FAIL(R), exit(R)).
-define(SLEEP(T), receive after T -> ok end).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
suite() ->
[{ct_hooks,[ts_install_cth]},
{timetrap,{minutes,1}}].
all() ->
[
{group, api}
%% {group, tickets}
].
groups() ->
[{api, [], api_cases()},
{api_basic, [], api_basic_cases()},
{api_op_with_timeout, [], api_op_with_timeout_cases()},
{api_options, [], api_options_cases()}
%% {tickets, [], ticket_cases()}
].
api_cases() ->
[
{group, api_basic},
{group, api_options},
{group, api_op_with_timeout}
].
api_basic_cases() ->
[
api_b_open_and_close_udp4,
api_b_open_and_close_tcp4,
api_b_sendto_and_recvfrom_udp4,
api_b_sendmsg_and_recvmsg_udp4,
api_b_send_and_recv_tcp4,
api_b_sendmsg_and_recvmsg_tcp4
].
api_options_cases() ->
[
api_opt_simple_otp_options,
api_opt_simple_otp_controlling_process
].
api_op_with_timeout_cases() ->
[
api_to_connect_tcp4,
api_to_connect_tcp6,
api_to_accept_tcp4,
api_to_accept_tcp6,
api_to_send_tcp4,
api_to_send_tcp6,
api_to_sendapi_to_udp4,
api_to_sendapi_to_udp6,
api_to_sendmsg_tcp4,
api_to_sendmsg_tcp6,
api_to_recv_udp4,
api_to_recv_udp6,
api_to_recv_tcp4,
api_to_recv_tcp6,
api_to_recvfrom_udp4,
api_to_recvfrom_udp6,
api_to_recvmsg_udp4,
api_to_recvmsg_udp6,
api_to_recvmsg_tcp4,
api_to_recvmsg_tcp6
].
%% ticket_cases() ->
%% [].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init_per_suite(Config) ->
Config.
end_per_suite(_) ->
ok.
init_per_testcase(_TC, Config) ->
Config.
end_per_testcase(_TC, Config) ->
Config.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Basically open (create) and close an IPv4 UDP (dgram) socket.
%% With some extra checks...
api_b_open_and_close_udp4(suite) ->
[];
api_b_open_and_close_udp4(doc) ->
[];
api_b_open_and_close_udp4(_Config) when is_list(_Config) ->
tc_begin(api_b_open_and_close_udp4),
State = #{domain => inet,
type => dgram,
protocol => udp},
ok = api_b_open_and_close(State),
tc_end().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Basically open (create) and close an IPv4 TCP (stream) socket.
%% With some extra checks...
api_b_open_and_close_tcp4(suite) ->
[];
api_b_open_and_close_tcp4(doc) ->
[];
api_b_open_and_close_tcp4(_Config) when is_list(_Config) ->
tc_begin(api_b_open_and_close_tcp4),
State = #{domain => inet,
type => stream,
protocol => tcp},
ok = api_b_open_and_close(State),
tc_end().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
api_b_open_and_close(InitState) ->
Seq =
[
#{desc => "open",
cmd => fun(#{domain := Domain,
type := Type,
protocol := Protocol} = S) ->
Res = socket:open(Domain, Type, Protocol),
{ok, {S, Res}}
end},
#{desc => "validate open",
cmd => fun({S, {ok, Sock}}) ->
NewS = S#{socket => Sock},
{ok, NewS};
({_, {error, _} = ERROR}) ->
ERROR
end},
#{desc => "get domain (maybe)",
cmd => fun(#{socket := Sock} = S) ->
Res = socket:getopt(Sock, socket, domain),
{ok, {S, Res}}
end},
#{desc => "validate domain (maybe)",
cmd => fun({#{domain := Domain} = S, {ok, Domain}}) ->
{ok, S};
({#{domain := ExpDomain}, {ok, Domain}}) ->
{error, {unexpected_domain, ExpDomain, Domain}};
%% Some platforms do not support this option
({S, {error, einval}}) ->
{ok, S};
({_, {error, _} = ERROR}) ->
ERROR
end},
#{desc => "get type",
cmd => fun(#{socket := Sock} = State) ->
Res = socket:getopt(Sock, socket, type),
{ok, {State, Res}}
end},
#{desc => "validate type",
cmd => fun({#{type := Type} = State, {ok, Type}}) ->
{ok, State};
({#{type := ExpType}, {ok, Type}}) ->
{error, {unexpected_type, ExpType, Type}};
({_, {error, _} = ERROR}) ->
ERROR
end},
#{desc => "get protocol",
cmd => fun(#{socket := Sock} = State) ->
Res = socket:getopt(Sock, socket, protocol),
{ok, {State, Res}}
end},
#{desc => "validate protocol",
cmd => fun({#{protocol := Protocol} = State, {ok, Protocol}}) ->
{ok, State};
({#{protocol := ExpProtocol}, {ok, Protocol}}) ->
{error, {unexpected_type, ExpProtocol, Protocol}};
({_, {error, _} = ERROR}) ->
ERROR
end},
#{desc => "get controlling-process",
cmd => fun(#{socket := Sock} = State) ->
Res = socket:getopt(Sock, otp, controlling_process),
{ok, {State, Res}}
end},
#{desc => "validate controlling-process",
cmd => fun({State, {ok, Pid}}) ->
case self() of
Pid ->
{ok, State};
_ ->
{error, {unexpected_owner, Pid}}
end;
({_, {error, _} = ERROR}) ->
ERROR
end},
#{desc => "close socket",
cmd => fun(#{socket := Sock} = State) ->
Res = socket:close(Sock),
{ok, {State, Res}}
end},
#{desc => "validate socket close",
cmd => fun({_, ok}) ->
{ok, normal};
({_, {error, _} = ERROR}) ->
ERROR
end}],
Evaluator = evaluator_start("tester", Seq, InitState),
ok = await_evaluator_finish([Evaluator]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Basically send and receive on an IPv4 UDP (dgram) socket using
%% sendto and recvfrom..
api_b_sendto_and_recvfrom_udp4(suite) ->
[];
api_b_sendto_and_recvfrom_udp4(doc) ->
[];
api_b_sendto_and_recvfrom_udp4(_Config) when is_list(_Config) ->
tc_begin(api_b_sendto_and_recvfrom_udp4),
Send = fun(Sock, Data, Dest) ->
socket:sendto(Sock, Data, Dest)
end,
Recv = fun(Sock) ->
socket:recvfrom(Sock)
end,
InitState = #{domain => inet,
send => Send,
recv => Recv},
ok = api_b_send_and_recv_udp(InitState),
tc_end().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Basically send and receive on an IPv4 UDP (dgram) socket
%% using sendmsg and recvmsg.
api_b_sendmsg_and_recvmsg_udp4(suite) ->
[];
api_b_sendmsg_and_recvmsg_udp4(doc) ->
[];
api_b_sendmsg_and_recvmsg_udp4(_Config) when is_list(_Config) ->
tc_begin(api_b_sendmsg_and_recvmsg_udp4),
Send = fun(Sock, Data, Dest) ->
%% CMsgHdr = #{level => ip, type => tos, data => reliability},
%% CMsgHdrs = [CMsgHdr],
MsgHdr = #{addr => Dest,
%% ctrl => CMsgHdrs,
iov => [Data]},
socket:sendmsg(Sock, MsgHdr)
end,
Recv = fun(Sock) ->
case socket:recvmsg(Sock) of
{ok, #{addr := Source,
iov := [Data]}} ->
{ok, {Source, Data}};
{error, _} = ERROR ->
ERROR
end
end,
InitState = #{domain => inet,
send => Send,
recv => Recv},
ok = api_b_send_and_recv_udp(InitState),
tc_end().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
api_b_send_and_recv_udp(InitState) ->
Seq =
[
#{desc => "local address",
cmd => fun(#{domain := Domain} = State) ->
LAddr = which_local_addr(Domain),
LSA = #{family => Domain, addr => LAddr},
{ok, State#{lsa => LSA}}
end},
#{desc => "open src socket",
cmd => fun(#{domain := Domain} = State) ->
Sock = sock_open(Domain, dgram, udp),
SASrc = sock_sockname(Sock),
{ok, State#{sock_src => Sock, sa_src => SASrc}}
end},
#{desc => "bind src",
cmd => fun(#{sock_src := Sock, lsa := LSA}) ->
sock_bind(Sock, LSA),
ok
end},
#{desc => "sockname src socket",
cmd => fun(#{sock_src := Sock} = State) ->
SASrc = sock_sockname(Sock),
%% ei("src sockaddr: ~p", [SASrc]),
{ok, State#{sa_src => SASrc}}
end},
#{desc => "open dst socket",
cmd => fun(#{domain := Domain} = State) ->
Sock = sock_open(Domain, dgram, udp),
{ok, State#{sock_dst => Sock}}
end},
#{desc => "bind dst",
cmd => fun(#{sock_dst := Sock, lsa := LSA}) ->
sock_bind(Sock, LSA),
ok
end},
#{desc => "sockname dst socket",
cmd => fun(#{sock_dst := Sock} = State) ->
SADst = sock_sockname(Sock),
%% ei("dst sockaddr: ~p", [SADst]),
{ok, State#{sa_dst => SADst}}
end},
#{desc => "send req (to dst)",
cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
ok = Send(Sock, ?BASIC_REQ, Dst)
end},
#{desc => "recv req (from src)",
cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
{ok, {Src, ?BASIC_REQ}} = Recv(Sock),
ok
end},
#{desc => "send rep (to src)",
cmd => fun(#{sock_dst := Sock, sa_src := Src, send := Send}) ->
ok = Send(Sock, ?BASIC_REP, Src)
end},
#{desc => "recv rep (from dst)",
cmd => fun(#{sock_src := Sock, sa_dst := Dst, recv := Recv}) ->
{ok, {Dst, ?BASIC_REP}} = Recv(Sock),
ok
end},
#{desc => "close src socket",
cmd => fun(#{sock_src := Sock}) ->
ok = socket:close(Sock)
end},
#{desc => "close dst socket",
cmd => fun(#{sock_dst := Sock}) ->
ok = socket:close(Sock),
{ok, normal}
end}
],
Evaluator = evaluator_start("tester", Seq, InitState),
ok = await_evaluator_finish([Evaluator]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Basically send and receive using the "common" functions (send and recv)
%% on an IPv4 TCP (stream) socket.
api_b_send_and_recv_tcp4(suite) ->
[];
api_b_send_and_recv_tcp4(doc) ->
[];
api_b_send_and_recv_tcp4(_Config) when is_list(_Config) ->
tc_begin(api_b_send_and_recv_tcp4),
Send = fun(Sock, Data) ->
socket:send(Sock, Data)
end,
Recv = fun(Sock) ->
socket:recv(Sock)
end,
InitState = #{domain => inet,
send => Send,
recv => Recv},
ok = api_b_send_and_recv_tcp(InitState),
tc_end().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Basically send and receive using the msg functions (sendmsg and recvmsg)
%% on an IPv4 TCP (stream) socket.
api_b_sendmsg_and_recvmsg_tcp4(suite) ->
[];
api_b_sendmsg_and_recvmsg_tcp4(doc) ->
[];
api_b_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) ->
tc_begin(api_b_sendmsg_and_recvmsg_tcp4),
Send = fun(Sock, Data) ->
MsgHdr = #{iov => [Data]},
socket:sendmsg(Sock, MsgHdr)
end,
Recv = fun(Sock) ->
case socket:recvmsg(Sock) of
{ok, #{addr := undefined,
iov := [Data]}} ->
{ok, Data};
{error, _} = ERROR ->
ERROR
end
end,
InitState = #{domain => inet,
send => Send,
recv => Recv},
ok = api_b_send_and_recv_tcp(InitState),
tc_end().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
api_b_send_and_recv_tcp(InitState) ->
process_flag(trap_exit, true),
ServerSeq =
[
#{desc => "which local address",
cmd => fun(#{domain := Domain} = State) ->
LAddr = which_local_addr(Domain),
LSA = #{family => Domain, addr => LAddr},
{ok, State#{lsa => LSA}}
end},
#{desc => "create listen socket",
cmd => fun(#{domain := Domain} = State) ->
case socket:open(Domain, stream, tcp) of
{ok, Sock} ->
{ok, State#{lsock => Sock}};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "bind to local address",
cmd => fun(#{lsock := LSock, lsa := LSA} = State) ->
case socket:bind(LSock, LSA) of
{ok, Port} ->
{ok, State#{lport => Port}};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "make listen socket",
cmd => fun(#{lsock := LSock}) ->
socket:listen(LSock)
end},
#{desc => "announce server port",
cmd => fun(#{parent := Parent, lport := Port}) ->
ei("announcing port to parent (~p)", [Parent]),
Parent ! {server_port, self(), Port},
ok
end},
#{desc => "await connection",
cmd => fun(#{lsock := LSock} = State) ->
case socket:accept(LSock) of
{ok, Sock} ->
ei("accepted: ~p", [Sock]),
{ok, State#{tsock => Sock}};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "await request",
cmd => fun(#{tsock := Sock, recv := Recv}) ->
case Recv(Sock) of
{ok, ?BASIC_REQ} ->
ok;
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "send reply",
cmd => fun(#{tsock := Sock, send := Send}) ->
Send(Sock, ?BASIC_REP)
end},
#{desc => "sleep some",
cmd => fun(_) ->
?SLEEP(1000),
ok
end},
#{desc => "close traffic socket",
cmd => fun(#{tsock := Sock}) ->
socket:close(Sock)
end},
#{desc => "close listen socket",
cmd => fun(#{lsock := Sock}) ->
socket:close(Sock)
end},
#{desc => "finish",
cmd => fun(_) ->
{ok, normal}
end}
],
ClientSeq =
[
#{desc => "which server (local) address",
cmd => fun(#{domain := Domain, server_port := Port} = State) ->
LAddr = which_local_addr(Domain),
LSA = #{family => Domain,
addr => LAddr},
SSA = LSA#{port => Port},
{ok, State#{lsa => LSA, ssa => SSA}}
end},
#{desc => "create socket",
cmd => fun(#{domain := Domain} = State) ->
case socket:open(Domain, stream, tcp) of
{ok, Sock} ->
{ok, State#{sock => Sock}};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "bind to local address",
cmd => fun(#{sock := Sock, lsa := LSA} = State) ->
case socket:bind(Sock, LSA) of
{ok, Port} ->
{ok, State#{port => Port}};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "connect to server",
cmd => fun(#{sock := Sock, ssa := SSA}) ->
socket:connect(Sock, SSA)
end},
#{desc => "send request (to server)",
cmd => fun(#{sock := Sock, send := Send}) ->
Send(Sock, ?BASIC_REQ)
end},
#{desc => "recv reply (from server)",
cmd => fun(#{sock := Sock, recv := Recv}) ->
{ok, ?BASIC_REP} = Recv(Sock),
ok
end},
#{desc => "close socket",
cmd => fun(#{sock := Sock}) ->
socket:close(Sock)
end},
#{desc => "finish",
cmd => fun(_) ->
{ok, normal}
end}
],
p("start server evaluator"),
Server = evaluator_start("server", ServerSeq, InitState),
p("await server (~p) port", [Server]),
SPort = receive
{server_port, Server, Port} ->
Port
end,
p("start client evaluator"),
Client = evaluator_start("client", ClientSeq, InitState#{server_port => SPort}),
p("await evaluator(s)"),
ok = await_evaluator_finish([Server, Client]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Perform some simple getopt and setopt with the level = otp options
api_opt_simple_otp_options(suite) ->
[];
api_opt_simple_otp_options(doc) ->
[];
api_opt_simple_otp_options(_Config) when is_list(_Config) ->
tc_begin(api_opt_simple_otp_options),
Get = fun(S, Key) ->
socket:getopt(S, otp, Key)
end,
Set = fun(S, Key, Val) ->
socket:setopt(S, otp, Key, Val)
end,
Seq =
[
%% *** Init part ***
#{desc => "create socket",
cmd => fun(#{domain := Domain,
type := Type,
protocol := Protocol} = State) ->
Sock = sock_open(Domain, Type, Protocol),
{ok, State#{sock => Sock}}
end},
#{desc => "create dummy process",
cmd => fun(State) ->
Pid = spawn_link(fun() ->
put(sname, "dummy"),
receive
die ->
exit(normal)
end
end),
{ok, State#{dummy => Pid}}
end},
%% *** Check iow part ***
#{desc => "get iow",
cmd => fun(#{sock := Sock} = State) ->
case Get(Sock, iow) of
{ok, IOW} when is_boolean(IOW) ->
{ok, State#{iow => IOW}};
{ok, InvalidIOW} ->
{error, {invalid, InvalidIOW}};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "set (new) iow",
cmd => fun(#{sock := Sock, iow := OldIOW} = State) ->
NewIOW = not OldIOW,
case Set(Sock, iow, NewIOW) of
ok ->
{ok, State#{iow => NewIOW}};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "get (new) iow",
cmd => fun(#{sock := Sock, iow := IOW}) ->
case Get(Sock, iow) of
{ok, IOW} ->
ok;
{ok, InvalidIOW} ->
{error, {invalid, InvalidIOW}};
{error, _} = ERROR ->
ERROR
end
end},
%% *** Check rcvbuf part ***
#{desc => "get rcvbuf",
cmd => fun(#{sock := Sock} = State) ->
case Get(Sock, rcvbuf) of
{ok, RcvBuf} when is_integer(RcvBuf) ->
{ok, State#{rcvbuf => RcvBuf}};
{ok, InvalidRcvBuf} ->
{error, {invalid, InvalidRcvBuf}};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "set (new) rcvbuf",
cmd => fun(#{sock := Sock, rcvbuf := OldRcvBuf} = State) ->
NewRcvBuf = 2 * OldRcvBuf,
case Set(Sock, rcvbuf, NewRcvBuf) of
ok ->
{ok, State#{rcvbuf => NewRcvBuf}};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "get (new) rcvbuf",
cmd => fun(#{sock := Sock, rcvbuf := RcvBuf}) ->
case Get(Sock, rcvbuf) of
{ok, RcvBuf} ->
ok;
{ok, InvalidRcvBuf} ->
{error, {invalid, InvalidRcvBuf}};
{error, _} = ERROR ->
ERROR
end
end},
%% *** Check rcvctrlbuf part ***
#{desc => "get rcvctrlbuf",
cmd => fun(#{sock := Sock} = State) ->
case Get(Sock, rcvctrlbuf) of
{ok, RcvCtrlBuf} when is_integer(RcvCtrlBuf) ->
{ok, State#{rcvctrlbuf => RcvCtrlBuf}};
{ok, InvalidRcvCtrlBuf} ->
{error, {invalid, InvalidRcvCtrlBuf}};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "set (new) rcvctrlbuf",
cmd => fun(#{sock := Sock, rcvctrlbuf := OldRcvCtrlBuf} = State) ->
NewRcvCtrlBuf = 2 * OldRcvCtrlBuf,
case Set(Sock, rcvctrlbuf, NewRcvCtrlBuf) of
ok ->
{ok, State#{rcvctrlbuf => NewRcvCtrlBuf}};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "get (new) rcvctrlbuf",
cmd => fun(#{sock := Sock, rcvctrlbuf := RcvCtrlBuf}) ->
case Get(Sock, rcvctrlbuf) of
{ok, RcvCtrlBuf} ->
ok;
{ok, InvalidRcvCtrlBuf} ->
{error, {invalid, InvalidRcvCtrlBuf}};
{error, _} = ERROR ->
ERROR
end
end},
%% *** Check rcvctrlbuf part ***
#{desc => "get rcvctrlbuf",
cmd => fun(#{sock := Sock} = State) ->
case Get(Sock, rcvctrlbuf) of
{ok, RcvCtrlBuf} when is_integer(RcvCtrlBuf) ->
{ok, State#{rcvctrlbuf => RcvCtrlBuf}};
{ok, InvalidRcvCtrlBuf} ->
{error, {invalid, InvalidRcvCtrlBuf}};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "set (new) rcvctrlbuf",
cmd => fun(#{sock := Sock, rcvctrlbuf := OldRcvCtrlBuf} = State) ->
NewRcvCtrlBuf = 2 * OldRcvCtrlBuf,
case Set(Sock, rcvctrlbuf, NewRcvCtrlBuf) of
ok ->
{ok, State#{rcvctrlbuf => NewRcvCtrlBuf}};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "get (new) rcvctrlbuf",
cmd => fun(#{sock := Sock, rcvctrlbuf := RcvCtrlBuf}) ->
case Get(Sock, rcvctrlbuf) of
{ok, RcvCtrlBuf} ->
ok;
{ok, InvalidRcvCtrlBuf} ->
{error, {invalid, InvalidRcvCtrlBuf}};
{error, _} = ERROR ->
ERROR
end
end},
%% *** Check sndctrlbuf part ***
#{desc => "get sndctrlbuf",
cmd => fun(#{sock := Sock} = State) ->
case Get(Sock, sndctrlbuf) of
{ok, SndCtrlBuf} when is_integer(SndCtrlBuf) ->
{ok, State#{sndctrlbuf => SndCtrlBuf}};
{ok, InvalidSndCtrlBuf} ->
{error, {invalid, InvalidSndCtrlBuf}};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "set (new) sndctrlbuf",
cmd => fun(#{sock := Sock, sndctrlbuf := OldSndCtrlBuf} = State) ->
NewSndCtrlBuf = 2 * OldSndCtrlBuf,
case Set(Sock, sndctrlbuf, NewSndCtrlBuf) of
ok ->
{ok, State#{sndctrlbuf => NewSndCtrlBuf}};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "get (new) sndctrlbuf",
cmd => fun(#{sock := Sock, sndctrlbuf := SndCtrlBuf}) ->
case Get(Sock, sndctrlbuf) of
{ok, SndCtrlBuf} ->
ok;
{ok, InvalidSndCtrlBuf} ->
{error, {invalid, InvalidSndCtrlBuf}};
{error, _} = ERROR ->
ERROR
end
end},
%% *** Check controlling-process part ***
#{desc => "verify self as controlling-process",
cmd => fun(#{sock := Sock}) ->
Self = self(),
case Get(Sock, controlling_process) of
{ok, Self} ->
ok;
{ok, InvalidPid} ->
{error, {invalid, InvalidPid}};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "set dummy as controlling-process",
cmd => fun(#{sock := Sock, dummy := Dummy}) ->
Set(Sock, controlling_process, Dummy)
end},
#{desc => "verify dummy as controlling-process",
cmd => fun(#{sock := Sock, dummy := Dummy}) ->
case Get(Sock, controlling_process) of
{ok, Dummy} ->
ok;
{ok, InvalidPid} ->
{error, {invalid, InvalidPid}};
{error, _} = ERROR ->
ERROR
end
end},
%% *** We are done ***
#{desc => "finish",
cmd => fun(_) ->
{ok, normal}
end}
],
p("Run test for stream/tcp socket"),
InitState1 = #{domain => inet, type => stream, protocol => tcp},
Tester1 = evaluator_start("tcp-tester", Seq, InitState1),
p("await evaluator 1"),
ok = await_evaluator_finish([Tester1]),
p("Run test for dgram/udp socket"),
InitState2 = #{domain => inet, type => dgram, protocol => udp},
Tester2 = evaluator_start("udp-tester", Seq, InitState2),
p("await evaluator 2"),
ok = await_evaluator_finish([Tester2]),
tc_end().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Perform some simple getopt and setopt with the level = otp options
api_opt_simple_otp_controlling_process(suite) ->
[];
api_opt_simple_otp_controlling_process(doc) ->
[];
api_opt_simple_otp_controlling_process(_Config) when is_list(_Config) ->
tc_begin(api_opt_simple_otp_controlling_process),
Get = fun(S, Key) ->
socket:getopt(S, otp, Key)
end,
Set = fun(S, Key, Val) ->
socket:setopt(S, otp, Key, Val)
end,
ClientSeq =
[
%% *** Init part ***
#{desc => "await start",
cmd => fun(State) ->
receive
{start, Tester, Socket} ->
{ok, State#{tester => Tester,
sock => Socket}}
end
end},
#{desc => "verify tester as controlling-process",
cmd => fun(#{tester := Tester, sock := Sock} = _State) ->
case Get(Sock, controlling_process) of
{ok, Tester} ->
ok;
{ok, InvalidPid} ->
{error, {invalid, InvalidPid}};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "attempt invalid controlling-process transfer (to self)",
cmd => fun(#{sock := Sock} = _State) ->
case Set(Sock, controlling_process, self()) of
{error, not_owner} ->
ok;
ok ->
{error, unexpected_success};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "announce ready (1)",
cmd => fun(#{tester := Tester} = _State) ->
Tester ! {ready, self()},
ok
end},
#{desc => "await continue",
cmd => fun(#{tester := Tester} = _State) ->
receive
{continue, Tester} ->
ok
end
end},
#{desc => "verify self as controlling-process",
cmd => fun(#{sock := Sock} = _State) ->
Self = self(),
case Get(Sock, controlling_process) of
{ok, Self} ->
ok;
{ok, InvalidPid} ->
{error, {invalid, InvalidPid}};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "attempt controlling-process transfer to tester",
cmd => fun(#{tester := Tester, sock := Sock} = _State) ->
Set(Sock, controlling_process, Tester)
end},
#{desc => "attempt invalid controlling-process transfer (to self)",
cmd => fun(#{sock := Sock} = _State) ->
case Set(Sock, controlling_process, self()) of
{error, not_owner} ->
ok;
ok ->
{error, unexpected_success};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "announce ready (2)",
cmd => fun(#{tester := Tester} = _State) ->
Tester ! {ready, self()},
ok
end},
#{desc => "await termination",
cmd => fun(#{tester := Tester} = State) ->
receive
{terminate, Tester} ->
State1 = maps:remove(tester, State),
State2 = maps:remove(sock, State1),
{ok, State2}
end
end},
%% *** We are done ***
#{desc => "finish",
cmd => fun(_) ->
{ok, normal}
end}
],
TesterSeq =
[
%% *** Init part ***
#{desc => "create socket",
cmd => fun(#{domain := Domain,
type := Type,
protocol := Protocol} = State) ->
Sock = sock_open(Domain, Type, Protocol),
{ok, State#{sock => Sock}}
end},
#{desc => "verify self as controlling-process",
cmd => fun(#{sock := Sock} = _State) ->
Self = self(),
case Get(Sock, controlling_process) of
{ok, Self} ->
ok;
{ok, InvalidPid} ->
{error, {invalid, InvalidPid}};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "order (client) start",
cmd => fun(#{client := Client, sock := Sock} = _State) ->
Client ! {start, self(), Sock},
ok
end},
#{desc => "await (client) ready (1)",
cmd => fun(#{client := Client} = _State) ->
receive
{ready, Client} ->
ok
end
end},
#{desc => "attempt controlling-process transfer to client",
cmd => fun(#{client := Client, sock := Sock} = _State) ->
Set(Sock, controlling_process, Client)
end},
#{desc => "verify client as controlling-process",
cmd => fun(#{client := Client, sock := Sock} = _State) ->
case Get(Sock, controlling_process) of
{ok, Client} ->
ok;
{ok, InvalidPid} ->
{error, {invalid, InvalidPid}};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "attempt invalid controlling-process transfer (to self)",
cmd => fun(#{sock := Sock} = _State) ->
case Set(Sock, controlling_process, self()) of
{error, not_owner} ->
ok;
ok ->
{error, unexpected_success};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "order (client) continue",
cmd => fun(#{client := Client} = _State) ->
Client ! {continue, self()},
ok
end},
#{desc => "await (client) ready (2)",
cmd => fun(#{client := Client} = _State) ->
receive
{ready, Client} ->
ok
end
end},
#{desc => "verify self as controlling-process",
cmd => fun(#{sock := Sock} = _State) ->
Self = self(),
case Get(Sock, controlling_process) of
{ok, Self} ->
ok;
{ok, InvalidPid} ->
{error, {invalid, InvalidPid}};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "monitor client",
cmd => fun(#{client := Client} = State) ->
MRef = erlang:monitor(process, Client),
{ok, State#{client_mref => MRef}}
end},
#{desc => "order (client) terminate",
cmd => fun(#{client := Client} = _State) ->
Client ! {terminate, self()},
ok
end},
#{desc => "await (client) down",
cmd => fun(#{client := Client} = State) ->
receive
{'DOWN', _, process, Client, _} ->
{ok, maps:remove(client, State)}
end
end},
#{desc => "close socket",
cmd => fun(#{sock := Sock} = State) ->
sock_close(Sock),
{ok, maps:remove(sock, State)}
end},
%% *** We are done ***
#{desc => "finish",
cmd => fun(_) ->
{ok, normal}
end}
],
p("Run test for stream/tcp socket"),
ClientInitState1 = #{},
Client1 = evaluator_start("tcp-client", ClientSeq, ClientInitState1),
TesterInitState1 = #{domain => inet,
type => stream,
protocol => tcp,
client => Client1},
Tester1 = evaluator_start("tcp-tester", TesterSeq, TesterInitState1),
p("await stream/tcp evaluator"),
ok = await_evaluator_finish([Tester1, Client1]),
p("Run test for dgram/udp socket"),
ClientInitState2 = #{},
Client2 = evaluator_start("udp-client", ClientSeq, ClientInitState2),
TesterInitState2 = #{domain => inet,
type => dgram,
protocol => udp,
client => Client2},
Tester2 = evaluator_start("udp-tester", TesterSeq, TesterInitState2),
p("await dgram/udp evaluator"),
ok = await_evaluator_finish([Tester2, Client2]),
tc_end().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This test case is intended to test the connect timeout option
%% on an IPv4 TCP (stream) socket.
api_to_connect_tcp4(suite) ->
[];
api_to_connect_tcp4(doc) ->
[];
api_to_connect_tcp4(_Config) when is_list(_Config) ->
tc_begin(api_to_connect_tcp4),
InitState = #{domain => inet},
ok = api_to_connect_tcp(InitState),
tc_end().
%% not_yet_implemented().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This test case is intended to test the connect timeout option
%% on an IPv6 TCP (stream) socket.
api_to_connect_tcp6(suite) ->
[];
api_to_connect_tcp6(doc) ->
[];
api_to_connect_tcp6(_Config) when is_list(_Config) ->
%% tc_begin(api_to_connect_tcp6),
%% InitState = #{domain => inet6},
%% ok = api_to_connect_tcp(InitState),
%% tc_end().
not_yet_implemented().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% We use the backlog (listen) argument to test this.
%% Note that the behaviour of the TCP "server side" can vary when
%% a client connect to a "busy" server (full backlog).
%% For instance, on FreeBSD (11.2) the reponse when the backlog is full
%% is a econreset.
api_to_connect_tcp(InitState) ->
process_flag(trap_exit, true),
ServerSeq =
[
%% *** Wait for start order part ***
#{desc => "await start (from tester)",
cmd => fun(State) ->
receive
{start, Tester} when is_pid(Tester) ->
{ok, State#{tester => Tester}}
end
end},
%% *** Init part ***
#{desc => "which local address",
cmd => fun(#{domain := Domain} = State) ->
LAddr = which_local_addr(Domain),
LSA = #{family => Domain, addr => LAddr},
{ok, State#{lsa => LSA}}
end},
#{desc => "create listen socket",
cmd => fun(#{domain := Domain} = State) ->
case socket:open(Domain, stream, tcp) of
{ok, Sock} ->
{ok, State#{lsock => Sock}};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "bind to local address",
cmd => fun(#{lsock := LSock, lsa := LSA} = State) ->
case socket:bind(LSock, LSA) of
{ok, Port} ->
{ok, State#{lport => Port}};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "make listen socket (with backlog = 1)",
cmd => fun(#{lsock := LSock}) ->
socket:listen(LSock, 1)
end},
#{desc => "monitor server",
cmd => fun(#{tester := Tester} = State) ->
MRef = erlang:monitor(process, Tester),
{ok, State#{tester_mref => MRef}}
end},
#{desc => "announce ready",
cmd => fun(#{tester := Tester, lport := Port}) ->
ei("announcing ready to tester (~p)", [Tester]),
Tester ! {ready, self(), Port},
ok
end},
#{desc => "await terminate (from tester)",
cmd => fun(#{tester := Tester}) ->
receive
{'DOWN', _, process, Tester, Reason} ->
{error, {unexpected_tester_exit, Reason}};
{terminate, Tester} ->
ok
end
end},
%% *** We are done ***
#{desc => "finish",
cmd => fun(_) ->
{ok, normal}
end}
],
TesterSeq =
[
%% *** Init part ***
#{desc => "which local address",
cmd => fun(#{domain := Domain} = State) ->
LAddr = which_local_addr(Domain),
LSA = #{family => Domain, addr => LAddr},
{ok, State#{lsa => LSA}}
end},
#{desc => "create socket 1",
cmd => fun(#{domain := Domain} = State) ->
case socket:open(Domain, stream, tcp) of
{ok, Sock} ->
{ok, State#{sock1 => Sock}};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "create socket 2",
cmd => fun(#{domain := Domain} = State) ->
case socket:open(Domain, stream, tcp) of
{ok, Sock} ->
{ok, State#{sock2 => Sock}};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "create socket 3",
cmd => fun(#{domain := Domain} = State) ->
case socket:open(Domain, stream, tcp) of
{ok, Sock} ->
{ok, State#{sock3 => Sock}};
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "bind socket 1 to local address",
cmd => fun(#{sock1 := Sock, lsa := LSA} = _State) ->
case socket:bind(Sock, LSA) of
{ok, _} ->
ok;
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "bind socket 2 to local address",
cmd => fun(#{sock2 := Sock, lsa := LSA} = _State) ->
case socket:bind(Sock, LSA) of
{ok, _} ->
ok;
{error, _} = ERROR ->
ERROR
end
end},
#{desc => "bind socket 3 to local address",
cmd => fun(#{sock3 := Sock, lsa := LSA} = _State) ->
case socket:bind(Sock, LSA) of
{ok, _} ->
ok;
{error, _} = ERROR ->
ERROR
end
end},
%% *** Synchronize with the server ***
#{desc => "order (server) start",
cmd => fun(#{server := Server}) ->
Server ! {start, self()},
ok
end},
#{desc => "await ready (from server)",
cmd => fun(#{server := Server, lsa := LSA} = State) ->
receive
{ready, Server, Port} ->
{ok, State#{ssa => LSA#{port => Port}}}
end
end},
%% *** Connect sequence ***
#{desc => "order (server) start",
cmd => fun(#{sock1 := Sock1,
sock2 := Sock2,
sock3 := Sock3,
ssa := SSA}) ->
Socks = [Sock1, Sock2, Sock3],
api_to_connect_tcp_await_timeout(Socks, SSA)
end},
%% *** Terminate server ***
#{desc => "monitor server",
cmd => fun(#{server := Server} = State) ->
MRef = erlang:monitor(process, Server),
{ok, State#{server_mref => MRef}}
end},
#{desc => "order (server) terminate",
cmd => fun(#{server := Server} = _State) ->
Server ! {terminate, self()},
ok
end},
#{desc => "await (server) down",
cmd => fun(#{server := Server} = State) ->
receive
{'DOWN', _, process, Server, _} ->
State1 = maps:remove(server, State),
State2 = maps:remove(ssa, State1),
{ok, State2}
end
end},
%% *** Close our sockets ***
#{desc => "close socket 3",
cmd => fun(#{sock3 := Sock} = State) ->
sock_close(Sock),
{ok, maps:remove(sock3, State)}
end},
#{desc => "close socket 2",
cmd => fun(#{sock2 := Sock} = State) ->
sock_close(Sock),
{ok, maps:remove(sock2, State)}
end},
#{desc => "close socket 1",
cmd => fun(#{sock1 := Sock} = State) ->
sock_close(Sock),
{ok, maps:remove(sock1, State)}
end},
%% *** We are done ***
#{desc => "finish",
cmd => fun(_) ->
{ok, normal}
end}
],
p("create server evaluator"),
ServerInitState = InitState,
Server = evaluator_start("server", ServerSeq, ServerInitState),
p("create tester evaluator"),
TesterInitState = InitState#{server => Server},
Tester = evaluator_start("tester", TesterSeq, TesterInitState),
p("await evaluator(s)"),
ok = await_evaluator_finish([Server, Tester]),
ok.
api_to_connect_tcp_await_timeout(Socks, ServerSA) ->
api_to_connect_tcp_await_timeout(Socks, ServerSA, 1).
api_to_connect_tcp_await_timeout([], _ServerSA, _ID) ->
?FAIL(unexpected_success);
api_to_connect_tcp_await_timeout([Sock|Socks], ServerSA, ID) ->
ei("~w: try connect", [ID]),
case socket:connect(Sock, ServerSA, 5000) of
{error, timeout} ->
ei("expected timeout (~w)", [ID]),
ok;
{error, econnreset = Reason} ->
ei("failed connecting: ~p - giving up", [Reason]),
ok;
{error, Reason} ->
ee("failed connecting: ~p", [Reason]),
?FAIL({connect, Reason});
ok ->
ei("unexpected success (~w) - try next", [ID]),
api_to_connect_tcp_await_timeout(Socks, ServerSA, ID+1)
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This test case is intended to test the accept timeout option
%% on an IPv4 TCP (stream) socket.
api_to_accept_tcp4(suite) ->
[];
api_to_accept_tcp4(doc) ->
[];
api_to_accept_tcp4(_Config) when is_list(_Config) ->
%% tc_begin(api_to_accept_tcp4),
%% ok = api_to_accept_tcp(inet),
%% tc_end().
not_yet_implemented().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This test case is intended to test the accept timeout option
%% on an IPv6 TCP (stream) socket.
api_to_accept_tcp6(suite) ->
[];
api_to_accept_tcp6(doc) ->
[];
api_to_accept_tcp6(_Config) when is_list(_Config) ->
%% tc_begin(api_to_accept_tcp6),
%% ok = api_to_accept_tcp(inet6),
%% tc_end().
not_yet_implemented().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This test case is intended to test the send timeout option
%% on an IPv4 TCP (stream) socket.
api_to_send_tcp4(suite) ->
[];
api_to_send_tcp4(doc) ->
[];
api_to_send_tcp4(_Config) when is_list(_Config) ->
%% tc_begin(api_to_send_tcp4),
%% ok = api_to_send_tcp(inet),
%% tc_end().
not_yet_implemented().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This test case is intended to test the send timeout option
%% on an IPv6 TCP (stream) socket.
api_to_send_tcp6(suite) ->
[];
api_to_send_tcp6(doc) ->
[];
api_to_send_tcp6(_Config) when is_list(_Config) ->
%% tc_begin(api_to_send_tcp6),
%% ok = api_to_send_tcp(inet6),
%% tc_end().
not_yet_implemented().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This test case is intended to test the sendto timeout option
%% on an IPv4 UDP (dgram) socket.
api_to_sendapi_to_udp4(suite) ->
[];
api_to_sendapi_to_udp4(doc) ->
[];
api_to_sendapi_to_udp4(_Config) when is_list(_Config) ->
%% tc_begin(api_to_sendapi_to_udp4),
%% ok = api_to_sendapi_to_udp(inet),
%% tc_end().
not_yet_implemented().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This test case is intended to test the sendto timeout option
%% on an IPv6 UDP (dgram) socket.
api_to_sendapi_to_udp6(suite) ->
[];
api_to_sendapi_to_udp6(doc) ->
[];
api_to_sendapi_to_udp6(_Config) when is_list(_Config) ->
%% tc_begin(api_to_sendapi_to_udp6),
%% ok = api_to_sendapi_to_udp(inet6),
%% tc_end().
not_yet_implemented().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This test case is intended to test the sendmsg timeout option
%% on an IPv4 TCP (stream) socket.
api_to_sendmsg_tcp4(suite) ->
[];
api_to_sendmsg_tcp4(doc) ->
[];
api_to_sendmsg_tcp4(_Config) when is_list(_Config) ->
%% tc_begin(api_to_sendmsg_tcp4),
%% ok = api_to_sendmsg_tcp(inet),
%% tc_end().
not_yet_implemented().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This test case is intended to test the sendmsg timeout option
%% on an IPv6 TCP (stream) socket.
api_to_sendmsg_tcp6(suite) ->
[];
api_to_sendmsg_tcp6(doc) ->
[];
api_to_sendmsg_tcp6(_Config) when is_list(_Config) ->
%% tc_begin(api_to_sendmsg_tcp6),
%% ok = api_to_sendmsg_tcp(inet6),
%% tc_end().
not_yet_implemented().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This test case is intended to test the recv timeout option
%% on an IPv4 UDP (dgram) socket. To test this we must connect
%% the socket.
api_to_recv_udp4(suite) ->
[];
api_to_recv_udp4(doc) ->
[];
api_to_recv_udp4(_Config) when is_list(_Config) ->
%% tc_begin(api_to_recv_udp4),
%% ok = api_to_recv_udp(inet),
%% tc_end().
not_yet_implemented().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This test case is intended to test the recv timeout option
%% on an IPv6 UDP (dgram) socket. To test this we must connect
%% the socket.
api_to_recv_udp6(suite) ->
[];
api_to_recv_udp6(doc) ->
[];
api_to_recv_udp6(_Config) when is_list(_Config) ->
%% tc_begin(api_to_recv_udp6),
%% ok = api_to_recv_udp(inet6),
%% tc_end().
not_yet_implemented().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This test case is intended to test the recv timeout option
%% on an IPv4 TCP (stream) socket.
api_to_recv_tcp4(suite) ->
[];
api_to_recv_tcp4(doc) ->
[];
api_to_recv_tcp4(_Config) when is_list(_Config) ->
tc_begin(api_to_recv_tcp4),
ok = api_to_recv_tcp(inet),
tc_end().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This test case is intended to test the recv timeout option
%% on an IPv6 TCP (stream) socket.
api_to_recv_tcp6(suite) ->
[];
api_to_recv_tcp6(doc) ->
[];
api_to_recv_tcp6(_Config) when is_list(_Config) ->
%% tc_begin(api_to_recv_tcp6),
%% ok = api_to_recv_tcp(inet6),
%% tc_end().
not_yet_implemented().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
api_to_recv_tcp(Domain) ->
process_flag(trap_exit, true),
p("server -> open"),
LSock = sock_open(Domain, stream, tcp),
LocalAddr = which_local_addr(Domain),
LocalSA = #{family => Domain, addr => LocalAddr},
p("server -> bind"),
ServerLPort = sock_bind(LSock, LocalSA),
p("server(~w) -> listen", [ServerLPort]),
sock_listen(LSock),
ClientName = f("~s:client", [get_tc_name()]),
Client = spawn_link(fun() ->
put(sname, ClientName),
p("open"),
CSock = sock_open(Domain, stream, tcp),
p("bind"),
ClientPort = sock_bind(CSock, LocalSA),
p("[~w] connect to ~w",
[ClientPort, ServerLPort]),
sock_connect(CSock, LocalSA#{port => ServerLPort}),
p("await termination command"),
receive
die ->
p("terminating"),
exit(normal)
end
end),
p("server -> accept on ~w", [ServerLPort]),
Sock = sock_accept(LSock),
p("server -> recv"),
%% The zero (0) represents "give me everything you have"
case socket:recv(Sock, 0, 5000) of
{error, timeout} ->
p("server -> expected timeout"),
ok;
{ok, _Data} ->
?FAIL(unexpected_success);
{error, Reason} ->
?FAIL({recv, Reason})
end,
Client ! die,
receive
{'EXIT', Client, _} ->
ok
end,
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This test case is intended to test the recvfrom timeout option
%% on an IPv4 UDP (dgram) socket.
api_to_recvfrom_udp4(suite) ->
[];
api_to_recvfrom_udp4(doc) ->
[];
api_to_recvfrom_udp4(_Config) when is_list(_Config) ->
tc_begin(api_to_recvfrom_udp4),
ok = api_to_recvfrom_udp(inet),
tc_end().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This test case is intended to test the recvfrom timeout option
%% on an IPv6 UDP (dgram) socket.
api_to_recvfrom_udp6(suite) ->
[];
api_to_recvfrom_udp6(doc) ->
[];
api_to_recvfrom_udp6(_Config) when is_list(_Config) ->
%% tc_begin(api_to_recvfrom_udp6),
%% ok = api_to_recvfrom_udp(inet6),
%% tc_end().
not_yet_implemented().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
api_to_recvfrom_udp(Domain) ->
process_flag(trap_exit, true),
p("init"),
LocalAddr = which_local_addr(Domain),
LocalSA = #{family => Domain, addr => LocalAddr},
p("open"),
Sock = sock_open(Domain, dgram, udp),
p("bind"),
_Port = sock_bind(Sock, LocalSA),
p("recv"),
case socket:recvfrom(Sock, 0, 5000) of
{error, timeout} ->
p("expected timeout"),
ok;
{ok, _SrcData} ->
?FAIL(unexpected_success);
{error, Reason} ->
?FAIL({recv, Reason})
end,
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This test case is intended to test the recvmsg timeout option
%% on an IPv4 UDP (dgram) socket.
api_to_recvmsg_udp4(suite) ->
[];
api_to_recvmsg_udp4(doc) ->
[];
api_to_recvmsg_udp4(_Config) when is_list(_Config) ->
%% not_yet_implemented().
tc_begin(api_to_recvmsg_udp4),
ok = api_to_recvmsg_udp(inet),
tc_end().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This test case is intended to test the recvmsg timeout option
%% on an IPv6 UDP (dgram) socket.
api_to_recvmsg_udp6(suite) ->
[];
api_to_recvmsg_udp6(doc) ->
[];
api_to_recvmsg_udp6(_Config) when is_list(_Config) ->
%% tc_begin(api_to_recvmsg_udp6),
%% ok = api_to_recvmsg_udp(inet6),
%% tc_end().
not_yet_implemented().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
api_to_recvmsg_udp(Domain) ->
process_flag(trap_exit, true),
p("init"),
LocalAddr = which_local_addr(Domain),
LocalSA = #{family => Domain, addr => LocalAddr},
p("open"),
Sock = sock_open(Domain, dgram, udp),
p("bind"),
_Port = sock_bind(Sock, LocalSA),
p("recv"),
case socket:recvmsg(Sock, 5000) of
{error, timeout} ->
p("expected timeout"),
ok;
{ok, _MsgHdr} ->
?FAIL(unexpected_success);
{error, Reason} ->
?FAIL({recv, Reason})
end,
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This test case is intended to test the recvmsg timeout option
%% on an IPv4 TCP (stream) socket.
api_to_recvmsg_tcp4(suite) ->
[];
api_to_recvmsg_tcp4(doc) ->
[];
api_to_recvmsg_tcp4(_Config) when is_list(_Config) ->
tc_begin(api_to_recvmsg_tcp4),
ok = api_to_recvmsg_tcp(inet),
tc_end().
%% not_yet_implemented().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This test case is intended to test the recvmsg timeout option
%% on an IPv6 TCP (stream) socket.
api_to_recvmsg_tcp6(suite) ->
[];
api_to_recvmsg_tcp6(doc) ->
[];
api_to_recvmsg_tcp6(_Config) when is_list(_Config) ->
%% tc_begin(api_to_recvmsg_tcp6),
%% ok = api_to_recvmsg_tcp(inet6),
%% tc_end().
not_yet_implemented().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
api_to_recvmsg_tcp(Domain) ->
process_flag(trap_exit, true),
p("server -> open"),
LSock = sock_open(Domain, stream, tcp),
LocalAddr = which_local_addr(Domain),
LocalSA = #{family => Domain, addr => LocalAddr},
p("server -> bind"),
ServerLPort = sock_bind(LSock, LocalSA),
p("server(~w) -> listen", [ServerLPort]),
sock_listen(LSock),
ClientName = f("~s:client", [get_tc_name()]),
Client = spawn_link(fun() ->
put(sname, ClientName),
p("open"),
CSock = sock_open(Domain, stream, tcp),
p("bind"),
ClientPort = sock_bind(CSock, LocalSA),
p("[~w] connect to ~w",
[ClientPort, ServerLPort]),
sock_connect(CSock, LocalSA#{port => ServerLPort}),
p("await termination command"),
receive
die ->
p("terminating"),
exit(normal)
end
end),
p("server -> accept on ~w", [ServerLPort]),
Sock = sock_accept(LSock),
p("server -> recv"),
%% The zero (0) represents "give me everything you have"
case socket:recvmsg(Sock, 5000) of
{error, timeout} ->
p("server -> expected timeout"),
ok;
{ok, _Data} ->
?FAIL(unexpected_success);
{error, Reason} ->
?FAIL({recv, Reason})
end,
Client ! die,
receive
{'EXIT', Client, _} ->
ok
end,
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This gets the local address (not 127.0...)
%% We should really implement this using the (new) net module,
%% but until that gets the necessary functionality...
which_local_addr(Domain) ->
case inet:getifaddrs() of
{ok, IFL} ->
which_addr(Domain, IFL);
{error, Reason} ->
?FAIL({inet, getifaddrs, Reason})
end.
which_addr(_Domain, []) ->
?FAIL(no_address);
which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") ->
which_addr2(Domain, IFO);
which_addr(Domain, [_|IFL]) ->
which_addr(Domain, IFL).
which_addr2(_Domain, []) ->
?FAIL(no_address);
which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) ->
Addr;
which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) ->
Addr;
which_addr2(Domain, [_|IFO]) ->
which_addr2(Domain, IFO).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% An evaluator is a process that executes a command sequence.
%% A test case will consist of atleast one evaluator (one for
%% each actor).
%% The evaluator process *always* run locally. Which means that
%% it will act as a "proxy" for remote nodes in necessary.
%% When the command sequence has been processed, the final state
%% will be used as exit reason.
%% A successful command shall evaluate to ok | {ok, NewState}
-spec evaluator_start(Name, Seq, Init) -> {Pid, MRef} when
Name :: string(),
Seq :: [command()],
Init :: initial_evaluator_state(),
Pid :: pid(),
MRef :: reference().
evaluator_start(Name, Seq, Init)
when is_list(Name) andalso is_list(Seq) andalso (Seq =/= []) ->
Init2 = Init#{parent => self()},
{Pid, _} = erlang:spawn_monitor(fun() -> evaluator_init(Name, Seq, Init2) end),
Pid.
evaluator_init(Name, Seq, Init) ->
put(sname, Name),
evaluator_loop(1, Seq, Init).
evaluator_loop(_ID, [], FinalState) ->
exit(FinalState);
evaluator_loop(ID, [#{desc := Desc,
cmd := Cmd}|Cmds], State) when is_function(Cmd, 1) ->
ei("evaluate command ~2w: ~s", [ID, Desc]),
try Cmd(State) of
ok ->
evaluator_loop(ID + 1, Cmds, State);
{ok, NewState} ->
evaluator_loop(ID + 1, Cmds, NewState);
{error, Reason} ->
ee("command ~w failed: "
"~n Reason: ~p", [ID, Reason]),
exit({command_failed, ID, Reason, State})
catch
C:E:S ->
ee("command ~w crashed: "
"~n Class: ~p"
"~n Error: ~p"
"~n Call Stack: ~p", [ID, C, E, S]),
exit({command_crashed, ID, {C,E,S}, State})
end.
await_evaluator_finish(Evs) ->
await_evaluator_finish(Evs, []).
await_evaluator_finish([], []) ->
ok;
await_evaluator_finish([], Fails) ->
Fails;
await_evaluator_finish(Evs, Fails) ->
receive
{'DOWN', _MRef, process, Pid, normal} ->
case lists:delete(Pid, Evs) of
Evs ->
p("unknown process ~p died (normal)", [Pid]),
await_evaluator_finish(Evs, Fails);
NewEvs ->
p("evaluator ~p success", [Pid]),
await_evaluator_finish(NewEvs, Fails)
end;
{'DOWN', _MRef, process, Pid, Reason} ->
case lists:delete(Pid, Evs) of
Evs ->
p("unknown process ~p died: "
"~n ~p", [Pid, Reason]),
await_evaluator_finish(Evs, Fails);
NewEvs ->
p("Evaluator ~p failed", [Pid]),
await_evaluator_finish(NewEvs, [{Pid, Reason}|Fails])
end
end.
ei(F, A) ->
eprint("", F, A).
ee(F, A) ->
eprint("<ERROR> ", F, A).
eprint(Prefix, F, A) ->
io:format(user, "[~s][~p] ~s" ++ F ++ "~n", [get(sname), self(), Prefix | A]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
sock_open(Domain, Type, Proto) ->
try socket:open(Domain, Type, Proto) of
{ok, Socket} ->
Socket;
{error, Reason} ->
?FAIL({open, Reason})
catch
C:E:S ->
?FAIL({open, C, E, S})
end.
sock_bind(Sock, SockAddr) ->
try socket:bind(Sock, SockAddr) of
{ok, Port} ->
Port;
{error, Reason} ->
p("sock_bind -> error: ~p", [Reason]),
?FAIL({bind, Reason})
catch
C:E:S ->
p("sock_bind -> failed: ~p, ~p, ~p", [C, E, S]),
?FAIL({bind, C, E, S})
end.
sock_connect(Sock, SockAddr) ->
try socket:connect(Sock, SockAddr) of
ok ->
ok;
{error, Reason} ->
?FAIL({connect, Reason})
catch
C:E:S ->
?FAIL({connect, C, E, S})
end.
sock_sockname(Sock) ->
try socket:sockname(Sock) of
{ok, SockAddr} ->
SockAddr;
{error, Reason} ->
?FAIL({sockname, Reason})
catch
C:E:S ->
?FAIL({sockname, C, E, S})
end.
sock_listen(Sock) ->
sock_listen2(fun() -> socket:listen(Sock) end).
sock_listen(Sock, BackLog) ->
sock_listen2(fun() -> socket:listen(Sock, BackLog) end).
sock_listen2(Listen) ->
try Listen() of
ok ->
ok;
{error, Reason} ->
?FAIL({listen, Reason})
catch
C:E:S ->
?FAIL({listen, C, E, S})
end.
sock_accept(LSock) ->
try socket:accept(LSock) of
{ok, Sock} ->
Sock;
{error, Reason} ->
p("sock_accept -> error: ~p", [Reason]),
?FAIL({accept, Reason})
catch
C:E:S ->
p("sock_accept -> failed: ~p, ~p, ~p", [C, E, S]),
?FAIL({accept, C, E, S})
end.
sock_close(Sock) ->
try socket:close(Sock) of
ok ->
ok;
{error, Reason} ->
p("sock_close -> error: ~p", [Reason]),
?FAIL({close, Reason})
catch
C:E:S ->
p("sock_close -> failed: ~p, ~p, ~p", [C, E, S]),
?FAIL({close, C, E, S})
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
not_yet_implemented() ->
{skip, "not yet implemented"}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
set_tc_name(N) when is_atom(N) ->
set_tc_name(atom_to_list(N));
set_tc_name(N) when is_list(N) ->
put(tc_name, N).
get_tc_name() ->
get(tc_name).
tc_begin(TC) ->
set_tc_name(TC),
p("begin ***").
tc_end() ->
p("done ***"),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
f(F, A) ->
lists:flatten(io_lib:format(F, A)).
p(F) ->
p(F, []).
p(F, A) ->
TcName =
case get(tc_name) of
undefined ->
case get(sname) of
undefined ->
"";
SName when is_list(SName) ->
SName
end;
Name when is_list(Name) ->
Name
end,
i("*** ~s[~p] " ++ F, [TcName,self()|A]).
%% i(F) ->
%% i(F, []).
i(F, A) ->
io:format(user, F ++ "~n", A).