diff options
Diffstat (limited to 'lib/stdlib/test/gen_statem_SUITE.erl')
-rw-r--r-- | lib/stdlib/test/gen_statem_SUITE.erl | 600 |
1 files changed, 497 insertions, 103 deletions
diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl index b9084cc825..9b4ee9413f 100644 --- a/lib/stdlib/test/gen_statem_SUITE.erl +++ b/lib/stdlib/test/gen_statem_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2016-2021. All Rights Reserved. +%% Copyright Ericsson AB 2016-2022. 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. @@ -43,7 +43,9 @@ all() -> {group, sys}, hibernate, auto_hibernate, enter_loop, {group, undef_callbacks}, undef_in_terminate, {group, format_log}, - reply_by_alias_with_payload]. + reply_by_alias_with_payload, + send_request_receive_reqid_collection, send_request_wait_reqid_collection, + send_request_check_reqid_collection]. groups() -> [{start, [], tcs(start)}, @@ -53,6 +55,7 @@ groups() -> {abnormal, [], tcs(abnormal)}, {abnormal_handle_event, [], tcs(abnormal)}, {sys, [], tcs(sys)}, + {format_status, [], tcs(format_status)}, {sys_handle_event, [], tcs(sys)}, {undef_callbacks, [], tcs(undef_callbacks)}, {format_log, [], tcs(format_log)}]. @@ -66,9 +69,10 @@ tcs(abnormal) -> [abnormal1, abnormal1clean, abnormal1dirty, abnormal2, abnormal3, abnormal4]; tcs(sys) -> - [sys1, call_format_status, - error_format_status, terminate_crash_format, - get_state, replace_state]; + [sys1, {group, format_status}, get_state, replace_state]; +tcs(format_status) -> + [call_format_status, error_format_status, terminate_crash_format, + format_all_status]; tcs(undef_callbacks) -> [undef_code_change, undef_terminate1, undef_terminate2, pop_too_many]; @@ -88,7 +92,13 @@ init_per_group(GroupName, Config) GroupName =:= sys_handle_event -> [{callback_mode,handle_event_function}|Config]; init_per_group(undef_callbacks, Config) -> - compile_oc_statem(Config), + try compile_oc_statem(Config) + catch Class : Reason : Stacktrace -> + {fail,{Class,Reason,Stacktrace}} + end, + Config; +init_per_group(sys, Config) -> + compile_format_status_statem(Config), Config; init_per_group(_GroupName, Config) -> Config. @@ -119,6 +129,12 @@ compile_oc_statem(Config) -> {ok, oc_statem} = compile:file(StatemPath), ok. +compile_format_status_statem(Config) -> + DataDir = ?config(data_dir, Config), + StatemPath = filename:join(DataDir, "format_status_statem.erl"), + {ok, format_status_statem} = compile:file(StatemPath), + ok. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -define(EXPECT_FAILURE(Code, Reason), try begin Code end of @@ -452,76 +468,64 @@ stop7(Config) -> %% Anonymous on remote node stop8(Config) -> - Node = gen_statem_stop8, - {ok,NodeName} = ct_slave:start(Node), - Statem = - try - Dir = filename:dirname(code:which(?MODULE)), - rpc:block_call(NodeName, code, add_path, [Dir]), - {ok,Pid} = - rpc:block_call( - NodeName, gen_statem,start, - [?MODULE,start_arg(Config, []),[]]), - ok = gen_statem:stop(Pid), - false = rpc:block_call(NodeName, erlang, is_process_alive, [Pid]), - noproc = - ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason1), - Pid - after - {ok,NodeName} = ct_slave:stop(Node) - end, + {ok,Peer,NodeName} = ?CT_PEER(), + Dir = filename:dirname(code:which(?MODULE)), + rpc:block_call(NodeName, code, add_path, [Dir]), + {ok,Pid} = + rpc:block_call( + NodeName, gen_statem,start, + [?MODULE,start_arg(Config, []),[]]), + ok = gen_statem:stop(Pid), + false = rpc:block_call(NodeName, erlang, is_process_alive, [Pid]), + noproc = + ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason1), + + peer:stop(Peer), {{nodedown,NodeName},{sys,terminate,_}} = - ?EXPECT_FAILURE(gen_statem:stop(Statem), Reason2), + ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason2), ok. %% Registered name on remote node stop9(Config) -> Name = to_stop, LocalSTM = {local,Name}, - Node = gen_statem__stop9, - {ok,NodeName} = ct_slave:start(Node), - Statem = - try - STM = {Name,NodeName}, - Dir = filename:dirname(code:which(?MODULE)), - rpc:block_call(NodeName, code, add_path, [Dir]), - {ok,Pid} = - rpc:block_call( - NodeName, gen_statem, start, - [LocalSTM,?MODULE,start_arg(Config, []),[]]), - ok = gen_statem:stop(STM), - undefined = rpc:block_call(NodeName,erlang,whereis,[Name]), - false = rpc:block_call(NodeName,erlang,is_process_alive,[Pid]), - noproc = - ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1), - STM - after - {ok,NodeName} = ct_slave:stop(Node) - end, + {ok,Peer,NodeName} = ?CT_PEER(), + + STM = {Name,NodeName}, + Dir = filename:dirname(code:which(?MODULE)), + rpc:block_call(NodeName, code, add_path, [Dir]), + {ok,Pid} = + rpc:block_call( + NodeName, gen_statem, start, + [LocalSTM,?MODULE,start_arg(Config, []),[]]), + ok = gen_statem:stop(STM), + undefined = rpc:block_call(NodeName,erlang,whereis,[Name]), + false = rpc:block_call(NodeName,erlang,is_process_alive,[Pid]), + noproc = + ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1), + peer:stop(Peer), + {{nodedown,NodeName},{sys,terminate,_}} = - ?EXPECT_FAILURE(gen_statem:stop(Statem), Reason2), + ?EXPECT_FAILURE(gen_statem:stop(STM), Reason2), ok. %% Globally registered name on remote node stop10(Config) -> - Node = gen_statem_stop10, STM = {global,to_stop}, - {ok,NodeName} = ct_slave:start(Node), - try - Dir = filename:dirname(code:which(?MODULE)), - rpc:block_call(NodeName,code,add_path,[Dir]), - {ok,Pid} = - rpc:block_call( - NodeName, gen_statem, start, - [STM,?MODULE,start_arg(Config, []),[]]), - global:sync(), - ok = gen_statem:stop(STM), - false = rpc:block_call(NodeName, erlang, is_process_alive, [Pid]), - noproc = - ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1) - after - {ok,NodeName} = ct_slave:stop(Node) - end, + {ok,Peer,NodeName} = ?CT_PEER(), + Dir = filename:dirname(code:which(?MODULE)), + rpc:block_call(NodeName,code,add_path,[Dir]), + {ok,Pid} = + rpc:block_call( + NodeName, gen_statem, start, + [STM,?MODULE,start_arg(Config, []),[]]), + global:sync(), + ok = gen_statem:stop(STM), + false = rpc:block_call(NodeName, erlang, is_process_alive, [Pid]), + noproc = + ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1), + peer:stop(Peer), + noproc = ?EXPECT_FAILURE(gen_statem:stop(STM), Reason2), ok. @@ -1240,20 +1244,24 @@ code_change(_Config) -> stop_it(Pid). call_format_status(Config) -> - {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), + call_format_status(Config,?MODULE,format_status_called), + call_format_status(Config, format_status_statem, + {data,[{"State",{format_status_called,format_data}}]}). +call_format_status(Config, Module, Match) -> + {ok,Pid} = gen_statem:start(Module, start_arg(Config, []), []), Status = sys:get_status(Pid), {status,Pid,_Mod,[_PDict,running,_,_, Data]} = Status, - [format_status_called|_] = lists:reverse(Data), + [Match|_] = lists:reverse(Data), stop_it(Pid), %% check that format_status can handle a name being an atom (pid is %% already checked by the previous test) {ok, Pid2} = gen_statem:start( - {local, gstm}, ?MODULE, start_arg(Config, []), []), + {local, gstm}, Module, start_arg(Config, []), []), Status2 = sys:get_status(gstm), {status,Pid2,Mod,[_PDict2,running,_,_,Data2]} = Status2, - [format_status_called|_] = lists:reverse(Data2), + [Match|_] = lists:reverse(Data2), stop_it(Pid2), %% check that format_status can handle a name being a term other than a @@ -1261,47 +1269,54 @@ call_format_status(Config) -> GlobalName1 = {global,"CallFormatStatus"}, {ok,Pid3} = gen_statem:start( - GlobalName1, ?MODULE, start_arg(Config, []), []), + GlobalName1, Module, start_arg(Config, []), []), Status3 = sys:get_status(GlobalName1), {status,Pid3,Mod,[_PDict3,running,_,_,Data3]} = Status3, - [format_status_called|_] = lists:reverse(Data3), + [Match|_] = lists:reverse(Data3), stop_it(Pid3), GlobalName2 = {global,{name, "term"}}, {ok,Pid4} = gen_statem:start( - GlobalName2, ?MODULE, start_arg(Config, []), []), + GlobalName2, Module, start_arg(Config, []), []), Status4 = sys:get_status(GlobalName2), {status,Pid4,Mod,[_PDict4,running,_,_, Data4]} = Status4, - [format_status_called|_] = lists:reverse(Data4), + [Match|_] = lists:reverse(Data4), stop_it(Pid4), %% check that format_status can handle a name being a term other than a %% pid or atom dummy_via:reset(), ViaName1 = {via,dummy_via,"CallFormatStatus"}, - {ok,Pid5} = gen_statem:start(ViaName1, ?MODULE, start_arg(Config, []), []), + {ok,Pid5} = gen_statem:start(ViaName1, Module, start_arg(Config, []), []), Status5 = sys:get_status(ViaName1), {status,Pid5,Mod, [_PDict5,running,_,_, Data5]} = Status5, - [format_status_called|_] = lists:reverse(Data5), + [Match|_] = lists:reverse(Data5), stop_it(Pid5), ViaName2 = {via,dummy_via,{name,"term"}}, {ok, Pid6} = gen_statem:start( - ViaName2, ?MODULE, start_arg(Config, []), []), + ViaName2, Module, start_arg(Config, []), []), Status6 = sys:get_status(ViaName2), {status,Pid6,Mod,[_PDict6,running,_,_,Data6]} = Status6, - [format_status_called|_] = lists:reverse(Data6), + [Match|_] = lists:reverse(Data6), stop_it(Pid6). - - error_format_status(Config) -> error_logger_forwarder:register(), OldFl = process_flag(trap_exit, true), + try + error_format_status(Config,?MODULE,{formatted,idle,"called format_status"}), + error_format_status(Config,format_status_statem, + {{formatted,idle},{formatted,"called format_status"}}) + after + process_flag(trap_exit, OldFl), + error_logger_forwarder:unregister() + end. +error_format_status(Config,Module,Match) -> Data = "called format_status", {ok,Pid} = gen_statem:start( - ?MODULE, start_arg(Config, {data,Data}), []), + Module, start_arg(Config, {data,Data}), []), %% bad return value in the gen_statem loop {{{bad_return_from_state_function,badreturn},_},_} = ?EXPECT_FAILURE(gen_statem:call(Pid, badreturn), Reason), @@ -1310,18 +1325,15 @@ error_format_status(Config) -> {Pid, "** State machine"++_, [Pid,{{call,_},badreturn}, - {formatted,idle,Data}, + Match, error,{bad_return_from_state_function,badreturn}|_]}} -> ok; Other when is_tuple(Other), element(1, Other) =:= error -> - error_logger_forwarder:unregister(), ct:fail({unexpected,Other}) after 1000 -> - error_logger_forwarder:unregister(), - ct:fail(timeout) + ct:fail({timeout,(fun F() -> receive M -> [M|F()] after 0 -> [] end end)()}) end, - process_flag(trap_exit, OldFl), - error_logger_forwarder:unregister(), + receive %% Comes with SASL {error_report,_,{Pid,crash_report,_}} -> @@ -1332,12 +1344,24 @@ error_format_status(Config) -> ok = verify_empty_msgq(). terminate_crash_format(Config) -> + dbg:tracer(), error_logger_forwarder:register(), OldFl = process_flag(trap_exit, true), + try + terminate_crash_format(Config,?MODULE,{formatted,idle,crash_terminate}), + terminate_crash_format(Config,format_status_statem, + {{formatted,idle},{formatted,crash_terminate}}) + after + dbg:stop_clear(), + process_flag(trap_exit, OldFl), + error_logger_forwarder:unregister() + end. + +terminate_crash_format(Config, Module, Match) -> Data = crash_terminate, {ok,Pid} = gen_statem:start( - ?MODULE, start_arg(Config, {data,Data}), []), + Module, start_arg(Config, {data,Data}), []), stop_it(Pid), Self = self(), receive @@ -1346,18 +1370,14 @@ terminate_crash_format(Config) -> "** State machine"++_, [Pid, {{call,{Self,_}},stop}, - {formatted,idle,Data}, - exit,{crash,terminate}|_]}} -> + Match,exit,{crash,terminate}|_]}} -> ok; Other when is_tuple(Other), element(1, Other) =:= error -> - error_logger_forwarder:unregister(), ct:fail({unexpected,Other}) after 1000 -> - error_logger_forwarder:unregister(), - ct:fail(timeout) + ct:fail({timeout,flush()}) end, - process_flag(trap_exit, OldFl), - error_logger_forwarder:unregister(), + receive %% Comes with SASL {error_report,_,{Pid,crash_report,_}} -> @@ -1367,6 +1387,67 @@ terminate_crash_format(Config) -> end, ok = verify_empty_msgq(). +%% We test that all of the different status items can be +%% formatted by the format_status/1 callback. +format_all_status(Config) -> + error_logger_forwarder:register(), + OldFl = process_flag(trap_exit, true), + + Data = fun(M) -> + maps:map( + fun(Key, Values) when Key =:= log; + Key =:= queue; + Key =:= postponed; + Key =:= timeouts -> + [{Key, Value} || Value <- Values]; + (Key, Value) -> + {Key, Value} + end, M) + end, + {ok,Pid} = + gen_statem:start( + format_status_statem, start_arg(Config, {data,Data}), []), + sys:log(Pid, true), + ok = gen_statem:cast(Pid, postpone_event), + ok = gen_statem:cast(Pid, {timeout, 100000}), + + {status,Pid, _, [_,_,_,_,Info]} = sys:get_status(Pid), + [{header, _Hdr}, + {data, [_Status,_Parent,_Modules, + {"Time-outs",{1,[{timeouts,_}]}}, + {"Logged Events",[{log,_}|_]}, + {"Postponed",[{postponed,_}]}]}, + {data, [{"State",{{state,idle},{data,Data}}}]}] = Info, + + %% bad return value in the gen_statem loop + {{{bad_return_from_state_function,badreturn},_},_} = + ?EXPECT_FAILURE(gen_statem:call(Pid, badreturn), Reason), + Self = self(), + receive + {error,_GroupLeader, + {Pid, + "** State machine"++_, + [Pid, + {queue,{{call,{Self,_}},badreturn}}, + {{state,idle},{data,Data}},error, + {reason,{bad_return_from_state_function,badreturn}}, + __Modules,_StateFunctions, + [{postponed,{cast,postpone_event}}], + [_|_] = _Stacktrace, + {1,[{timeouts,{timeout,idle}}]}, + [{log,_}|_] |_]}} -> + ok; + Other when is_tuple(Other), element(1, Other) =:= error -> + ct:fail({unexpected,Other}) + after 1000 -> + ct:fail({timeout,flush()}) + end, + receive + {error_report,_,_} -> ok + end, + error_logger_forwarder:unregister(), + process_flag(trap_exit, OldFl), + ok. get_state(Config) -> State = self(), @@ -1851,12 +1932,14 @@ pop_too_many(_Config) -> Machine = #{init => fun () -> - {ok,start,undefined} + {ok,state_1,undefined} end, - start => - fun ({call, From}, {change_callback_module, _Module} = Action, - undefined = _Data) -> - {keep_state_and_data, + state_1 => + fun (enter, state_2, undefined) -> + {keep_state, enter}; % OTP-18239, should not be called + ({call, From}, {change_callback_module, _Module} = Action, + undefined = Data) -> + {next_state, state_2, Data, [Action, {reply,From,ok}]}; ({call, From}, {verify, ?MODULE}, @@ -1864,8 +1947,8 @@ pop_too_many(_Config) -> {keep_state_and_data, [{reply,From,ok}]}; ({call, From}, pop_callback_module = Action, - undefined = _Data) -> - {keep_state_and_data, + undefined = Data) -> + {next_state, state_2, Data, [Action, {reply,From,ok}]} end}, @@ -1875,10 +1958,11 @@ pop_too_many(_Config) -> {map_statem, Machine, []}, [{debug, [trace]}]), - ok = gen_statem:call(STM, {change_callback_module, oc_statem}), - ok = gen_statem:call(STM, {push_callback_module, ?MODULE}), - ok = gen_statem:call(STM, {verify, ?MODULE}), - ok = gen_statem:call(STM, pop_callback_module), + ok = gen_statem:call(STM, {change_callback_module, oc_statem}), + enter = gen_statem:call(STM, get_data), % OTP-18239 + ok = gen_statem:call(STM, {push_callback_module, ?MODULE}), + ok = gen_statem:call(STM, {verify, ?MODULE}), + ok = gen_statem:call(STM, pop_callback_module), BadAction = {bad_action_from_state_function, pop_callback_module}, {{BadAction, _}, {gen_statem,call,[STM,pop_callback_module,infinity]}} = @@ -2229,6 +2313,311 @@ reply_by_alias_with_payload(Config) when is_list(Config) -> ok end. +send_request_receive_reqid_collection(Config) when is_list(Config) -> + {ok,Pid1} = gen_statem:start(?MODULE, start_arg(Config, []), []), + {ok,Pid2} = gen_statem:start(?MODULE, start_arg(Config, []), []), + {ok,Pid3} = gen_statem:start(?MODULE, start_arg(Config, []), []), + send_request_receive_reqid_collection(Pid1, Pid2, Pid3), + send_request_receive_reqid_collection_timeout(Pid1, Pid2, Pid3), + send_request_receive_reqid_collection_error(Pid1, Pid2, Pid3), + stopped = gen_statem:call(Pid1, {stop,shutdown}), + stopped = gen_statem:call(Pid3, {stop,shutdown}), + check_stopped(Pid1), + check_stopped(Pid2), + check_stopped(Pid3), + ok. + +send_request_receive_reqid_collection(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_statem:send_request(Pid1, 'alive?'), + + ReqIdC0 = gen_statem:reqids_new(), + + ReqId1 = gen_statem:send_request(Pid1, {delayed_answer,400}), + ReqIdC1 = gen_statem:reqids_add(ReqId1, req1, ReqIdC0), + 1 = gen_statem:reqids_size(ReqIdC1), + + ReqIdC2 = gen_statem:send_request(Pid2, {delayed_answer,1}, req2, ReqIdC1), + 2 = gen_statem:reqids_size(ReqIdC2), + + ReqIdC3 = gen_statem:send_request(Pid3, {delayed_answer,200}, req3, ReqIdC2), + 3 = gen_statem:reqids_size(ReqIdC3), + + {{reply, delayed}, req2, ReqIdC4} = gen_statem:receive_response(ReqIdC3, infinity, true), + 2 = gen_statem:reqids_size(ReqIdC4), + + {{reply, delayed}, req3, ReqIdC5} = gen_statem:receive_response(ReqIdC4, 5678, true), + 1 = gen_statem:reqids_size(ReqIdC5), + + {{reply, delayed}, req1, ReqIdC6} = gen_statem:receive_response(ReqIdC5, 5000, true), + 0 = gen_statem:reqids_size(ReqIdC6), + + no_request = gen_statem:receive_response(ReqIdC6, 5000, true), + + {reply, yes} = gen_statem:receive_response(ReqId0, infinity), + + ok. + +send_request_receive_reqid_collection_timeout(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_statem:send_request(Pid1, 'alive?'), + + ReqIdC0 = gen_statem:reqids_new(), + + ReqId1 = gen_statem:send_request(Pid1, {delayed_answer,1000}), + ReqIdC1 = gen_statem:reqids_add(ReqId1, req1, ReqIdC0), + + ReqIdC2 = gen_statem:send_request(Pid2, {delayed_answer,1}, req2, ReqIdC1), + + ReqId3 = gen_statem:send_request(Pid3, {delayed_answer,500}), + ReqIdC3 = gen_statem:reqids_add(ReqId3, req3, ReqIdC2), + + Deadline = erlang:monotonic_time(millisecond) + 100, + + {{reply, delayed}, req2, ReqIdC4} = gen_statem:receive_response(ReqIdC3, {abs, Deadline}, true), + 2 = gen_statem:reqids_size(ReqIdC4), + + timeout = gen_statem:receive_response(ReqIdC4, {abs, Deadline}, true), + + Abandoned = lists:sort([{ReqId1, req1}, {ReqId3, req3}]), + Abandoned = lists:sort(gen_statem:reqids_to_list(ReqIdC4)), + + %% Make sure requests were abandoned... + timeout = gen_statem:receive_response(ReqIdC4, {abs, Deadline+1000}, true), + + {reply, yes} = gen_statem:receive_response(ReqId0, infinity), + + ok. + +send_request_receive_reqid_collection_error(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_statem:send_request(Pid1, 'alive?'), + + ReqIdC0 = gen_statem:reqids_new(), + + ReqId1 = gen_statem:send_request(Pid1, {delayed_answer,400}), + ReqIdC1 = gen_statem:reqids_add(ReqId1, req1, ReqIdC0), + try + nope = gen_statem:reqids_add(ReqId1, req2, ReqIdC1) + catch + error:badarg -> ok + end, + + unlink(Pid2), + exit(Pid2, kill), + ReqIdC2 = gen_statem:send_request(Pid2, {delayed_answer,1}, req2, ReqIdC1), + ReqIdC3 = gen_statem:send_request(Pid3, {delayed_answer,200}, req3, ReqIdC2), + 3 = gen_statem:reqids_size(ReqIdC3), + + {{error, {noproc, _}}, req2, ReqIdC4} = gen_statem:receive_response(ReqIdC3, 2000, true), + 2 = gen_statem:reqids_size(ReqIdC4), + + {{reply, delayed}, req3, ReqIdC4} = gen_statem:receive_response(ReqIdC4, infinity, false), + + {{reply, delayed}, req1, ReqIdC4} = gen_statem:receive_response(ReqIdC4, infinity, false), + + {reply, yes} = gen_statem:receive_response(ReqId0, infinity), + + ok. + +send_request_wait_reqid_collection(Config) when is_list(Config) -> + {ok,Pid1} = gen_statem:start(?MODULE, start_arg(Config, []), []), + {ok,Pid2} = gen_statem:start(?MODULE, start_arg(Config, []), []), + {ok,Pid3} = gen_statem:start(?MODULE, start_arg(Config, []), []), + send_request_wait_reqid_collection(Pid1, Pid2, Pid3), + send_request_wait_reqid_collection_timeout(Pid1, Pid2, Pid3), + send_request_wait_reqid_collection_error(Pid1, Pid2, Pid3), + stopped = gen_statem:call(Pid1, {stop,shutdown}), + stopped = gen_statem:call(Pid3, {stop,shutdown}), + check_stopped(Pid1), + check_stopped(Pid2), + check_stopped(Pid3), + ok. + +send_request_wait_reqid_collection(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_statem:send_request(Pid1, 'alive?'), + + ReqIdC0 = gen_statem:reqids_new(), + + ReqId1 = gen_statem:send_request(Pid1, {delayed_answer,400}), + ReqIdC1 = gen_statem:reqids_add(ReqId1, req1, ReqIdC0), + 1 = gen_statem:reqids_size(ReqIdC1), + + ReqIdC2 = gen_statem:send_request(Pid2, {delayed_answer,1}, req2, ReqIdC1), + 2 = gen_statem:reqids_size(ReqIdC2), + + ReqIdC3 = gen_statem:send_request(Pid3, {delayed_answer,200}, req3, ReqIdC2), + 3 = gen_statem:reqids_size(ReqIdC3), + + {{reply, delayed}, req2, ReqIdC4} = gen_statem:wait_response(ReqIdC3, infinity, true), + 2 = gen_statem:reqids_size(ReqIdC4), + + {{reply, delayed}, req3, ReqIdC5} = gen_statem:wait_response(ReqIdC4, 5678, true), + 1 = gen_statem:reqids_size(ReqIdC5), + + {{reply, delayed}, req1, ReqIdC6} = gen_statem:wait_response(ReqIdC5, 5000, true), + 0 = gen_statem:reqids_size(ReqIdC6), + + no_request = gen_statem:wait_response(ReqIdC6, 5000, true), + + {reply, yes} = gen_statem:receive_response(ReqId0, infinity), + + ok. + +send_request_wait_reqid_collection_timeout(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_statem:send_request(Pid1, 'alive?'), + + ReqIdC0 = gen_statem:reqids_new(), + + ReqId1 = gen_statem:send_request(Pid1, {delayed_answer,1000}), + ReqIdC1 = gen_statem:reqids_add(ReqId1, req1, ReqIdC0), + + ReqIdC2 = gen_statem:send_request(Pid2, {delayed_answer,1}, req2, ReqIdC1), + + ReqId3 = gen_statem:send_request(Pid3, {delayed_answer,500}), + ReqIdC3 = gen_statem:reqids_add(ReqId3, req3, ReqIdC2), + + Deadline = erlang:monotonic_time(millisecond) + 100, + + {{reply, delayed}, req2, ReqIdC4} = gen_statem:wait_response(ReqIdC3, {abs, Deadline}, true), + 2 = gen_statem:reqids_size(ReqIdC4), + + timeout = gen_statem:wait_response(ReqIdC4, {abs, Deadline}, true), + + Unhandled = lists:sort([{ReqId1, req1}, {ReqId3, req3}]), + Unhandled = lists:sort(gen_statem:reqids_to_list(ReqIdC4)), + + %% Make sure requests were not abandoned... + {{reply, delayed}, req3, ReqIdC4} = gen_statem:wait_response(ReqIdC4, {abs, Deadline+1500}, false), + {{reply, delayed}, req1, ReqIdC4} = gen_statem:wait_response(ReqIdC4, {abs, Deadline+1500}, false), + + {reply, yes} = gen_statem:receive_response(ReqId0), + + ok. + +send_request_wait_reqid_collection_error(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_statem:send_request(Pid1, 'alive?'), + + ReqIdC0 = gen_statem:reqids_new(), + + ReqId1 = gen_statem:send_request(Pid1, {delayed_answer,400}), + ReqIdC1 = gen_statem:reqids_add(ReqId1, req1, ReqIdC0), + try + nope = gen_statem:reqids_add(ReqId1, req2, ReqIdC1) + catch + error:badarg -> ok + end, + + unlink(Pid2), + exit(Pid2, kill), + ReqIdC2 = gen_statem:send_request(Pid2, {delayed_answer,1}, req2, ReqIdC1), + ReqIdC3 = gen_statem:send_request(Pid3, {delayed_answer,200}, req3, ReqIdC2), + 3 = gen_statem:reqids_size(ReqIdC3), + + {{error, {noproc, _}}, req2, ReqIdC4} = gen_statem:wait_response(ReqIdC3, 2000, true), + 2 = gen_statem:reqids_size(ReqIdC4), + + {{reply, delayed}, req3, ReqIdC4} = gen_statem:wait_response(ReqIdC4, infinity, false), + + {{reply, delayed}, req1, ReqIdC4} = gen_statem:wait_response(ReqIdC4, infinity, false), + + {reply, yes} = gen_statem:receive_response(ReqId0, infinity), + + ok. + +send_request_check_reqid_collection(Config) when is_list(Config) -> + {ok,Pid1} = gen_statem:start(?MODULE, start_arg(Config, []), []), + {ok,Pid2} = gen_statem:start(?MODULE, start_arg(Config, []), []), + {ok,Pid3} = gen_statem:start(?MODULE, start_arg(Config, []), []), + send_request_check_reqid_collection(Pid1, Pid2, Pid3), + send_request_check_reqid_collection_error(Pid1, Pid2, Pid3), + stopped = gen_statem:call(Pid1, {stop,shutdown}), + stopped = gen_statem:call(Pid3, {stop,shutdown}), + check_stopped(Pid1), + check_stopped(Pid2), + check_stopped(Pid3), + ok. + +send_request_check_reqid_collection(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_statem:send_request(Pid1, 'alive?'), + + receive after 100 -> ok end, + + ReqIdC0 = gen_statem:reqids_new(), + + ReqIdC1 = gen_statem:send_request(Pid1, {delayed_answer,400}, req1, ReqIdC0), + 1 = gen_statem:reqids_size(ReqIdC1), + + ReqId2 = gen_statem:send_request(Pid2, {delayed_answer,1}), + ReqIdC2 = gen_statem:reqids_add(ReqId2, req2, ReqIdC1), + 2 = gen_statem:reqids_size(ReqIdC2), + + ReqIdC3 = gen_statem:send_request(Pid3, {delayed_answer,200}, req3, ReqIdC2), + 3 = gen_statem:reqids_size(ReqIdC3), + + Msg0 = next_msg(), + no_reply = gen_statem:check_response(Msg0, ReqIdC3, true), + + {{reply, delayed}, req2, ReqIdC4} = gen_statem:check_response(next_msg(), ReqIdC3, true), + 2 = gen_statem:reqids_size(ReqIdC4), + + {{reply, delayed}, req3, ReqIdC5} = gen_statem:check_response(next_msg(), ReqIdC4, true), + 1 = gen_statem:reqids_size(ReqIdC5), + + {{reply, delayed}, req1, ReqIdC6} = gen_statem:check_response(next_msg(), ReqIdC5, true), + 0 = gen_statem:reqids_size(ReqIdC6), + + no_request = gen_statem:check_response(Msg0, ReqIdC6, true), + + {reply, yes} = gen_statem:check_response(Msg0, ReqId0), + + ok. + +send_request_check_reqid_collection_error(Pid1, Pid2, Pid3) -> + + ReqId0 = gen_statem:send_request(Pid1, 'alive?'), + + receive after 100 -> ok end, + + ReqIdC0 = gen_statem:reqids_new(), + + ReqId1 = gen_statem:send_request(Pid1, {delayed_answer,400}), + ReqIdC1 = gen_statem:reqids_add(ReqId1, req1, ReqIdC0), + try + nope = gen_statem:reqids_add(ReqId1, req2, ReqIdC1) + catch + error:badarg -> ok + end, + + unlink(Pid2), + exit(Pid2, kill), + ReqIdC2 = gen_statem:send_request(Pid2, {delayed_answer,1}, req2, ReqIdC1), + + ReqIdC3 = gen_statem:send_request(Pid3, {delayed_answer,200}, req3, ReqIdC2), + 3 = gen_statem:reqids_size(ReqIdC3), + + Msg0 = next_msg(), + + no_reply = gen_statem:check_response(Msg0, ReqIdC3, true), + + {{error, {noproc, _}}, req2, ReqIdC4} = gen_statem:check_response(next_msg(), ReqIdC3, true), + 2 = gen_statem:reqids_size(ReqIdC4), + + {{reply, delayed}, req3, ReqIdC4} = gen_statem:check_response(next_msg(), ReqIdC4, false), + + {{reply, delayed}, req1, ReqIdC4} = gen_statem:check_response(next_msg(), ReqIdC4, false), + + {reply, yes} = gen_statem:check_response(Msg0, ReqId0), + + ok. + +next_msg() -> + receive M -> M end. + %% %% Functionality check %% @@ -2473,6 +2862,11 @@ idle({call,From}, {timeout,Time}, _Data) -> AbsTime = erlang:monotonic_time(millisecond) + Time, {next_state,timeout,{From,Time}, {timeout,AbsTime,idle,[{abs,true}]}}; +idle(cast, {timeout,Time}, _Data) -> + AbsTime = erlang:monotonic_time(millisecond) + Time, + {keep_state_and_data,{timeout,AbsTime,idle,[{abs,true}]}}; +idle(cast, postpone_event, _Data) -> + {keep_state_and_data,postpone}; idle(cast, next_event, _Data) -> {next_state,next_events,[a,b,c], [{next_event,internal,a}, |