aboutsummaryrefslogblamecommitdiffstats
path: root/lib/kernel/test/socket_SUITE.erl
blob: 2d406458f0138ed49a1a7d8fd2f52fa8459a0353 (plain) (tree)


























                                                                           

                                                   


             
                     




                                          

                                          

                                      
                                                  
 




















                                  







                   
                                         






                                                                      
                                             

 






                                                                         

                                             













                                                                         

                                                  

                                                            
                                                 



              
                        
                          
                                 


                    

                               






                                    

                      

                                           

      
                              
     



















                            


      






                                                                         















                                                                         







                                                                



                                     












                                                                         



                                     




                                                                         























































































                                                                               

 
 















                                                                         



                                            


             

                                                                         
























                                                                                  



                                            


             

                                                                         
                                     













































                                                                           
                                                                           























                                                                           


















                                                                          



                                            


             

                                                                         




















                                                                           



                                            


             

                                                                         
                                     
                                  











































































































































                                                                                    



                                                                         
 







                                                                     






                                                 














































































































































































































































                                                                               





                                                                         







                                                                        






                                                 




































































































































































































































                                                                                  





                                                                         

                                                                
                             
       
                           
       

                                                     

                                       







                                                                         
                             
       
                           
       

                                                     

                                          




                                                                         




                                                                       
 
                                
                                  






















































































































































































































                                                                             


       

                                                         
 
                                                       
                              
                                                               
                                

                                                
                                              
               
                                       
                                                              
               
                          

                                                  
             
                                                          
                                                                   







                                                                         
                            
       
                          
       


                                                    







                                                                         
                            
       
                          
       


                                                    







                                                                         
                          
       
                        
       


                                                  







                                                                         
                          
       
                        
       


                                                  







                                                                         
                                
       
                              
       


                                                        







                                                                         
                                
       
                              
       


                                                        







                                                                         
                             
       
                           
       


                                                     







                                                                         
                             
       
                           
       


                                                     








                                                                         
                          
       
                        
       


                                                  








                                                                         
                          
       
                        
       


                                                  







                                                                         
                          
       
                        
       


                                                  






                                                                         
                          
       
                        
       


                                                  





                                                                         
                          










                                                        
                                                       






































                                                                                   
                              
       
                            
       


                                                      






                                                                         
                              
       
                            
       


                                                      





                                                                         
                              
























                                                                         
                             
       
                           
       
                                                     
                             

                                  






                                                                         
                             
       
                           
       


                                                     





                                                                         
                             
























                                                                         
                             
       
                           
       


                                                     







                                                                         
                             
       
                           
       


                                                     





                                                                         
                             










                                                        
                                                       



































                                                                                   





























                                                                            


                                                                         











                                                                
                                        




                                                                 


                                                                                   





































                                                                            
                                          







                                                                 
                                          























                                                                                  















































































                                                              













                                                             
















                                                                         
               

                    

           
                  

       





                                                                         



             


                            





                                                







                                             
 

                                  
%%
%% %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).