aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorRaimo Niskanen <[email protected]>2016-02-16 17:54:15 +0100
committerRaimo Niskanen <[email protected]>2016-02-18 08:27:55 +0100
commit99d48f5921f95073c8e46280494746b0e1a2c375 (patch)
treeb58a5fe5f5c4c6d701d66879fa25e2e68abbde93 /lib
parent3815de0c7337058991066454c246587c0dbaa664 (diff)
downloadotp-99d48f5921f95073c8e46280494746b0e1a2c375.tar.gz
otp-99d48f5921f95073c8e46280494746b0e1a2c375.tar.bz2
otp-99d48f5921f95073c8e46280494746b0e1a2c375.zip
Implement option callback_mode
Diffstat (limited to 'lib')
-rw-r--r--lib/stdlib/doc/src/gen_statem.xml272
-rw-r--r--lib/stdlib/src/gen_statem.erl134
-rw-r--r--lib/stdlib/test/gen_statem_SUITE.erl242
3 files changed, 439 insertions, 209 deletions
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index f7ce0f61ae..f608e10469 100644
--- a/lib/stdlib/doc/src/gen_statem.xml
+++ b/lib/stdlib/doc/src/gen_statem.xml
@@ -31,8 +31,15 @@
<module>gen_statem</module>
<modulesummary>Generic State Machine Behaviour</modulesummary>
<description>
- <p>A behaviour module for implementing a state machine, primarily
- a finite state machine, but an infinite state space is possible.
+ <p>A behaviour module for implementing a state machine.
+ Two callback modes are supported. One for a finite state
+ machine like <seealso marker="gen_fsm">gen_fsm</seealso>
+ that require the state to be an atom and use that state as
+ the name of the callback function for a particular state,
+ and one without restriction on the state that use the same
+ callback function for all states.
+ </p>
+ <p>
A generic state machine process (gen_statem) implemented using
this module will have a standard set of interface functions
and include functionality for tracing and error reporting.
@@ -55,7 +62,7 @@ gen_statem:stop -----> Module:terminate/2
gen_statem:call
gen_statem:cast
erlang:send
-erlang:'!' -----> Module:State/5
+erlang:'!' -----> Module:StateName/5
Module:handle_event/5
- -----> Module:terminate/3
@@ -75,47 +82,39 @@ erlang:'!' -----> Module:State/5
<p>The "<em>state function</em>" for a specific
<seealso marker="#type-state">state</seealso>
in a gen_statem is the callback function that is called
- for all events in this state.
- An event can can be postponed causing it to be retried
- after the state has changed, or more precisely;
- after a state change all postponed events are retried
- in the new state.
+ for all events in this state, and is selected depending on
+ <seealso marker="#type-callback_mode">callback_mode</seealso>
+ that the implementation selects during gen_statem init.
</p>
- <p>The state machine
- <seealso marker="#type-state"><c>State</c></seealso>
- is normally an atom in which case the
- <seealso marker="#state_function">state function</seealso>
- that will be called is
- <seealso marker="#Module:State/5"><c>Module:State/5</c></seealso>.
- For a
- <seealso marker="#type-state"><c>State</c></seealso>
- that is <em>not</em> an atom the
- <seealso marker="#state_function">state function</seealso>
- <seealso marker="#Module:handle_event/5">
- <c>Module:handle_event/5</c>
- </seealso> will be called.
- If you use <c>handle_event</c> as a
- <seealso marker="#type-state">state</seealso> and later
- decide to use non-atom states you will then have to
- rewrite your code to stop using that state.
+ <p>When
+ <seealso marker="#type-callback_mode">callback_mode</seealso>
+ is <c>state_functions</c> the state has to be an atom and
+ is used as the state function name.
+ See
+ <seealso marker="#Module:StateName/5">
+ <c>Module:StateName/5</c>
+ </seealso>.
+ This naturally collects all code for a specific state
+ in one function and hence dispatches on state first.
</p>
- <p>When using an atom-only
- <seealso marker="#type-state">State</seealso>
- it becomes fairly obvious in the implementation code
- which events are handled in which state
- since there are different callback functions for different states.
+ <p>When
+ <seealso marker="#type-callback_mode">callback_mode</seealso>
+ is <c>handle_event_function</c> the state can be any term
+ and the state function name is
+ <seealso marker="#Module:handle_event/5">
+ <c>Module:handle_event/5</c>
+ </seealso>.
+ This makes it easy to dispatch on state or on event as
+ you like but you will have to implement it.
+ Also be careful about which events you handle in which
+ states so you do not accidentally postpone one event
+ forever creating an infinite busy loop.
</p>
<p>
- When using a non-atom <seealso marker="#type-state">State</seealso>
- all events are handled in the callback function
- <seealso marker="#Module:handle_event/5">
- <c>Module:handle_event/5</c>
- </seealso>
- so it may require more coding discipline to ensure what events
- are handled in which state. Therefore it might be a wee bit
- easier to accidentally postpone an event in two or more states
- and not handling it in any of them causing a tight infinite
- loop when the event bounces to be retried between the states.
+ An event can can be postponed causing it to be retried
+ after the state has changed, or more precisely;
+ after a state change all postponed events are retried
+ in the new state.
</p>
<p>A gen_statem handles system messages as documented in
<seealso marker="sys">sys</seealso>.
@@ -239,19 +238,23 @@ erlang:'!' -----> Module:State/5
<datatype>
<name name="state" />
<desc>
- <p>If the gen_statem <c>State</c> is an <c>atom()</c>, the
- <seealso marker="#state_function">state function</seealso> is
- <seealso marker="#Module:State/5">Module:State/5</seealso>.
- If it is any other <c>term()</c> the
- <seealso marker="#state_function">state function</seealso> is
- <seealso marker="#Module:handle_event/5">
- Module:handle_event/5
- </seealso>. After a state change (<c>NewState =/= State</c>)
+ <p>After a state change (<c>NewState =/= State</c>)
all postponed events are retried.
</p>
</desc>
</datatype>
<datatype>
+ <name name="state_name" />
+ <desc>
+ <p>If
+ <seealso marker="#type-callback_mode">
+ callback_mode
+ </seealso> is <c>state_functions</c>, which is the default,
+ the state has to be of this type i.e an <c>atom()</c>.
+ </p>
+ </desc>
+ </datatype>
+ <datatype>
<name name="state_data" />
<desc>
<p>A <c>term()</c> in which the state machine implementation
@@ -297,6 +300,33 @@ erlang:'!' -----> Module:State/5
</desc>
</datatype>
<datatype>
+ <name name="init_option" />
+ <desc>
+ <p>Option that only is valid when initializing the gen_statem</p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="callback_mode" />
+ <desc>
+ <taglist>
+ <tag><c>state_functions</c></tag>
+ <item>The state has to be of type
+ <seealso marker="#type-state_name"><c>state_name()</c></seealso>
+ and one callback function per state that is
+ <seealso marker="#Module:StateName/5">
+ <c>Module:StateName/5</c>
+ </seealso> is used. This is the default.
+ </item>
+ <tag><c>handle_event_function</c></tag>
+ <item>The state can be any term and the callback function
+ <seealso marker="#Module:handle_event/5">
+ <c>Module:handle_event/5</c>
+ </seealso> is used for all states.
+ </item>
+ </taglist>
+ </desc>
+ </datatype>
+ <datatype>
<name name="state_op" />
<desc>
<p>Either a
@@ -472,6 +502,53 @@ erlang:'!' -----> Module:State/5
</taglist>
</desc>
</datatype>
+ <datatype>
+ <name name="state_callback_result" />
+ <desc>
+ <taglist>
+ <tag>
+ <c>{stop,<anno>Reason</anno>,<anno>NewStateData</anno>}</c>
+ </tag>
+ <item>The same as
+ <c>{stop,<anno>Reason</anno>,[],<anno>NewStateData</anno>}</c>
+ </item>
+ <tag><c>{stop,
+ <anno>Reason</anno>,
+ <anno>Replies</anno>,
+ <anno>NewStateData</anno>}</c>
+ </tag>
+ <item>The gen_statem will first send all
+ <c><anno>Replies</anno></c> and then terminate by calling
+ <seealso marker="#Module:terminate/3">
+ <c>Module:terminate/3</c>
+ </seealso> with <c>Reason</c>.
+ </item>
+ <tag>
+ <c>
+ {next_state,<anno>NewState</anno>,<anno>NewStateData</anno>}
+ </c>
+ </tag>
+ <item>The same as
+ <c>
+ {next_state,<anno>NewState</anno>,<anno>NewStateData</anno>,[]}
+ </c>
+ </item>
+ <tag>
+ <c>
+ {next_state,
+ <anno>NewState</anno>,
+ <anno>NewStateData</anno>,
+ <anno>StateOps</anno>}
+ </c>
+ </tag>
+ <item>The gen_statem will do a state transition to
+ <c><anno>NewState</anno></c>
+ (which may be the same as <c>State</c>)
+ and execute all <c>StateOps</c>
+ </item>
+ </taglist>
+ </desc>
+ </datatype>
</datatypes>
<funcs>
@@ -777,13 +854,16 @@ erlang:'!' -----> Module:State/5
<p><c><anno>Module</anno></c>, <c><anno>Options</anno></c> and
<c><anno>Server</anno></c> have the same meanings
as when calling
- <seealso marker="#start_link/3">gen_statem:start[_link]/3,4</seealso>.
+ <seealso marker="#start_link/3">
+ gen_statem:start[_link]/3,4
+ </seealso>.
However, the
<seealso marker="#type-server_name">
<c>server_name()</c>
</seealso> name must have been registered accordingly
<em>before</em> this function is called.</p>
- <p><c><anno>State</anno></c> and <c><anno>StateData</anno></c>
+ <p><c><anno>State</anno></c>, <c><anno>StateData</anno></c>
+ and <c><anno>StateOps</anno></c>
have the same meanings as in the return value of
<seealso marker="#Module:init/1">Module:init/1</seealso>.
Also, the callback module <c><anno>Module</anno></c>
@@ -821,8 +901,13 @@ erlang:'!' -----> Module:State/5
<v>&nbsp;| {ok,State,StateData,StateOps}</v>
<v>&nbsp;| {stop,Reason} | ignore</v>
<v>State = <seealso marker="#type-state">state()</seealso></v>
- <v>StateData = <seealso marker="#type-state_data">state_data()</seealso></v>
- <v>StateOps = [<seealso marker="#type-state_op">state_op()</seealso>]</v>
+ <v>StateData =
+ <seealso marker="#type-state_data">state_data()</seealso>
+ </v>
+ <v>StateOps =
+ [<seealso marker="#type-state_op">state_op()</seealso>
+ | <seealso marker="#type-init_option">init_option()</seealso>]
+ </v>
<v>Reason = term()</v>
</type>
<desc>
@@ -843,10 +928,15 @@ erlang:'!' -----> Module:State/5
of the gen_statem.
</p>
<p>The <seealso marker="#type-state_op"><c>StateOps</c></seealso>
- are executed before entering the first
+ are executed when entering the first
<seealso marker="#type-state">state</seealso> just as for a
<seealso marker="#state_function">state function</seealso>.
</p>
+ <p>This function allows an option to select the callback mode
+ of the gen_statem. See
+ <seealso marker="#type-init_option">init_option</seealso>.
+ This option is not allowed from the state function(s).
+ </p>
<p>If something goes wrong during the initialization
the function should return <c>{stop,Reason}</c>
or <c>ignore</c>. See
@@ -856,10 +946,10 @@ erlang:'!' -----> Module:State/5
</func>
<func>
- <name>Module:handle_event(EventType, EventContent,
- PrevState, State, StateData) -> Result
+ <name>Module:StateName(EventType, EventContent,
+ PrevStateName, StateName, StateData) -> Result
</name>
- <name>Module:State(EventType, EventContent,
+ <name>Module:handle_event(EventType, EventContent,
PrevState, State, StateData) -> Result
</name>
<fsummary>Handle an event</fsummary>
@@ -868,41 +958,23 @@ erlang:'!' -----> Module:State/5
<seealso marker="#type-event_type">event_type()</seealso>
</v>
<v>EventContent = term()</v>
- <v>Result = {stop,Reason,NewStateData}</v>
- <d>&nbsp;&nbsp;The same as <c>{stop,Reason,[],NewStateData}</c></d>
- <v>&nbsp;&nbsp;| {stop,Reason,Reply,NewStateData}</v>
- <d>&nbsp;&nbsp;The same as
- <c>{stop,Reason,[Reply],NewStateData}</c>
- </d>
- <v>&nbsp;&nbsp;| {stop,Reason,Replies,NewStateData}</v>
- <d>&nbsp;&nbsp;The gen_statem will first send all
- <c>Replies</c> and then call
- <seealso marker="#Module:terminate/3">
- <c>Module:terminate/3</c>
- </seealso> with <c>Reason</c> and terminate.
- </d>
- <v>&nbsp;&nbsp;| {next_state,NewState,NewStateData}</v>
- <d>&nbsp;&nbsp;The same as
- <c>{next_state,NewState,NewStateData,NewStateData,[]}</c>
- </d>
- <v>&nbsp;&nbsp;| {next_state,NewState,NewStateData,StateOps}</v>
- <d>&nbsp;&nbsp;The gen_statem will do a state transition to
- <c>NewState</c> (which may be the same as <c>State</c>)
- and execute all <c>StateOps</c>
- </d>
- <v>Reason = term()</v>
- <v>PrevState = State = NewState =
+ <v>PrevStateName =
+ <seealso marker="#type-state_name">state_name()</seealso>
+ | reference()
+ </v>
+ <v>StateName =
+ <seealso marker="#type-state_name">state_name()</seealso>
+ </v>
+ <v>PrevState = State =
<seealso marker="#type-state">state()</seealso>
</v>
<v>StateData = NewStateData =
<seealso marker="#type-state_data">state_data()</seealso>
</v>
- <v>Reply =
- <seealso marker="#type-reply_operation">reply_operation()</seealso>
- </v>
- <v>Replies = [Reply]</v>
- <v>StateOps =
- [<seealso marker="#type-state_op">state_op()</seealso>]
+ <v>Result =
+ <seealso marker="#type-state_callback_result">
+ state_callback_result()
+ </seealso>
</v>
</type>
<desc>
@@ -923,19 +995,18 @@ erlang:'!' -----> Module:State/5
<c>gen_statem:reply(Client, Reply)</c>
</seealso>.
</p>
- <p><seealso marker="#type-state"><c>State</c></seealso>
- is the internal state of the gen_statem which
- when <c>State</c> is an <c>atom()</c>
- is the same as this function's name, so it is seldom useful,
- except for example when comparing with <c>PrevState</c>
- that is the gen_statem's previous state, or in
- <seealso marker="#Module:handle_event/5">
- Module:handle_event/5
- </seealso> since that function is common for all states
- that are not an <c>atom()</c>.
- </p>
- <p>If this function returns with
- <seealso marker="#type-state"><c>NewState =/= State</c></seealso>
+ <p><c>StateName</c> is rarely useful. One exception is if
+ you call a common event handling function from your state
+ function then you might want to pass <c>StateName</c>.
+ </p>
+ <p><c>PrevStateName</c> and <c>PrevState</c> are rarely useful.
+ They can be used when you want to do something only at the
+ first event in a state. Note that when gen_statem enters
+ its first state this is set to a <c>reference()</c> since
+ that can not match the state.
+ </p>
+ <p>If this function returns with a new state that
+ does not match equal (<c>=/=</c>) to the current state
all postponed events will be retried in the new state.
</p>
<p>See <seealso marker="#type-state_op">state_op()</seealso>
@@ -946,7 +1017,7 @@ erlang:'!' -----> Module:State/5
</func>
<func>
- <name>Module:terminate(Reason, State, StateData)</name>
+ <name>Module:terminate(Reason, State, StateData) -> Ignored</name>
<fsummary>Clean up before termination</fsummary>
<type>
<v>Reason = normal | shutdown | {shutdown,term()} | term()</v>
@@ -956,6 +1027,7 @@ erlang:'!' -----> Module:State/5
state_data()
</seealso>
</v>
+ <v>Ignored = term()</v>
</type>
<desc>
<p>This function is called by a gen_statem when it is about to
diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl
index 7c3cd8c2f3..e6bb38fc2c 100644
--- a/lib/stdlib/src/gen_statem.erl
+++ b/lib/stdlib/src/gen_statem.erl
@@ -44,6 +44,9 @@
-export(
[wakeup_from_hibernate/3]).
+%% Fix problem for doc build
+-export_type([state_callback_result/0]).
+
%%%==========================================================================
%%% Interface functions.
%%%==========================================================================
@@ -51,14 +54,18 @@
-type client() ::
{To :: pid(), Tag :: term()}. % Reply-to specifier for call
-type state() ::
- atom() | % Calls state callback function State/5
- term(). % Calls state callback function handle_event/5
+ state_name() | % For state callback function StateName/5
+ term(). % For state callback function handle_event/5
+-type state_name() :: atom().
-type state_data() :: term().
-type event_type() ::
{'call',Client :: client()} | 'cast' |
'info' | 'timeout' | 'internal'.
-type event_predicate() :: % Return true for the event in question
fun((event_type(), term()) -> boolean()).
+-type init_option() ::
+ {'callback_mode', callback_mode()}.
+-type callback_mode() :: 'state_functions' | 'handle_event_function'.
-type state_op() ::
%% First NewState and NewStateData are set,
%% then all state_operations() are executed in order of
@@ -97,6 +104,21 @@
-type reply_operation() ::
{'reply', % Reply to a client
Client :: client(), Reply :: term()}.
+-type state_callback_result() ::
+ {stop, % Stop the server
+ Reason :: term(),
+ NewStateData :: state_data()} |
+ {stop, % Stop the server
+ Reason :: term(),
+ Replies :: [reply_operation()] | reply_operation(),
+ NewStateData :: state_data()} |
+ {next_state, % {next_state,NewState,NewStateData,[]}
+ NewState :: state(),
+ NewStateData :: state_data()} |
+ {next_state, % State transition, maybe to the same state
+ NewState :: state(),
+ NewStateData :: state_data(),
+ StateOps :: [state_op()] | state_op()}.
%% The state machine init function. It is called only once and
%% the server is not running until this function has returned
@@ -104,40 +126,36 @@
%% for all events to this server.
-callback init(Args :: term()) ->
{'ok', state(), state_data()} |
- {'ok', state(), state_data(), [state_op()]} |
+ {'ok', state(), state_data(), [state_op()|init_option()]} |
'ignore' |
{'stop', Reason :: term()}.
-%% An example callback for a fictive state 'handle_event'
-%% that you should avoid having. See below.
+%% Example callback for callback_mode =:= state_functions
+%% state name 'state_name'.
+%%
+%% In this mode all states has to be type state_name() i.e atom().
%%
%% Note that state callbacks and only state callbacks have arity 5
%% and that is intended.
+-callback state_name(
+ event_type(),
+ EventContent :: term(),
+ PrevStateName :: state_name() | reference(),
+ StateName :: state_name(), % Current state
+ StateData :: state_data()) ->
+ state_callback_result().
+%%
+%% Callback for callback_mode =:= handle_event_function.
%%
-%% You should not actually use 'handle_event' as a state name,
-%% since it is the callback function that is used if you would use
-%% a State that is not an atom(). This is because since there is
-%% no obvious way to decide on a state function name from any term().
+%% Note that state callbacks and only state callbacks have arity 5
+%% and that is intended.
-callback handle_event(
event_type(),
EventContent :: term(),
PrevState :: state(),
State :: state(), % Current state
StateData :: state_data()) ->
- {stop, % Stop the server
- Reason :: term(),
- NewStateData :: state_data()} |
- {stop, % Stop the server
- Reason :: term(),
- [reply_operation()] | reply_operation(),
- NewStateData :: state_data()} |
- {next_state, % {next_state,NewState,NewStateData,[]}
- NewState :: state(),
- NewStateData :: state_data()} |
- {next_state, % State transition, maybe to the same state
- NewState :: state(),
- NewStateData :: state_data(),
- [state_op()] | state_op()}.
+ state_callback_result().
%% Clean up before the server terminates.
-callback terminate(
@@ -171,8 +189,11 @@
-optional_callbacks(
[format_status/2, % Has got a default implementation
- handle_event/5]). % Only needed for State not an atom()
-%% For every atom() State there has to be a State/5 callback function
+ %%
+ state_name/5, % Example for callback_mode =:= state_functions:
+ %% there has to be a StateName/5 callback function for every StateName.
+ %%
+ handle_event/5]). % For callback_mode =:= handle_event_function
%% Type validation functions
client({Pid,Tag}) when is_pid(Pid), is_reference(Tag) ->
@@ -366,7 +387,8 @@ enter_loop(Module, Options, State, StateData) ->
-spec enter_loop(
Module :: module(), Options :: [debug_opt()],
State :: state(), StateData :: state_data(),
- Server_or_StateOps :: server_name() | pid() | [state_op()]) ->
+ Server_or_StateOps ::
+ server_name() | pid() | [state_op()|init_option()]) ->
no_return().
enter_loop(Module, Options, State, StateData, Server_or_StateOps) ->
if
@@ -384,7 +406,7 @@ enter_loop(Module, Options, State, StateData, Server_or_StateOps) ->
Module :: module(), Options :: [debug_opt()],
State :: state(), StateData :: state_data(),
Server :: server_name() | pid(),
- StateOps :: [state_op()]) ->
+ StateOps :: [state_op()|init_option()]) ->
no_return().
enter_loop(Module, Options, State, StateData, Server, StateOps) ->
Parent = gen:get_parent(),
@@ -410,11 +432,12 @@ do_send(Proc, Msg) ->
end.
%% Here init_it and all enter_loop functions converge
-enter(Module, Options, State, StateData, Server, StateOps, Parent) ->
+enter(Module, Options, State, StateData, Server, InitOps, Parent) ->
Name = gen:get_proc_name(Server),
Debug = gen:debug_options(Name, Options),
PrevState = make_ref(),
S = #{
+ callback_mode => state_functions,
module => Module,
name => Name,
prev_state => PrevState,
@@ -423,9 +446,17 @@ enter(Module, Options, State, StateData, Server, StateOps, Parent) ->
timer => undefined,
postponed => [],
hibernate => false},
- loop_event_state_ops(
- Parent, Debug, S, [], {event,undefined},
- State, StateData, [{retry,false}|StateOps]).
+ case collect_init_options(InitOps) of
+ {CallbackMode,StateOps} ->
+ loop_event_state_ops(
+ Parent, Debug,
+ S#{callback_mode := CallbackMode},
+ [], {event,undefined},
+ State, StateData,
+ [{retry,false}|StateOps]);
+ [Reason] ->
+ terminate(Reason, Debug, S, [])
+ end.
%%%==========================================================================
%%% gen callbacks
@@ -643,13 +674,13 @@ loop_receive(Parent, Debug, S, Event, Timer) ->
%% The loop_event* functions optimize S map handling by dismantling it,
%% passing the parts in arguments to avoid map lookups and construct the
%% new S map in one go on exit. Premature optimization, I know, but
-%% the code was way to readable and there were quite some map lookups
-%% repeated in different functions.
+%% there were quite some map lookups repeated in different functions.
loop_events(Parent, Debug, S, [], _Timer) ->
loop(Parent, Debug, S);
loop_events(
Parent, Debug,
- #{module := Module,
+ #{callback_mode := CallbackMode,
+ module := Module,
prev_state := PrevState,
state := State,
state_data := StateData} = S,
@@ -657,11 +688,11 @@ loop_events(
_ = (Timer =/= undefined) andalso
cancel_timer(Timer),
Func =
- if
- is_atom(State) ->
- State;
- true ->
- handle_event
+ case CallbackMode of
+ handle_event_function ->
+ handle_event;
+ state_functions ->
+ State
end,
try Module:Func(Type, Content, PrevState, State, StateData) of
Result ->
@@ -675,7 +706,10 @@ loop_events(
%% Process an undef to check for the simple mistake
%% of calling a nonexistent state function
case erlang:get_stacktrace() of
- [{Module,State,[Event,StateData]=Args,_}|Stacktrace] ->
+ [{Module,Func,
+ [Type,Content,PrevState,State,StateData]=Args,
+ _}
+ |Stacktrace] ->
terminate(
error,
{undef_state_function,{Module,State,Args}},
@@ -709,7 +743,7 @@ loop_event_result(Parent, Debug, S, Events, Event, Result) ->
end,
BadReplies =
reply_then_terminate(Reason, Debug, NewS, Q, Replies),
- %% Since it returned Replies was bad
+ %% Since we got back here Replies was bad
terminate(
{bad_return_value,{stop,Reason,BadReplies,NewStateData}},
Debug, NewS, Q);
@@ -794,6 +828,24 @@ loop_event_state_ops(
%%---------------------------------------------------------------------------
%% Server helpers
+collect_init_options(InitOps) ->
+ collect_init_options(lists:reverse(InitOps), state_functions, []).
+%% Keep the last of each kind
+collect_init_options([], CallbackMode, StateOps) ->
+ {CallbackMode,StateOps};
+collect_init_options([InitOp|InitOps] = IOIOs, CallbackMode, StateOps) ->
+ case InitOp of
+ {callback_mode,Mode}
+ when Mode =:= state_functions;
+ Mode =:= handle_event_function ->
+ collect_init_options(InitOps, Mode, StateOps);
+ {callback_mode,_} ->
+ [{bad_init_ops,IOIOs}];
+ _ -> % Collect others as StateOps
+ collect_init_options(
+ InitOps, CallbackMode, [InitOp|StateOps])
+ end.
+
collect_state_options(StateOps) ->
collect_state_options(
lists:reverse(StateOps), false, false, undefined, []).
diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl
index 51e08f5ec1..8a96f0e8e2 100644
--- a/lib/stdlib/test/gen_statem_SUITE.erl
+++ b/lib/stdlib/test/gen_statem_SUITE.erl
@@ -19,7 +19,7 @@
%%
-module(gen_statem_SUITE).
--include_lib("test_server/include/test_server.hrl").
+-include_lib("common_test/include/ct.hrl").
-compile(export_all).
-behaviour(gen_statem).
@@ -30,7 +30,11 @@ suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[{group, start},
+ {group, start_handle_event},
+ {group, stop},
+ {group, stop_handle_event},
{group, abnormal},
+ {group, abnormal_handle_event},
shutdown,
{group, sys}, hibernate, enter_loop].
@@ -38,13 +42,24 @@ groups() ->
[{start, [],
[start1, start2, start3, start4, start5, start6, start7,
start8, start9, start10, start11, start12]},
+ {start_handle_event, [],
+ [start1, start2, start3, start4, start5, start6, start7,
+ start8, start9, start10, start11, start12]},
{stop, [],
[stop1, stop2, stop3, stop4, stop5, stop6, stop7, stop8, stop9, stop10]},
+ {stop_handle_event, [],
+ [stop1, stop2, stop3, stop4, stop5, stop6, stop7, stop8, stop9, stop10]},
{abnormal, [], [abnormal1, abnormal2]},
+ {abnormal_handle_event, [], [abnormal1, abnormal2]},
{sys, [],
[sys1,
call_format_status,
error_format_status, terminate_crash_format,
+ get_state, replace_state]},
+ {sys_handle_event, [],
+ [sys1,
+ call_format_status,
+ error_format_status, terminate_crash_format,
get_state, replace_state]}].
init_per_suite(Config) ->
@@ -53,6 +68,12 @@ init_per_suite(Config) ->
end_per_suite(_Config) ->
ok.
+init_per_group(GroupName, Config)
+ when GroupName =:= start_handle_event;
+ GroupName =:= stop_handle_event;
+ GroupName =:= abnormal_handle_event;
+ GroupName =:= sys_handle_event ->
+ [{init_ops,[{callback_mode,handle_event_function}]}|Config];
init_per_group(_GroupName, Config) ->
Config.
@@ -65,6 +86,7 @@ init_per_testcase(_CaseName, Config) ->
%%% dbg:tracer(),
%%% dbg:p(all, c),
%%% dbg:tpl(gen_statem, cx),
+%%% dbg:tpl(sys, cx),
[{watchdog, Dog} | Config].
end_per_testcase(_CaseName, Config) ->
@@ -88,7 +110,7 @@ end_per_testcase(_CaseName, Config) ->
start1(Config) when is_list(Config) ->
%%OldFl = process_flag(trap_exit, true),
- {ok,Pid0} = gen_statem:start_link(?MODULE, [], []),
+ {ok,Pid0} = gen_statem:start_link(?MODULE, start_arg(Config, []), []),
ok = do_func_test(Pid0),
ok = do_sync_func_test(Pid0),
stop_it(Pid0),
@@ -102,7 +124,7 @@ start1(Config) when is_list(Config) ->
%% anonymous w. shutdown
start2(Config) when is_list(Config) ->
%% Dont link when shutdown
- {ok,Pid0} = gen_statem:start(?MODULE, [], []),
+ {ok,Pid0} = gen_statem:start(?MODULE, start_arg(Config, []), []),
ok = do_func_test(Pid0),
ok = do_sync_func_test(Pid0),
stopped = gen_statem:call(Pid0, {stop,shutdown}),
@@ -113,13 +135,15 @@ start2(Config) when is_list(Config) ->
start3(Config) when is_list(Config) ->
%%OldFl = process_flag(trap_exit, true),
- {ok,Pid0} = gen_statem:start(?MODULE, [], [{timeout,5}]),
+ {ok,Pid0} =
+ gen_statem:start(?MODULE, start_arg(Config, []), [{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}]),
+ {error,timeout} =
+ gen_statem:start(
+ ?MODULE, start_arg(Config, sleep), [{timeout,5}]),
%%process_flag(trap_exit, OldFl),
ok = verify_empty_msgq().
@@ -128,7 +152,7 @@ start3(Config) when is_list(Config) ->
start4(Config) when is_list(Config) ->
OldFl = process_flag(trap_exit, true),
- ignore = gen_statem:start(?MODULE, ignore, []),
+ ignore = gen_statem:start(?MODULE, start_arg(Config, ignore), []),
process_flag(trap_exit, OldFl),
ok = verify_empty_msgq().
@@ -138,14 +162,14 @@ start5(suite) -> [];
start5(Config) when is_list(Config) ->
OldFl = process_flag(trap_exit, true),
- {error,stopped} = gen_statem:start(?MODULE, stop, []),
+ {error,stopped} = gen_statem:start(?MODULE, start_arg(Config, 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,Pid} = gen_statem:start_link(?MODULE, start_arg(Config, []), []),
ok = do_func_test(Pid),
ok = do_sync_func_test(Pid),
stop_it(Pid),
@@ -157,11 +181,11 @@ start7(Config) when is_list(Config) ->
STM = {global,my_stm},
{ok,Pid} =
- gen_statem:start_link(STM, ?MODULE, [], []),
+ gen_statem:start_link(STM, ?MODULE, start_arg(Config, []), []),
{error,{already_started,Pid}} =
- gen_statem:start_link(STM, ?MODULE, [], []),
+ gen_statem:start_link(STM, ?MODULE, start_arg(Config, []), []),
{error,{already_started,Pid}} =
- gen_statem:start(STM, ?MODULE, [], []),
+ gen_statem:start(STM, ?MODULE, start_arg(Config, []), []),
ok = do_func_test(Pid),
ok = do_sync_func_test(Pid),
@@ -179,9 +203,9 @@ start8(Config) when is_list(Config) ->
STM = {local,Name},
{ok,Pid} =
- gen_statem:start(STM, ?MODULE, [], []),
+ gen_statem:start(STM, ?MODULE, start_arg(Config, []), []),
{error,{already_started,Pid}} =
- gen_statem:start(STM, ?MODULE, [], []),
+ gen_statem:start(STM, ?MODULE, start_arg(Config, []), []),
ok = do_func_test(Pid),
ok = do_sync_func_test(Pid),
@@ -199,9 +223,9 @@ start9(Config) when is_list(Config) ->
STM = {local,Name},
{ok,Pid} =
- gen_statem:start_link(STM, ?MODULE, [], []),
+ gen_statem:start_link(STM, ?MODULE, start_arg(Config, []), []),
{error,{already_started,Pid}} =
- gen_statem:start(STM, ?MODULE, [], []),
+ gen_statem:start(STM, ?MODULE, start_arg(Config, []), []),
ok = do_func_test(Pid),
ok = do_sync_func_test(Pid),
@@ -217,11 +241,11 @@ start10(Config) when is_list(Config) ->
STM = {global,my_stm},
{ok,Pid} =
- gen_statem:start(STM, ?MODULE, [], []),
+ gen_statem:start(STM, ?MODULE, start_arg(Config, []), []),
{error,{already_started,Pid}} =
- gen_statem:start(STM, ?MODULE, [], []),
+ gen_statem:start(STM, ?MODULE, start_arg(Config, []), []),
{error,{already_started,Pid}} =
- gen_statem:start_link(STM, ?MODULE, [], []),
+ gen_statem:start_link(STM, ?MODULE, start_arg(Config, []), []),
ok = do_func_test(Pid),
ok = do_sync_func_test(Pid),
@@ -238,19 +262,19 @@ start11(Config) when is_list(Config) ->
GlobalSTM = {global,Name},
{ok,Pid} =
- gen_statem:start_link(LocalSTM, ?MODULE, [], []),
+ gen_statem:start_link(LocalSTM, ?MODULE, start_arg(Config, []), []),
stop_it(Pid),
{ok,_Pid1} =
- gen_statem:start_link(LocalSTM, ?MODULE, [], []),
+ gen_statem:start_link(LocalSTM, ?MODULE, start_arg(Config, []), []),
stop_it(Name),
{ok,Pid2} =
- gen_statem:start(GlobalSTM, ?MODULE, [], []),
+ gen_statem:start(GlobalSTM, ?MODULE, start_arg(Config, []), []),
stop_it(Pid2),
receive after 1 -> true end,
Result =
- gen_statem:start(GlobalSTM, ?MODULE, [], []),
+ gen_statem:start(GlobalSTM, ?MODULE, start_arg(Config, []), []),
io:format("Result = ~p~n",[Result]),
{ok,_Pid3} = Result,
stop_it(GlobalSTM),
@@ -263,11 +287,11 @@ start12(Config) when is_list(Config) ->
VIA = {via,dummy_via,my_stm},
{ok,Pid} =
- gen_statem:start_link(VIA, ?MODULE, [], []),
+ gen_statem:start_link(VIA, ?MODULE, start_arg(Config, []), []),
{error,{already_started,Pid}} =
- gen_statem:start_link(VIA, ?MODULE, [], []),
+ gen_statem:start_link(VIA, ?MODULE, start_arg(Config, []), []),
{error,{already_started,Pid}} =
- gen_statem:start(VIA, ?MODULE, [], []),
+ gen_statem:start(VIA, ?MODULE, start_arg(Config, []), []),
ok = do_func_test(Pid),
ok = do_sync_func_test(Pid),
@@ -279,23 +303,23 @@ start12(Config) when is_list(Config) ->
%% Anonymous, reason 'normal'
-stop1(_Config) ->
- {ok,Pid} = gen_statem:start(?MODULE, [], []),
+stop1(Config) ->
+ {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []),
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, [], []),
+stop2(Config) ->
+ {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []),
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, [], []),
+stop3(Config) ->
+ {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []),
_ =
?EXPECT_FAILURE(
gen_statem:stop(Pid, other_reason, invalid_timeout),
@@ -306,8 +330,10 @@ stop3(_Config) ->
ok.
%% Registered name
-stop4(_Config) ->
- {ok,Pid} = gen_statem:start({local,to_stop},?MODULE, [], []),
+stop4(Config) ->
+ {ok,Pid} =
+ gen_statem:start(
+ {local,to_stop},?MODULE, start_arg(Config, []), []),
ok = gen_statem:stop(to_stop),
false = erlang:is_process_alive(Pid),
noproc =
@@ -315,9 +341,11 @@ stop4(_Config) ->
ok.
%% Registered name and local node
-stop5(_Config) ->
+stop5(Config) ->
Name = to_stop,
- {ok,Pid} = gen_statem:start({local,Name},?MODULE, [], []),
+ {ok,Pid} =
+ gen_statem:start(
+ {local,Name},?MODULE, start_arg(Config, []), []),
ok = gen_statem:stop({Name,node()}),
false = erlang:is_process_alive(Pid),
noproc =
@@ -325,9 +353,9 @@ stop5(_Config) ->
ok.
%% Globally registered name
-stop6(_Config) ->
+stop6(Config) ->
STM = {global,to_stop},
- {ok,Pid} = gen_statem:start(STM, ?MODULE, [], []),
+ {ok,Pid} = gen_statem:start(STM, ?MODULE, start_arg(Config, []), []),
ok = gen_statem:stop(STM),
false = erlang:is_process_alive(Pid),
noproc =
@@ -335,11 +363,10 @@ stop6(_Config) ->
ok.
%% 'via' registered name
-stop7(_Config) ->
+stop7(Config) ->
VIA = {via,dummy_via,to_stop},
dummy_via:reset(),
- {ok,Pid} = gen_statem:start(VIA,
- ?MODULE, [], []),
+ {ok,Pid} = gen_statem:start(VIA, ?MODULE, start_arg(Config, []), []),
ok = gen_statem:stop(VIA),
false = erlang:is_process_alive(Pid),
noproc =
@@ -347,46 +374,55 @@ stop7(_Config) ->
ok.
%% Anonymous on remote node
-stop8(_Config) ->
+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,Pid} =
+ rpc:call(
+ Node, gen_statem,start,
+ [?MODULE,start_arg(Config, []),[]]),
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} =
+ {{nodedown,Node},{sys,terminate,_}} =
?EXPECT_FAILURE(gen_statem:stop(Pid), Reason2),
ok.
%% Registered name on remote node
-stop9(_Config) ->
+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,Pid} =
+ rpc:call(
+ Node, gen_statem, start,
+ [LocalSTM,?MODULE,start_arg(Config, []),[]]),
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} =
+ {{nodedown,Node},{sys,terminate,_}} =
?EXPECT_FAILURE(gen_statem:stop(STM), Reason2),
ok.
%% Globally registered name on remote node
-stop10(_Config) ->
+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,[],[]]),
+ {ok,Pid} =
+ rpc:call(
+ Node, gen_statem, start,
+ [STM,?MODULE,start_arg(Config, []),[]]),
global:sync(),
ok = gen_statem:stop(STM),
false = rpc:call(Node, erlang, is_process_alive, [Pid]),
@@ -402,7 +438,8 @@ abnormal1(Config) when is_list(Config) ->
Name = abnormal1,
LocalSTM = {local,Name},
- {ok, _Pid} = gen_statem:start(LocalSTM, ?MODULE, [], []),
+ {ok, _Pid} =
+ gen_statem:start(LocalSTM, ?MODULE, start_arg(Config, []), []),
%% timeout call.
delayed = gen_statem:call(Name, {delayed_answer,1}, 100),
@@ -410,13 +447,14 @@ abnormal1(Config) when is_list(Config) ->
?EXPECT_FAILURE(
gen_statem:call(Name, {delayed_answer,1000}, 10),
Reason),
+ ok = gen_statem:stop(Name),
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, [], []),
+ {ok,Pid} = gen_statem:start_link(?MODULE, start_arg(Config, []), []),
%% bad return value in the gen_statem loop
{{bad_return_value,badreturn},_} =
@@ -433,7 +471,7 @@ abnormal2(Config) when is_list(Config) ->
shutdown(Config) when is_list(Config) ->
process_flag(trap_exit, true),
- {ok,Pid0} = gen_statem:start_link(?MODULE, [], []),
+ {ok,Pid0} = gen_statem:start_link(?MODULE, start_arg(Config, []), []),
ok = do_func_test(Pid0),
ok = do_sync_func_test(Pid0),
stopped = gen_statem:call(Pid0, {stop,{shutdown,reason}}),
@@ -454,7 +492,7 @@ shutdown(Config) when is_list(Config) ->
sys1(Config) when is_list(Config) ->
- {ok,Pid} = gen_statem:start(?MODULE, [], []),
+ {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []),
{status, Pid, {module,gen_statem}, _} = sys:get_status(Pid),
sys:suspend(Pid),
Parent = self(),
@@ -477,7 +515,7 @@ sys1(Config) when is_list(Config) ->
stop_it(Pid).
call_format_status(Config) when is_list(Config) ->
- {ok,Pid} = gen_statem:start(?MODULE, [], []),
+ {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),
@@ -485,7 +523,9 @@ call_format_status(Config) when is_list(Config) ->
%% 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, [], []),
+ {ok, Pid2} =
+ gen_statem:start(
+ {local, gstm}, ?MODULE, start_arg(Config, []), []),
Status2 = sys:get_status(gstm),
{status,Pid2,_Mod,[_PDict2,running,_,_,Data2]} = Status2,
[format_status_called|_] = lists:reverse(Data2),
@@ -494,13 +534,17 @@ call_format_status(Config) when is_list(Config) ->
%% 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, [], []),
+ {ok,Pid3} =
+ gen_statem:start(
+ GlobalName1, ?MODULE, start_arg(Config, []), []),
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, [], []),
+ {ok,Pid4} =
+ gen_statem:start(
+ GlobalName2, ?MODULE, start_arg(Config, []), []),
Status4 = sys:get_status(GlobalName2),
{status,Pid4,_Mod,[_PDict4,running,_,_, Data4]} = Status4,
[format_status_called|_] = lists:reverse(Data4),
@@ -510,13 +554,15 @@ call_format_status(Config) when is_list(Config) ->
%% pid or atom
dummy_via:reset(),
ViaName1 = {via,dummy_via,"CallFormatStatus"},
- {ok,Pid5} = gen_statem:start(ViaName1, ?MODULE, [], []),
+ {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),
stop_it(Pid5),
ViaName2 = {via,dummy_via,{name,"term"}},
- {ok, Pid6} = gen_statem:start(ViaName2, ?MODULE, [], []),
+ {ok, Pid6} =
+ gen_statem:start(
+ ViaName2, ?MODULE, start_arg(Config, []), []),
Status6 = sys:get_status(ViaName2),
{status,Pid6,_Mod,[_PDict6,running,_,_,Data6]} = Status6,
[format_status_called|_] = lists:reverse(Data6),
@@ -528,7 +574,9 @@ 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}, []),
+ {ok,Pid} =
+ gen_statem:start(
+ ?MODULE, start_arg(Config, {state_data,StateData}), []),
%% bad return value in the gen_statem loop
{{bad_return_value,badreturn},_} =
?EXPECT_FAILURE(gen_statem:call(Pid, badreturn), Reason),
@@ -562,7 +610,9 @@ 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}, []),
+ {ok,Pid} =
+ gen_statem:start(
+ ?MODULE, start_arg(Config, {state_data,StateData}), []),
stop_it(Pid),
Self = self(),
receive
@@ -595,7 +645,9 @@ terminate_crash_format(Config) when is_list(Config) ->
get_state(Config) when is_list(Config) ->
State = self(),
- {ok,Pid} = gen_statem:start(?MODULE, {state_data,State}, []),
+ {ok,Pid} =
+ gen_statem:start(
+ ?MODULE, start_arg(Config, {state_data,State}), []),
{idle,State} = sys:get_state(Pid),
{idle,State} = sys:get_state(Pid, 5000),
stop_it(Pid),
@@ -603,13 +655,16 @@ get_state(Config) when is_list(Config) ->
%% 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}, []),
+ gen_statem:start(
+ {local,gstm}, ?MODULE, start_arg(Config, {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}, []),
+ {ok,Pid3} =
+ gen_statem:start(
+ ?MODULE, start_arg(Config, {state_data,State}), []),
{idle,State} = sys:get_state(Pid3),
ok = sys:suspend(Pid3),
{idle,State} = sys:get_state(Pid3, 5000),
@@ -619,7 +674,9 @@ get_state(Config) when is_list(Config) ->
replace_state(Config) when is_list(Config) ->
State = self(),
- {ok, Pid} = gen_statem:start(?MODULE, {state_data,State}, []),
+ {ok, Pid} =
+ gen_statem:start(
+ ?MODULE, start_arg(Config, {state_data,State}), []),
{idle,State} = sys:get_state(Pid),
NState1 = "replaced",
Replace1 = fun({StateName, _}) -> {StateName,NState1} end,
@@ -650,7 +707,9 @@ replace_state(Config) when is_list(Config) ->
hibernate(Config) when is_list(Config) ->
OldFl = process_flag(trap_exit, true),
- {ok,Pid0} = gen_statem:start_link(?MODULE, hiber_now, []),
+ {ok,Pid0} =
+ gen_statem:start_link(
+ ?MODULE, start_arg(Config, hiber_now), []),
is_in_erlang_hibernate(Pid0),
stop_it(Pid0),
receive
@@ -659,7 +718,8 @@ hibernate(Config) when is_list(Config) ->
?t:fail(gen_statem_did_not_die)
end,
- {ok,Pid} = gen_statem:start_link(?MODULE, hiber, []),
+ {ok,Pid} =
+ gen_statem:start_link(?MODULE, start_arg(Config, hiber), []),
true = ({current_function,{erlang,hibernate,3}} =/=
erlang:process_info(Pid,current_function)),
hibernating = gen_statem:call(Pid, hibernate_sync),
@@ -1031,6 +1091,14 @@ verify_empty_msgq() ->
[] = ?t:messages_get(),
ok.
+start_arg(Config, Arg) ->
+ case lists:keyfind(init_ops, 1, Config) of
+ {_,Ops} ->
+ {init_ops,Arg,Ops};
+ false ->
+ Arg
+ end.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% The State Machine
@@ -1052,7 +1120,16 @@ init(hiber_now) ->
{ok,hiber_idle,[],[hibernate]};
init({state_data, StateData}) ->
{ok,idle,StateData};
-init(_) ->
+init({init_ops,Arg,InitOps}) ->
+ case init(Arg) of
+ {ok,State,Data,Ops} ->
+ {ok,State,Data,InitOps++Ops};
+ {ok,State,Data} ->
+ {ok,State,Data,InitOps};
+ Other ->
+ Other
+ end;
+init([]) ->
{ok,idle,state_data}.
terminate(_, _State, crash_terminate) ->
@@ -1247,6 +1324,35 @@ handle_common_events(cast, {'alive?',Pid}, _, State, Data) ->
handle_common_events(_, _, _, _, _) ->
undefined.
+%% Dispatcher to test callback_mode handle_event_function
+%%
+%% Wrap the state in a 1 element list just to test non-atom
+%% states. Note that the state from init/1 is not wrapped
+%% so both atom and non-atom states are tested.
+handle_event(Type, Event, PrevState, State, Data) ->
+ PrevStateName = unwrap(PrevState),
+ StateName = unwrap(State),
+ case
+ ?MODULE:StateName(
+ Type, Event, PrevStateName, StateName, Data) of
+ {next_state,NewState,NewStateData} ->
+ {next_state,wrap(NewState),NewStateData};
+ {next_state,NewState,NewStateData,StateOps} ->
+ {next_state,wrap(NewState),NewStateData,StateOps};
+ Other ->
+ Other
+ end.
+
+unwrap([State]) ->
+ State;
+unwrap(State) ->
+ State.
+
+wrap(State) ->
+ [State].
+
+
+
code_change(_OldVsn, State, StateData, _Extra) ->
{ok,State,StateData}.