%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-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_fsm_SUITE).

-include_lib("common_test/include/ct.hrl").

%% Test cases
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
	 init_per_group/2,end_per_group/2]).

-export([start1/1, start2/1, start3/1, start4/1, start5/1, start6/1,
	 start7/1, start8/1, start9/1, start10/1, start11/1, start12/1]).

-export([stop1/1, stop2/1, stop3/1, stop4/1, stop5/1, stop6/1, stop7/1,
	 stop8/1, stop9/1, stop10/1]).

-export([ abnormal1/1, abnormal2/1]).

-export([shutdown/1]).

-export([ sys1/1,
	  call_format_status/1, error_format_status/1, terminate_crash_format/1,
	  get_state/1, replace_state/1]).

-export([hibernate/1,hiber_idle/3,hiber_wakeup/3,hiber_idle/2,hiber_wakeup/2]).

-export([enter_loop/1]).

%% Exports for apply
-export([enter_loop/2]).

%% The gen_fsm behaviour
-export([init/1, handle_event/3, handle_sync_event/4, terminate/3,
	 handle_info/3, format_status/2]).
-export([idle/2,	idle/3,
	 timeout/2,
	 wfor_conf/2,	wfor_conf/3,
	 connected/2,	connected/3]).
-export([state0/3]).


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


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.

%% anonymous
start1(Config) when is_list(Config) ->
    %%OldFl = process_flag(trap_exit, true),

    {ok, Pid0} = gen_fsm:start_link(gen_fsm_SUITE, [], []),
    ok = do_func_test(Pid0),
    ok = do_sync_func_test(Pid0),
    stop_it(Pid0),
    %%    stopped = gen_fsm:sync_send_all_state_event(Pid0, stop),
    %%    {'EXIT', {timeout,_}} =
    %%	(catch gen_fsm:sync_send_event(Pid0, hej)),

    [] = get_messages(),
    %%process_flag(trap_exit, OldFl),
    ok.

%% anonymous w. shutdown
start2(Config) when is_list(Config) ->
    %% Dont link when shutdown
    {ok, Pid0} = gen_fsm:start(gen_fsm_SUITE, [], []),
    ok = do_func_test(Pid0),
    ok = do_sync_func_test(Pid0),
    shutdown_stopped =
	gen_fsm:sync_send_all_state_event(Pid0, stop_shutdown),
    {'EXIT', {noproc,_}} =
	(catch gen_fsm:sync_send_event(Pid0, hej)),

    [] = get_messages(),
    ok.

%% anonymous with timeout
start3(Config) when is_list(Config) ->
    %%OldFl = process_flag(trap_exit, true),

    {ok, Pid0} = gen_fsm:start(gen_fsm_SUITE, [], [{timeout,5}]),
    ok = do_func_test(Pid0),
    ok = do_sync_func_test(Pid0),
    stop_it(Pid0),

    {error, timeout} = gen_fsm:start(gen_fsm_SUITE, sleep,
				     [{timeout,5}]),

    [] = get_messages(),
    %%process_flag(trap_exit, OldFl),
    ok.

%% anonymous with ignore
start4(Config) when is_list(Config) ->
    OldFl = process_flag(trap_exit, true),

    ignore = gen_fsm:start(gen_fsm_SUITE, ignore, []),

    [] = get_messages(),
    process_flag(trap_exit, OldFl),
    ok.

%% anonymous with stop
start5(Config) when is_list(Config) ->
    OldFl = process_flag(trap_exit, true),

    {error, stopped} = gen_fsm:start(gen_fsm_SUITE, stop, []),

    [] = get_messages(),
    process_flag(trap_exit, OldFl),
    ok.

%% anonymous linked
start6(Config) when is_list(Config) ->
    {ok, Pid} = gen_fsm:start_link(gen_fsm_SUITE, [], []),
    ok = do_func_test(Pid),
    ok = do_sync_func_test(Pid),
    stop_it(Pid),

    [] = get_messages(),

    ok.

%% global register linked
start7(Config) when is_list(Config) ->
    {ok, Pid} =
	gen_fsm:start_link({global, my_fsm}, gen_fsm_SUITE, [], []),
    {error, {already_started, Pid}} =
	gen_fsm:start_link({global, my_fsm}, gen_fsm_SUITE, [], []),
    {error, {already_started, Pid}} =
	gen_fsm:start({global, my_fsm}, gen_fsm_SUITE, [], []),

    ok = do_func_test(Pid),
    ok = do_sync_func_test(Pid),
    ok = do_func_test({global, my_fsm}),
    ok = do_sync_func_test({global, my_fsm}),
    stop_it({global, my_fsm}),

    [] = get_messages(),
    ok.


%% local register
start8(Config) when is_list(Config) ->
    %%OldFl = process_flag(trap_exit, true),

    {ok, Pid} =
	gen_fsm:start({local, my_fsm}, gen_fsm_SUITE, [], []),
    {error, {already_started, Pid}} =
	gen_fsm:start({local, my_fsm}, gen_fsm_SUITE, [], []),

    ok = do_func_test(Pid),
    ok = do_sync_func_test(Pid),
    ok = do_func_test(my_fsm),
    ok = do_sync_func_test(my_fsm),
    stop_it(Pid),

    [] = get_messages(),
    %%process_flag(trap_exit, OldFl),
    ok.

%% local register linked
start9(Config) when is_list(Config) ->
    %%OldFl = process_flag(trap_exit, true),

    {ok, Pid} =
	gen_fsm:start_link({local, my_fsm}, gen_fsm_SUITE, [], []),
    {error, {already_started, Pid}} =
	gen_fsm:start({local, my_fsm}, gen_fsm_SUITE, [], []),

    ok = do_func_test(Pid),
    ok = do_sync_func_test(Pid),
    ok = do_func_test(my_fsm),
    ok = do_sync_func_test(my_fsm),
    stop_it(Pid),

    [] = get_messages(),
    %%process_flag(trap_exit, OldFl),
    ok.

%% global register
start10(Config) when is_list(Config) ->
    {ok, Pid} =
	gen_fsm:start({global, my_fsm}, gen_fsm_SUITE, [], []),
    {error, {already_started, Pid}} =
	gen_fsm:start({global, my_fsm}, gen_fsm_SUITE, [], []),
    {error, {already_started, Pid}} =
	gen_fsm:start_link({global, my_fsm}, gen_fsm_SUITE, [], []),

    ok = do_func_test(Pid),
    ok = do_sync_func_test(Pid),
    ok = do_func_test({global, my_fsm}),
    ok = do_sync_func_test({global, my_fsm}),
    stop_it({global, my_fsm}),

    [] = get_messages(),
    ok.


%% Stop registered processes
start11(Config) when is_list(Config) ->
    {ok, Pid} =
	gen_fsm:start_link({local, my_fsm}, gen_fsm_SUITE, [], []),
    stop_it(Pid),

    {ok, _Pid1} =
	gen_fsm:start_link({local, my_fsm}, gen_fsm_SUITE, [], []),
    stop_it(my_fsm),

    {ok, Pid2} =
	gen_fsm:start({global, my_fsm}, gen_fsm_SUITE, [], []),
    stop_it(Pid2),
    receive after 1 -> true end,
    Result =
	gen_fsm:start({global, my_fsm}, gen_fsm_SUITE, [], []),
    io:format("Result = ~p~n",[Result]),
    {ok, _Pid3} = Result,
    stop_it({global, my_fsm}),

    [] = get_messages(),
    ok.

%% Via register linked
start12(Config) when is_list(Config) ->
    dummy_via:reset(),
    {ok, Pid} =
	gen_fsm:start_link({via, dummy_via, my_fsm}, gen_fsm_SUITE, [], []),
    {error, {already_started, Pid}} =
	gen_fsm:start_link({via, dummy_via, my_fsm}, gen_fsm_SUITE, [], []),
    {error, {already_started, Pid}} =
	gen_fsm:start({via, dummy_via, my_fsm}, gen_fsm_SUITE, [], []),

    ok = do_func_test(Pid),
    ok = do_sync_func_test(Pid),
    ok = do_func_test({via, dummy_via, my_fsm}),
    ok = do_sync_func_test({via, dummy_via, my_fsm}),
    stop_it({via, dummy_via, my_fsm}),

    [] = get_messages(),
    ok.


%% Anonymous, reason 'normal'
stop1(_Config) ->
    {ok, Pid} = gen_fsm:start(?MODULE, [], []),
    ok = gen_fsm:stop(Pid),
    false = erlang:is_process_alive(Pid),
    {'EXIT',noproc} = (catch gen_fsm:stop(Pid)),
    ok.

%% Anonymous, other reason
stop2(_Config) ->
    {ok,Pid} = gen_fsm:start(?MODULE, [], []),
    ok = gen_fsm:stop(Pid, other_reason, infinity),
    false = erlang:is_process_alive(Pid),
    ok.

%% Anonymous, invalid timeout
stop3(_Config) ->
    {ok,Pid} = gen_fsm:start(?MODULE, [], []),
    {'EXIT',_} = (catch gen_fsm:stop(Pid, other_reason, invalid_timeout)),
    true = erlang:is_process_alive(Pid),
    ok = gen_fsm:stop(Pid),
    false = erlang:is_process_alive(Pid),
    ok.

%% Registered name
stop4(_Config) ->
    {ok,Pid} = gen_fsm:start({local,to_stop},?MODULE, [], []),
    ok = gen_fsm:stop(to_stop),
    false = erlang:is_process_alive(Pid),
    {'EXIT',noproc} = (catch gen_fsm:stop(to_stop)),
    ok.

%% Registered name and local node
stop5(_Config) ->
    {ok,Pid} = gen_fsm:start({local,to_stop},?MODULE, [], []),
    ok = gen_fsm:stop({to_stop,node()}),
    false = erlang:is_process_alive(Pid),
    {'EXIT',noproc} = (catch gen_fsm:stop({to_stop,node()})),
    ok.

%% Globally registered name
stop6(_Config) ->
    {ok, Pid} = gen_fsm:start({global, to_stop}, ?MODULE, [], []),
    ok = gen_fsm:stop({global,to_stop}),
    false = erlang:is_process_alive(Pid),
    {'EXIT',noproc} = (catch gen_fsm:stop({global,to_stop})),
    ok.

%% 'via' registered name
stop7(_Config) ->
    dummy_via:reset(),
    {ok, Pid} = gen_fsm:start({via, dummy_via, to_stop},
			      ?MODULE, [], []),
    ok = gen_fsm:stop({via, dummy_via, to_stop}),
    false = erlang:is_process_alive(Pid),
    {'EXIT',noproc} = (catch gen_fsm:stop({via, dummy_via, to_stop})),
    ok.

%% Anonymous on remote node
stop8(_Config) ->
    {ok,Node} = test_server:start_node(gen_fsm_SUITE_stop8,slave,[]),
    Dir = filename:dirname(code:which(?MODULE)),
    rpc:call(Node,code,add_path,[Dir]),
    {ok, Pid} = rpc:call(Node,gen_fsm,start,[?MODULE,[],[]]),
    ok = gen_fsm:stop(Pid),
    false = rpc:call(Node,erlang,is_process_alive,[Pid]),
    {'EXIT',noproc} = (catch gen_fsm:stop(Pid)),
    true = test_server:stop_node(Node),
    {'EXIT',{{nodedown,Node},_}} = (catch gen_fsm:stop(Pid)),
    ok.

%% Registered name on remote node
stop9(_Config) ->
    {ok,Node} = test_server:start_node(gen_fsm_SUITE_stop9,slave,[]),
    Dir = filename:dirname(code:which(?MODULE)),
    rpc:call(Node,code,add_path,[Dir]),
    {ok, Pid} = rpc:call(Node,gen_fsm,start,[{local,to_stop},?MODULE,[],[]]),
    ok = gen_fsm:stop({to_stop,Node}),
    undefined = rpc:call(Node,erlang,whereis,[to_stop]),
    false = rpc:call(Node,erlang,is_process_alive,[Pid]),
    {'EXIT',noproc} = (catch gen_fsm:stop({to_stop,Node})),
    true = test_server:stop_node(Node),
    {'EXIT',{{nodedown,Node},_}} = (catch gen_fsm:stop({to_stop,Node})),
    ok.

%% Globally registered name on remote node
stop10(_Config) ->
    {ok,Node} = test_server:start_node(gen_fsm_SUITE_stop10,slave,[]),
    Dir = filename:dirname(code:which(?MODULE)),
    rpc:call(Node,code,add_path,[Dir]),
    {ok, Pid} = rpc:call(Node,gen_fsm,start,[{global,to_stop},?MODULE,[],[]]),
    global:sync(),
    ok = gen_fsm:stop({global,to_stop}),
    false = rpc:call(Node,erlang,is_process_alive,[Pid]),
    {'EXIT',noproc} = (catch gen_fsm:stop({global,to_stop})),
    true = test_server:stop_node(Node),
    {'EXIT',noproc} = (catch gen_fsm:stop({global,to_stop})),
    ok.

%% Check that time outs in calls work
abnormal1(Config) when is_list(Config) ->
    {ok, _Pid} = gen_fsm:start({local, my_fsm}, gen_fsm_SUITE, [], []),

    %% timeout call.
    delayed = gen_fsm:sync_send_event(my_fsm, {delayed_answer,1}, 100),
    {'EXIT',{timeout,_}} =
	(catch gen_fsm:sync_send_event(my_fsm, {delayed_answer,10}, 1)),
    receive
	Msg ->
	    %% Ignore the delayed answer from the server.
	    io:format("Delayed message: ~p", [Msg])
    end,

    [] = get_messages(),
    ok.

%% Check that bad return values makes the fsm 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_fsm:start_link(gen_fsm_SUITE, [], []),

    %% bad return value in the gen_fsm loop
    {'EXIT',{{bad_return_value, badreturn},_}} =
	(catch gen_fsm:sync_send_event(Pid, badreturn)),

    [{'EXIT',Pid,{bad_return_value,badreturn}}] = get_messages(),
    process_flag(trap_exit, OldFl),
    ok.

shutdown(Config) when is_list(Config) ->
    error_logger_forwarder:register(),

    process_flag(trap_exit, true),

    {ok,Pid0} = gen_fsm:start_link(gen_fsm_SUITE, [], []),
    ok = do_func_test(Pid0),
    ok = do_sync_func_test(Pid0),
    {shutdown,reason} =
	gen_fsm:sync_send_all_state_event(Pid0, stop_shutdown_reason),
    receive {'EXIT',Pid0,{shutdown,reason}} -> ok end,
    process_flag(trap_exit, false),

    {'EXIT', {noproc,_}} =
	(catch gen_fsm:sync_send_event(Pid0, hej)),

    receive
	Any ->
	    io:format("Unexpected: ~p", [Any]),
	    ct:fail(failed)
    after 500 ->
	    ok
    end,

    ok.



sys1(Config) when is_list(Config) ->
    {ok, Pid} =
	gen_fsm:start(gen_fsm_SUITE, [], []),
    {status, Pid, {module,gen_fsm}, _} = sys:get_status(Pid),
    sys:suspend(Pid),
    {'EXIT', {timeout,_}} =
	(catch gen_fsm:sync_send_event(Pid, hej)),
    sys:resume(Pid),
    stop_it(Pid).

call_format_status(Config) when is_list(Config) ->
    {ok, Pid} = gen_fsm:start(gen_fsm_SUITE, [], []),
    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_fsm:start({local, gfsm}, gen_fsm_SUITE, [], []),
    Status2 = sys:get_status(gfsm),
    {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_fsm:start(GlobalName1, gen_fsm_SUITE, [], []),
    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_fsm:start(GlobalName2, gen_fsm_SUITE, [], []),
    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_fsm:start(ViaName1, gen_fsm_SUITE, [], []),
    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_fsm:start(ViaName2, gen_fsm_SUITE, [], []),
    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_fsm:start(gen_fsm_SUITE, {state_data, StateData}, []),
    %% bad return value in the gen_fsm loop
    {'EXIT',{{bad_return_value, badreturn},_}} =
	(catch gen_fsm:sync_send_event(Pid, badreturn)),
    receive
	{error,_GroupLeader,{Pid,
			     "** State machine"++_,
			     [Pid,{_,_,badreturn},idle,{formatted,StateData},_]}} ->
	    ok;
	Other ->
	    io:format("Unexpected: ~p", [Other]),
	    ct:fail(failed)
    end,
    process_flag(trap_exit, OldFl),
    ok.

terminate_crash_format(Config) when is_list(Config) ->
    error_logger_forwarder:register(),
    OldFl = process_flag(trap_exit, true),
    StateData = crash_terminate,
    {ok, Pid} = gen_fsm:start(gen_fsm_SUITE, {state_data, StateData}, []),
    stop_it(Pid),
    receive
	{error,_GroupLeader,{Pid,
			     "** State machine"++_,
			     [Pid,{_,_,_},idle,{formatted, StateData},_]}} ->
	    ok;
	Other ->
	    io:format("Unexpected: ~p", [Other]),
	    ct:fail(failed)
    after 5000 ->
	    io:format("Timeout: expected error logger msg", []),
	    ct:fail(failed)
    end,
    process_flag(trap_exit, OldFl),
    ok.


get_state(Config) when is_list(Config) ->
    State = self(),
    {ok, Pid} = gen_fsm: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_fsm:start({local, gfsm}, gen_fsm_SUITE, {state_data, State}, []),
    {idle, State} = sys:get_state(gfsm),
    {idle, State} = sys:get_state(gfsm, 5000),
    stop_it(Pid2),

    %% check that get_state works when pid is sys suspended
    {ok, Pid3} = gen_fsm:start(gen_fsm_SUITE, {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.

replace_state(Config) when is_list(Config) ->
    State = self(),
    {ok, Pid} = gen_fsm: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,
    {'EXIT',{{callback_failed,
	      {gen_fsm,system_replace_state},{error,fail}},_}} =
	(catch sys:replace_state(Pid, Replace3)),
    {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.

%% Hibernation
hibernate(Config) when is_list(Config) ->
    OldFl = process_flag(trap_exit, true),

    {ok, Pid0} = gen_fsm:start_link(?MODULE, hiber_now, []),
    is_in_erlang_hibernate(Pid0),
    stop_it(Pid0),
    receive
	{'EXIT',Pid0,normal} -> ok
    end,

    {ok, Pid} = gen_fsm:start_link(?MODULE, hiber, []),
    true = ({current_function,{erlang,hibernate,3}} =/=
		erlang:process_info(Pid,current_function)),
    hibernating = gen_fsm:sync_send_event(Pid, hibernate_sync),
    is_in_erlang_hibernate(Pid),
    good_morning = gen_fsm:sync_send_event(Pid, wakeup_sync),
    is_not_in_erlang_hibernate(Pid),
    hibernating = gen_fsm:sync_send_event(Pid, hibernate_sync),
    is_in_erlang_hibernate(Pid),
    five_more = gen_fsm:sync_send_event(Pid, snooze_sync),
    is_in_erlang_hibernate(Pid),
    good_morning = gen_fsm:sync_send_event(Pid, wakeup_sync),
    is_not_in_erlang_hibernate(Pid),
    ok = gen_fsm:send_event(Pid, hibernate_async),
    is_in_erlang_hibernate(Pid),
    ok = gen_fsm:send_event(Pid, wakeup_async),
    is_not_in_erlang_hibernate(Pid),
    ok = gen_fsm:send_event(Pid, hibernate_async),
    is_in_erlang_hibernate(Pid),
    ok = gen_fsm:send_event(Pid, snooze_async),
    is_in_erlang_hibernate(Pid),
    ok = gen_fsm:send_event(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_fsm:sync_send_event(Pid,'alive?'),
    true = ({current_function,{erlang,hibernate,3}} =/=
		erlang:process_info(Pid, current_function)),
    Pid ! hibernate_now,
    is_in_erlang_hibernate(Pid),

    'alive!' = gen_fsm:sync_send_event(Pid,'alive?'),
    true = ({current_function,{erlang,hibernate,3}} =/=
		erlang:process_info(Pid, current_function)),

    hibernating = gen_fsm:sync_send_all_state_event(Pid, hibernate_sync),
    is_in_erlang_hibernate(Pid),
    good_morning = gen_fsm:sync_send_all_state_event(Pid, wakeup_sync),
    is_not_in_erlang_hibernate(Pid),
    hibernating = gen_fsm:sync_send_all_state_event(Pid, hibernate_sync),
    is_in_erlang_hibernate(Pid),
    five_more = gen_fsm:sync_send_all_state_event(Pid, snooze_sync),
    is_in_erlang_hibernate(Pid),
    good_morning = gen_fsm:sync_send_all_state_event(Pid, wakeup_sync),
    is_not_in_erlang_hibernate(Pid),
    ok = gen_fsm:send_all_state_event(Pid, hibernate_async),
    is_in_erlang_hibernate(Pid),
    ok  = gen_fsm:send_all_state_event(Pid, wakeup_async),
    is_not_in_erlang_hibernate(Pid),
    ok = gen_fsm:send_all_state_event(Pid, hibernate_async),
    is_in_erlang_hibernate(Pid),
    ok = gen_fsm:send_all_state_event(Pid, snooze_async),
    is_in_erlang_hibernate(Pid),
    ok = gen_fsm:send_all_state_event(Pid, wakeup_async),
    is_not_in_erlang_hibernate(Pid),

    hibernating = gen_fsm:sync_send_all_state_event(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_fsm:sync_send_all_state_event(Pid, wakeup_sync),
    is_not_in_erlang_hibernate(Pid),
    stop_it(Pid),
    receive
	{'EXIT',Pid,normal} -> ok
    end,

    [] = get_messages(),
    process_flag(trap_exit, OldFl),
    ok.

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)]),
    ct: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)]),
    ct: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.

%% Test gen_fsm:enter_loop/4,5,6.
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_fsm:sync_send_event(Pid1a, 'alive?'),
    stopped = gen_fsm:sync_send_event(Pid1a, stop),
    receive
	{'EXIT', Pid1a, normal} ->
	    ok
    after 5000 ->
	    ct:fail(gen_fsm_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 ->
	    ct:fail(gen_fsm_did_not_die)
    end,

    %% Globally registered process + {global, Name}
    {ok, Pid2a} =
	proc_lib:start_link(?MODULE, enter_loop, [global, global]),
    yes = gen_fsm:sync_send_event(Pid2a, 'alive?'),
    stopped = gen_fsm:sync_send_event(Pid2a, stop),
    receive
	{'EXIT', Pid2a, normal} ->
	    ok
    after 5000 ->
	    ct:fail(gen_fsm_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 ->
	    ct:fail(gen_fsm_did_not_die)
    end,

    %% Unregistered process + no name
    {ok, Pid3} =
	proc_lib:start_link(?MODULE, enter_loop, [anon, anon]),
    yes = gen_fsm:sync_send_event(Pid3, 'alive?'),
    stopped = gen_fsm:sync_send_event(Pid3, stop),
    receive
	{'EXIT', Pid3, normal} ->
	    ok
    after 5000 ->
	    ct:fail(gen_fsm_did_not_die)
    end,

    %% Process not started using proc_lib
    Pid4 =
	spawn_link(gen_fsm, enter_loop, [?MODULE, [], state0, []]),
    receive
	{'EXIT', Pid4, process_was_not_started_by_proc_lib} ->
	    ok
    after 5000 ->
	    ct:fail(gen_fsm_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_fsm:sync_send_event(Pid5, 'alive?'),
    exit(Pid5, shutdown),
    receive
	{'EXIT', Pid5, shutdown} ->
	    ok
    after 5000 ->
	    ct:fail(gen_fsm_did_not_die)
    end,

    %% Make sure gen_fsm: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 ->
	    ct:fail(gen_fsm_started)
    end,
    unregister(armitage),

    %% Make sure gen_fsm: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 ->
	    ct:fail(gen_fsm_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 ->
	    ct:fail({gen_fsm_started, process_info(self(), messages)})
    end,
    dummy_via:unregister_name(armitage),

    process_flag(trap_exit, OldFlag),
    ok.

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_fsm:enter_loop(?MODULE, [], state0, [], {local,armitage});
	global ->
	    gen_fsm:enter_loop(?MODULE, [], state0, [], {global,armitage});
	via ->
	    gen_fsm:enter_loop(?MODULE, [], state0, [],
			       {via, dummy_via, armitage});
	anon ->
	    gen_fsm:enter_loop(?MODULE, [], state0, [])
    end.

%%
%% Functionality check
%%

wfor(Msg) ->
    receive 
	Msg -> ok
    after 5000 -> 
	    throw(timeout)
    end.


stop_it(FSM) ->
    stopped = gen_fsm:sync_send_all_state_event(FSM, stop),
    {'EXIT',_} = 	(catch gen_fsm:sync_send_event(FSM, hej)),
    ok.



do_func_test(FSM) ->
    ok = gen_fsm:send_all_state_event(FSM, {'alive?', self()}),
    wfor(yes),
    ok = do_connect(FSM),
    ok = gen_fsm:send_all_state_event(FSM, {'alive?', self()}),
    wfor(yes),
    _ = [do_msg(FSM) || _ <- lists:seq(1, 3)],
    ok = gen_fsm:send_all_state_event(FSM, {'alive?', self()}),
    wfor(yes),
    ok = do_disconnect(FSM),
    ok = gen_fsm:send_all_state_event(FSM, {'alive?', self()}),
    wfor(yes),
    ok.


do_connect(FSM) ->
    check_state(FSM, idle),
    gen_fsm:send_event(FSM, {connect, self()}),
    wfor(accept),
    check_state(FSM, wfor_conf),
    gen_fsm:send_event(FSM, confirmation),
    check_state(FSM, connected),
    ok.

do_msg(FSM) ->
    check_state(FSM, connected),
    R = make_ref(),
    ok = gen_fsm:send_event(FSM, {msg, R, self(), hej_pa_dig_quasimodo}),
    wfor({ak, R}).


do_disconnect(FSM) ->
    ok = gen_fsm:send_event(FSM, disconnect),
    check_state(FSM, idle).

check_state(FSM, State) ->
    case gen_fsm:sync_send_all_state_event(FSM, {get, self()}) of
	{state, State, _} -> ok
    end.

do_sync_func_test(FSM) ->
    yes = gen_fsm:sync_send_all_state_event(FSM, 'alive?'),
    ok = do_sync_connect(FSM),
    yes = gen_fsm:sync_send_all_state_event(FSM, 'alive?'),
    _ = [do_sync_msg(FSM) || _ <- lists:seq(1, 3)],
    yes = gen_fsm:sync_send_all_state_event(FSM, 'alive?'),
    ok = do_sync_disconnect(FSM),
    yes = gen_fsm:sync_send_all_state_event(FSM, 'alive?'),
    check_state(FSM, idle),
    ok = gen_fsm:sync_send_event(FSM, {timeout,200}),
    yes = gen_fsm:sync_send_all_state_event(FSM, 'alive?'),
    check_state(FSM, idle),
    ok.


do_sync_connect(FSM) ->
    check_state(FSM, idle),
    accept = gen_fsm:sync_send_event(FSM, {connect, self()}),
    check_state(FSM, wfor_conf),
    yes = gen_fsm:sync_send_event(FSM, confirmation),
    check_state(FSM, connected),
    ok.

do_sync_msg(FSM) ->
    check_state(FSM, connected),
    R = make_ref(),
    Res = gen_fsm:sync_send_event(FSM, {msg, R, self(), hej_pa_dig_quasimodo}),
    if  Res == {ak, R} ->
	    ok
    end.

do_sync_disconnect(FSM) ->
    yes = gen_fsm:sync_send_event(FSM, disconnect),
    check_state(FSM, idle).



%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% The Finite State Machine
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

init(ignore) ->
    ignore;
init(stop) ->
    {stop, stopped};
init(stop_shutdown) ->
    {stop, shutdown};
init(sleep) ->
    timer:sleep(1000),
    {ok, idle, data};
init({timeout, T}) ->
    {ok, idle, state, T};
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.


idle({connect, Pid}, Data) ->
    Pid ! accept,
    {next_state, wfor_conf, Data};
idle(badreturn, _Data) ->
    badreturn;
idle(_, Data) ->
    {next_state, idle, Data}.

idle({connect, _Pid}, _From, Data) ->
    {reply, accept, wfor_conf, Data};
idle({delayed_answer, T}, _From, Data) ->
    timer:sleep(T),
    {reply, delayed, idle, Data};
idle(badreturn, _From, _Data) ->
    badreturn;
idle({timeout,Time}, From, _Data) ->
    gen_fsm:send_event_after(Time, {timeout,Time}),
    {next_state, timeout, From};
idle(_, _From, Data) ->
    {reply, 'eh?', idle, Data}.

timeout({timeout,Time}, From) ->
    Ref = gen_fsm:start_timer(Time, {timeout,Time}),
    {next_state, timeout, {From,Ref}};
timeout({timeout,Ref,{timeout,Time}}, {From,Ref}) ->
    Ref2 = gen_fsm:start_timer(Time, ok),
    Cref = gen_fsm:start_timer(Time, cancel),
    Time4 = Time*4,
    receive after Time4 -> ok end,
    gen_fsm:cancel_timer(Cref),
    {next_state, timeout, {From,Ref2}};
timeout({timeout,Ref2,ok},{From,Ref2}) ->
    gen_fsm:reply(From, ok),
    {next_state, idle, state}.

wfor_conf(confirmation, Data) ->
    {next_state, connected, Data};
wfor_conf(_, Data) ->
    {next_state, idle, Data}.

wfor_conf(confirmation, _From, Data) ->
    {reply, yes, connected, Data};
wfor_conf(_, _From, Data) ->
    {reply, 'eh?', idle, Data}.

connected({msg, Ref, From, _Msg}, Data) ->
    From ! {ak, Ref},
    {next_state, connected, Data};
connected(disconnect, Data) ->
    {next_state, idle, Data};
connected(_, Data) ->
    {next_state, connected, Data}.

connected({msg, Ref, _From, _Msg}, _, Data) ->
    {reply, {ak, Ref}, connected, Data};
connected(disconnect, _From, Data) ->
    {reply, yes, idle, Data};
connected(_, _, Data) ->
    {reply, 'eh?', connected, Data}.

state0('alive?', _From, Data) ->
    {reply, yes, state0, Data};
state0(stop, _From, Data) ->
    {stop, normal, stopped, Data}.

hiber_idle('alive?', _From, Data) ->
    {reply, 'alive!', hiber_idle, Data};
hiber_idle(hibernate_sync, _From, Data) ->
    {reply, hibernating, hiber_wakeup, Data,hibernate}.
hiber_idle(timeout, hibernate_me) ->
    %% Arrive here from handle_info(hibernate_later,...)
    {next_state, hiber_idle, [], hibernate};
hiber_idle(hibernate_async, Data) ->
    {next_state,hiber_wakeup, Data, hibernate}.

hiber_wakeup(wakeup_sync,_From,Data) ->
    {reply,good_morning,hiber_idle,Data};
hiber_wakeup(snooze_sync,_From,Data) ->
    {reply,five_more,hiber_wakeup,Data,hibernate}.
hiber_wakeup(wakeup_async,Data) ->
    {next_state,hiber_idle,Data};
hiber_wakeup(snooze_async,Data) ->
    {next_state,hiber_wakeup,Data,hibernate}.


handle_info(hibernate_now, _SName, _State) ->
    %% Arrive here from by direct ! from testcase
    {next_state, hiber_idle, [], hibernate};
handle_info(hibernate_later, _SName, _State) ->
    {next_state, hiber_idle, hibernate_me, 1000};

handle_info(Info, _State, Data) ->
    {stop, {unexpected,Info}, Data}.

handle_event(hibernate_async, hiber_idle, Data) ->
    {next_state,hiber_wakeup, Data, hibernate};
handle_event(wakeup_async,hiber_wakeup,Data) ->
    {next_state,hiber_idle,Data};
handle_event(snooze_async,hiber_wakeup,Data) ->
    {next_state,hiber_wakeup,Data,hibernate};
handle_event({get, Pid}, State, Data) ->
    Pid ! {state, State, Data},
    {next_state, State, Data};
handle_event(stop, _State, Data) ->
    {stop, normal, Data};
handle_event(stop_shutdown, _State, Data) ->
    {stop, shutdown, Data};
handle_event(stop_shutdown_reason, _State, Data) ->
    {stop, shutdown, Data};
handle_event({'alive?', Pid}, State, Data) ->
    Pid ! yes,
    {next_state, State, Data}.

handle_sync_event(hibernate_sync, _From, hiber_idle, Data) ->
    {reply, hibernating, hiber_wakeup, Data, hibernate};
handle_sync_event(wakeup_sync,_From,hiber_wakeup, Data) ->
    {reply,good_morning,hiber_idle,Data};
handle_sync_event(snooze_sync,_From,hiber_wakeup,Data) ->
    {reply,five_more,hiber_wakeup,Data,hibernate};
handle_sync_event('alive?', _From, State, Data) ->
    {reply, yes, State, Data};
handle_sync_event(stop, _From, _State, Data) ->
    {stop, normal, stopped, Data};
handle_sync_event(stop_shutdown, _From, _State, Data) ->
    {stop, shutdown, shutdown_stopped, Data};
handle_sync_event(stop_shutdown_reason, _From, _State, Data) ->
    {stop, {shutdown,reason}, {shutdown,reason}, Data};
handle_sync_event({get, _Pid}, _From, State, Data) ->
    {reply, {state, State, Data}, State, Data}.

format_status(terminate, [_Pdict, StateData]) ->
    {formatted, StateData};
format_status(normal, [_Pdict, _StateData]) ->
    [format_status_called].

get_messages() ->
    receive
	Msg -> [Msg|get_messages()]
    after 1 -> []
    end.
