aboutsummaryrefslogtreecommitdiffstats
path: root/lib/stdlib/test/gen_statem_SUITE.erl
diff options
context:
space:
mode:
authorRaimo Niskanen <[email protected]>2015-10-26 11:52:17 +0100
committerRaimo Niskanen <[email protected]>2016-02-09 10:07:56 +0100
commit6ace96d3e5c9ac8ace3d8967bcafb3e6a081d9be (patch)
tree2a5dc8326bf0a52a23ac3c00de0ea365d91866e4 /lib/stdlib/test/gen_statem_SUITE.erl
parentadcc726c36555434204dd0fccd13ed984741a7fb (diff)
downloadotp-6ace96d3e5c9ac8ace3d8967bcafb3e6a081d9be.tar.gz
otp-6ace96d3e5c9ac8ace3d8967bcafb3e6a081d9be.tar.bz2
otp-6ace96d3e5c9ac8ace3d8967bcafb3e6a081d9be.zip
New state machine
Diffstat (limited to 'lib/stdlib/test/gen_statem_SUITE.erl')
-rw-r--r--lib/stdlib/test/gen_statem_SUITE.erl1250
1 files changed, 1250 insertions, 0 deletions
diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl
new file mode 100644
index 0000000000..342be32acb
--- /dev/null
+++ b/lib/stdlib/test/gen_statem_SUITE.erl
@@ -0,0 +1,1250 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2016. 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(gen_statem_SUITE).
+
+-include_lib("test_server/include/test_server.hrl").
+
+-compile(export_all).
+-behaviour(gen_statem).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+suite() -> [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [{group, start},
+ {group, abnormal},
+ shutdown,
+ {group, sys}, hibernate, enter_loop].
+
+groups() ->
+ [{start, [],
+ [start1, start2, start3, start4, start5, start6, start7,
+ start8, start9, start10, start11, start12]},
+ {stop, [],
+ [stop1, stop2, stop3, stop4, stop5, stop6, stop7, stop8, stop9, stop10]},
+ {abnormal, [], [abnormal1, abnormal2]},
+ {sys, [],
+ [sys1,
+ call_format_status,
+ error_format_status, terminate_crash_format,
+ get_state, replace_state]}].
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+init_per_testcase(_CaseName, Config) ->
+ ?t:messages_get(),
+ Dog = ?t:timetrap(?t:minutes(1)),
+%%% dbg:tracer(),
+%%% dbg:p(all, c),
+%%% dbg:tpl(gen_statem, cx),
+ [{watchdog, Dog} | Config].
+
+end_per_testcase(_CaseName, Config) ->
+%%% dbg:stop(),
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ Config.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-define(EXPECT_FAILURE(Code, Reason),
+ try begin Code end of
+ _ ->
+ ?t:fail()
+ catch
+ error:Reason -> Reason;
+ exit:Reason -> Reason
+ end).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% anonymous
+start1(Config) when is_list(Config) ->
+ %%OldFl = process_flag(trap_exit, true),
+
+ {ok,Pid0} = gen_statem:start_link(?MODULE, [], []),
+ ok = do_func_test(Pid0),
+ ok = do_sync_func_test(Pid0),
+ stop_it(Pid0),
+%% stopped = gen_statem:call(Pid0, stop),
+%% timeout =
+%% ?EXPECT_FAILURE(gen_statem:call(Pid0, hej), Reason),
+
+ %%process_flag(trap_exit, OldFl),
+ ok = verify_empty_msgq().
+
+%% anonymous w. shutdown
+start2(Config) when is_list(Config) ->
+ %% Dont link when shutdown
+ {ok,Pid0} = gen_statem:start(?MODULE, [], []),
+ ok = do_func_test(Pid0),
+ ok = do_sync_func_test(Pid0),
+ stopped = gen_statem:call(Pid0, {stop,shutdown}),
+ check_stopped(Pid0),
+ ok = verify_empty_msgq().
+
+%% anonymous with timeout
+start3(Config) when is_list(Config) ->
+ %%OldFl = process_flag(trap_exit, true),
+
+ {ok,Pid0} = gen_statem:start(?MODULE, [], [{timeout,5}]),
+ ok = do_func_test(Pid0),
+ ok = do_sync_func_test(Pid0),
+ stop_it(Pid0),
+
+ {error,timeout} = gen_statem:start(?MODULE, sleep,
+ [{timeout,5}]),
+
+ %%process_flag(trap_exit, OldFl),
+ ok = verify_empty_msgq().
+
+%% anonymous with ignore
+start4(Config) when is_list(Config) ->
+ OldFl = process_flag(trap_exit, true),
+
+ ignore = gen_statem:start(?MODULE, ignore, []),
+
+ process_flag(trap_exit, OldFl),
+ ok = verify_empty_msgq().
+
+%% anonymous with stop
+start5(suite) -> [];
+start5(Config) when is_list(Config) ->
+ OldFl = process_flag(trap_exit, true),
+
+ {error,stopped} = gen_statem:start(?MODULE, stop, []),
+
+ process_flag(trap_exit, OldFl),
+ ok = verify_empty_msgq().
+
+%% anonymous linked
+start6(Config) when is_list(Config) ->
+ {ok,Pid} = gen_statem:start_link(?MODULE, [], []),
+ ok = do_func_test(Pid),
+ ok = do_sync_func_test(Pid),
+ stop_it(Pid),
+
+ ok = verify_empty_msgq().
+
+%% global register linked
+start7(Config) when is_list(Config) ->
+ STM = {global,my_stm},
+
+ {ok,Pid} =
+ gen_statem:start_link(STM, ?MODULE, [], []),
+ {error,{already_started,Pid}} =
+ gen_statem:start_link(STM, ?MODULE, [], []),
+ {error,{already_started,Pid}} =
+ gen_statem:start(STM, ?MODULE, [], []),
+
+ ok = do_func_test(Pid),
+ ok = do_sync_func_test(Pid),
+ ok = do_func_test(STM),
+ ok = do_sync_func_test(STM),
+ stop_it(STM),
+
+ ok = verify_empty_msgq().
+
+
+%% local register
+start8(Config) when is_list(Config) ->
+ %%OldFl = process_flag(trap_exit, true),
+ Name = my_stm,
+ STM = {local,Name},
+
+ {ok,Pid} =
+ gen_statem:start(STM, ?MODULE, [], []),
+ {error,{already_started,Pid}} =
+ gen_statem:start(STM, ?MODULE, [], []),
+
+ ok = do_func_test(Pid),
+ ok = do_sync_func_test(Pid),
+ ok = do_func_test(Name),
+ ok = do_sync_func_test(Name),
+ stop_it(Pid),
+
+ %%process_flag(trap_exit, OldFl),
+ ok = verify_empty_msgq().
+
+%% local register linked
+start9(Config) when is_list(Config) ->
+ %%OldFl = process_flag(trap_exit, true),
+ Name = my_stm,
+ STM = {local,Name},
+
+ {ok,Pid} =
+ gen_statem:start_link(STM, ?MODULE, [], []),
+ {error,{already_started,Pid}} =
+ gen_statem:start(STM, ?MODULE, [], []),
+
+ ok = do_func_test(Pid),
+ ok = do_sync_func_test(Pid),
+ ok = do_func_test(Name),
+ ok = do_sync_func_test(Name),
+ stop_it(Pid),
+
+ %%process_flag(trap_exit, OldFl),
+ ok = verify_empty_msgq().
+
+%% global register
+start10(Config) when is_list(Config) ->
+ STM = {global,my_stm},
+
+ {ok,Pid} =
+ gen_statem:start(STM, ?MODULE, [], []),
+ {error,{already_started,Pid}} =
+ gen_statem:start(STM, ?MODULE, [], []),
+ {error,{already_started,Pid}} =
+ gen_statem:start_link(STM, ?MODULE, [], []),
+
+ ok = do_func_test(Pid),
+ ok = do_sync_func_test(Pid),
+ ok = do_func_test(STM),
+ ok = do_sync_func_test(STM),
+ stop_it(STM),
+
+ ok = verify_empty_msgq().
+
+%% Stop registered processes
+start11(Config) when is_list(Config) ->
+ Name = my_stm,
+ LocalSTM = {local,Name},
+ GlobalSTM = {global,Name},
+
+ {ok,Pid} =
+ gen_statem:start_link(LocalSTM, ?MODULE, [], []),
+ stop_it(Pid),
+
+ {ok,_Pid1} =
+ gen_statem:start_link(LocalSTM, ?MODULE, [], []),
+ stop_it(Name),
+
+ {ok,Pid2} =
+ gen_statem:start(GlobalSTM, ?MODULE, [], []),
+ stop_it(Pid2),
+ receive after 1 -> true end,
+ Result =
+ gen_statem:start(GlobalSTM, ?MODULE, [], []),
+ io:format("Result = ~p~n",[Result]),
+ {ok,_Pid3} = Result,
+ stop_it(GlobalSTM),
+
+ ok = verify_empty_msgq().
+
+%% Via register linked
+start12(Config) when is_list(Config) ->
+ dummy_via:reset(),
+ VIA = {via,dummy_via,my_stm},
+
+ {ok,Pid} =
+ gen_statem:start_link(VIA, ?MODULE, [], []),
+ {error,{already_started,Pid}} =
+ gen_statem:start_link(VIA, ?MODULE, [], []),
+ {error,{already_started,Pid}} =
+ gen_statem:start(VIA, ?MODULE, [], []),
+
+ ok = do_func_test(Pid),
+ ok = do_sync_func_test(Pid),
+ ok = do_func_test(VIA),
+ ok = do_sync_func_test(VIA),
+ stop_it(VIA),
+
+ ok = verify_empty_msgq().
+
+
+%% Anonymous, reason 'normal'
+stop1(_Config) ->
+ {ok,Pid} = gen_statem:start(?MODULE, [], []),
+ ok = gen_statem:stop(Pid),
+ false = erlang:is_process_alive(Pid),
+ noproc =
+ ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason).
+
+%% Anonymous, other reason
+stop2(_Config) ->
+ {ok,Pid} = gen_statem:start(?MODULE, [], []),
+ ok = gen_statem:stop(Pid, other_reason, infinity),
+ false = erlang:is_process_alive(Pid),
+ ok.
+
+%% Anonymous, invalid timeout
+stop3(_Config) ->
+ {ok,Pid} = gen_statem:start(?MODULE, [], []),
+ _ =
+ ?EXPECT_FAILURE(
+ gen_statem:stop(Pid, other_reason, invalid_timeout),
+ Reason),
+ true = erlang:is_process_alive(Pid),
+ ok = gen_statem:stop(Pid),
+ false = erlang:is_process_alive(Pid),
+ ok.
+
+%% Registered name
+stop4(_Config) ->
+ {ok,Pid} = gen_statem:start({local,to_stop},?MODULE, [], []),
+ ok = gen_statem:stop(to_stop),
+ false = erlang:is_process_alive(Pid),
+ noproc =
+ ?EXPECT_FAILURE(gen_statem:stop(to_stop), Reason),
+ ok.
+
+%% Registered name and local node
+stop5(_Config) ->
+ Name = to_stop,
+ {ok,Pid} = gen_statem:start({local,Name},?MODULE, [], []),
+ ok = gen_statem:stop({Name,node()}),
+ false = erlang:is_process_alive(Pid),
+ noproc =
+ ?EXPECT_FAILURE(gen_statem:stop({Name,node()}), Reason),
+ ok.
+
+%% Globally registered name
+stop6(_Config) ->
+ STM = {global,to_stop},
+ {ok,Pid} = gen_statem:start(STM, ?MODULE, [], []),
+ ok = gen_statem:stop(STM),
+ false = erlang:is_process_alive(Pid),
+ noproc =
+ ?EXPECT_FAILURE(gen_statem:stop(STM), Reason),
+ ok.
+
+%% 'via' registered name
+stop7(_Config) ->
+ VIA = {via,dummy_via,to_stop},
+ dummy_via:reset(),
+ {ok,Pid} = gen_statem:start(VIA,
+ ?MODULE, [], []),
+ ok = gen_statem:stop(VIA),
+ false = erlang:is_process_alive(Pid),
+ noproc =
+ ?EXPECT_FAILURE(gen_statem:stop(VIA), Reason),
+ ok.
+
+%% Anonymous on remote node
+stop8(_Config) ->
+ {ok,Node} = ?t:start_node(gen_statem_stop8, slave, []),
+ Dir = filename:dirname(code:which(?MODULE)),
+ rpc:call(Node, code, add_path, [Dir]),
+ {ok,Pid} = rpc:call(Node, gen_statem,start, [?MODULE,[],[]]),
+ ok = gen_statem:stop(Pid),
+ false = rpc:call(Node, erlang, is_process_alive, [Pid]),
+ noproc =
+ ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason1),
+ true = ?t:stop_node(Node),
+ {nodedown,Node} =
+ ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason2),
+ ok.
+
+%% Registered name on remote node
+stop9(_Config) ->
+ Name = to_stop,
+ LocalSTM = {local,Name},
+ {ok,Node} = ?t:start_node(gen_statem__stop9, slave, []),
+ STM = {Name,Node},
+ Dir = filename:dirname(code:which(?MODULE)),
+ rpc:call(Node, code, add_path, [Dir]),
+ {ok,Pid} = rpc:call(Node, gen_statem, start, [LocalSTM,?MODULE,[],[]]),
+ ok = gen_statem:stop(STM),
+ undefined = rpc:call(Node,erlang,whereis,[Name]),
+ false = rpc:call(Node,erlang,is_process_alive,[Pid]),
+ noproc =
+ ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1),
+ true = ?t:stop_node(Node),
+ {nodedown,Node} =
+ ?EXPECT_FAILURE(gen_statem:stop(STM), Reason2),
+ ok.
+
+%% Globally registered name on remote node
+stop10(_Config) ->
+ STM = {global,to_stop},
+ {ok,Node} = ?t:start_node(gen_statem_stop10, slave, []),
+ Dir = filename:dirname(code:which(?MODULE)),
+ rpc:call(Node,code,add_path,[Dir]),
+ {ok,Pid} = rpc:call(Node, gen_statem, start, [STM,?MODULE,[],[]]),
+ global:sync(),
+ ok = gen_statem:stop(STM),
+ false = rpc:call(Node, erlang, is_process_alive, [Pid]),
+ noproc =
+ ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1),
+ true = ?t:stop_node(Node),
+ noproc =
+ ?EXPECT_FAILURE(gen_statem:stop(STM), Reason2),
+ ok.
+
+%% Check that time outs in calls work
+abnormal1(Config) when is_list(Config) ->
+ Name = abnormal1,
+ LocalSTM = {local,Name},
+
+ {ok, _Pid} = gen_statem:start(LocalSTM, ?MODULE, [], []),
+
+ %% timeout call.
+ delayed = gen_statem:call(Name, {delayed_answer,1}, 100),
+ {timeout,_} =
+ ?EXPECT_FAILURE(
+ gen_statem:call(Name, {delayed_answer,1000}, 10),
+ Reason),
+ ok = verify_empty_msgq().
+
+%% Check that bad return values makes the stm crash. Note that we must
+%% trap exit since we must link to get the real bad_return_ error
+abnormal2(Config) when is_list(Config) ->
+ OldFl = process_flag(trap_exit, true),
+ {ok,Pid} = gen_statem:start_link(?MODULE, [], []),
+
+ %% bad return value in the gen_statem loop
+ {{bad_return_value,badreturn},_} =
+ ?EXPECT_FAILURE(gen_statem:call(Pid, badreturn), Reason),
+ receive
+ {'EXIT',Pid,{bad_return_value,badreturn}} -> ok
+ after 5000 ->
+ ?t:fail(gen_statem_did_not_die)
+ end,
+
+ process_flag(trap_exit, OldFl),
+ ok = verify_empty_msgq().
+
+shutdown(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+
+ {ok,Pid0} = gen_statem:start_link(?MODULE, [], []),
+ ok = do_func_test(Pid0),
+ ok = do_sync_func_test(Pid0),
+ stopped = gen_statem:call(Pid0, {stop,{shutdown,reason}}),
+ receive {'EXIT',Pid0,{shutdown,reason}} -> ok end,
+ process_flag(trap_exit, false),
+
+ {noproc,_} =
+ ?EXPECT_FAILURE(gen_statem:call(Pid0, hej), Reason),
+
+ receive
+ Any ->
+ io:format("Unexpected: ~p", [Any]),
+ ?t:fail()
+ after 500 ->
+ ok
+ end.
+
+
+
+sys1(Config) when is_list(Config) ->
+ {ok,Pid} = gen_statem:start(?MODULE, [], []),
+ {status, Pid, {module,gen_statem}, _} = sys:get_status(Pid),
+ sys:suspend(Pid),
+ Parent = self(),
+ Tag = make_ref(),
+ Caller =
+ spawn(
+ fun () ->
+ Parent ! {Tag,gen_statem:call(Pid, hej)}
+ end),
+ receive
+ {Tag,_} ->
+ ?t:fail()
+ after 3000 ->
+ exit(Caller, ok)
+ end,
+
+ %% {timeout,_} =
+ %% ?EXPECT_FAILURE(gen_statem:call(Pid, hej), Reason),
+ sys:resume(Pid),
+ stop_it(Pid).
+
+call_format_status(Config) when is_list(Config) ->
+ {ok,Pid} = gen_statem:start(?MODULE, [], []),
+ Status = sys:get_status(Pid),
+ {status,Pid,_Mod,[_PDict,running,_,_, Data]} = Status,
+ [format_status_called|_] = 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, [], []),
+ Status2 = sys:get_status(gstm),
+ {status,Pid2,_Mod,[_PDict2,running,_,_,Data2]} = Status2,
+ [format_status_called|_] = lists:reverse(Data2),
+ stop_it(Pid2),
+
+ %% check that format_status can handle a name being a term other than a
+ %% pid or atom
+ GlobalName1 = {global,"CallFormatStatus"},
+ {ok,Pid3} = gen_statem:start(GlobalName1, ?MODULE, [], []),
+ Status3 = sys:get_status(GlobalName1),
+ {status,Pid3,_Mod,[_PDict3,running,_,_,Data3]} = Status3,
+ [format_status_called|_] = lists:reverse(Data3),
+ stop_it(Pid3),
+ GlobalName2 = {global,{name, "term"}},
+ {ok,Pid4} = gen_statem:start(GlobalName2, ?MODULE, [], []),
+ Status4 = sys:get_status(GlobalName2),
+ {status,Pid4,_Mod,[_PDict4,running,_,_, Data4]} = Status4,
+ [format_status_called|_] = 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, [], []),
+ Status5 = sys:get_status(ViaName1),
+ {status,Pid5,_Mod, [_PDict5,running,_,_, Data5]} = Status5,
+ [format_status_called|_] = lists:reverse(Data5),
+ stop_it(Pid5),
+ ViaName2 = {via,dummy_via,{name,"term"}},
+ {ok, Pid6} = gen_statem:start(ViaName2, ?MODULE, [], []),
+ Status6 = sys:get_status(ViaName2),
+ {status,Pid6,_Mod,[_PDict6,running,_,_,Data6]} = Status6,
+ [format_status_called|_] = lists:reverse(Data6),
+ stop_it(Pid6).
+
+
+
+error_format_status(Config) when is_list(Config) ->
+ error_logger_forwarder:register(),
+ OldFl = process_flag(trap_exit, true),
+ StateData = "called format_status",
+ {ok,Pid} = gen_statem:start(?MODULE, {state_data,StateData}, []),
+ %% bad return value in the gen_statem loop
+ {{bad_return_value,badreturn},_} =
+ ?EXPECT_FAILURE(gen_statem:call(Pid, badreturn), Reason),
+ receive
+ {error,_,
+ {Pid,
+ "** State machine"++_,
+ [Pid,{{call,_},badreturn},
+ {formatted,idle,StateData},
+ exit,{bad_return_value,badreturn}|_]}} ->
+ ok;
+ Other when is_tuple(Other), element(1, Other) =:= error ->
+ error_logger_forwarder:unregister(),
+ ?t:fail({unexpected,Other})
+ after 1000 ->
+ error_logger_forwarder:unregister(),
+ ?t:fail()
+ end,
+ process_flag(trap_exit, OldFl),
+ error_logger_forwarder:unregister(),
+ receive
+ %% Comes with SASL
+ {error_report,_,{Pid,crash_report,_}} ->
+ ok
+ after 500 ->
+ ok
+ end,
+ ok = verify_empty_msgq().
+
+terminate_crash_format(Config) when is_list(Config) ->
+ error_logger_forwarder:register(),
+ OldFl = process_flag(trap_exit, true),
+ StateData = crash_terminate,
+ {ok,Pid} = gen_statem:start(?MODULE, {state_data,StateData}, []),
+ stop_it(Pid),
+ Self = self(),
+ receive
+ {error,_GroupLeader,
+ {Pid,
+ "** State machine"++_,
+ [Pid,
+ {{call,{Self,_}},stop},
+ {formatted,idle,StateData},
+ exit,{crash,terminate}|_]}} ->
+ ok;
+ Other when is_tuple(Other), element(1, Other) =:= error ->
+ error_logger_forwarder:unregister(),
+ ?t:fail({unexpected,Other})
+ after 1000 ->
+ error_logger_forwarder:unregister(),
+ ?t:fail()
+ end,
+ process_flag(trap_exit, OldFl),
+ error_logger_forwarder:unregister(),
+ receive
+ %% Comes with SASL
+ {error_report,_,{Pid,crash_report,_}} ->
+ ok
+ after 500 ->
+ ok
+ end,
+ ok = verify_empty_msgq().
+
+
+get_state(Config) when is_list(Config) ->
+ State = self(),
+ {ok,Pid} = gen_statem:start(?MODULE, {state_data,State}, []),
+ {idle,State} = sys:get_state(Pid),
+ {idle,State} = sys:get_state(Pid, 5000),
+ stop_it(Pid),
+
+ %% check that get_state can handle a name being an atom (pid is
+ %% already checked by the previous test)
+ {ok,Pid2} =
+ gen_statem:start({local,gstm}, ?MODULE, {state_data,State}, []),
+ {idle,State} = sys:get_state(gstm),
+ {idle,State} = sys:get_state(gstm, 5000),
+ stop_it(Pid2),
+
+ %% check that get_state works when pid is sys suspended
+ {ok,Pid3} = gen_statem:start(?MODULE, {state_data,State}, []),
+ {idle,State} = sys:get_state(Pid3),
+ ok = sys:suspend(Pid3),
+ {idle,State} = sys:get_state(Pid3, 5000),
+ ok = sys:resume(Pid3),
+ stop_it(Pid3),
+ ok = verify_empty_msgq().
+
+replace_state(Config) when is_list(Config) ->
+ State = self(),
+ {ok, Pid} = gen_statem:start(?MODULE, {state_data,State}, []),
+ {idle,State} = sys:get_state(Pid),
+ NState1 = "replaced",
+ Replace1 = fun({StateName, _}) -> {StateName,NState1} end,
+ {idle,NState1} = sys:replace_state(Pid, Replace1),
+ {idle,NState1} = sys:get_state(Pid),
+ NState2 = "replaced again",
+ Replace2 = fun({idle, _}) -> {state0,NState2} end,
+ {state0,NState2} = sys:replace_state(Pid, Replace2, 5000),
+ {state0,NState2} = sys:get_state(Pid),
+ %% verify no change in state if replace function crashes
+ Replace3 = fun(_) -> error(fail) end,
+ {callback_failed,
+ {gen_statem,system_replace_state},{error,fail}} =
+ ?EXPECT_FAILURE(sys:replace_state(Pid, Replace3), Reason),
+ {state0, NState2} = sys:get_state(Pid),
+ %% verify state replaced if process sys suspended
+ ok = sys:suspend(Pid),
+ Suffix2 = " and again",
+ NState3 = NState2 ++ Suffix2,
+ Replace4 = fun({StateName, _}) -> {StateName, NState3} end,
+ {state0,NState3} = sys:replace_state(Pid, Replace4),
+ ok = sys:resume(Pid),
+ {state0,NState3} = sys:get_state(Pid, 5000),
+ stop_it(Pid),
+ ok = verify_empty_msgq().
+
+%% Hibernation
+hibernate(Config) when is_list(Config) ->
+ OldFl = process_flag(trap_exit, true),
+
+ {ok,Pid0} = gen_statem:start_link(?MODULE, hiber_now, []),
+ is_in_erlang_hibernate(Pid0),
+ stop_it(Pid0),
+ receive
+ {'EXIT',Pid0,normal} -> ok
+ after 5000 ->
+ ?t:fail(gen_statem_did_not_die)
+ end,
+
+ {ok,Pid} = gen_statem:start_link(?MODULE, hiber, []),
+ true = ({current_function,{erlang,hibernate,3}} =/=
+ erlang:process_info(Pid,current_function)),
+ hibernating = gen_statem:call(Pid, hibernate_sync),
+ is_in_erlang_hibernate(Pid),
+ good_morning = gen_statem:call(Pid, wakeup_sync),
+ is_not_in_erlang_hibernate(Pid),
+ hibernating = gen_statem:call(Pid, hibernate_sync),
+ is_in_erlang_hibernate(Pid),
+ please_just_five_more = gen_statem:call(Pid, snooze_sync),
+ is_in_erlang_hibernate(Pid),
+ good_morning = gen_statem:call(Pid, wakeup_sync),
+ is_not_in_erlang_hibernate(Pid),
+ ok = gen_statem:cast(Pid, hibernate_async),
+ is_in_erlang_hibernate(Pid),
+ ok = gen_statem:cast(Pid, wakeup_async),
+ is_not_in_erlang_hibernate(Pid),
+ ok = gen_statem:cast(Pid, hibernate_async),
+ is_in_erlang_hibernate(Pid),
+ ok = gen_statem:cast(Pid, snooze_async),
+ is_in_erlang_hibernate(Pid),
+ ok = gen_statem:cast(Pid, wakeup_async),
+ is_not_in_erlang_hibernate(Pid),
+
+ Pid ! hibernate_later,
+ true =
+ ({current_function,{erlang,hibernate,3}} =/=
+ erlang:process_info(Pid, current_function)),
+ is_in_erlang_hibernate(Pid),
+
+ 'alive!' = gen_statem:call(Pid, 'alive?'),
+ true =
+ ({current_function,{erlang,hibernate,3}} =/=
+ erlang:process_info(Pid, current_function)),
+ Pid ! hibernate_now,
+ is_in_erlang_hibernate(Pid),
+
+ 'alive!' = gen_statem:call(Pid, 'alive?'),
+ true =
+ ({current_function,{erlang,hibernate,3}} =/=
+ erlang:process_info(Pid, current_function)),
+
+ hibernating = gen_statem:call(Pid, hibernate_sync),
+ is_in_erlang_hibernate(Pid),
+ good_morning = gen_statem:call(Pid, wakeup_sync),
+ is_not_in_erlang_hibernate(Pid),
+ hibernating = gen_statem:call(Pid, hibernate_sync),
+ is_in_erlang_hibernate(Pid),
+ please_just_five_more = gen_statem:call(Pid, snooze_sync),
+ is_in_erlang_hibernate(Pid),
+ good_morning = gen_statem:call(Pid, wakeup_sync),
+ is_not_in_erlang_hibernate(Pid),
+ ok = gen_statem:cast(Pid, hibernate_async),
+ is_in_erlang_hibernate(Pid),
+ ok = gen_statem:cast(Pid, wakeup_async),
+ is_not_in_erlang_hibernate(Pid),
+ ok = gen_statem:cast(Pid, hibernate_async),
+ is_in_erlang_hibernate(Pid),
+ ok = gen_statem:cast(Pid, snooze_async),
+ is_in_erlang_hibernate(Pid),
+ ok = gen_statem:cast(Pid, wakeup_async),
+ is_not_in_erlang_hibernate(Pid),
+
+ hibernating = gen_statem:call(Pid, hibernate_sync),
+ is_in_erlang_hibernate(Pid),
+ sys:suspend(Pid),
+ is_in_erlang_hibernate(Pid),
+ sys:resume(Pid),
+ is_in_erlang_hibernate(Pid),
+ receive after 1000 -> ok end,
+ is_in_erlang_hibernate(Pid),
+
+ good_morning = gen_statem:call(Pid, wakeup_sync),
+ is_not_in_erlang_hibernate(Pid),
+ stop_it(Pid),
+ process_flag(trap_exit, OldFl),
+ receive
+ {'EXIT',Pid,normal} -> ok
+ after 5000 ->
+ ?t:fail(gen_statem_did_not_die)
+ end,
+ ok = verify_empty_msgq().
+
+is_in_erlang_hibernate(Pid) ->
+ receive after 1 -> ok end,
+ is_in_erlang_hibernate_1(200, Pid).
+
+is_in_erlang_hibernate_1(0, Pid) ->
+ io:format("~p\n", [erlang:process_info(Pid, current_function)]),
+ ?t:fail(not_in_erlang_hibernate_3);
+is_in_erlang_hibernate_1(N, Pid) ->
+ {current_function,MFA} = erlang:process_info(Pid, current_function),
+ case MFA of
+ {erlang,hibernate,3} ->
+ ok;
+ _ ->
+ receive after 10 -> ok end,
+ is_in_erlang_hibernate_1(N-1, Pid)
+ end.
+
+is_not_in_erlang_hibernate(Pid) ->
+ receive after 1 -> ok end,
+ is_not_in_erlang_hibernate_1(200, Pid).
+
+is_not_in_erlang_hibernate_1(0, Pid) ->
+ io:format("~p\n", [erlang:process_info(Pid, current_function)]),
+ ?t:fail(not_in_erlang_hibernate_3);
+is_not_in_erlang_hibernate_1(N, Pid) ->
+ {current_function,MFA} = erlang:process_info(Pid, current_function),
+ case MFA of
+ {erlang,hibernate,3} ->
+ receive after 10 -> ok end,
+ is_not_in_erlang_hibernate_1(N-1, Pid);
+ _ ->
+ ok
+ end.
+
+%%sys1(suite) -> [];
+%%sys1(_) ->
+
+enter_loop(Config) when is_list(Config) ->
+ OldFlag = process_flag(trap_exit, true),
+
+ dummy_via:reset(),
+
+ %% Locally registered process + {local,Name}
+ {ok,Pid1a} =
+ proc_lib:start_link(?MODULE, enter_loop, [local,local]),
+ yes = gen_statem:call(Pid1a, 'alive?'),
+ stopped = gen_statem:call(Pid1a, stop),
+ receive
+ {'EXIT',Pid1a,normal} ->
+ ok
+ after 5000 ->
+ ?t:fail(gen_statem_did_not_die)
+ end,
+
+ %% Unregistered process + {local,Name}
+ {ok,Pid1b} =
+ proc_lib:start_link(?MODULE, enter_loop, [anon,local]),
+ receive
+ {'EXIT',Pid1b,process_not_registered} ->
+ ok
+ after 5000 ->
+ ?t:fail(gen_statem_did_not_die)
+ end,
+
+ %% Globally registered process + {global,Name}
+ {ok,Pid2a} =
+ proc_lib:start_link(?MODULE, enter_loop, [global,global]),
+ yes = gen_statem:call(Pid2a, 'alive?'),
+ stopped = gen_statem:call(Pid2a, stop),
+ receive
+ {'EXIT',Pid2a,normal} ->
+ ok
+ after 5000 ->
+ ?t:fail(gen_statem_did_not_die)
+ end,
+
+ %% Unregistered process + {global,Name}
+ {ok,Pid2b} =
+ proc_lib:start_link(?MODULE, enter_loop, [anon,global]),
+ receive
+ {'EXIT',Pid2b,process_not_registered_globally} ->
+ ok
+ after 5000 ->
+ ?t:fail(gen_statem_did_not_die)
+ end,
+
+ %% Unregistered process + no name
+ {ok,Pid3} =
+ proc_lib:start_link(?MODULE, enter_loop, [anon,anon]),
+ yes = gen_statem:call(Pid3, 'alive?'),
+ stopped = gen_statem:call(Pid3, stop),
+ receive
+ {'EXIT',Pid3,normal} ->
+ ok
+ after 5000 ->
+ ?t:fail(gen_statem_did_not_die)
+ end,
+
+ %% Process not started using proc_lib
+ Pid4 =
+ spawn_link(gen_statem, enter_loop, [?MODULE,[],state0,[]]),
+ receive
+ {'EXIT',Pid4,process_was_not_started_by_proc_lib} ->
+ ok
+ after 5000 ->
+ ?t:fail(gen_statem_did_not_die)
+ end,
+
+ %% Make sure I am the parent, ie that ordering a shutdown will
+ %% result in the process terminating with Reason==shutdown
+ {ok,Pid5} =
+ proc_lib:start_link(?MODULE, enter_loop, [anon,anon]),
+ yes = gen_statem:call(Pid5, 'alive?'),
+ exit(Pid5, shutdown),
+ receive
+ {'EXIT',Pid5,shutdown} ->
+ ok
+ after 5000 ->
+ ?t:fail(gen_statem_did_not_die)
+ end,
+
+ %% Make sure gen_statem:enter_loop does not accept {local,Name}
+ %% when it's another process than the calling one which is
+ %% registered under that name
+ register(armitage, self()),
+ {ok,Pid6a} =
+ proc_lib:start_link(?MODULE, enter_loop, [anon,local]),
+ receive
+ {'EXIT',Pid6a,process_not_registered} ->
+ ok
+ after 1000 ->
+ ?t:fail(gen_statem_started)
+ end,
+ unregister(armitage),
+
+ %% Make sure gen_statem:enter_loop does not accept {global,Name}
+ %% when it's another process than the calling one which is
+ %% registered under that name
+ global:register_name(armitage, self()),
+ {ok,Pid6b} =
+ proc_lib:start_link(?MODULE, enter_loop, [anon,global]),
+ receive
+ {'EXIT',Pid6b,process_not_registered_globally} ->
+ ok
+ after 1000 ->
+ ?t:fail(gen_statem_started)
+ end,
+ global:unregister_name(armitage),
+
+ dummy_via:register_name(armitage, self()),
+ {ok,Pid6c} =
+ proc_lib:start_link(?MODULE, enter_loop, [anon,via]),
+ receive
+ {'EXIT',Pid6c,{process_not_registered_via,dummy_via}} ->
+ ok
+ after 1000 ->
+ ?t:fail(
+ {gen_statem_started,
+ process_info(self(), messages)})
+ end,
+ dummy_via:unregister_name(armitage),
+
+ process_flag(trap_exit, OldFlag),
+ ok = verify_empty_msgq().
+
+enter_loop(Reg1, Reg2) ->
+ process_flag(trap_exit, true),
+ case Reg1 of
+ local -> register(armitage, self());
+ global -> global:register_name(armitage, self());
+ via -> dummy_via:register_name(armitage, self());
+ anon -> ignore
+ end,
+ proc_lib:init_ack({ok, self()}),
+ case Reg2 of
+ local ->
+ gen_statem:enter_loop(?MODULE, [], state0, [], {local,armitage});
+ global ->
+ gen_statem:enter_loop(?MODULE, [], state0, [], {global,armitage});
+ via ->
+ gen_statem:enter_loop(?MODULE, [], state0, [],
+ {via, dummy_via, armitage});
+ anon ->
+ gen_statem:enter_loop(?MODULE, [], state0, [])
+ end.
+
+%%
+%% Functionality check
+%%
+
+wfor(Msg) ->
+ receive
+ Msg -> ok
+ after 5000 ->
+ error(timeout)
+ end.
+
+
+stop_it(STM) ->
+ stopped = gen_statem:call(STM, stop),
+ check_stopped(STM).
+
+
+check_stopped(STM) ->
+ Call = there_you_are,
+ {_,{gen_statem,call,[_,Call,infinity]}} =
+ ?EXPECT_FAILURE(gen_statem:call(STM, Call), Reason),
+ ok.
+
+
+do_func_test(STM) ->
+ ok = gen_statem:cast(STM, {'alive?',self()}),
+ wfor(yes),
+ ok = do_connect(STM),
+ ok = gen_statem:cast(STM, {'alive?',self()}),
+ wfor(yes),
+ ?t:do_times(3, ?MODULE, do_msg, [STM]),
+ ok = gen_statem:cast(STM, {'alive?',self()}),
+ wfor(yes),
+ ok = do_disconnect(STM),
+ ok = gen_statem:cast(STM, {'alive?',self()}),
+ wfor(yes),
+ ok.
+
+
+do_connect(STM) ->
+ check_state(STM, idle),
+ gen_statem:cast(STM, {connect,self()}),
+ wfor(accept),
+ check_state(STM, wfor_conf),
+ gen_statem:cast(STM, confirm),
+ check_state(STM, connected),
+ ok.
+
+do_msg(STM) ->
+ check_state(STM, connected),
+ R = make_ref(),
+ ok = gen_statem:cast(STM, {msg,self(),R}),
+ wfor({ack,R}).
+
+
+do_disconnect(STM) ->
+ ok = gen_statem:cast(STM, disconnect),
+ check_state(STM, idle).
+
+check_state(STM, State) ->
+ case gen_statem:call(STM, get) of
+ {state, State, _} -> ok
+ end.
+
+do_sync_func_test(STM) ->
+ yes = gen_statem:call(STM, 'alive?'),
+ ok = do_sync_connect(STM),
+ yes = gen_statem:call(STM, 'alive?'),
+ ?t:do_times(3, ?MODULE, do_sync_msg, [STM]),
+ yes = gen_statem:call(STM, 'alive?'),
+ ok = do_sync_disconnect(STM),
+ yes = gen_statem:call(STM, 'alive?'),
+ check_state(STM, idle),
+ ok = gen_statem:call(STM, {timeout,200}),
+ yes = gen_statem:call(STM, 'alive?'),
+ check_state(STM, idle),
+ ok.
+
+
+do_sync_connect(STM) ->
+ check_state(STM, idle),
+ accept = gen_statem:call(STM, connect),
+ check_state(STM, wfor_conf),
+ yes = gen_statem:call(STM, confirm),
+ check_state(STM, connected),
+ ok.
+
+do_sync_msg(STM) ->
+ check_state(STM, connected),
+ R = make_ref(),
+ {ack,R} = gen_statem:call(STM, {msg,R}),
+ ok.
+
+do_sync_disconnect(STM) ->
+ yes = gen_statem:call(STM, disconnect),
+ check_state(STM, idle).
+
+
+verify_empty_msgq() ->
+ receive after 500 -> ok end,
+ [] = ?t:messages_get(),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% The State Machine
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+init(ignore) ->
+ ignore;
+init(stop) ->
+ {stop,stopped};
+init(stop_shutdown) ->
+ {stop,shutdown};
+init(sleep) ->
+ ?t:sleep(1000),
+ {ok,idle,data};
+init(hiber) ->
+ {ok,hiber_idle,[]};
+init(hiber_now) ->
+ {ok,hiber_idle,[],[hibernate]};
+init({state_data, StateData}) ->
+ {ok,idle,StateData};
+init(_) ->
+ {ok,idle,state_data}.
+
+terminate(_, _State, crash_terminate) ->
+ exit({crash,terminate});
+terminate({From,stopped}, State, _Data) ->
+ From ! {self(),{stopped,State}},
+ ok;
+terminate(_Reason, _State, _Data) ->
+ ok.
+
+
+%% State functions
+
+idle(cast, {connect,Pid}, _, _, Data) ->
+ Pid ! accept,
+ {wfor_conf,Data};
+idle({call,From}, connect, _, _, Data) ->
+ {wfor_conf,Data,[{reply,From,accept}]};
+idle(cast, badreturn, _, _, _Data) ->
+ badreturn;
+idle({call,_From}, badreturn, _, _, _Data) ->
+ badreturn;
+idle({call,From}, {delayed_answer,T}, _, _, _Data) ->
+ receive
+ after T ->
+ [{reply,From,delayed}]
+ end;
+idle({call,From}, {timeout,Time}, _, State, _Data) ->
+ {timeout, {From,Time}, [{timeout,Time,State}]};
+idle(Type, Content, PrevState, State, Data) ->
+ case handle_common_events(Type, Content, PrevState, State, Data) of
+ [] ->
+ case Type of
+ {call,From} ->
+ {State,Data,[{reply,From,'eh?'}]};
+ _ ->
+ {State,Data,
+ [{stop,{unexpected,State,PrevState,Type,Content}}]}
+ end;
+ Result ->
+ Result
+ end.
+
+timeout(timeout, idle, idle, timeout, {From,Time}) ->
+ TRef2 = erlang:start_timer(Time, self(), ok),
+ TRefC1 = erlang:start_timer(Time, self(), cancel1),
+ TRefC2 = erlang:start_timer(Time, self(), cancel2),
+ {timeout2,
+ {From, Time, TRef2},
+ [{cancel_timer, TRefC1},
+ {insert_event,internal,{cancel_timer,TRefC2}}]};
+timeout(_, _, _, _, Data) ->
+ {Data}.
+
+timeout2(
+ internal, {cancel_timer,TRefC2}, timeout, _, {From,Time,TRef2}) ->
+ Time4 = Time * 4,
+ receive after Time4 -> ok end,
+ {timeout3,{From,TRef2},[{cancel_timer,TRefC2}]};
+timeout2(_, _, _, _, Data) ->
+ {Data}.
+
+timeout3(info, {timeout,TRef2,Result}, _, _, {From,TRef2}) ->
+ {idle,state,[{reply,From,Result}]};
+timeout3(_, _, _, _, Data) ->
+ {Data}.
+
+wfor_conf({call,From}, confirm, _, _, Data) ->
+ {connected,Data,[{reply,From,yes}]};
+wfor_conf(cast, confirm, _, _, Data) ->
+ {connected,Data};
+wfor_conf(Type, Content, PrevState, State, Data) ->
+ case handle_common_events(Type, Content, PrevState, State, Data) of
+ [] ->
+ case Type of
+ {call,From} ->
+ {idle,Data,[{reply,From,'eh?'}]};
+ _ ->
+ {Data}
+ end;
+ Result ->
+ Result
+ end.
+
+connected({call,From}, {msg,Ref}, _, State, Data) ->
+ {State,Data,[{reply,From,{ack,Ref}}]};
+connected(cast, {msg,From,Ref}, _, _, _Data) ->
+ From ! {ack,Ref},
+ {};
+connected({call,From}, disconnect, _, _, Data) ->
+ {idle,Data,[{reply,From,yes}]};
+connected(cast, disconnect, _, _, Data) ->
+ {idle,Data};
+connected(Type, Content, PrevState, State, Data) ->
+ case handle_common_events(Type, Content, PrevState, State, Data) of
+ [] ->
+ case Type of
+ {call,From} ->
+ [{reply,From,'eh?'}];
+ _ ->
+ {Data}
+ end;
+ Result ->
+ Result
+ end.
+
+state0({call,From}, stop, _, State, Data) ->
+ {State,Data,
+ [{reply,From,stopped},
+ {stop,normal}]};
+state0(Type, Content, PrevState, State, Data) ->
+ case handle_common_events(Type, Content, PrevState, State, Data) of
+ [] ->
+ {Data};
+ Result ->
+ Result
+ end.
+
+hiber_idle({call,From}, 'alive?', _, _, _) ->
+ [{reply,From,'alive!'}];
+hiber_idle({call,From}, hibernate_sync, _, _, Data) ->
+ {hiber_wakeup,Data,
+ [{reply,From,hibernating},
+ hibernate]};
+hiber_idle(info, hibernate_later, _, State, _) ->
+ Tref = erlang:start_timer(1000, self(), hibernate),
+ {State,Tref};
+hiber_idle(info, hibernate_now, _, State, Data) ->
+ {State,Data,[hibernate]};
+hiber_idle(info, {timeout,Tref,hibernate}, _, State, Tref) ->
+ {State,[],
+ [hibernate]};
+hiber_idle(cast, hibernate_async, _, _, Data) ->
+ {hiber_wakeup,Data,
+ [hibernate]};
+hiber_idle(Type, Content, PrevState, State, Data) ->
+ case handle_common_events(Type, Content, PrevState, State, Data) of
+ [] ->
+ {Data};
+ Result ->
+ Result
+ end.
+
+hiber_wakeup({call,From}, wakeup_sync, _, _, Data) ->
+ {hiber_idle,Data,[{reply,From,good_morning}]};
+hiber_wakeup({call,From}, snooze_sync, _, State, Data) ->
+ {State,Data,
+ [{reply,From,please_just_five_more},
+ hibernate]};
+hiber_wakeup(cast, wakeup_async, _, _, Data) ->
+ {hiber_idle,Data};
+hiber_wakeup(cast, snooze_async, _, _, _Data) ->
+ [hibernate];
+hiber_wakeup(Type, Content, PrevState, State, Data) ->
+ case handle_common_events(Type, Content, PrevState, State, Data) of
+ [] ->
+ {Data};
+ Result ->
+ Result
+ end.
+
+
+handle_common_events({call,From}, get, _, State, Data) ->
+ [{reply,From,{state,State,Data}}];
+handle_common_events(cast, {get,Pid}, _, State, Data) ->
+ Pid ! {state,State,Data},
+ {};
+handle_common_events({call,From}, stop, _, _, _) ->
+ [{reply,From,stopped},
+ {stop,normal}];
+handle_common_events(cast, stop, _, State, Data) ->
+ {State,Data,
+ [{stop,normal}]};
+handle_common_events({call,From}, {stop,Reason}, _, State, Data) ->
+ {State,Data,
+ [{reply,From,stopped},
+ {stop,Reason}]};
+handle_common_events(cast, {stop,Reason}, _, _, _) ->
+ [{stop,Reason}];
+handle_common_events({call,From}, 'alive?', _, State, Data) ->
+ {State,Data,
+ [{reply,From,yes}]};
+handle_common_events(cast, {'alive?',Pid}, _, State, Data) ->
+ Pid ! yes,
+ {State,Data};
+handle_common_events(_, _, _, _, _) ->
+ [].
+
+code_change(_OldVsn, State, StateData, _Extra) ->
+ {ok,State,StateData}.
+
+format_status(terminate, [_Pdict,State,StateData]) ->
+ {formatted,State,StateData};
+format_status(normal, [_Pdict,_State,_StateData]) ->
+ [format_status_called].