diff options
Diffstat (limited to 'lib/stdlib/test/gen_server_SUITE.erl')
-rw-r--r-- | lib/stdlib/test/gen_server_SUITE.erl | 400 |
1 files changed, 388 insertions, 12 deletions
diff --git a/lib/stdlib/test/gen_server_SUITE.erl b/lib/stdlib/test/gen_server_SUITE.erl index 5fa604d4fd..52f6a2ceb9 100644 --- a/lib/stdlib/test/gen_server_SUITE.erl +++ b/lib/stdlib/test/gen_server_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2021. All Rights Reserved. +%% Copyright Ericsson AB 1996-2023. 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. @@ -26,12 +26,16 @@ -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2]). --export([start/1, crash/1, call/1, send_request/1, +-export([start/1, crash/1, loop_start_fail/1, call/1, send_request/1, send_request_receive_reqid_collection/1, send_request_wait_reqid_collection/1, send_request_check_reqid_collection/1, - cast/1, cast_fast/1, - continue/1, info/1, abcast/1, multicall/1, multicall_down/1, + cast/1, cast_fast/1, continue/1, info/1, abcast/1, + multicall/1, multicall_down/1, multicall_remote/1, + multicall_remote_old1/1, multicall_remote_old2/1, + multicall_recv_opt_success/1, + multicall_recv_opt_timeout/1, + multicall_recv_opt_noconnection/1, call_remote1/1, call_remote2/1, call_remote3/1, calling_self/1, call_remote_n1/1, call_remote_n2/1, call_remote_n3/1, spec_init/1, spec_init_local_registered_parent/1, @@ -59,20 +63,33 @@ spec_init_anonymous_default_timeout/1, spec_init_not_proc_lib/1, cast_fast_messup/0]). +%% Internal test specific exports +-export([multicall_srv_ctrlr/2, multicall_suspender/2]). %% The gen_server behaviour -export([init/1, handle_call/3, handle_cast/2, handle_continue/2, handle_info/2, code_change/3, terminate/2, format_status/2]). +%% This module needs to compile on old nodes... +-ifndef(CT_PEER). +-define(CT_PEER(), {ok, undefined, undefined}). +-define(CT_PEER(Opts), {ok, undefined, undefined}). +-endif. +-ifndef(CT_PEER_REL). +-define(CT_PEER_REL(Opts, Release, PrivDir), {ok, undefined, undefined}). +-endif. + suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,1}}]. all() -> - [start, {group,stop}, crash, call, send_request, + [start, {group,stop}, crash, loop_start_fail, call, send_request, send_request_receive_reqid_collection, send_request_wait_reqid_collection, send_request_check_reqid_collection, cast, cast_fast, info, abcast, - continue, multicall, multicall_down, call_remote1, call_remote2, calling_self, + continue, + {group, multi_call}, + call_remote1, call_remote2, calling_self, call_remote3, call_remote_n1, call_remote_n2, call_remote_n3, spec_init, spec_init_local_registered_parent, @@ -87,6 +104,13 @@ all() -> groups() -> [{stop, [], [stop1, stop2, stop3, stop4, stop5, stop6, stop7, stop8, stop9, stop10]}, + {multi_call, [], [{group, multi_call_parallel}, {group, multi_call_sequence}]}, + {multi_call_parallel, [parallel], + [multicall, multicall_down, multicall_remote, multicall_remote_old1, + multicall_remote_old2]}, + {multi_call_sequence, [], + [multicall_recv_opt_success, multicall_recv_opt_timeout, + multicall_recv_opt_noconnection]}, {format_status, [], [call_format_status, error_format_status, terminate_crash_format, crash_in_format_status, throw_in_format_status, format_all_status]}, @@ -135,7 +159,10 @@ end_per_testcase(_Case, Config) -> undefined -> ok; Peer -> - peer:stop(Peer) + try peer:stop(Peer) + catch exit : noproc -> + ok + end end, ok. @@ -148,6 +175,7 @@ start(Config) when is_list(Config) -> OldFl = process_flag(trap_exit, true), %% anonymous + io:format("anonymous~n", []), {ok, Pid0} = gen_server:start(gen_server_SUITE, [], []), ok = gen_server:call(Pid0, started_p), ok = gen_server:call(Pid0, stop), @@ -155,6 +183,7 @@ start(Config) when is_list(Config) -> {'EXIT', {noproc,_}} = (catch gen_server:call(Pid0, started_p, 1)), %% anonymous with timeout + io:format("try init timeout~n", []), {ok, Pid00} = gen_server:start(gen_server_SUITE, [], [{timeout,1000}]), ok = gen_server:call(Pid00, started_p), @@ -163,9 +192,16 @@ start(Config) when is_list(Config) -> [{timeout,100}]), %% anonymous with ignore + io:format("try init ignore~n", []), ignore = gen_server:start(gen_server_SUITE, ignore, []), + %% anonymous with shutdown + io:format("try init shutdown~n", []), + {error, foobar} = + gen_server:start(gen_server_SUITE, {error, foobar}, []), + %% anonymous with stop + io:format("try init stop~n", []), {error, stopped} = gen_server:start(gen_server_SUITE, stop, []), %% anonymous linked @@ -485,6 +521,55 @@ crash(Config) when is_list(Config) -> ok. + +loop_start_fail(Config) -> + _ = process_flag(trap_exit, true), + loop_start_fail( + Config, + [{start, []}, {start, [link]}, + {start_link, []}, + {start_monitor, [link]}, {start_monitor, []}]). + +loop_start_fail(_Config, []) -> + ok; +loop_start_fail(Config, [{Start, Opts} | Start_Opts]) -> + loop_start_fail( + fun gen_server:Start/3, + {ets, {return, {stop, failed_to_start}}}, Opts, + fun ({error, failed_to_start}) -> ok end), + loop_start_fail( + fun gen_server:Start/3, + {ets, {return, ignore}}, Opts, + fun (ignore) -> ok end), + loop_start_fail( + fun gen_server:Start/3, + {ets, {return, 4711}}, Opts, + fun ({error, {bad_return_value, 4711}}) -> ok end), + loop_start_fail( + fun gen_server:Start/3, + {ets, {crash, error, bailout}}, Opts, + fun ({error, {bailout, ST}}) when is_list(ST) -> ok end), + loop_start_fail( + fun gen_server:Start/3, + {ets, {crash, exit, bailout}}, Opts, + fun ({error, bailout}) -> ok end), + loop_start_fail( + fun gen_server:Start/3, + {ets, {wait, 1000, void}}, [{timeout, 200} | Opts], + fun ({error, timeout}) -> ok end), + loop_start_fail(Config, Start_Opts). + +loop_start_fail(GenStartFun, Arg, Opts, ValidateFun) -> + loop_start_fail(GenStartFun, Arg, Opts, ValidateFun, 5). +%% +loop_start_fail(_GenStartFun, _Arg, _Opts, _ValidateFun, 0) -> + ok; +loop_start_fail(GenStartFun, Arg, Opts, ValidateFun, N) -> + ok = ValidateFun(GenStartFun(?MODULE, Arg, Opts)), + loop_start_fail(GenStartFun, Arg, Opts, ValidateFun, N - 1). + + + %% -------------------------------------- %% Test gen_server:call and handle_call. %% Test all different return values from @@ -1434,6 +1519,251 @@ busy_wait_for_process(Pid,N) -> _ -> ok end. + +multicall_remote(Config) when is_list(Config) -> + PNs = lists:map(fun (_) -> + {ok, P, N} = ?CT_PEER(), + {P, N} + end, lists:seq(1, 4)), + multicall_remote_test(PNs, ?FUNCTION_NAME), + ok. + +multicall_remote_old1(Config) when is_list(Config) -> + multicall_remote_old_test(Config, 1, ?FUNCTION_NAME). + +multicall_remote_old2(Config) when is_list(Config) -> + multicall_remote_old_test(Config, 2, ?FUNCTION_NAME). + + +multicall_remote_old_test(Config, OldN, Name) -> + try + {OldRelName, OldRel} = old_release(OldN), + PD = proplists:get_value(priv_dir, Config), + PNs = lists:map(fun (I) -> + Dir = atom_to_list(Name)++"-"++integer_to_list(I), + AbsDir = filename:join([PD, Dir]), + ok = file:make_dir(AbsDir), + case ?CT_PEER_REL(#{connection => 0}, OldRelName, AbsDir) of + not_available -> + throw({skipped, "No OTP "++OldRel++" available"}); + {ok, P, N} -> + {P, N} + end + end, lists:seq(1, 4)), + OldNodes = lists:map(fun ({_, N}) -> N end, PNs), + %% Recompile on one old node and load this on all old nodes... + SrcFile = filename:rootname(code:which(?MODULE)) ++ ".erl", + {ok, ?MODULE, BeamCode} = erpc:call(hd(OldNodes), compile, file, [SrcFile, [binary]]), + LoadResult = lists:duplicate(length(OldNodes), {ok, {module, ?MODULE}}), + LoadResult = erpc:multicall(OldNodes, code, load_binary, [?MODULE, SrcFile, BeamCode]), + multicall_remote_test(PNs, Name) + catch + throw:Res -> + Res + end. + +multicall_remote_test([{Peer1, Node1}, + {Peer2, Node2}, + {Peer3, Node3}, + {Peer4, Node4}], + Name) -> + Tester = self(), + ThisNode = node(), + + Nodes = [Node1, Node2, Node3, Node4, ThisNode], + + SrvList = + lists:map(fun (Node) -> + Ctrl = spawn_link(Node, ?MODULE, + multicall_srv_ctrlr, + [Tester, Name]), + receive + {Ctrl, _Srv} = Procs -> + {Node, Procs} + end + end, Nodes), + SrvMap = maps:from_list(SrvList), + + Res0 = {lists:map(fun (Node) -> + {Node,ok} + end, Nodes), []}, + + Res0 = gen_server:multi_call(Nodes, Name, started_p), + + true = try + _ = gen_server:multi_call([Node1, Node2, Node3, node(), {Node4}], + Name, {delayed_answer,1}), + false + catch + _:_ -> + true + end, + + Res1 = {lists:map(fun (Node) -> + {Node,delayed} + end, Nodes), []}, + + Res1 = gen_server:multi_call(Nodes, Name, {delayed_answer,1}), + + Res2 = {[], Nodes}, + + Start = erlang:monotonic_time(millisecond), + Res2 = gen_server:multi_call(Nodes, Name, {delayed_answer,1000}, 100), + End = erlang:monotonic_time(millisecond), + Time = End-Start, + ct:log("Time: ~p ms~n", [Time]), + true = 200 >= Time, + + {Ctrl2, Srv2} = maps:get(Node2, SrvMap), + unlink(Ctrl2), + exit(Ctrl2, kill), + wait_until(fun () -> + false == erpc:call(Node2, erlang, + is_process_alive, [Srv2]) + end), + + {Ctrl3, _Srv3} = maps:get(Node3, SrvMap), + unlink(Ctrl3), + peer:stop(Peer3), + + {Ctrl4, Srv4} = maps:get(Node4, SrvMap), + Spndr = spawn_link(Node4, ?MODULE, multicall_suspender, [Tester, Srv4]), + + Res3 = {[{Node1, delayed}, {ThisNode, delayed}], + [Node2, Node3, Node4]}, + + Res3 = gen_server:multi_call(Nodes, Name, {delayed_answer,1}, 1000), + + Spndr ! {Tester, resume_it}, + + receive Msg -> ct:fail({unexpected_msg, Msg}) + after 1000 -> ok + end, + + unlink(Ctrl4), + + {Ctrl1, _Srv1} = maps:get(Node1, SrvMap), + + unlink(Ctrl1), + + peer:stop(Peer1), + peer:stop(Peer2), + peer:stop(Peer4), + + ok. + +multicall_srv_ctrlr(Tester, Name) -> + {ok, Srv} = gen_server:start_link({local, Name}, + gen_server_SUITE, [], []), + Tester ! {self(), Srv}, + receive after infinity -> ok end. + +multicall_suspender(Tester, Suspendee) -> + true = erlang:suspend_process(Suspendee), + receive + {Tester, resume_it} -> + erlang:resume_process(Suspendee) + end. + +multicall_recv_opt_success(Config) when is_list(Config) -> + multicall_recv_opt_test(success). + +multicall_recv_opt_timeout(Config) when is_list(Config) -> + multicall_recv_opt_test(timeout). + +multicall_recv_opt_noconnection(Config) when is_list(Config) -> + multicall_recv_opt_test(noconnection). + +multicall_recv_opt_test(Type) -> + Tester = self(), + Name = ?FUNCTION_NAME, + Loops = 1000, + HugeMsgQ = 500000, + process_flag(message_queue_data, off_heap), + + {ok, Peer1, Node1} = ?CT_PEER(), + {ok, Peer2, Node2} = ?CT_PEER(), + + if Type == noconnection -> peer:stop(Peer2); + true -> ok + end, + + Nodes = [Node1, Node2], + + SrvList = + lists:map(fun (Node) -> + Ctrl = spawn_link(Node, ?MODULE, + multicall_srv_ctrlr, + [Tester, Name]), + receive + {Ctrl, _Srv} = Procs -> + {Node, Procs} + end + end, + if Type == noconnection -> [Node1]; + true -> Nodes + end), + + {Req, ExpRes, Tmo} = case Type of + success -> + {ping, + {[{Node1, pong}, {Node2, pong}], []}, + infinity}; + timeout -> + {{delayed_answer,100}, + {[], Nodes}, + 1}; + noconnection -> + {ping, + {[{Node1, pong}], [Node2]}, + infinity} + end, + + _Warmup = time_multicall(ExpRes, Nodes, Name, Req, Tmo, Loops div 10), + + Empty = time_multicall(ExpRes, Nodes, Name, Req, Tmo, Loops), + ct:pal("Time with empty message queue: ~p microsecond~n", + [erlang:convert_time_unit(Empty, native, microsecond)]), + + make_msgq(HugeMsgQ), + + Huge = time_multicall(ExpRes, Nodes, Name, Req, Tmo, Loops), + ct:pal("Time with huge message queue: ~p microsecond~n", + [erlang:convert_time_unit(Huge, native, microsecond)]), + + lists:foreach(fun ({_Node, {Ctrl, _Srv}}) -> unlink(Ctrl) end, SrvList), + + peer:stop(Peer1), + if Type == noconnection -> ok; + true -> peer:stop(Peer2) + end, + + Q = Huge / Empty, + HugeMsgQ = flush_msgq(), + case Q > 10 of + true -> + ct:fail({ratio, Q}); + false -> + {comment, "Ratio: "++erlang:float_to_list(Q)} + end. + +time_multicall(Expect, Nodes, Name, Req, Tmo, Times) -> + Start = erlang:monotonic_time(), + ok = do_time_multicall(Expect, Nodes, Name, Req, Tmo, Times), + erlang:monotonic_time() - Start. + +do_time_multicall(_Expect, _Nodes, _Name, _Req, _Tmo, 0) -> + ok; +do_time_multicall(Expect, Nodes, Name, Req, Tmo, N) -> + Expect = gen_server:multi_call(Nodes, Name, Req, Tmo), + do_time_multicall(Expect, Nodes, Name, Req, Tmo, N-1). + +make_msgq(0) -> + ok; +make_msgq(N) -> + self() ! {a, msg}, + make_msgq(N-1). + %%-------------------------------------------------------------- %% Test gen_server:enter_loop/[3,4,5]. Used when you want to write %% your own special init-phase. @@ -2049,11 +2379,10 @@ undef_init(_Config) -> {error, {undef, [{oc_init_server, init, [_], _}|_]}} = (catch gen_server:start_link(oc_init_server, [], [])), receive - {'EXIT', Server, - {undef, [{oc_init_server, init, [_], _}|_]}} when is_pid(Server) -> + Msg -> + ct:fail({unexpected_msg, Msg}) + after 500 -> ok - after 1000 -> - ct:fail(expected_exit_msg) end. %% The upgrade should fail if code_change is expected in the callback module @@ -2483,23 +2812,44 @@ spec_init_not_proc_lib(Options) -> init([]) -> {ok, []}; init(ignore) -> + io:format("init(ignore)~n"), ignore; +init({error, Reason}) -> + io:format("init(error) -> ~w~n", [Reason]), + {error, Reason}; init(stop) -> + io:format("init(stop)~n"), {stop, stopped}; init(hibernate) -> + io:format("init(hibernate)~n"), {ok,[],hibernate}; init(sleep) -> + io:format("init(sleep)~n"), ct:sleep(1000), {ok, []}; init({continue, Pid}) -> + io:format("init(continue) -> ~p~n", [Pid]), self() ! {after_continue, Pid}, {ok, [], {continue, {message, Pid}}}; init({state,State}) -> - {ok,State}. + io:format("init(state) -> ~p~n", [State]), + {ok,State}; +init({ets,InitResult}) -> + ?MODULE = ets:new(?MODULE, [named_table]), + case InitResult of + {return, Value} -> + Value; + {crash, Class, Reason} -> + erlang:Class(Reason); + {wait, Time, Value} -> + receive after Time -> Value end + end. handle_call(started_p, _From, State) -> io:format("FROZ"), {reply,ok,State}; +handle_call(ping, _From, State) -> + {reply,pong,State}; handle_call({delayed_answer, T}, From, State) -> {noreply,{reply_to,From,State},T}; handle_call({call_within, T}, _From, _) -> @@ -2507,6 +2857,7 @@ handle_call({call_within, T}, _From, _) -> handle_call(next_call, _From, call_within) -> {reply,ok,[]}; handle_call(next_call, _From, State) -> + io:format("handle_call(next_call) -> State: ~p~n", [State]), {reply,false,State}; handle_call(badreturn, _From, _State) -> badreturn; @@ -2645,3 +2996,28 @@ format_status(terminate, [_PDict, State]) -> {formatted, State}; format_status(normal, [_PDict, _State]) -> format_status_called. + +%% Utils... + +wait_until(Fun) -> + case catch Fun() of + true -> + ok; + _ -> + receive after 100 -> ok end, + wait_until(Fun) + end. + +old_release(N) -> + OldRel = integer_to_list(list_to_integer(erlang:system_info(otp_release))-N), + {OldRel++"_latest", OldRel}. + +flush_msgq() -> + flush_msgq(0). +flush_msgq(N) -> + receive + _ -> + flush_msgq(N+1) + after 0 -> + N + end. |