diff options
author | Micael Karlberg <[email protected]> | 2018-10-16 15:57:17 +0200 |
---|---|---|
committer | Micael Karlberg <[email protected]> | 2018-10-16 15:57:17 +0200 |
commit | f945aa4a8067d745ee75fe695272104e220d7bc3 (patch) | |
tree | 47e258ceec9c4eaeeffdd7ce12815dc788bf9386 /lib/kernel/test | |
parent | 043624804888dc021a75b01c0a3d8c1c0a1fde23 (diff) | |
download | otp-f945aa4a8067d745ee75fe695272104e220d7bc3.tar.gz otp-f945aa4a8067d745ee75fe695272104e220d7bc3.tar.bz2 otp-f945aa4a8067d745ee75fe695272104e220d7bc3.zip |
[socket-nif|test] Added test case for closed socket while recv
Added a test cases for testing "socket cleanup" while
process is reading using recv.
OTP-14831
Diffstat (limited to 'lib/kernel/test')
-rw-r--r-- | lib/kernel/test/socket_SUITE.erl | 632 |
1 files changed, 596 insertions, 36 deletions
diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index 42420a37dc..8ab39f6ffe 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -66,11 +66,11 @@ api_to_recvmsg_tcp4/1, api_to_recvmsg_tcp6/1, - %% Controlling Process - sc_socket_cleanup_tcp4/1, - sc_socket_cleanup_tcp6/1, - sc_socket_cleanup_udp4/1, - sc_socket_cleanup_udp6/1, + %% Socket Closure + sc_cpe_socket_cleanup_tcp4/1, + sc_cpe_socket_cleanup_tcp6/1, + sc_cpe_socket_cleanup_udp4/1, + sc_cpe_socket_cleanup_udp6/1, sc_recv_response_local_close_tcp4/1, sc_recv_response_local_close_tcp6/1, sc_recv_response_remote_close_tcp4/1, @@ -130,7 +130,8 @@ groups() -> {api_basic, [], api_basic_cases()}, {api_options, [], api_options_cases()}, {api_op_with_timeout, [], api_op_with_timeout_cases()}, - {socket_closure, [], socket_closure_cases()} + {socket_closure, [], socket_closure_cases()}, + {sc_ctrl_proc_exit, [], sc_cp_exit_cases()} %% {tickets, [], ticket_cases()} ]. @@ -185,10 +186,7 @@ api_op_with_timeout_cases() -> %% remotely. socket_closure_cases() -> [ - sc_socket_cleanup_tcp4, - sc_socket_cleanup_tcp6, - sc_socket_cleanup_udp4, - sc_socket_cleanup_udp6, + {group, sc_ctrl_proc_exit}, sc_recv_response_local_close_tcp4, sc_recv_response_local_close_tcp6, @@ -206,6 +204,16 @@ socket_closure_cases() -> sc_acceptor_response_remote_close_tcp6 ]. +%% These cases are all about socket cleanup after the controlling process +%% exits *without* calling socket:close/1. +sc_cp_exit_cases() -> + [ + sc_cpe_socket_cleanup_tcp4, + sc_cpe_socket_cleanup_tcp6, + sc_cpe_socket_cleanup_udp4, + sc_cpe_socket_cleanup_udp6 + ]. + %% ticket_cases() -> %% []. @@ -1782,7 +1790,7 @@ api_to_maccept_tcp(InitState) -> #{desc => "close (listen) socket", cmd => fun(#{lsock := LSock} = State) -> sock_close(LSock), - {ok, maps:remove(sock3, State)} + {ok, maps:remove(lsock, State)} end}, %% *** We are done *** @@ -2805,18 +2813,18 @@ api_to_recvmsg_tcp6(_Config) when is_list(_Config) -> %% ("removed") when the controlling process terminates (without explicitly %% calling the close function). For a IPv4 TCP (stream) socket. -sc_socket_cleanup_tcp4(suite) -> +sc_cpe_socket_cleanup_tcp4(suite) -> []; -sc_socket_cleanup_tcp4(doc) -> +sc_cpe_socket_cleanup_tcp4(doc) -> []; -sc_socket_cleanup_tcp4(_Config) when is_list(_Config) -> - tc_try(sc_socket_cleanup_tcp4, +sc_cpe_socket_cleanup_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_cpe_socket_cleanup_tcp4, fun() -> %% not_yet_implemented(), InitState = #{domain => inet, type => stream, protocol => tcp}, - ok = sc_socket_cleanup(InitState) + ok = sc_cpe_socket_cleanup(InitState) end). @@ -2825,18 +2833,18 @@ sc_socket_cleanup_tcp4(_Config) when is_list(_Config) -> %% ("removed") when the controlling process terminates (without explicitly %% calling the close function). For a IPv6 TCP (stream) socket. -sc_socket_cleanup_tcp6(suite) -> +sc_cpe_socket_cleanup_tcp6(suite) -> []; -sc_socket_cleanup_tcp6(doc) -> +sc_cpe_socket_cleanup_tcp6(doc) -> []; -sc_socket_cleanup_tcp6(_Config) when is_list(_Config) -> - tc_try(sc_socket_cleanup_tcp6, +sc_cpe_socket_cleanup_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_cpe_socket_cleanup_tcp6, fun() -> not_yet_implemented(), InitState = #{domain => inet6, type => stream, protocol => tcp}, - ok = sc_socket_cleanup(InitState) + ok = sc_cpe_socket_cleanup(InitState) end). @@ -2845,17 +2853,17 @@ sc_socket_cleanup_tcp6(_Config) when is_list(_Config) -> %% ("removed") when the controlling process terminates (without explicitly %% calling the close function). For a IPv4 UDP (dgram) socket. -sc_socket_cleanup_udp4(suite) -> +sc_cpe_socket_cleanup_udp4(suite) -> []; -sc_socket_cleanup_udp4(doc) -> +sc_cpe_socket_cleanup_udp4(doc) -> []; -sc_socket_cleanup_udp4(_Config) when is_list(_Config) -> - tc_try(sc_socket_cleanup_udp4, +sc_cpe_socket_cleanup_udp4(_Config) when is_list(_Config) -> + tc_try(sc_cpe_socket_cleanup_udp4, fun() -> InitState = #{domain => inet, type => dgram, protocol => udp}, - ok = sc_socket_cleanup(InitState) + ok = sc_cpe_socket_cleanup(InitState) end). @@ -2865,24 +2873,24 @@ sc_socket_cleanup_udp4(_Config) when is_list(_Config) -> %% (removed) when the controlling process terminates (without explicitly %% calling the close function). For a IPv6 UDP (dgram) socket. -sc_socket_cleanup_udp6(suite) -> +sc_cpe_socket_cleanup_udp6(suite) -> []; -sc_socket_cleanup_udp6(doc) -> +sc_cpe_socket_cleanup_udp6(doc) -> []; -sc_socket_cleanup_udp6(_Config) when is_list(_Config) -> - tc_try(sc_socket_cleanup_udp6, +sc_cpe_socket_cleanup_udp6(_Config) when is_list(_Config) -> + tc_try(sc_cpe_socket_cleanup_udp6, fun() -> not_yet_implemented(), InitState = #{domain => inet6, type => dgram, protocol => udp}, - ok = sc_socket_cleanup(InitState) + ok = sc_cpe_socket_cleanup(InitState) end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -sc_socket_cleanup(InitState) -> +sc_cpe_socket_cleanup(InitState) -> OwnerSeq = [ %% *** Wait for start order part *** @@ -3022,8 +3030,16 @@ sc_socket_cleanup(InitState) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test what happens when a socket is -%% locally closed while the process is calling the recv function. +%% locally closed while a process is calling the recv function. %% Socket is IPv4. +%% +%% <KOLLA> +%% +%% We should really have a similar test cases for when the controlling +%% process exits and there are other processes in recv, accept, and +%% all the other functions. +%% +%% </KOLLA> sc_recv_response_local_close_tcp4(suite) -> []; @@ -3032,7 +3048,7 @@ sc_recv_response_local_close_tcp4(doc) -> sc_recv_response_local_close_tcp4(_Config) when is_list(_Config) -> tc_try(sc_recv_response_local_close_tcp4, fun() -> - not_yet_implemented(), + %% not_yet_implemented(), Recv = fun(Sock) -> socket:recv(Sock) end, InitState = #{domain => inet, type => stream, @@ -3066,8 +3082,548 @@ sc_recv_response_local_close_tcp6(_Config) when is_list(_Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -sc_receive_response_local_close_tcp(_InitState) -> - ok. +sc_receive_response_local_close_tcp(InitState) -> + %% This is the server that accepts connections. + %% But it is also suppose to close the connection socket, + %% and trigger the read failure for the handler process. + AcceptorSeq = + [ + %% *** 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}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + 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, + type := Type, + protocol := Proto} = State) -> + case socket:open(Domain, Type, Proto) 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 ready (init)", + cmd => fun(#{tester := Tester, lport := Port}) -> + Tester ! {ready, self(), Port}, + ok + end}, + + %% The actual test + #{desc => "await continue (connection)", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {continue, Tester, Handler} -> + {ok, State#{handler => Handler}} + end + end}, + #{desc => "await connection", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ei("connection accepted"), + {ok, State#{csock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "transfer new connection to handler", + cmd => fun(#{handler := Handler, csock := Sock}) -> + ok = socket:setopt(Sock, + otp, controlling_process, + Handler), + Handler ! {connection, Sock}, + ok + end}, + #{desc => "announce ready (connection)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "await continue (close)", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {continue, Tester} -> + ok + end + end}, + %% #{desc => "enable debug", + %% cmd => fun(#{csock := Sock}) -> + %% socket:setopt(Sock, otp, debug, true) + %% end}, + #{desc => "close (the connection) socket", + cmd => fun(#{csock := Sock}) -> + socket:close(Sock) + end}, + + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + #{desc => "socket cleanup", + cmd => fun(#{lsock := Sock} = State) -> + ok = socket:close(Sock), + State1 = maps:remove(csock, State), + State2 = maps:remove(lsock, State1), + State3 = maps:remove(lport, State2), + {ok, State3} + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + %% The point of this is to perform the recv for which we are testing the reponse + HandlerSeq = + [ + %% *** 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}, + #{desc => "monitor server", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + + %% The actual test + #{desc => "await connection socket", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {connection, Sock} -> + {ok, State#{sock => Sock}} + end + end}, + #{desc => "announce ready (connection)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + %% #{desc => "enable debug", + %% cmd => fun(#{sock := Sock}) -> + %% socket:setopt(Sock, otp, debug, true) + %% end}, + %% #{desc => "monitored-by", + %% cmd => fun(_) -> + %% {_, Mons} = process_info(self(), monitored_by), + %% ei("Monitored By: ~p", [Mons]), + %% ok + %% end}, + #{desc => "attempt recv", + cmd => fun(#{sock := Sock} = State) -> + case socket:recv(Sock) of + {ok, _Data} -> + ee("Unexpected data received"), + {error, unexpected_data}; + {error, closed} -> + State1 = maps:remove(sock, State), + {ok, State1}; + {error, Reason} = ERROR -> + ee("Unexpected read faulure: " + "~n ~p", [Reason]), + ERROR + end + end}, + %% #{desc => "monitored-by", + %% cmd => fun(_) -> + %% {_, Mons} = process_info(self(), monitored_by), + %% ei("Monitored By: ~p", [Mons]), + %% ok + %% end}, + #{desc => "announce ready (close)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "sleep some", + cmd => fun(_) -> + ?SLEEP(1000), + ok + end}, + %% #{desc => "monitored-by", + %% cmd => fun(_) -> + %% {_, Mons} = process_info(self(), monitored_by), + %% ei("Monitored By: ~p", [Mons]), + %% ok + %% end}, + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + ok + end + end}, + + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + %% The point of this is basically just to create the connection. + ClientSeq = + [ + %% *** 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 + #{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", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Proto} = State) -> + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind socket to local address", + cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + + %% The actual test + #{desc => "await continue", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Tester, Reason]), + {error, {unexpected_exit, tester, Reason}}; + {continue, Tester, Port} -> + {ok, State#{lport => Port}} + end + end}, + #{desc => "connect to server", + cmd => fun(#{sock := Sock, lsa := LSA, lport := LPort}) -> + socket:connect(Sock, LSA#{port => LPort}) + end}, + #{desc => "announce ready (connection)", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + + %% Cleaning up + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Tester, Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + {ok, maps:remove(tester, 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} + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor acceptor", + cmd => fun(#{acceptor := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor handler", + cmd => fun(#{handler := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the acceptor + #{desc => "order acceptor start", + cmd => fun(#{acceptor := Pid} = _State) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await acceptor ready (init)", + cmd => fun(#{acceptor := Pid} = State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding acceptor ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid, Port} -> + {ok, State#{lport => Port}} + end + end}, + + %% Start the handler + #{desc => "order handler start", + cmd => fun(#{handler := Pid} = _State) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await handler ready (init)", + cmd => fun(#{handler := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding handler ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid} = _State) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding cient ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + + %% The actual test + #{desc => "order acceptor to continue", + cmd => fun(#{acceptor := Pid, handler := Handler} = _State) -> + Pid ! {continue, self(), Handler}, + ok + end}, + #{desc => "order client to continue", + cmd => fun(#{client := Pid, lport := Port} = _State) -> + Pid ! {continue, self(), Port}, + ok + end}, + #{desc => "await acceptor ready (connection)", + cmd => fun(#{acceptor := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding acceptor ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await client ready (connection)", + cmd => fun(#{client := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding client ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await handler ready (connection)", + cmd => fun(#{handler := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding handler ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "sleep some", + cmd => fun(_State) -> + ?SLEEP(1000), + ok + end}, + #{desc => "order acceptor to continue (close)", + cmd => fun(#{acceptor := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + #{desc => "await handler ready (close)", + cmd => fun(#{handler := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding handler ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + + %% Terminations + #{desc => "order handler to terminate", + cmd => fun(#{handler := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await handler termination", + cmd => fun(#{handler := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(handler, State)} + end + end}, + #{desc => "order client to terminate", + cmd => fun(#{client := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(client, State)} + end + end}, + #{desc => "order acceptor to terminate", + cmd => fun(#{acceptor := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await acceptor termination", + cmd => fun(#{acceptor := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(acceptor, State)} + end + end}, + + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("start acceptor evaluator"), + AccInitState = InitState, + Acceptor = evaluator_start("acceptor", AcceptorSeq, AccInitState), + + p("start handler evaluator"), + HandlerInitState = #{}, + Handler = evaluator_start("handler", HandlerSeq, HandlerInitState), + + p("start client evaluator"), + ClientInitState = InitState, + Client = evaluator_start("client", ClientSeq, ClientInitState), + + p("start tester evaluator"), + TesterInitState = #{acceptor => Acceptor, + handler => Handler, + client => Client}, + Tester = evaluator_start("tester", TesterSeq, TesterInitState), + + p("await evaluator"), + ok = await_evaluator_finish([Acceptor, Handler, Client, Tester]). @@ -3426,9 +3982,13 @@ await_evaluator_finish(Evs, Fails) -> end. +ei(F) -> + ei(F, []). ei(F, A) -> eprint("", F, A). +ee(F) -> + ee(F, []). ee(F, A) -> eprint("<ERROR> ", F, A). |