diff options
-rw-r--r-- | erts/emulator/test/Makefile | 5 | ||||
-rw-r--r-- | erts/emulator/test/socket_SUITE.erl | 151 | ||||
-rw-r--r-- | erts/emulator/test/socket_test_evaluator.erl | 487 | ||||
-rw-r--r-- | erts/emulator/test/socket_test_evaluator.hrl | 57 | ||||
-rw-r--r-- | erts/emulator/test/socket_test_lib.erl | 72 |
5 files changed, 695 insertions, 77 deletions
diff --git a/erts/emulator/test/Makefile b/erts/emulator/test/Makefile index 39a3ef2a3d..22c2f24a80 100644 --- a/erts/emulator/test/Makefile +++ b/erts/emulator/test/Makefile @@ -32,6 +32,8 @@ SOCKET_MODULES = \ socket_lib \ socket_server \ socket_client \ + socket_test_lib \ + socket_test_evaluator \ socket_SUITE MODULES= \ @@ -156,6 +158,7 @@ NATIVE_MODULES= $(NATIVE:%=%_native_SUITE) NATIVE_ERL_FILES= $(NATIVE_MODULES:%=%.erl) ERL_FILES= $(MODULES:%=%.erl) +HRL_FILES= socket_test_evaluator.hrl TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) SOCKET_TARGETS = $(SOCKET_MODULES:%=$(EBIN)/%.$(EMULATOR)) @@ -228,7 +231,7 @@ release_spec: release_tests_spec: make_emakefile $(INSTALL_DIR) "$(RELSYSDIR)" $(INSTALL_DATA) $(EMAKEFILE) $(TEST_SPEC_FILES) \ - $(ERL_FILES) "$(RELSYSDIR)" + $(ERL_FILES) $(HRL_FILES) "$(RELSYSDIR)" $(INSTALL_DATA) $(NO_OPT_ERL_FILES) "$(RELSYSDIR)" $(INSTALL_DATA) $(NATIVE_ERL_FILES) "$(RELSYSDIR)" chmod -R u+w "$(RELSYSDIR)" diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 8d96065076..f4b8d94346 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -93,23 +93,26 @@ %% Tickets ]). + +-include("socket_test_evaluator.hrl"). + %% Internal exports %% -export([]). --record(ev, {name :: string(), - pid :: pid(), - mref :: reference()}). --type ev() :: #ev{}. +%% -record(ev, {name :: string(), +%% pid :: pid(), +%% mref :: reference()}). +%% -type ev() :: #ev{}. -define(MKEV(N,P,R), #ev{name = N, pid = P, mref = R}). --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 initial_evaluator_state() :: map(). +%% -type evaluator_state() :: map(). +%% -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()}. +%% -type command() :: #{desc := string(), +%% cmd := command_fun()}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -126,6 +129,7 @@ -define(TT(T), ct:timetrap(T)). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% suite() -> @@ -403,12 +407,16 @@ api_b_open_and_close(InitState) -> end}, #{desc => "validate socket close", cmd => fun({_, ok}) -> - {ok, normal}; + ok; ({_, {error, _} = ERROR}) -> ERROR - end}], - Evaluator = evaluator_start("tester", Seq, InitState), - ok = await_evaluator_finish([Evaluator]). + end}, + + %% *** We are done *** + ?FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). @@ -543,12 +551,14 @@ api_b_send_and_recv_udp(InitState) -> end}, #{desc => "close dst socket", cmd => fun(#{sock_dst := Sock}) -> - ok = socket:close(Sock), - {ok, normal} - end} + ok = socket:close(Sock) + end}, + + %% *** We are done *** + ?FINISH_NORMAL ], - Evaluator = evaluator_start("tester", Seq, InitState), - ok = await_evaluator_finish([Evaluator]). + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). @@ -618,7 +628,7 @@ api_b_send_and_recv_tcp(InitState) -> %% *** Wait for start order *** #{desc => "await start (from tester)", cmd => fun(State) -> - Tester = ev_await_start(), + Tester = ?SEV_AWAIT_START(), {ok, State#{tester => Tester}} end}, #{desc => "monitor tester", @@ -658,14 +668,14 @@ api_b_send_and_recv_tcp(InitState) -> end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester, lport := Port}) -> - ev_ready(Tester, init, Port), + ?SEV_ANNOUNCE_READY(Tester, init, Port), ok end}, %% The actual test #{desc => "await continue (accept)", cmd => fun(#{tester := Tester}) -> - ev_await_continue(Tester, tester, accept) + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) end}, #{desc => "await connection", cmd => fun(#{lsock := LSock} = State) -> @@ -679,7 +689,7 @@ api_b_send_and_recv_tcp(InitState) -> end}, #{desc => "announce ready (accept)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, accept), + ?SEV_ANNOUNCE_READY(Tester, accept), ok end}, #{desc => "await (recv) request", @@ -693,13 +703,12 @@ api_b_send_and_recv_tcp(InitState) -> end}, #{desc => "announce ready (recv request)", cmd => fun(#{tester := Tester}) -> - %% Tester ! {ready, self(), Port}, - ev_ready(Tester, recv_req), + ?SEV_ANNOUNCE_READY(Tester, recv_req), ok end}, #{desc => "await continue (with send reply)", cmd => fun(#{tester := Tester}) -> - ev_await_continue(Tester, tester, send_reply) + ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply) end}, #{desc => "send reply", cmd => fun(#{csock := Sock, send := Send}) -> @@ -707,15 +716,14 @@ api_b_send_and_recv_tcp(InitState) -> end}, #{desc => "announce ready (send reply)", cmd => fun(#{tester := Tester}) -> - %% Tester ! {ready, self(), Port}, - ev_ready(Tester, send_reply), + ?SEV_ANNOUNCE_READY(Tester, send_reply), ok end}, %% *** Termination *** #{desc => "await terminate", cmd => fun(#{tester := Tester} = State) -> - case ev_await_terminate(Tester, tester) of + case ?SEV_AWAIT_TERMINATE(Tester, tester) of ok -> {ok, maps:remove(tester, State)}; {error, _} = ERROR -> @@ -732,10 +740,7 @@ api_b_send_and_recv_tcp(InitState) -> end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?FINISH_NORMAL ], ClientSeq = @@ -743,7 +748,7 @@ api_b_send_and_recv_tcp(InitState) -> %% *** Wait for start order *** #{desc => "await start (from tester)", cmd => fun(State) -> - {Tester, Port} = ev_await_start(), + {Tester, Port} = ?SEV_AWAIT_START(), {ok, State#{tester => Tester, server_port => Port}} end}, #{desc => "monitor tester", @@ -781,14 +786,14 @@ api_b_send_and_recv_tcp(InitState) -> end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, init), + ?SEV_ANNOUNCE_READY(Tester, init), ok end}, %% *** The actual test *** #{desc => "await continue (connect)", cmd => fun(#{tester := Tester} = _State) -> - ev_await_continue(Tester, tester, connect) + ?SEV_AWAIT_CONTINUE(Tester, tester, connect) end}, #{desc => "connect to server", cmd => fun(#{sock := Sock, server_sa := SSA}) -> @@ -796,12 +801,12 @@ api_b_send_and_recv_tcp(InitState) -> end}, #{desc => "announce ready (connect)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, connect), + ?SEV_ANNOUNCE_READY(Tester, connect), ok end}, #{desc => "await continue (send request)", cmd => fun(#{tester := Tester} = _State) -> - ev_await_continue(Tester, tester, send_req) + ?SEV_AWAIT_CONTINUE(Tester, tester, send_req) end}, #{desc => "send request (to server)", cmd => fun(#{sock := Sock, send := Send}) -> @@ -809,7 +814,7 @@ api_b_send_and_recv_tcp(InitState) -> end}, #{desc => "announce ready (send request)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, send_req), + ?SEV_ANNOUNCE_READY(Tester, send_req), ok end}, #{desc => "await recv reply (from server)", @@ -819,14 +824,14 @@ api_b_send_and_recv_tcp(InitState) -> end}, #{desc => "announce ready (recv reply)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, recv_reply), + ?SEV_ANNOUNCE_READY(Tester, recv_reply), ok end}, %% *** Termination *** #{desc => "await terminate", cmd => fun(#{tester := Tester} = State) -> - case ev_await_terminate(Tester, tester) of + case ?SEV_AWAIT_TERMINATE(Tester, tester) of ok -> {ok, maps:remove(tester, State)}; {error, _} = ERROR -> @@ -839,10 +844,7 @@ api_b_send_and_recv_tcp(InitState) -> end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?FINISH_NORMAL ], TesterSeq = @@ -862,31 +864,31 @@ api_b_send_and_recv_tcp(InitState) -> %% Start the server #{desc => "order server start", cmd => fun(#{server := Pid} = _State) -> - ev_start(Pid), + ?SEV_ANNOUNCE_START(Pid), ok end}, #{desc => "await server ready (init)", cmd => fun(#{server := Pid} = State) -> - {ok, Port} = ev_await_ready(Pid, server, init), + {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init), {ok, State#{server_port => Port}} end}, %% Start the client #{desc => "order client start", cmd => fun(#{client := Pid, server_port := Port} = _State) -> - ev_start(Pid, Port), + ?SEV_ANNOUNCE_START(Pid, Port), ok end}, #{desc => "await client ready (init)", cmd => fun(#{client := Pid} = _State) -> - ev_await_ready(Pid, client, init), + ?SEV_AWAIT_READY(Pid, client, init), ok end}, %% *** The actual test *** #{desc => "order server to continue (with accept)", cmd => fun(#{server := Server} = _State) -> - ev_continue(Server, accept), + ?SEV_ANNOUNCE_CONTINUE(Server, accept), ok end}, #{desc => "sleep", @@ -896,89 +898,86 @@ api_b_send_and_recv_tcp(InitState) -> end}, #{desc => "order client to continue (with connect)", cmd => fun(#{client := Client} = _State) -> - ev_continue(Client, connect), + ?SEV_ANNOUNCE_CONTINUE(Client, connect), ok end}, #{desc => "await client ready (connect)", cmd => fun(#{client := Client} = _State) -> - ev_await_ready(Client, client, connect) + ?SEV_AWAIT_READY(Client, client, connect) end}, #{desc => "await server ready (accept)", cmd => fun(#{server := Server} = _State) -> - ev_await_ready(Server, server, accept) + ?SEV_AWAIT_READY(Server, server, accept) end}, #{desc => "order client to continue (with send request)", cmd => fun(#{client := Client} = _State) -> - ev_continue(Client, send_req), + ?SEV_ANNOUNCE_CONTINUE(Client, send_req), ok end}, #{desc => "await client ready (with send request)", cmd => fun(#{client := Client} = _State) -> - ev_await_ready(Client, client, send_req) + ?SEV_AWAIT_READY(Client, client, send_req) end}, #{desc => "await server ready (request recv)", cmd => fun(#{server := Server} = _State) -> - ev_await_ready(Server, server, recv_req) + ?SEV_AWAIT_READY(Server, server, recv_req) end}, #{desc => "order server to continue (with send reply)", cmd => fun(#{server := Server} = _State) -> - ev_continue(Server, send_reply), + ?SEV_ANNOUNCE_CONTINUE(Server, send_reply), ok end}, #{desc => "await server ready (with reply sent)", cmd => fun(#{server := Server} = _State) -> - ev_await_ready(Server, server, send_reply) + ?SEV_AWAIT_READY(Server, server, send_reply) end}, #{desc => "await client ready (reply recv)", cmd => fun(#{client := Client} = _State) -> - ev_await_ready(Client, client, recv_reply) + ?SEV_AWAIT_READY(Client, client, recv_reply) end}, %% *** Termination *** #{desc => "order client to terminate", cmd => fun(#{client := Client} = _State) -> - ev_terminate(Client), + ?SEV_ANNOUNCE_TERMINATE(Client), ok end}, #{desc => "await client termination", cmd => fun(#{client := Client} = State) -> - ev_await_termination(Client), + ?SEV_AWAIT_TERMINATION(Client), State1 = maps:remove(client, State), {ok, State1} end}, #{desc => "order server to terminate", cmd => fun(#{server := Server} = _State) -> - ev_terminate(Server), + ?SEV_ANNOUNCE_TERMINATE(Server), ok end}, #{desc => "await server termination", cmd => fun(#{server := Server} = State) -> - ev_await_termination(Server), + ?SEV_AWAIT_TERMINATION(Server), State1 = maps:remove(server, State), {ok, State1} end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?FINISH_NORMAL ], i("start server evaluator"), - Server = evaluator_start("server", ServerSeq, InitState), + Server = ?SEV_START("server", ServerSeq, InitState), i("start client evaluator"), - Client = evaluator_start("client", ClientSeq, InitState), + Client = ?SEV_START("client", ClientSeq, InitState), i("await evaluator(s)"), i("start tester evaluator"), TesterInitState = #{server => Server#ev.pid, client => Client#ev.pid}, - Tester = evaluator_start("tester", TesterSeq, TesterInitState), + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), - ok = await_evaluator_finish([Server, Client, Tester]). + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). @@ -5891,10 +5890,10 @@ which_addr2(Domain, [_|IFO]) -> %% will be used as exit reason. %% A successful command shall evaluate to ok | {ok, NewState} --spec evaluator_start(Name, Seq, Init) -> ev() when +-spec evaluator_start(Name, Seq, Init) -> socket_test_evaluator:ev() when Name :: string(), - Seq :: [command()], - Init :: initial_evaluator_state(). + Seq :: [socket_test_evaluator:command()], + Init :: socket_test_evaluator:initial_evaluator_state(). evaluator_start(Name, Seq, Init) when is_list(Name) andalso is_list(Seq) andalso (Seq =/= []) -> diff --git a/erts/emulator/test/socket_test_evaluator.erl b/erts/emulator/test/socket_test_evaluator.erl new file mode 100644 index 0000000000..8498ef6a0c --- /dev/null +++ b/erts/emulator/test/socket_test_evaluator.erl @@ -0,0 +1,487 @@ +%% +%% %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_test_evaluator). + +%% Evaluator control functions +-export([ + start/3, + await_finish/1 + ]). + +%% Functions used by evaluators to interact with eachother +-export([ + %% Announce functions + %% (Send an announcement from one evaluator to another) + announce_start/1, announce_start/2, + announce_continue/2, announce_continue/3, + announce_ready/2, announce_ready/3, + announce_terminate/1, + + %% Await functions + %% (Wait for an announcement from another evaluator) + await_start/0, + await_continue/3, await_continue/4, + await_ready/3, await_ready/4, + await_terminate/2, await_terminate/3, + await_termination/1, await_termination/2 + ]). + +-export_type([ + ev/0, + initial_evaluator_state/0, + evaluator_state/0, + command_fun/0, + command/0 + ]). + + +-include("socket_test_evaluator.hrl"). + +-type ev() :: #ev{}. +-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(LIB, socket_test_lib). + +-define(EXTRA_NOTHING, '$nothing'). +-define(ANNOUNCEMENT_START, '$start'). +-define(ANNOUNCEMENT_READY, '$ready'). +-define(ANNOUNCEMENT_CONTINUE, '$continue'). +-define(ANNOUNCEMENT_TERMINATE, '$terminate'). + +-define(START_NAME_NONE, '$no-name'). +-define(START_SLOGAN, ?ANNOUNCEMENT_START). +-define(TERMINATE_SLOGAN, ?ANNOUNCEMENT_TERMINATE). + + +%% ============================================================================ + +-spec start(Name, Seq, Init) -> ev() when + Name :: string(), + Seq :: [command()], + Init :: initial_evaluator_state(). + +start(Name, Seq, InitState) + when is_list(Name) andalso is_list(Seq) andalso (Seq =/= []) -> + %% Make sure 'parent' is not already used + case maps:find(parent, InitState) of + {ok, _} -> + erlang:error({already_used, parent}); + error -> + InitState2 = InitState#{parent => self()}, + {Pid, MRef} = erlang:spawn_monitor( + fun() -> init(Name, Seq, InitState2) end), + #ev{name = Name, pid = Pid, mref = MRef} + end. + +init(Name, Seq, Init) -> + put(sname, Name), + loop(1, Seq, Init). + +loop(_ID, [], FinalState) -> + exit(FinalState); +loop(ID, [#{desc := Desc, + cmd := Cmd}|Cmds], State) when is_function(Cmd, 1) -> + i("evaluate command ~2w: ~s", [ID, Desc]), + try Cmd(State) of + ok -> + loop(ID + 1, Cmds, State); + {ok, NewState} -> + loop(ID + 1, Cmds, NewState); + {error, Reason} -> + e("command ~w failed: " + "~n Reason: ~p", [ID, Reason]), + exit({command_failed, ID, Reason, State}) + catch + C:E:S -> + e("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. + + +%% ============================================================================ + +-spec await_finish(Evs) -> term() when + Evs :: [ev()]. + +await_finish(Evs) -> + await_finish(Evs, []). + +await_finish([], []) -> + ok; +await_finish([], Fails) -> + Fails; +await_finish(Evs, Fails) -> + receive + {'DOWN', _MRef, process, Pid, normal} -> + case lists:keysearch(Pid, #ev.pid, Evs) of + {value, #ev{name = Name}} -> + + + i("evaluator '~s' (~p) success", [Name, Pid]), + + + + + NewEvs = lists:keydelete(Pid, #ev.pid, Evs), + await_finish(NewEvs, Fails); + false -> + i("unknown process ~p died (normal)", [Pid]), + await_finish(Evs, Fails) + end; + {'DOWN', _MRef, process, Pid, Reason} -> + case lists:keysearch(Pid, #ev.pid, Evs) of + {value, #ev{name = Name}} -> + i("evaluator '~s' (~p) failed", [Name, Pid]), + NewEvs = lists:keydelete(Pid, #ev.pid, Evs), + await_finish(NewEvs, [{Pid, Reason}|Fails]); + false -> + i("unknown process ~p died: " + "~n ~p", [Pid, Reason]), + await_finish(Evs, Fails) + end + end. + + +%% ============================================================================ + +-spec announce_start(To) -> ok when + To :: pid(). + +announce_start(To) -> + announce(To, ?ANNOUNCEMENT_START, ?START_SLOGAN). + +-spec announce_start(To, Extra) -> ok when + To :: pid(), + Extra :: term(). + +announce_start(To, Extra) -> + announce(To, ?ANNOUNCEMENT_START, ?START_SLOGAN, Extra). + + +%% ============================================================================ + +-spec announce_continue(To, Slogan) -> ok when + To :: pid(), + Slogan :: atom(). + +announce_continue(To, Slogan) -> + announce_continue(To, Slogan, ?EXTRA_NOTHING). + +-spec announce_continue(To, Slogan, Extra) -> ok when + To :: pid(), + Slogan :: atom(), + Extra :: term(). + +announce_continue(To, Slogan, Extra) -> + announce(To, ?ANNOUNCEMENT_CONTINUE, Slogan, Extra). + + +%% ============================================================================ + +-spec announce_ready(To, Slogan) -> ok when + To :: pid(), + Slogan :: atom(). + +announce_ready(To, Slogan) -> + announce_ready(To, Slogan, ?EXTRA_NOTHING). + +-spec announce_ready(To, Slogan, Extra) -> ok when + To :: pid(), + Slogan :: atom(), + Extra :: term(). + +announce_ready(To, Slogan, Extra) -> + announce(To, ?ANNOUNCEMENT_READY, Slogan, Extra). + + +%% ============================================================================ + +-spec announce_terminate(To) -> ok when + To :: pid(). + +announce_terminate(To) -> + announce(To, ?ANNOUNCEMENT_TERMINATE, ?TERMINATE_SLOGAN). + + +%% ============================================================================ + +-spec announce(To, Announcement, Slogan) -> ok when + To :: pid(), + Announcement :: atom(), + Slogan :: atom(). + +announce(To, Announcement, Slogan) -> + announce(To, Announcement, Slogan, ?EXTRA_NOTHING). + +-spec announce(To, Announcement, Slogan, Extra) -> ok when + To :: pid(), + Announcement :: atom(), + Slogan :: atom(), + Extra :: term(). + +announce(To, Announcement, Slogan, Extra) + when is_pid(To) andalso + is_atom(Announcement) andalso + is_atom(Slogan) -> + To ! {Announcement, self(), Slogan, Extra}, + ok. + + + +%% ============================================================================ + +-spec await_start() -> Pid | {Pid, Extra} when + Pid :: pid(), + Extra :: term(). + +await_start() -> + case await(any, ?START_NAME_NONE, ?ANNOUNCEMENT_START, ?START_SLOGAN, []) of + {ok, Pid} when is_pid(Pid) -> + Pid; + {ok, {Pid, _} = OK} when is_pid(Pid) -> + OK + end. + + + +%% ============================================================================ + +-spec await_continue(From, Name, Slogan) -> ok | {ok, Extra} | {error, Reason} when + From :: pid(), + Name :: atom(), + Slogan :: atom(), + Extra :: term(), + Reason :: term(). + +await_continue(From, Name, Slogan) -> + await_continue(From, Name, Slogan, []). + +-spec await_continue(From, Name, Slogan, OtherPids) -> + ok | {ok, Extra} | {error, Reason} when + From :: pid(), + Name :: atom(), + Slogan :: atom(), + OtherPids :: [{pid(), atom()}], + Extra :: term(), + Reason :: term(). + +await_continue(From, Name, Slogan, OtherPids) + when is_pid(From) andalso + is_atom(Name) andalso + is_atom(Slogan) andalso + is_list(OtherPids) -> + await(From, Name, ?ANNOUNCEMENT_CONTINUE, Slogan, OtherPids). + + + +%% ============================================================================ + +-spec await_ready(From, Name, Slogan) -> ok | {ok, Extra} | {error, Reason} when + From :: pid(), + Name :: atom(), + Slogan :: atom(), + Extra :: term(), + Reason :: term(). + +await_ready(From, Name, Slogan) -> + await_ready(From, Name, Slogan, []). + +-spec await_ready(From, Name, Slogan, OtherPids) -> + ok | {ok, Extra} | {error, Reason} when + From :: pid(), + Name :: atom(), + Slogan :: atom(), + OtherPids :: [{pid(), atom()}], + Extra :: term(), + Reason :: term(). + +await_ready(From, Name, Slogan, OtherPids) + when is_pid(From) andalso + is_atom(Name) andalso + is_atom(Slogan) andalso + is_list(OtherPids) -> + await(From, Name, ?ANNOUNCEMENT_READY, Slogan, OtherPids). + + + +%% ============================================================================ + +-spec await_terminate(Pid, Name) -> ok | {error, Reason} when + Pid :: pid(), + Name :: atom(), + Reason :: term(). + +await_terminate(Pid, Name) when is_pid(Pid) andalso is_atom(Name) -> + await_terminate(Pid, Name, []). + +-spec await_terminate(Pid, Name, OtherPids) -> ok | {error, Reason} when + Pid :: pid(), + Name :: atom(), + OtherPids :: [{pid(), atom()}], + Reason :: term(). + +await_terminate(Pid, Name, OtherPids) -> + await(Pid, Name, ?ANNOUNCEMENT_TERMINATE, ?TERMINATE_SLOGAN, OtherPids). + + +%% ============================================================================ + +-spec await_termination(Pid) -> ok | {error, Reason} when + Pid :: pid(), + Reason :: term(). + +await_termination(Pid) when is_pid(Pid) -> + await_termination(Pid, any). + +-spec await_termination(Pid, ExpReason) -> ok | {error, Reason} when + Pid :: pid(), + ExpReason :: term(), + Reason :: term(). + +await_termination(Pid, ExpReason) -> + receive + {'DOWN', _, process, Pid, _} when (ExpReason =:= any) -> + ok; + {'DOWN', _, process, Pid, Reason} when (ExpReason =:= Reason) -> + ok; + {'DOWN', _, process, Pid, Reason} -> + {error, {unexpected_exit, ExpReason, Reason}} + end. + + +%% ============================================================================ + +%% We expect a message (announcement) from Pid, but we also watch for DOWN from +%% both Pid and OtherPids, in which case the test has failed! + +-spec await(ExpPid, Name, Announcement, Slogan, OtherPids) -> + ok | {ok, Extra} | {error, Reason} when + ExpPid :: any | pid(), + Name :: atom(), + Announcement :: atom(), + Slogan :: atom(), + OtherPids :: [{pid(), atom()}], + Extra :: term(), + Reason :: term(). + +await(ExpPid, Name, Announcement, Slogan, OtherPids) + when (is_pid(ExpPid) orelse (ExpPid =:= any)) andalso + is_atom(Name) andalso + is_atom(Announcement) andalso + is_atom(Slogan) andalso + is_list(OtherPids) -> + receive + {Announcement, Pid, Slogan, ?EXTRA_NOTHING} when (ExpPid =:= any) -> + {ok, Pid}; + {Announcement, Pid, Slogan, Extra} when (ExpPid =:= any) -> + {ok, {Pid, Extra}}; + {Announcement, Pid, Slogan, ?EXTRA_NOTHING} when (Pid =:= ExpPid) -> + ok; + {Announcement, Pid, Slogan, Extra} when (Pid =:= ExpPid) -> + {ok, Extra}; + {'DOWN', _, process, Pid, Reason} when (Pid =:= ExpPid) -> + e("Unexpected DOWN regarding ~w ~p: " + "~n ~p", [Name, Pid, Reason]), + {error, {unexpected_exit, Name}}; + {'DOWN', _, process, OtherPid, Reason} -> + case check_down(OtherPid, Reason, OtherPids) of + ok -> + i("DOWN from unknown process ~p: " + "~n ~p", [OtherPid, Reason]), + await(ExpPid, Name, Announcement, Slogan, OtherPids); + {error, _} = ERROR -> + ERROR + end + after infinity -> % For easy debugging, just change to some valid time (5000) + i("await -> timeout for msg from ~p (~w): " + "~n Announcement: ~p" + "~n Slogan: ~p" + "~nwhen" + "~n Messages: ~p", + [ExpPid, Name, Announcement, Slogan, pi(messages)]), + await(ExpPid, Name, Announcement, Slogan, OtherPids) + end. + +pi(Item) -> + pi(self(), Item). + +pi(Pid, Item) -> + {Item, Info} = process_info(Pid, Item), + Info. + +check_down(Pid, DownReason, Pids) -> + case lists:keymember(Pid, 1, Pids) of + {value, {_, Name}} -> + e("Unexpected DOWN regarding ~w ~p: " + "~n ~p", [Name, Pid, DownReason]), + {error, {unexpected_exit, Name}}; + false -> + ok + end. + + +%% ============================================================================ + +f(F, A) -> + lists:flatten(io_lib:format(F, A)). + + +%% i(F) -> +%% i(F, []). +i(F, A) -> + print("", F, A). + +%% e(F) -> +%% e(F, []). +e(F, A) -> + print("<ERROR> ", F, A). + +print(Prefix, F, A) -> + %% The two prints is to get the output both in the shell (for when + %% "personal" testing is going on) and in the logs. + IDStr = + case get(sname) of + undefined -> + %% This means its not an evaluator, + %% or a named process. Instead its + %% most likely the test case itself, + %% so skip the name and the pid. + ""; + SName -> + f("[~s][~p]", [SName, self()]) + end, + FStr = f("[~s]~s ~s" ++ F, [?LIB:formated_timestamp(), IDStr, Prefix | A]), + io:format(user, FStr ++ "~n", []), + io:format(FStr, []). diff --git a/erts/emulator/test/socket_test_evaluator.hrl b/erts/emulator/test/socket_test_evaluator.hrl new file mode 100644 index 0000000000..1fb42a33b2 --- /dev/null +++ b/erts/emulator/test/socket_test_evaluator.hrl @@ -0,0 +1,57 @@ +%% +%% %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% +%% + +-ifndef(socket_test_evaluator). +-define(socket_test_evaluator, true). + +-define(SEV, socket_test_evaluator). + +-define(SEV_START(N, S, IS), ?SEV:start(N, S, IS)). +-define(SEV_AWAIT_FINISH(Evs), ?SEV:await_finish(Evs)). + +-define(SEV_ANNOUNCE_START(To), ?SEV:announce_start(To)). +-define(SEV_ANNOUNCE_START(To, Ex), ?SEV:announce_start(To, Ex)). +-define(SEV_ANNOUNCE_CONTINUE(To, S), ?SEV:announce_continue(To, S)). +-define(SEV_ANNOUNCE_CONTINUE(To, S, Ex), ?SEV:announce_continue(To, S, Ex)). +-define(SEV_ANNOUNCE_READY(To, S), ?SEV:announce_ready(To, S)). +-define(SEV_ANNOUNCE_READY(To, S, Ex), ?SEV:announce_ready(To, S, Ex)). +-define(SEV_ANNOUNCE_TERMINATE(To), ?SEV:announce_terminate(To)). + +-define(SEV_AWAIT_START(), ?SEV:await_start()). +-define(SEV_AWAIT_CONTINUE(F, N, S), ?SEV:await_continue(F, N, S)). +-define(SEV_AWAIT_CONTINUE(F, N, S, Ps), ?SEV:await_continue(F, N, S, Ps)). +-define(SEV_AWAIT_READY(F, N, S), ?SEV:await_ready(F, N, S)). +-define(SEV_AWAIT_READY(F, N, S, Ps), ?SEV:await_ready(F, N, S, Ps)). +-define(SEV_AWAIT_TERMINATE(F, N), ?SEV:await_terminate(F, N)). +-define(SEV_AWAIT_TERMINATE(F, N, Ps), ?SEV:await_terminate(F, N, Ps)). +-define(SEV_AWAIT_TERMINATION(P), ?SEV:await_termination(P)). +-define(SEV_AWAIT_TERMINATION(P, R), ?SEV:await_termination(P, R)). + +-record(ev, {name :: string(), + pid :: pid(), + mref :: reference()}). + +-define(FINISH_NORMAL, #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end}). + +-endif. % -ifdef(socket_test_evaluator). + diff --git a/erts/emulator/test/socket_test_lib.erl b/erts/emulator/test/socket_test_lib.erl new file mode 100644 index 0000000000..c36cc4fbfa --- /dev/null +++ b/erts/emulator/test/socket_test_lib.erl @@ -0,0 +1,72 @@ +%% +%% %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_test_lib). + +-export([ + %% Time stuff + timestamp/0, + tdiff/2, + formated_timestamp/0, + format_timestamp/1, + + %% Skipping + not_yet_implemented/0, + skip/1 + ]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +timestamp() -> + os:timestamp(). + + +tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> + T1 = A1*1000000000+B1*1000+(C1 div 1000), + T2 = A2*1000000000+B2*1000+(C2 div 1000), + T2 - T1. + + +formated_timestamp() -> + format_timestamp(os:timestamp()). + +format_timestamp({_N1, _N2, _N3} = TS) -> + {_Date, Time} = calendar:now_to_local_time(TS), + %% {YYYY,MM,DD} = Date, + {Hour,Min,Sec} = Time, + %% FormatTS = + %% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w.~w", + %% [YYYY, MM, DD, Hour, Min, Sec, N3]), + FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w", [Hour, Min, Sec]), + lists:flatten(FormatTS). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +not_yet_implemented() -> + skip("not yet implemented"). + +skip(Reason) -> + throw({skip, Reason}). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |