1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2016-2018. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20-module(gen_statem_SUITE). 21 22-include_lib("common_test/include/ct.hrl"). 23 24-compile([export_all, nowarn_export_all]). 25-behaviour(gen_statem). 26 27%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 28 29suite() -> 30 [{ct_hooks,[ts_install_cth]}, 31 {timetrap,{minutes,1}}]. 32 33all() -> 34 [{group, start}, 35 {group, start_handle_event}, 36 {group, stop}, 37 {group, stop_handle_event}, 38 {group, abnormal}, 39 {group, abnormal_handle_event}, 40 shutdown, stop_and_reply, state_enter, event_order, 41 state_timeout, event_types, generic_timers, code_change, 42 {group, sys}, 43 hibernate, auto_hibernate, enter_loop, {group, undef_callbacks}, 44 undef_in_terminate]. 45 46groups() -> 47 [{start, [], tcs(start)}, 48 {start_handle_event, [], tcs(start)}, 49 {stop, [], tcs(stop)}, 50 {stop_handle_event, [], tcs(stop)}, 51 {abnormal, [], tcs(abnormal)}, 52 {abnormal_handle_event, [], tcs(abnormal)}, 53 {sys, [], tcs(sys)}, 54 {sys_handle_event, [], tcs(sys)}, 55 {undef_callbacks, [], tcs(undef_callbacks)}]. 56 57tcs(start) -> 58 [start1, start2, start3, start4, start5, start6, start7, 59 start8, start9, start10, start11, start12, next_events]; 60tcs(stop) -> 61 [stop1, stop2, stop3, stop4, stop5, stop6, stop7, stop8, stop9, stop10]; 62tcs(abnormal) -> 63 [abnormal1, abnormal1clean, abnormal1dirty, 64 abnormal2, abnormal3, abnormal4]; 65tcs(sys) -> 66 [sys1, call_format_status, 67 error_format_status, terminate_crash_format, 68 get_state, replace_state]; 69tcs(undef_callbacks) -> 70 [undef_code_change, undef_terminate1, undef_terminate2]. 71 72init_per_suite(Config) -> 73 Config. 74 75end_per_suite(_Config) -> 76 ok. 77 78init_per_group(GroupName, Config) 79 when GroupName =:= start_handle_event; 80 GroupName =:= stop_handle_event; 81 GroupName =:= abnormal_handle_event; 82 GroupName =:= sys_handle_event -> 83 [{callback_mode,handle_event_function}|Config]; 84init_per_group(undef_callbacks, Config) -> 85 DataDir = ?config(data_dir, Config), 86 StatemPath = filename:join(DataDir, "oc_statem.erl"), 87 {ok, oc_statem} = compile:file(StatemPath), 88 Config; 89init_per_group(_GroupName, Config) -> 90 Config. 91 92end_per_group(_GroupName, Config) -> 93 Config. 94 95init_per_testcase(_CaseName, Config) -> 96 flush(), 97%%% dbg:tracer(), 98%%% dbg:p(all, c), 99%%% dbg:tpl(gen_statem, cx), 100%%% dbg:tpl(proc_lib, cx), 101%%% dbg:tpl(gen, cx), 102%%% dbg:tpl(sys, cx), 103 Config. 104 105end_per_testcase(_CaseName, Config) -> 106%%% dbg:stop(), 107 Config. 108 109%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 110-define(EXPECT_FAILURE(Code, Reason), 111 try begin Code end of 112 Reason -> 113 ct:fail({unexpected,Reason}) 114 catch 115 error:Reason -> Reason; 116 exit:Reason -> Reason 117 end). 118%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 119 120%% anonymous 121start1(Config) -> 122 %%OldFl = process_flag(trap_exit, true), 123 124 {ok,Pid0} = gen_statem:start_link(?MODULE, start_arg(Config, []), []), 125 ok = do_func_test(Pid0), 126 ok = do_sync_func_test(Pid0), 127 stop_it(Pid0), 128%% stopped = gen_statem:call(Pid0, stop), 129%% timeout = 130%% ?EXPECT_FAILURE(gen_statem:call(Pid0, hej), Reason), 131 132 %%process_flag(trap_exit, OldFl), 133 ok = verify_empty_msgq(). 134 135%% anonymous w. shutdown 136start2(Config) -> 137 %% Dont link when shutdown 138 {ok,Pid0} = gen_statem:start(?MODULE, start_arg(Config, []), []), 139 ok = do_func_test(Pid0), 140 ok = do_sync_func_test(Pid0), 141 stopped = gen_statem:call(Pid0, {stop,shutdown}), 142 check_stopped(Pid0), 143 ok = verify_empty_msgq(). 144 145%% anonymous with timeout 146start3(Config) -> 147 %%OldFl = process_flag(trap_exit, true), 148 149 {ok,Pid0} = 150 gen_statem:start(?MODULE, start_arg(Config, []), [{timeout,5}]), 151 ok = do_func_test(Pid0), 152 ok = do_sync_func_test(Pid0), 153 stop_it(Pid0), 154 155 {error,timeout} = 156 gen_statem:start( 157 ?MODULE, start_arg(Config, sleep), [{timeout,5}]), 158 159 %%process_flag(trap_exit, OldFl), 160 ok = verify_empty_msgq(). 161 162%% anonymous with ignore 163start4(Config) -> 164 OldFl = process_flag(trap_exit, true), 165 166 ignore = gen_statem:start(?MODULE, start_arg(Config, ignore), []), 167 168 process_flag(trap_exit, OldFl), 169 ok = verify_empty_msgq(). 170 171%% anonymous with stop 172start5(Config) -> 173 OldFl = process_flag(trap_exit, true), 174 175 {error,stopped} = gen_statem:start(?MODULE, start_arg(Config, stop), []), 176 177 process_flag(trap_exit, OldFl), 178 ok = verify_empty_msgq(). 179 180%% anonymous linked 181start6(Config) -> 182 {ok,Pid} = gen_statem:start_link(?MODULE, start_arg(Config, []), []), 183 ok = do_func_test(Pid), 184 ok = do_sync_func_test(Pid), 185 stop_it(Pid), 186 187 ok = verify_empty_msgq(). 188 189%% global register linked 190start7(Config) -> 191 STM = {global,my_stm}, 192 193 {ok,Pid} = 194 gen_statem:start_link(STM, ?MODULE, start_arg(Config, []), []), 195 {error,{already_started,Pid}} = 196 gen_statem:start_link(STM, ?MODULE, start_arg(Config, []), []), 197 {error,{already_started,Pid}} = 198 gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), 199 200 ok = do_func_test(Pid), 201 ok = do_sync_func_test(Pid), 202 ok = do_func_test(STM), 203 ok = do_sync_func_test(STM), 204 stop_it(STM), 205 206 ok = verify_empty_msgq(). 207 208 209%% local register 210start8(Config) -> 211 %%OldFl = process_flag(trap_exit, true), 212 Name = my_stm, 213 STM = {local,Name}, 214 215 {ok,Pid} = 216 gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), 217 {error,{already_started,Pid}} = 218 gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), 219 220 ok = do_func_test(Pid), 221 ok = do_sync_func_test(Pid), 222 ok = do_func_test(Name), 223 ok = do_sync_func_test(Name), 224 stop_it(Pid), 225 226 %%process_flag(trap_exit, OldFl), 227 ok = verify_empty_msgq(). 228 229%% local register linked 230start9(Config) -> 231 %%OldFl = process_flag(trap_exit, true), 232 Name = my_stm, 233 STM = {local,Name}, 234 235 {ok,Pid} = 236 gen_statem:start_link(STM, ?MODULE, start_arg(Config, []), []), 237 {error,{already_started,Pid}} = 238 gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), 239 240 ok = do_func_test(Pid), 241 ok = do_sync_func_test(Pid), 242 ok = do_func_test(Name), 243 ok = do_sync_func_test(Name), 244 stop_it(Pid), 245 246 %%process_flag(trap_exit, OldFl), 247 ok = verify_empty_msgq(). 248 249%% global register 250start10(Config) -> 251 STM = {global,my_stm}, 252 253 {ok,Pid} = 254 gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), 255 {error,{already_started,Pid}} = 256 gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), 257 {error,{already_started,Pid}} = 258 gen_statem:start_link(STM, ?MODULE, start_arg(Config, []), []), 259 260 ok = do_func_test(Pid), 261 ok = do_sync_func_test(Pid), 262 ok = do_func_test(STM), 263 ok = do_sync_func_test(STM), 264 stop_it(STM), 265 266 ok = verify_empty_msgq(). 267 268%% Stop registered processes 269start11(Config) -> 270 Name = my_stm, 271 LocalSTM = {local,Name}, 272 GlobalSTM = {global,Name}, 273 274 {ok,Pid} = 275 gen_statem:start_link(LocalSTM, ?MODULE, start_arg(Config, []), []), 276 stop_it(Pid), 277 278 {ok,_Pid1} = 279 gen_statem:start_link(LocalSTM, ?MODULE, start_arg(Config, []), []), 280 stop_it(Name), 281 282 {ok,Pid2} = 283 gen_statem:start(GlobalSTM, ?MODULE, start_arg(Config, []), []), 284 stop_it(Pid2), 285 receive after 1 -> true end, 286 Result = 287 gen_statem:start(GlobalSTM, ?MODULE, start_arg(Config, []), []), 288 ct:log("Result = ~p~n",[Result]), 289 {ok,_Pid3} = Result, 290 stop_it(GlobalSTM), 291 292 ok = verify_empty_msgq(). 293 294%% Via register linked 295start12(Config) -> 296 dummy_via:reset(), 297 VIA = {via,dummy_via,my_stm}, 298 299 {ok,Pid} = 300 gen_statem:start_link(VIA, ?MODULE, start_arg(Config, []), []), 301 {error,{already_started,Pid}} = 302 gen_statem:start_link(VIA, ?MODULE, start_arg(Config, []), []), 303 {error,{already_started,Pid}} = 304 gen_statem:start(VIA, ?MODULE, start_arg(Config, []), []), 305 306 ok = do_func_test(Pid), 307 ok = do_sync_func_test(Pid), 308 ok = do_func_test(VIA), 309 ok = do_sync_func_test(VIA), 310 stop_it(VIA), 311 312 ok = verify_empty_msgq(). 313 314 315%% Anonymous, reason 'normal' 316stop1(Config) -> 317 {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), 318 ok = gen_statem:stop(Pid), 319 false = erlang:is_process_alive(Pid), 320 noproc = 321 ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason). 322 323%% Anonymous, other reason 324stop2(Config) -> 325 {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), 326 ok = gen_statem:stop(Pid, other_reason, infinity), 327 false = erlang:is_process_alive(Pid), 328 ok. 329 330%% Anonymous, invalid timeout 331stop3(Config) -> 332 {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), 333 _ = 334 ?EXPECT_FAILURE( 335 gen_statem:stop(Pid, other_reason, invalid_timeout), 336 Reason), 337 true = erlang:is_process_alive(Pid), 338 ok = gen_statem:stop(Pid), 339 false = erlang:is_process_alive(Pid), 340 ok. 341 342%% Registered name 343stop4(Config) -> 344 {ok,Pid} = 345 gen_statem:start( 346 {local,to_stop},?MODULE, start_arg(Config, []), []), 347 ok = gen_statem:stop(to_stop), 348 false = erlang:is_process_alive(Pid), 349 noproc = 350 ?EXPECT_FAILURE(gen_statem:stop(to_stop), Reason), 351 ok. 352 353%% Registered name and local node 354stop5(Config) -> 355 Name = to_stop, 356 {ok,Pid} = 357 gen_statem:start( 358 {local,Name},?MODULE, start_arg(Config, []), []), 359 ok = gen_statem:stop({Name,node()}), 360 false = erlang:is_process_alive(Pid), 361 noproc = 362 ?EXPECT_FAILURE(gen_statem:stop({Name,node()}), Reason), 363 ok. 364 365%% Globally registered name 366stop6(Config) -> 367 STM = {global,to_stop}, 368 {ok,Pid} = gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), 369 ok = gen_statem:stop(STM), 370 false = erlang:is_process_alive(Pid), 371 noproc = 372 ?EXPECT_FAILURE(gen_statem:stop(STM), Reason), 373 ok. 374 375%% 'via' registered name 376stop7(Config) -> 377 VIA = {via,dummy_via,to_stop}, 378 dummy_via:reset(), 379 {ok,Pid} = gen_statem:start(VIA, ?MODULE, start_arg(Config, []), []), 380 ok = gen_statem:stop(VIA), 381 false = erlang:is_process_alive(Pid), 382 noproc = 383 ?EXPECT_FAILURE(gen_statem:stop(VIA), Reason), 384 ok. 385 386%% Anonymous on remote node 387stop8(Config) -> 388 Node = gen_statem_stop8, 389 {ok,NodeName} = ct_slave:start(Node), 390 Dir = filename:dirname(code:which(?MODULE)), 391 rpc:call(NodeName, code, add_path, [Dir]), 392 {ok,Pid} = 393 rpc:call( 394 NodeName, gen_statem,start, 395 [?MODULE,start_arg(Config, []),[]]), 396 ok = gen_statem:stop(Pid), 397 false = rpc:call(NodeName, erlang, is_process_alive, [Pid]), 398 noproc = 399 ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason1), 400 {ok,NodeName} = ct_slave:stop(Node), 401 {{nodedown,NodeName},{sys,terminate,_}} = 402 ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason2), 403 ok. 404 405%% Registered name on remote node 406stop9(Config) -> 407 Name = to_stop, 408 LocalSTM = {local,Name}, 409 Node = gen_statem__stop9, 410 {ok,NodeName} = ct_slave:start(Node), 411 STM = {Name,NodeName}, 412 Dir = filename:dirname(code:which(?MODULE)), 413 rpc:call(NodeName, code, add_path, [Dir]), 414 {ok,Pid} = 415 rpc:call( 416 NodeName, gen_statem, start, 417 [LocalSTM,?MODULE,start_arg(Config, []),[]]), 418 ok = gen_statem:stop(STM), 419 undefined = rpc:call(NodeName,erlang,whereis,[Name]), 420 false = rpc:call(NodeName,erlang,is_process_alive,[Pid]), 421 noproc = 422 ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1), 423 {ok,NodeName} = ct_slave:stop(Node), 424 {{nodedown,NodeName},{sys,terminate,_}} = 425 ?EXPECT_FAILURE(gen_statem:stop(STM), Reason2), 426 ok. 427 428%% Globally registered name on remote node 429stop10(Config) -> 430 Node = gen_statem_stop10, 431 STM = {global,to_stop}, 432 {ok,NodeName} = ct_slave:start(Node), 433 Dir = filename:dirname(code:which(?MODULE)), 434 rpc:call(NodeName,code,add_path,[Dir]), 435 {ok,Pid} = 436 rpc:call( 437 NodeName, gen_statem, start, 438 [STM,?MODULE,start_arg(Config, []),[]]), 439 global:sync(), 440 ok = gen_statem:stop(STM), 441 false = rpc:call(NodeName, erlang, is_process_alive, [Pid]), 442 noproc = 443 ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1), 444 {ok,NodeName} = ct_slave:stop(Node), 445 noproc = 446 ?EXPECT_FAILURE(gen_statem:stop(STM), Reason2), 447 ok. 448 449%% Check that time outs in calls work 450abnormal1(Config) -> 451 Name = abnormal1, 452 LocalSTM = {local,Name}, 453 454 {ok, _Pid} = 455 gen_statem:start(LocalSTM, ?MODULE, start_arg(Config, []), []), 456 457 %% timeout call. 458 delayed = gen_statem:call(Name, {delayed_answer,1}, 100), 459 {timeout,_} = 460 ?EXPECT_FAILURE( 461 gen_statem:call(Name, {delayed_answer,1000}, 10), 462 Reason), 463 ok = gen_statem:stop(Name), 464 ?t:sleep(1100), 465 ok = verify_empty_msgq(). 466 467%% Check that time outs in calls work 468abnormal1clean(Config) -> 469 Name = abnormal1clean, 470 LocalSTM = {local,Name}, 471 472 {ok, _Pid} = 473 gen_statem:start(LocalSTM, ?MODULE, start_arg(Config, []), []), 474 475 %% timeout call. 476 delayed = 477 gen_statem:call(Name, {delayed_answer,1}, {clean_timeout,100}), 478 {timeout,_} = 479 ?EXPECT_FAILURE( 480 gen_statem:call( 481 Name, {delayed_answer,1000}, {clean_timeout,10}), 482 Reason), 483 ok = gen_statem:stop(Name), 484 ?t:sleep(1100), 485 ok = verify_empty_msgq(). 486 487%% Check that time outs in calls work 488abnormal1dirty(Config) -> 489 Name = abnormal1dirty, 490 LocalSTM = {local,Name}, 491 492 {ok, _Pid} = 493 gen_statem:start(LocalSTM, ?MODULE, start_arg(Config, []), []), 494 495 %% timeout call. 496 delayed = 497 gen_statem:call(Name, {delayed_answer,1}, {dirty_timeout,100}), 498 {timeout,_} = 499 ?EXPECT_FAILURE( 500 gen_statem:call( 501 Name, {delayed_answer,1000}, {dirty_timeout,10}), 502 Reason), 503 ok = gen_statem:stop(Name), 504 ?t:sleep(1100), 505 case flush() of 506 [{Ref,delayed}] when is_reference(Ref) -> 507 ok 508 end. 509 510%% Check that bad return values makes the stm crash. Note that we must 511%% trap exit since we must link to get the real bad_return_ error 512abnormal2(Config) -> 513 OldFl = process_flag(trap_exit, true), 514 {ok,Pid} = gen_statem:start_link(?MODULE, start_arg(Config, []), []), 515 516 %% bad return value in the gen_statem loop 517 {{{bad_return_from_state_function,badreturn},_},_} = 518 ?EXPECT_FAILURE(gen_statem:call(Pid, badreturn), Reason), 519 receive 520 {'EXIT',Pid,{{bad_return_from_state_function,badreturn},_}} -> ok 521 after 5000 -> 522 ct:fail(gen_statem_did_not_die) 523 end, 524 525 process_flag(trap_exit, OldFl), 526 ok = verify_empty_msgq(). 527 528%% Check that bad return actions makes the stm crash. Note that we must 529%% trap exit since we must link to get the real bad_return_ error 530abnormal3(Config) -> 531 OldFl = process_flag(trap_exit, true), 532 {ok,Pid} = gen_statem:start_link(?MODULE, start_arg(Config, []), []), 533 534 %% bad return value in the gen_statem loop 535 {{{bad_action_from_state_function,badaction},_},_} = 536 ?EXPECT_FAILURE(gen_statem:call(Pid, badaction), Reason), 537 receive 538 {'EXIT',Pid,{{bad_action_from_state_function,badaction},_}} -> ok 539 after 5000 -> 540 ct:fail(gen_statem_did_not_die) 541 end, 542 543 process_flag(trap_exit, OldFl), 544 ok = verify_empty_msgq(). 545 546%% Check that bad timeout actions makes the stm crash. Note that we must 547%% trap exit since we must link to get the real bad_return_ error 548abnormal4(Config) -> 549 OldFl = process_flag(trap_exit, true), 550 {ok,Pid} = gen_statem:start_link(?MODULE, start_arg(Config, []), []), 551 552 %% bad return value in the gen_statem loop 553 BadTimeout = {badtimeout,4711,ouch}, 554 {{{bad_action_from_state_function,BadTimeout},_},_} = 555 ?EXPECT_FAILURE(gen_statem:call(Pid, BadTimeout), Reason), 556 receive 557 {'EXIT',Pid,{{bad_action_from_state_function,BadTimeout},_}} -> ok 558 after 5000 -> 559 ct:fail(gen_statem_did_not_die) 560 end, 561 562 process_flag(trap_exit, OldFl), 563 ok = verify_empty_msgq(). 564 565shutdown(Config) -> 566 process_flag(trap_exit, true), 567 568 {ok,Pid0} = gen_statem:start_link(?MODULE, start_arg(Config, []), []), 569 ok = do_func_test(Pid0), 570 ok = do_sync_func_test(Pid0), 571 stopped = gen_statem:call(Pid0, {stop,{shutdown,reason}}), 572 receive {'EXIT',Pid0,{shutdown,reason}} -> ok end, 573 process_flag(trap_exit, false), 574 575 {noproc,_} = 576 ?EXPECT_FAILURE(gen_statem:call(Pid0, hej), Reason), 577 578 receive 579 Any -> 580 ct:log("Unexpected: ~p", [Any]), 581 ct:fail({unexpected,Any}) 582 after 500 -> 583 ok 584 end. 585 586 587 588stop_and_reply(_Config) -> 589 process_flag(trap_exit, true), 590 591 Machine = 592 %% Abusing the internal format of From... 593 #{init => 594 fun () -> 595 {ok,start,undefined} 596 end, 597 start => 598 fun (cast, {echo,From1,Reply1}, undefined) -> 599 {next_state,wait,{reply,From1,Reply1}} 600 end, 601 wait => 602 fun (cast, {stop_and_reply,Reason,From2,Reply2},R1) -> 603 {stop_and_reply,Reason, 604 [R1,{reply,From2,Reply2}]} 605 end}, 606 {ok,STM} = 607 gen_statem:start_link(?MODULE, {map_statem,Machine,[]}, []), 608 609 Self = self(), 610 Tag1 = make_ref(), 611 gen_statem:cast(STM, {echo,{Self,Tag1},reply1}), 612 Tag2 = make_ref(), 613 gen_statem:cast(STM, {stop_and_reply,reason,{Self,Tag2},reply2}), 614 case flush() of 615 [{Tag1,reply1},{Tag2,reply2},{'EXIT',STM,reason}] -> 616 ok; 617 Other1 -> 618 ct:fail({unexpected,Other1}) 619 end, 620 621 {noproc,_} = 622 ?EXPECT_FAILURE(gen_statem:call(STM, hej), Reason), 623 case flush() of 624 [] -> 625 ok; 626 Other2 -> 627 ct:fail({unexpected,Other2}) 628 end. 629 630 631 632state_enter(_Config) -> 633 process_flag(trap_exit, true), 634 Self = self(), 635 636 Machine = 637 %% Abusing the internal format of From... 638 #{init => 639 fun () -> 640 {ok,start,1} 641 end, 642 start => 643 fun (enter, Prev, N) -> 644 Self ! {enter,start,Prev,N}, 645 {keep_state,N + 1}; 646 (internal, Prev, N) -> 647 Self ! {internal,start,Prev,N}, 648 {keep_state,N + 1}; 649 ({call,From}, repeat, N) -> 650 {repeat_state,N + 1, 651 [{reply,From,{repeat,start,N}}]}; 652 ({call,From}, echo, N) -> 653 {next_state,wait,N + 1, 654 {reply,From,{echo,start,N}}}; 655 ({call,From}, {stop,Reason}, N) -> 656 {stop_and_reply,Reason, 657 [{reply,From,{stop,N}}],N + 1} 658 end, 659 wait => 660 fun (enter, Prev, N) when N < 5 -> 661 {repeat_state,N + 1, 662 {reply,{Self,N},{enter,Prev}}}; 663 (enter, Prev, N) -> 664 Self ! {enter,wait,Prev,N}, 665 {keep_state,N + 1}; 666 ({call,From}, repeat, N) -> 667 {repeat_state_and_data, 668 [{reply,From,{repeat,wait,N}}]}; 669 ({call,From}, echo, N) -> 670 {next_state,start,N + 1, 671 [{next_event,internal,wait}, 672 {reply,From,{echo,wait,N}}]} 673 end}, 674 {ok,STM} = 675 gen_statem:start_link( 676 ?MODULE, {map_statem,Machine,[state_enter]}, []), 677 678 [{enter,start,start,1}] = flush(), 679 {echo,start,2} = gen_statem:call(STM, echo), 680 [{3,{enter,start}},{4,{enter,start}},{enter,wait,start,5}] = flush(), 681 {wait,[6|_]} = sys:get_state(STM), 682 {repeat,wait,6} = gen_statem:call(STM, repeat), 683 [{enter,wait,wait,6}] = flush(), 684 {echo,wait,7} = gen_statem:call(STM, echo), 685 [{enter,start,wait,8},{internal,start,wait,9}] = flush(), 686 {repeat,start,10} = gen_statem:call(STM, repeat), 687 [{enter,start,start,11}] = flush(), 688 {stop,12} = gen_statem:call(STM, {stop,bye}), 689 [{'EXIT',STM,bye}] = flush(), 690 691 {noproc,_} = 692 ?EXPECT_FAILURE(gen_statem:call(STM, hej), Reason), 693 case flush() of 694 [] -> 695 ok; 696 Other2 -> 697 ct:fail({unexpected,Other2}) 698 end. 699 700 701 702event_order(_Config) -> 703 process_flag(trap_exit, true), 704 705 Machine = 706 %% Abusing the internal format of From... 707 #{init => 708 fun () -> 709 {ok,start,undefined} 710 end, 711 start => 712 fun (cast, _, _) -> 713 {keep_state_and_data,postpone}; %% Handled in 'buffer' 714 ({call,From}, {buffer,Pid,[Tag3,Tag4,Tag5]}, 715 undefined) -> 716 {next_state,buffer,[], 717 [{next_event,internal,{reply,{Pid,Tag3},ok3}}, 718 {next_event,internal,{reply,{Pid,Tag4},ok4}}, 719 {timeout,0,{reply,{Pid,Tag5},ok5}}, 720 %% The timeout should not happen since there 721 %% are events that cancel it i.e next_event 722 %% and postponed 723 {reply,From,ok}]} 724 end, 725 buffer => 726 fun (internal, Reply, Replies) -> 727 {keep_state,[Reply|Replies]}; 728 (timeout, Reply, Replies) -> 729 {keep_state,[Reply|Replies]}; 730 (cast, Reply, Replies) -> 731 {keep_state,[Reply|Replies]}; 732 ({call,From}, {stop,Reason}, Replies) -> 733 {next_state,stop,undefined, 734 lists:reverse( 735 Replies, 736 [{reply,From,ok}, 737 {next_event,internal,{stop,Reason}}])} 738 end, 739 stop => 740 fun (internal, Result, undefined) -> 741 Result 742 end}, 743 744 {ok,STM} = gen_statem:start_link(?MODULE, {map_statem,Machine,[]}, []), 745 Self = self(), 746 Tag1 = make_ref(), 747 gen_statem:cast(STM, {reply,{Self,Tag1},ok1}), 748 Tag2 = make_ref(), 749 gen_statem:cast(STM, {reply,{Self,Tag2},ok2}), 750 Tag3 = make_ref(), 751 Tag4 = make_ref(), 752 Tag5 = make_ref(), 753 ok = gen_statem:call(STM, {buffer,Self,[Tag3,Tag4,Tag5]}), 754 ok = gen_statem:call(STM, {stop,reason}), 755 case flush() of 756 [{Tag3,ok3},{Tag4,ok4},{Tag1,ok1},{Tag2,ok2}, 757 {'EXIT',STM,reason}] -> 758 ok; 759 Other1 -> 760 ct:fail({unexpected,Other1}) 761 end, 762 763 {noproc,_} = 764 ?EXPECT_FAILURE(gen_statem:call(STM, hej), Reason), 765 case flush() of 766 [] -> 767 ok; 768 Other2 -> 769 ct:fail({unexpected,Other2}) 770 end. 771 772 773 774state_timeout(_Config) -> 775 process_flag(trap_exit, true), 776 777 Machine = 778 #{init => 779 fun () -> 780 {ok,start,0} 781 end, 782 start => 783 fun 784 ({call,From}, {go,Time}, 0) -> 785 self() ! message_to_self, 786 {next_state, state1, {Time,From}, 787 %% Verify that internal events goes before external 788 [{state_timeout,Time,1}, 789 {next_event,internal,1}]} 790 end, 791 state1 => 792 fun 793 (internal, 1, Data) -> 794 %% Verify that a state change cancels timeout 1 795 {next_state, state2, Data, 796 [{timeout,0,2}, 797 {state_timeout,0,2}, 798 {next_event,internal,2}]} 799 end, 800 state2 => 801 fun 802 (internal, 2, Data) -> 803 %% Verify that {state_timeout,0,_} 804 %% comes after next_event and that 805 %% {timeout,0,_} is cancelled by 806 %% pending {state_timeout,0,_} 807 {keep_state, {ok,2,Data}, 808 [{timeout,0,3}]}; 809 (state_timeout, 2, {ok,2,Data}) -> 810 %% Verify that timeout 0's are processed 811 %% in order 812 {keep_state, {ok,3,Data}, 813 [{timeout,0,4},{state_timeout,0,5}]}; 814 (timeout, 4, {ok,3,Data}) -> 815 %% Verify that timeout 0 is cancelled by 816 %% enqueued state_timeout 0 and that 817 %% multiple state_timeout 0 can be enqueued 818 {keep_state, {ok,4,Data}, 819 [{state_timeout,0,6},{timeout,0,7}]}; 820 (state_timeout, 5, {ok,4,Data}) -> 821 {keep_state, {ok,5,Data}}; 822 (state_timeout, 6, {ok,5,{Time,From}}) -> 823 {next_state, state3, 6, 824 [{reply,From,ok}, 825 {state_timeout,Time,8}]} 826 end, 827 state3 => 828 fun 829 (info, message_to_self, 6) -> 830 {keep_state, 7}; 831 ({call,From}, check, 7) -> 832 {keep_state, From}; 833 (state_timeout, 8, From) -> 834 {stop_and_reply, normal, 835 {reply,From,ok}} 836 end}, 837 838 {ok,STM} = gen_statem:start_link(?MODULE, {map_statem,Machine,[]}, []), 839 sys:trace(STM, true), 840 TRef = erlang:start_timer(1000, self(), kull), 841 ok = gen_statem:call(STM, {go,500}), 842 ok = gen_statem:call(STM, check), 843 receive 844 {timeout,TRef,kull} -> 845 ct:fail(late_timeout) 846 after 0 -> 847 receive 848 {timeout,TRef,kull} -> 849 ok 850 after 1000 -> 851 ct:fail(no_check_timeout) 852 end 853 end, 854 receive 855 {'EXIT',STM,normal} -> 856 ok 857 after 500 -> 858 ct:fail(did_not_stop) 859 end, 860 861 verify_empty_msgq(). 862 863 864 865%% Test that all event types can be sent with {next_event,EventType,_} 866event_types(_Config) -> 867 process_flag(trap_exit, true), 868 869 Machine = 870 %% Abusing the internal format of From... 871 #{init => 872 fun () -> 873 {ok, start1, undefined, 874 [{next_event,internal,0}]} 875 end, 876 start1 => 877 fun (internal, 0, undefined) -> 878 {next_state, start2, undefined} 879 end, 880 start2 => 881 fun ({call,_} = Call, Req, undefined) -> 882 {next_state, state1, undefined, 883 [{next_event,internal,1}, 884 {next_event,state_timeout,2}, 885 {next_event,timeout,3}, 886 {next_event,info,4}, 887 {next_event,cast,5}, 888 {next_event,{timeout,6}, 6}, 889 {next_event,Call,Req}]} 890 end, 891 state1 => 892 fun (internal, 1, undefined) -> 893 {next_state, state2, undefined} 894 end, 895 state2 => 896 fun (state_timeout, 2, undefined) -> 897 {next_state, state3, undefined} 898 end, 899 state3 => 900 fun (timeout, 3, undefined) -> 901 {next_state, state4, undefined} 902 end, 903 state4 => 904 fun (info, 4, undefined) -> 905 {next_state, state5, undefined} 906 end, 907 state5 => 908 fun (cast, 5, undefined) -> 909 {next_state, state6, undefined} 910 end, 911 state6 => 912 fun ({timeout,6}, 6, undefined) -> 913 {next_state, state7, undefined} 914 end, 915 state7 => 916 fun ({call,From}, stop, undefined) -> 917 {stop_and_reply, shutdown, 918 [{reply,From,stopped}]} 919 end}, 920 {ok,STM} = 921 gen_statem:start_link( 922 ?MODULE, {map_statem,Machine,[]}, [{debug,[trace]}]), 923 924 stopped = gen_statem:call(STM, stop), 925 receive 926 {'EXIT',STM,shutdown} -> 927 ok 928 after 500 -> 929 ct:fail(did_not_stop) 930 end, 931 932 {noproc,_} = 933 ?EXPECT_FAILURE(gen_statem:call(STM, hej), Reason), 934 case flush() of 935 [] -> 936 ok; 937 Other2 -> 938 ct:fail({unexpected,Other2}) 939 end. 940 941 942 943generic_timers(_Config) -> 944 process_flag(trap_exit, true), 945 946 Machine = 947 %% Abusing the internal format of From... 948 #{init => 949 fun () -> 950 {ok, start, undefined} 951 end, 952 start => 953 fun ({call,_} = Call, Req, undefined) -> 954 {next_state, state1, undefined, 955 [{{timeout,a},1500,1}, 956 {state_timeout,1500,1}, 957 {{timeout,b},1000,1}, 958 {next_event,Call,Req}]} 959 end, 960 state1 => 961 fun ({call,_} = Call, Req, undefined) -> 962 T = erlang:monotonic_time(millisecond) + 500, 963 {next_state, state2, undefined, 964 [{{timeout,c},T,2,{abs,true}}, 965 {{timeout,d},0,2,[{abs,false}]}, 966 {timeout,0,2}, 967 {{timeout,b},infinity,2}, 968 {{timeout,a},1000,{Call,Req}}]} 969 end, 970 state2 => 971 fun ({timeout,d}, 2, undefined) -> 972 {next_state, state3, undefined} 973 end, 974 state3 => 975 fun ({timeout,c}, 2, undefined) -> 976 {next_state, state4, undefined} 977 end, 978 state4 => 979 fun ({timeout,a}, {{call,From},stop}, undefined) -> 980 {stop_and_reply, shutdown, 981 [{reply,From,stopped}]} 982 end}, 983 {ok,STM} = 984 gen_statem:start_link( 985 ?MODULE, {map_statem,Machine,[]}, [{debug,[trace]}]), 986 987 stopped = gen_statem:call(STM, stop), 988 receive 989 {'EXIT',STM,shutdown} -> 990 ok 991 after 500 -> 992 ct:fail(did_not_stop) 993 end, 994 995 {noproc,_} = 996 ?EXPECT_FAILURE(gen_statem:call(STM, hej), Reason), 997 case flush() of 998 [] -> 999 ok; 1000 Other2 -> 1001 ct:fail({unexpected,Other2}) 1002 end. 1003 1004 1005 1006sys1(Config) -> 1007 {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), 1008 {status, Pid, {module,gen_statem}, _} = sys:get_status(Pid), 1009 sys:suspend(Pid), 1010 Parent = self(), 1011 Tag = make_ref(), 1012 Caller = 1013 spawn( 1014 fun () -> 1015 Parent ! {Tag,gen_statem:call(Pid, hej)} 1016 end), 1017 receive 1018 {Tag,_} -> 1019 ct:fail(should_be_suspended) 1020 after 3000 -> 1021 exit(Caller, ok) 1022 end, 1023 1024 %% {timeout,_} = 1025 %% ?EXPECT_FAILURE(gen_statem:call(Pid, hej), Reason), 1026 sys:resume(Pid), 1027 stop_it(Pid). 1028 1029code_change(_Config) -> 1030 {ok,Pid} = 1031 gen_statem:start( 1032 ?MODULE, {callback_mode,state_functions,[]}, []), 1033 {idle,data} = sys:get_state(Pid), 1034 sys:suspend(Pid), 1035 Mode = handle_event_function, 1036 sys:change_code(Pid, ?MODULE, old_vsn, Mode), 1037 sys:resume(Pid), 1038 {idle,{old_vsn,data,Mode}} = sys:get_state(Pid), 1039 Mode = gen_statem:call(Pid, get_callback_mode), 1040 stop_it(Pid). 1041 1042call_format_status(Config) -> 1043 {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), 1044 Status = sys:get_status(Pid), 1045 {status,Pid,_Mod,[_PDict,running,_,_, Data]} = Status, 1046 [format_status_called|_] = lists:reverse(Data), 1047 stop_it(Pid), 1048 1049 %% check that format_status can handle a name being an atom (pid is 1050 %% already checked by the previous test) 1051 {ok, Pid2} = 1052 gen_statem:start( 1053 {local, gstm}, ?MODULE, start_arg(Config, []), []), 1054 Status2 = sys:get_status(gstm), 1055 {status,Pid2,_Mod,[_PDict2,running,_,_,Data2]} = Status2, 1056 [format_status_called|_] = lists:reverse(Data2), 1057 stop_it(Pid2), 1058 1059 %% check that format_status can handle a name being a term other than a 1060 %% pid or atom 1061 GlobalName1 = {global,"CallFormatStatus"}, 1062 {ok,Pid3} = 1063 gen_statem:start( 1064 GlobalName1, ?MODULE, start_arg(Config, []), []), 1065 Status3 = sys:get_status(GlobalName1), 1066 {status,Pid3,_Mod,[_PDict3,running,_,_,Data3]} = Status3, 1067 [format_status_called|_] = lists:reverse(Data3), 1068 stop_it(Pid3), 1069 GlobalName2 = {global,{name, "term"}}, 1070 {ok,Pid4} = 1071 gen_statem:start( 1072 GlobalName2, ?MODULE, start_arg(Config, []), []), 1073 Status4 = sys:get_status(GlobalName2), 1074 {status,Pid4,_Mod,[_PDict4,running,_,_, Data4]} = Status4, 1075 [format_status_called|_] = lists:reverse(Data4), 1076 stop_it(Pid4), 1077 1078 %% check that format_status can handle a name being a term other than a 1079 %% pid or atom 1080 dummy_via:reset(), 1081 ViaName1 = {via,dummy_via,"CallFormatStatus"}, 1082 {ok,Pid5} = gen_statem:start(ViaName1, ?MODULE, start_arg(Config, []), []), 1083 Status5 = sys:get_status(ViaName1), 1084 {status,Pid5,_Mod, [_PDict5,running,_,_, Data5]} = Status5, 1085 [format_status_called|_] = lists:reverse(Data5), 1086 stop_it(Pid5), 1087 ViaName2 = {via,dummy_via,{name,"term"}}, 1088 {ok, Pid6} = 1089 gen_statem:start( 1090 ViaName2, ?MODULE, start_arg(Config, []), []), 1091 Status6 = sys:get_status(ViaName2), 1092 {status,Pid6,_Mod,[_PDict6,running,_,_,Data6]} = Status6, 1093 [format_status_called|_] = lists:reverse(Data6), 1094 stop_it(Pid6). 1095 1096 1097 1098error_format_status(Config) -> 1099 error_logger_forwarder:register(), 1100 OldFl = process_flag(trap_exit, true), 1101 Data = "called format_status", 1102 {ok,Pid} = 1103 gen_statem:start( 1104 ?MODULE, start_arg(Config, {data,Data}), []), 1105 %% bad return value in the gen_statem loop 1106 {{{bad_return_from_state_function,badreturn},_},_} = 1107 ?EXPECT_FAILURE(gen_statem:call(Pid, badreturn), Reason), 1108 receive 1109 {error,_, 1110 {Pid, 1111 "** State machine"++_, 1112 [Pid,{{call,_},badreturn}, 1113 {formatted,idle,Data}, 1114 error,{bad_return_from_state_function,badreturn}|_]}} -> 1115 ok; 1116 Other when is_tuple(Other), element(1, Other) =:= error -> 1117 error_logger_forwarder:unregister(), 1118 ct:fail({unexpected,Other}) 1119 after 1000 -> 1120 error_logger_forwarder:unregister(), 1121 ct:fail(timeout) 1122 end, 1123 process_flag(trap_exit, OldFl), 1124 error_logger_forwarder:unregister(), 1125 receive 1126 %% Comes with SASL 1127 {error_report,_,{Pid,crash_report,_}} -> 1128 ok 1129 after 500 -> 1130 ok 1131 end, 1132 ok = verify_empty_msgq(). 1133 1134terminate_crash_format(Config) -> 1135 error_logger_forwarder:register(), 1136 OldFl = process_flag(trap_exit, true), 1137 Data = crash_terminate, 1138 {ok,Pid} = 1139 gen_statem:start( 1140 ?MODULE, start_arg(Config, {data,Data}), []), 1141 stop_it(Pid), 1142 Self = self(), 1143 receive 1144 {error,_GroupLeader, 1145 {Pid, 1146 "** State machine"++_, 1147 [Pid, 1148 {{call,{Self,_}},stop}, 1149 {formatted,idle,Data}, 1150 exit,{crash,terminate}|_]}} -> 1151 ok; 1152 Other when is_tuple(Other), element(1, Other) =:= error -> 1153 error_logger_forwarder:unregister(), 1154 ct:fail({unexpected,Other}) 1155 after 1000 -> 1156 error_logger_forwarder:unregister(), 1157 ct:fail(timeout) 1158 end, 1159 process_flag(trap_exit, OldFl), 1160 error_logger_forwarder:unregister(), 1161 receive 1162 %% Comes with SASL 1163 {error_report,_,{Pid,crash_report,_}} -> 1164 ok 1165 after 500 -> 1166 ok 1167 end, 1168 ok = verify_empty_msgq(). 1169 1170 1171get_state(Config) -> 1172 State = self(), 1173 {ok,Pid} = 1174 gen_statem:start( 1175 ?MODULE, start_arg(Config, {data,State}), []), 1176 {idle,State} = sys:get_state(Pid), 1177 {idle,State} = sys:get_state(Pid, 5000), 1178 stop_it(Pid), 1179 1180 %% check that get_state can handle a name being an atom (pid is 1181 %% already checked by the previous test) 1182 {ok,Pid2} = 1183 gen_statem:start( 1184 {local,gstm}, ?MODULE, start_arg(Config, {data,State}), []), 1185 {idle,State} = sys:get_state(gstm), 1186 {idle,State} = sys:get_state(gstm, 5000), 1187 stop_it(Pid2), 1188 1189 %% check that get_state works when pid is sys suspended 1190 {ok,Pid3} = 1191 gen_statem:start( 1192 ?MODULE, start_arg(Config, {data,State}), []), 1193 {idle,State} = sys:get_state(Pid3), 1194 ok = sys:suspend(Pid3), 1195 {idle,State} = sys:get_state(Pid3, 5000), 1196 ok = sys:resume(Pid3), 1197 stop_it(Pid3), 1198 ok = verify_empty_msgq(). 1199 1200replace_state(Config) -> 1201 State = self(), 1202 {ok, Pid} = 1203 gen_statem:start( 1204 ?MODULE, start_arg(Config, {data,State}), []), 1205 {idle,State} = sys:get_state(Pid), 1206 NState1 = "replaced", 1207 Replace1 = fun({StateName, _}) -> {StateName,NState1} end, 1208 {idle,NState1} = sys:replace_state(Pid, Replace1), 1209 {idle,NState1} = sys:get_state(Pid), 1210 NState2 = "replaced again", 1211 Replace2 = fun({idle, _}) -> {state0,NState2} end, 1212 {state0,NState2} = sys:replace_state(Pid, Replace2, 5000), 1213 {state0,NState2} = sys:get_state(Pid), 1214 %% verify no change in state if replace function crashes 1215 Replace3 = fun(_) -> error(fail) end, 1216 {callback_failed, 1217 {gen_statem,system_replace_state},{error,fail}} = 1218 ?EXPECT_FAILURE(sys:replace_state(Pid, Replace3), Reason), 1219 {state0, NState2} = sys:get_state(Pid), 1220 %% verify state replaced if process sys suspended 1221 ok = sys:suspend(Pid), 1222 Suffix2 = " and again", 1223 NState3 = NState2 ++ Suffix2, 1224 Replace4 = fun({StateName, _}) -> {StateName, NState3} end, 1225 {state0,NState3} = sys:replace_state(Pid, Replace4), 1226 ok = sys:resume(Pid), 1227 {state0,NState3} = sys:get_state(Pid, 5000), 1228 stop_it(Pid), 1229 ok = verify_empty_msgq(). 1230 1231%% Hibernation 1232hibernate(Config) -> 1233 OldFl = process_flag(trap_exit, true), 1234 1235 {ok,Pid0} = 1236 gen_statem:start_link( 1237 ?MODULE, start_arg(Config, hiber_now), []), 1238 is_in_erlang_hibernate(Pid0), 1239 stop_it(Pid0), 1240 receive 1241 {'EXIT',Pid0,normal} -> ok 1242 after 5000 -> 1243 ct:fail(gen_statem_did_not_die) 1244 end, 1245 1246 {ok,Pid} = 1247 gen_statem:start_link(?MODULE, start_arg(Config, hiber), []), 1248 true = ({current_function,{erlang,hibernate,3}} =/= 1249 erlang:process_info(Pid,current_function)), 1250 hibernating = gen_statem:call(Pid, hibernate_sync), 1251 is_in_erlang_hibernate(Pid), 1252 good_morning = gen_statem:call(Pid, wakeup_sync), 1253 is_not_in_erlang_hibernate(Pid), 1254 hibernating = gen_statem:call(Pid, hibernate_sync), 1255 is_in_erlang_hibernate(Pid), 1256 please_just_five_more = gen_statem:call(Pid, snooze_sync), 1257 is_in_erlang_hibernate(Pid), 1258 good_morning = gen_statem:call(Pid, wakeup_sync), 1259 is_not_in_erlang_hibernate(Pid), 1260 ok = gen_statem:cast(Pid, hibernate_async), 1261 is_in_erlang_hibernate(Pid), 1262 ok = gen_statem:cast(Pid, wakeup_async), 1263 is_not_in_erlang_hibernate(Pid), 1264 ok = gen_statem:cast(Pid, hibernate_async), 1265 is_in_erlang_hibernate(Pid), 1266 ok = gen_statem:cast(Pid, snooze_async), 1267 is_in_erlang_hibernate(Pid), 1268 ok = gen_statem:cast(Pid, wakeup_async), 1269 is_not_in_erlang_hibernate(Pid), 1270 1271 Pid ! hibernate_later, 1272 true = 1273 ({current_function,{erlang,hibernate,3}} =/= 1274 erlang:process_info(Pid, current_function)), 1275 is_in_erlang_hibernate(Pid), 1276 1277 'alive!' = gen_statem:call(Pid, 'alive?'), 1278 true = 1279 ({current_function,{erlang,hibernate,3}} =/= 1280 erlang:process_info(Pid, current_function)), 1281 Pid ! hibernate_now, 1282 is_in_erlang_hibernate(Pid), 1283 1284 'alive!' = gen_statem:call(Pid, 'alive?'), 1285 true = 1286 ({current_function,{erlang,hibernate,3}} =/= 1287 erlang:process_info(Pid, current_function)), 1288 1289 hibernating = gen_statem:call(Pid, hibernate_sync), 1290 is_in_erlang_hibernate(Pid), 1291 good_morning = gen_statem:call(Pid, wakeup_sync), 1292 is_not_in_erlang_hibernate(Pid), 1293 hibernating = gen_statem:call(Pid, hibernate_sync), 1294 is_in_erlang_hibernate(Pid), 1295 please_just_five_more = gen_statem:call(Pid, snooze_sync), 1296 is_in_erlang_hibernate(Pid), 1297 good_morning = gen_statem:call(Pid, wakeup_sync), 1298 is_not_in_erlang_hibernate(Pid), 1299 ok = gen_statem:cast(Pid, hibernate_async), 1300 is_in_erlang_hibernate(Pid), 1301 ok = gen_statem:cast(Pid, wakeup_async), 1302 is_not_in_erlang_hibernate(Pid), 1303 ok = gen_statem:cast(Pid, hibernate_async), 1304 is_in_erlang_hibernate(Pid), 1305 ok = gen_statem:cast(Pid, snooze_async), 1306 is_in_erlang_hibernate(Pid), 1307 ok = gen_statem:cast(Pid, wakeup_async), 1308 is_not_in_erlang_hibernate(Pid), 1309 1310 hibernating = gen_statem:call(Pid, hibernate_sync), 1311 is_in_erlang_hibernate(Pid), 1312 sys:suspend(Pid), 1313 is_in_erlang_hibernate(Pid), 1314 sys:resume(Pid), 1315 is_in_erlang_hibernate(Pid), 1316 receive after 1000 -> ok end, 1317 is_in_erlang_hibernate(Pid), 1318 1319 good_morning = gen_statem:call(Pid, wakeup_sync), 1320 is_not_in_erlang_hibernate(Pid), 1321 stop_it(Pid), 1322 process_flag(trap_exit, OldFl), 1323 receive 1324 {'EXIT',Pid,normal} -> ok 1325 after 5000 -> 1326 ct:fail(gen_statem_did_not_die) 1327 end, 1328 ok = verify_empty_msgq(). 1329 1330%% Auto-hibernation timeout 1331auto_hibernate(Config) -> 1332 OldFl = process_flag(trap_exit, true), 1333 HibernateAfterTimeout = 100, 1334 1335 {ok,Pid} = 1336 gen_statem:start_link( 1337 ?MODULE, start_arg(Config, []), [{hibernate_after, HibernateAfterTimeout}]), 1338 %% After init test 1339 is_not_in_erlang_hibernate(Pid), 1340 timer:sleep(HibernateAfterTimeout), 1341 is_in_erlang_hibernate(Pid), 1342 %% After info test 1343 Pid ! {hping, self()}, 1344 receive 1345 {Pid, hpong} -> 1346 ok 1347 after 1000 -> 1348 ct:fail(info) 1349 end, 1350 is_not_in_erlang_hibernate(Pid), 1351 timer:sleep(HibernateAfterTimeout), 1352 is_in_erlang_hibernate(Pid), 1353 %% After cast test 1354 ok = gen_statem:cast(Pid, {hping, self()}), 1355 receive 1356 {Pid, hpong} -> 1357 ok 1358 after 1000 -> 1359 ct:fail(cast) 1360 end, 1361 is_not_in_erlang_hibernate(Pid), 1362 timer:sleep(HibernateAfterTimeout), 1363 is_in_erlang_hibernate(Pid), 1364 %% After call test 1365 hpong = gen_statem:call(Pid, hping), 1366 is_not_in_erlang_hibernate(Pid), 1367 timer:sleep(HibernateAfterTimeout), 1368 is_in_erlang_hibernate(Pid), 1369 %% Timer test 1 1370 TimerTimeout1 = 50, 1371 ok = gen_statem:call(Pid, {arm_htimer, self(), TimerTimeout1}), 1372 is_not_in_erlang_hibernate(Pid), 1373 timer:sleep(TimerTimeout1), 1374 is_not_in_erlang_hibernate(Pid), 1375 receive 1376 {Pid, htimer_armed} -> 1377 ok 1378 after 1000 -> 1379 ct:fail(timer1) 1380 end, 1381 is_not_in_erlang_hibernate(Pid), 1382 timer:sleep(HibernateAfterTimeout), 1383 is_in_erlang_hibernate(Pid), 1384 %% Timer test 2 1385 TimerTimeout2 = 150, 1386 ok = gen_statem:call(Pid, {arm_htimer, self(), TimerTimeout2}), 1387 is_not_in_erlang_hibernate(Pid), 1388 timer:sleep(HibernateAfterTimeout), 1389 is_in_erlang_hibernate(Pid), 1390 receive 1391 {Pid, htimer_armed} -> 1392 ok 1393 after 1000 -> 1394 ct:fail(timer2) 1395 end, 1396 is_not_in_erlang_hibernate(Pid), 1397 timer:sleep(HibernateAfterTimeout), 1398 is_in_erlang_hibernate(Pid), 1399 stop_it(Pid), 1400 process_flag(trap_exit, OldFl), 1401 receive 1402 {'EXIT',Pid,normal} -> ok 1403 after 5000 -> 1404 ct:fail(gen_statem_did_not_die) 1405 end, 1406 ok = verify_empty_msgq(). 1407 1408is_in_erlang_hibernate(Pid) -> 1409 receive after 1 -> ok end, 1410 is_in_erlang_hibernate_1(200, Pid). 1411 1412is_in_erlang_hibernate_1(0, Pid) -> 1413 ct:log("~p\n", [erlang:process_info(Pid, current_function)]), 1414 ct:fail(not_in_erlang_hibernate_3); 1415is_in_erlang_hibernate_1(N, Pid) -> 1416 {current_function,MFA} = erlang:process_info(Pid, current_function), 1417 case MFA of 1418 {erlang,hibernate,3} -> 1419 ok; 1420 _ -> 1421 receive after 10 -> ok end, 1422 is_in_erlang_hibernate_1(N-1, Pid) 1423 end. 1424 1425is_not_in_erlang_hibernate(Pid) -> 1426 receive after 1 -> ok end, 1427 is_not_in_erlang_hibernate_1(200, Pid). 1428 1429is_not_in_erlang_hibernate_1(0, Pid) -> 1430 ct:log("~p\n", [erlang:process_info(Pid, current_function)]), 1431 ct:fail(not_in_erlang_hibernate_3); 1432is_not_in_erlang_hibernate_1(N, Pid) -> 1433 {current_function,MFA} = erlang:process_info(Pid, current_function), 1434 case MFA of 1435 {erlang,hibernate,3} -> 1436 receive after 10 -> ok end, 1437 is_not_in_erlang_hibernate_1(N-1, Pid); 1438 _ -> 1439 ok 1440 end. 1441 1442 1443enter_loop(_Config) -> 1444 OldFlag = process_flag(trap_exit, true), 1445 1446 dummy_via:reset(), 1447 1448 %% Locally registered process + {local,Name} 1449 {ok,Pid1a} = 1450 proc_lib:start_link(?MODULE, enter_loop, [local,local]), 1451 yes = gen_statem:call(Pid1a, 'alive?'), 1452 stopped = gen_statem:call(Pid1a, stop), 1453 receive 1454 {'EXIT',Pid1a,normal} -> 1455 ok 1456 after 5000 -> 1457 ct:fail(gen_statem_did_not_die) 1458 end, 1459 1460 %% Unregistered process + {local,Name} 1461 {ok,Pid1b} = 1462 proc_lib:start_link(?MODULE, enter_loop, [anon,local]), 1463 receive 1464 {'EXIT',Pid1b,process_not_registered} -> 1465 ok 1466 after 5000 -> 1467 ct:fail(gen_statem_did_not_die) 1468 end, 1469 1470 %% Globally registered process + {global,Name} 1471 {ok,Pid2a} = 1472 proc_lib:start_link(?MODULE, enter_loop, [global,global]), 1473 yes = gen_statem:call(Pid2a, 'alive?'), 1474 stopped = gen_statem:call(Pid2a, stop), 1475 receive 1476 {'EXIT',Pid2a,normal} -> 1477 ok 1478 after 5000 -> 1479 ct:fail(gen_statem_did_not_die) 1480 end, 1481 1482 %% Unregistered process + {global,Name} 1483 {ok,Pid2b} = 1484 proc_lib:start_link(?MODULE, enter_loop, [anon,global]), 1485 receive 1486 {'EXIT',Pid2b,process_not_registered_globally} -> 1487 ok 1488 after 5000 -> 1489 ct:fail(gen_statem_did_not_die) 1490 end, 1491 1492 %% Unregistered process + no name 1493 {ok,Pid3} = 1494 proc_lib:start_link(?MODULE, enter_loop, [anon,anon]), 1495 yes = gen_statem:call(Pid3, 'alive?'), 1496 stopped = gen_statem:call(Pid3, stop), 1497 receive 1498 {'EXIT',Pid3,normal} -> 1499 ok 1500 after 5000 -> 1501 ct:fail(gen_statem_did_not_die) 1502 end, 1503 1504 %% Process not started using proc_lib 1505 Pid4 = spawn_link(gen_statem, enter_loop, [?MODULE,[],state0,[]]), 1506 receive 1507 {'EXIT',Pid4,process_was_not_started_by_proc_lib} -> 1508 ok 1509 after 5000 -> 1510 ct:fail(gen_statem_did_not_die) 1511 end, 1512 1513 %% Make sure I am the parent, ie that ordering a shutdown will 1514 %% result in the process terminating with Reason==shutdown 1515 {ok,Pid5} = 1516 proc_lib:start_link(?MODULE, enter_loop, [anon,anon]), 1517 yes = gen_statem:call(Pid5, 'alive?'), 1518 exit(Pid5, shutdown), 1519 receive 1520 {'EXIT',Pid5,shutdown} -> 1521 ok 1522 after 5000 -> 1523 ct:fail(gen_statem_did_not_die) 1524 end, 1525 1526 %% Make sure gen_statem:enter_loop does not accept {local,Name} 1527 %% when it's another process than the calling one which is 1528 %% registered under that name 1529 register(armitage, self()), 1530 {ok,Pid6a} = 1531 proc_lib:start_link(?MODULE, enter_loop, [anon,local]), 1532 receive 1533 {'EXIT',Pid6a,process_not_registered} -> 1534 ok 1535 after 1000 -> 1536 ct:fail(gen_statem_started) 1537 end, 1538 unregister(armitage), 1539 1540 %% Make sure gen_statem:enter_loop does not accept {global,Name} 1541 %% when it's another process than the calling one which is 1542 %% registered under that name 1543 global:register_name(armitage, self()), 1544 {ok,Pid6b} = 1545 proc_lib:start_link(?MODULE, enter_loop, [anon,global]), 1546 receive 1547 {'EXIT',Pid6b,process_not_registered_globally} -> 1548 ok 1549 after 1000 -> 1550 ct:fail(gen_statem_started) 1551 end, 1552 global:unregister_name(armitage), 1553 1554 dummy_via:register_name(armitage, self()), 1555 {ok,Pid6c} = 1556 proc_lib:start_link(?MODULE, enter_loop, [anon,via]), 1557 receive 1558 {'EXIT',Pid6c,{process_not_registered_via,dummy_via}} -> 1559 ok 1560 after 1000 -> 1561 ct:fail( 1562 {gen_statem_started, 1563 process_info(self(), messages)}) 1564 end, 1565 dummy_via:unregister_name(armitage), 1566 1567 process_flag(trap_exit, OldFlag), 1568 ok = verify_empty_msgq(). 1569 1570enter_loop(Reg1, Reg2) -> 1571 process_flag(trap_exit, true), 1572 case Reg1 of 1573 local -> register(armitage, self()); 1574 global -> global:register_name(armitage, self()); 1575 via -> dummy_via:register_name(armitage, self()); 1576 anon -> ignore 1577 end, 1578 proc_lib:init_ack({ok, self()}), 1579 case Reg2 of 1580 local -> 1581 gen_statem:enter_loop( 1582 ?MODULE, [], state0, [], {local,armitage}); 1583 global -> 1584 gen_statem:enter_loop( 1585 ?MODULE, [], state0, [], {global,armitage}); 1586 via -> 1587 gen_statem:enter_loop( 1588 ?MODULE, [], state0, [], {via, dummy_via, armitage}); 1589 anon -> 1590 gen_statem:enter_loop(?MODULE, [], state0, []) 1591 end. 1592 1593undef_code_change(_Config) -> 1594 {ok, Statem} = gen_statem:start(oc_statem, [], []), 1595 {error, {'EXIT', 1596 {undef, [{oc_statem, code_change, [_, _, _, _], _}|_]}}} 1597 = fake_upgrade(Statem, oc_statem). 1598 1599fake_upgrade(Pid, Mod) -> 1600 sys:suspend(Pid), 1601 sys:replace_state(Pid, fun(State) -> {new, State} end), 1602 Ret = sys:change_code(Pid, Mod, old_vsn, []), 1603 ok = sys:resume(Pid), 1604 Ret. 1605 1606undef_terminate1(_Config) -> 1607 {ok, Statem} = gen_statem:start(oc_statem, [], []), 1608 MRef = monitor(process, Statem), 1609 ok = gen_statem:stop(Statem), 1610 verify_down(Statem, MRef, normal), 1611 ok. 1612 1613undef_terminate2(_Config) -> 1614 Reason = {error, test}, 1615 {ok, Statem} = oc_statem:start(), 1616 MRef = monitor(process, Statem), 1617 ok = gen_statem:stop(Statem, Reason, infinity), 1618 verify_down(Statem, MRef, Reason). 1619 1620undef_in_terminate(_Config) -> 1621 Data = {undef_in_terminate, {?MODULE, terminate}}, 1622 {ok, Statem} = gen_statem:start(?MODULE, {data, Data}, []), 1623 try 1624 gen_statem:stop(Statem), 1625 ct:fail(should_crash) 1626 catch 1627 exit:{undef, [{?MODULE, terminate, _, _}|_]} -> 1628 ok 1629 end. 1630 1631verify_down(Statem, MRef, Reason) -> 1632 receive 1633 {'DOWN', MRef, process, Statem, Reason} -> 1634 ok 1635 after 5000 -> 1636 ct:fail(default_terminate_failed) 1637 end. 1638 1639%% Test the order for multiple {next_event,T,C} 1640next_events(Config) -> 1641 {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), 1642 ok = gen_statem:cast(Pid, next_event), 1643 {state,next_events,[]} = gen_statem:call(Pid, get), 1644 ok = gen_statem:stop(Pid), 1645 false = erlang:is_process_alive(Pid), 1646 noproc = 1647 ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason). 1648 1649 1650%% 1651%% Functionality check 1652%% 1653 1654wfor(Msg) -> 1655 receive 1656 Msg -> ok 1657 after 5000 -> 1658 error(timeout) 1659 end. 1660 1661 1662stop_it(STM) -> 1663 stopped = gen_statem:call(STM, stop), 1664 check_stopped(STM). 1665 1666 1667check_stopped(STM) -> 1668 Call = there_you_are, 1669 {_,{gen_statem,call,[_,Call,infinity]}} = 1670 ?EXPECT_FAILURE(gen_statem:call(STM, Call), Reason), 1671 ok. 1672 1673 1674do_func_test(STM) -> 1675 ok = gen_statem:cast(STM, {'alive?',self()}), 1676 wfor(yes), 1677 ok = do_connect(STM), 1678 ok = gen_statem:cast(STM, {'alive?',self()}), 1679 wfor(yes), 1680 ?t:do_times(3, ?MODULE, do_msg, [STM]), 1681 ok = gen_statem:cast(STM, {'alive?',self()}), 1682 wfor(yes), 1683 ok = do_disconnect(STM), 1684 ok = gen_statem:cast(STM, {'alive?',self()}), 1685 wfor(yes), 1686 ok. 1687 1688 1689do_connect(STM) -> 1690 check_state(STM, idle), 1691 gen_statem:cast(STM, {connect,self()}), 1692 wfor(accept), 1693 check_state(STM, wfor_conf), 1694 Tag = make_ref(), 1695 gen_statem:cast(STM, {ping,self(),Tag}), 1696 gen_statem:cast(STM, confirm), 1697 wfor({pong,Tag}), 1698 check_state(STM, connected), 1699 ok. 1700 1701do_msg(STM) -> 1702 check_state(STM, connected), 1703 R = make_ref(), 1704 ok = gen_statem:cast(STM, {msg,self(),R}), 1705 wfor({ack,R}). 1706 1707 1708do_disconnect(STM) -> 1709 ok = gen_statem:cast(STM, disconnect), 1710 check_state(STM, idle). 1711 1712check_state(STM, State) -> 1713 case gen_statem:call(STM, get) of 1714 {state, State, _} -> ok 1715 end. 1716 1717do_sync_func_test(STM) -> 1718 yes = gen_statem:call(STM, 'alive?'), 1719 ok = do_sync_connect(STM), 1720 yes = gen_statem:call(STM, 'alive?'), 1721 ?t:do_times(3, ?MODULE, do_sync_msg, [STM]), 1722 yes = gen_statem:call(STM, 'alive?'), 1723 ok = do_sync_disconnect(STM), 1724 yes = gen_statem:call(STM, 'alive?'), 1725 check_state(STM, idle), 1726 ok = gen_statem:call(STM, {timeout,200}), 1727 yes = gen_statem:call(STM, 'alive?'), 1728 check_state(STM, idle), 1729 ok. 1730 1731 1732do_sync_connect(STM) -> 1733 check_state(STM, idle), 1734 accept = gen_statem:call(STM, connect), 1735 check_state(STM, wfor_conf), 1736 Tag = make_ref(), 1737 gen_statem:cast(STM, {ping,self(),Tag}), 1738 yes = gen_statem:call(STM, confirm), 1739 wfor({pong,Tag}), 1740 check_state(STM, connected), 1741 ok. 1742 1743do_sync_msg(STM) -> 1744 check_state(STM, connected), 1745 R = make_ref(), 1746 {ack,R} = gen_statem:call(STM, {msg,R}), 1747 ok. 1748 1749do_sync_disconnect(STM) -> 1750 yes = gen_statem:call(STM, disconnect), 1751 check_state(STM, idle). 1752 1753 1754verify_empty_msgq() -> 1755 [] = flush(), 1756 ok. 1757 1758start_arg(Config, Arg) -> 1759 case lists:keyfind(callback_mode, 1, Config) of 1760 {_,CallbackMode} -> 1761 {callback_mode,CallbackMode,Arg}; 1762 false -> 1763 Arg 1764 end. 1765 1766%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1767%% 1768%% The State Machine 1769%% 1770%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1771 1772init(ignore) -> 1773 ignore; 1774init(stop) -> 1775 {stop,stopped}; 1776init(stop_shutdown) -> 1777 {stop,shutdown}; 1778init(sleep) -> 1779 ?t:sleep(1000), 1780 {ok,idle,data}; 1781init(hiber) -> 1782 {ok,hiber_idle,[]}; 1783init(hiber_now) -> 1784 {ok,hiber_idle,[],[hibernate]}; 1785init({data, Data}) -> 1786 {ok,idle,Data}; 1787init({callback_mode,CallbackMode,Arg}) -> 1788 ets:new(?MODULE, [named_table,private]), 1789 ets:insert(?MODULE, {callback_mode,CallbackMode}), 1790 init(Arg); 1791init({map_statem,#{init := Init}=Machine,Modes}) -> 1792 ets:new(?MODULE, [named_table,private]), 1793 ets:insert(?MODULE, {callback_mode,[handle_event_function|Modes]}), 1794 case Init() of 1795 {ok,State,Data,Ops} -> 1796 {ok,State,[Data|Machine],Ops}; 1797 {ok,State,Data} -> 1798 {ok,State,[Data|Machine]}; 1799 Other -> 1800 Other 1801 end; 1802init([]) -> 1803 {ok,idle,data}. 1804 1805callback_mode() -> 1806 try ets:lookup(?MODULE, callback_mode) of 1807 [{callback_mode,CallbackMode}] -> 1808 CallbackMode 1809 catch 1810 error:badarg -> 1811 state_functions 1812 end. 1813 1814terminate(_, _State, crash_terminate) -> 1815 exit({crash,terminate}); 1816terminate(_, _State, {undef_in_terminate, {Mod, Fun}}) -> 1817 Mod:Fun(), 1818 ok; 1819terminate({From,stopped}, State, _Data) -> 1820 From ! {self(),{stopped,State}}, 1821 ok; 1822terminate(_Reason, _State, _Data) -> 1823 ok. 1824 1825 1826%% State functions 1827 1828idle(info, {hping,Pid}, _Data) -> 1829 Pid ! {self(), hpong}, 1830 keep_state_and_data; 1831idle(cast, {hping,Pid}, Data) -> 1832 Pid ! {self(), hpong}, 1833 {keep_state, Data}; 1834idle({call, From}, hping, _Data) -> 1835 {keep_state_and_data, [{reply, From, hpong}]}; 1836idle({call, From}, {arm_htimer, Pid, Timeout}, _Data) -> 1837 {keep_state_and_data, [{reply, From, ok}, {timeout, Timeout, {arm_htimer, Pid}}]}; 1838idle(timeout, {arm_htimer, Pid}, _Data) -> 1839 Pid ! {self(), htimer_armed}, 1840 keep_state_and_data; 1841idle(cast, {connect,Pid}, Data) -> 1842 Pid ! accept, 1843 {next_state,wfor_conf,Data,infinity}; % NoOp timeout just to test API 1844idle({call,From}, connect, Data) -> 1845 gen_statem:reply(From, accept), 1846 {next_state,wfor_conf,Data,infinity}; % NoOp timeout just to test API 1847idle({call,_From}, badreturn, _Data) -> 1848 badreturn; 1849idle({call,_From}, badaction, Data) -> 1850 {keep_state, Data, [badaction]}; 1851idle({call,_From}, {badtimeout,_,_} = BadTimeout, Data) -> 1852 {keep_state, Data, BadTimeout}; 1853idle({call,From}, {delayed_answer,T}, Data) -> 1854 receive 1855 after T -> 1856 gen_statem:reply({reply,From,delayed}), 1857 throw({keep_state,Data}) 1858 end; 1859idle({call,From}, {timeout,Time}, _Data) -> 1860 AbsTime = erlang:monotonic_time(millisecond) + Time, 1861 {next_state,timeout,{From,Time}, 1862 {timeout,AbsTime,idle,[{abs,true}]}}; 1863idle(cast, next_event, _Data) -> 1864 {next_state,next_events,[a,b,c], 1865 [{next_event,internal,a}, 1866 {next_event,internal,b}, 1867 {next_event,internal,c}]}; 1868idle(Type, Content, Data) -> 1869 case handle_common_events(Type, Content, idle, Data) of 1870 undefined -> 1871 case Type of 1872 {call,From} -> 1873 throw({keep_state,Data,[{reply,From,'eh?'}]}); 1874 _ -> 1875 throw( 1876 {stop,{unexpected,idle,Type,Content}}) 1877 end; 1878 Result -> 1879 Result 1880 end. 1881 1882timeout(timeout, idle, {From,Time}) -> 1883 TRef = erlang:start_timer(Time, self(), ok), 1884 {keep_state,{From,TRef},0}; % Immediate timeout 0 1885timeout(timeout, 0, {From,TRef}) -> 1886 {next_state,timeout2,{From,TRef}, 1887 [{timeout,1,should_be_cancelled}, 1888 postpone]}; % Should cancel state timeout 1889timeout(_, _, _) -> 1890 keep_state_and_data. 1891 1892timeout2(timeout, 0, _) -> 1893 keep_state_and_data; 1894timeout2(timeout, Reason, _) -> 1895 {stop,Reason}; 1896timeout2(info, {timeout,TRef,Result}, {From,TRef}) -> 1897 gen_statem:reply([{reply,From,Result}]), 1898 {next_state,idle,state}; 1899timeout2(_, _, _) -> 1900 {keep_state_and_data,[]}. 1901 1902wfor_conf({call,From}, confirm, Data) -> 1903 {next_state,connected,Data, 1904 {reply,From,yes}}; 1905wfor_conf(cast, {ping,_,_}, _) -> 1906 {keep_state_and_data,[postpone]}; 1907wfor_conf(cast, confirm, Data) -> 1908 {next_state,connected,Data}; 1909wfor_conf(Type, Content, Data) -> 1910 case handle_common_events(Type, Content, wfor_conf, Data) of 1911 undefined -> 1912 case Type of 1913 {call,From} -> 1914 {next_state,idle,Data, 1915 [{reply,From,'eh?'}]}; 1916 _ -> 1917 throw(keep_state_and_data) 1918 end; 1919 Result -> 1920 Result 1921 end. 1922 1923connected({call,From}, {msg,Ref}, Data) -> 1924 {keep_state,Data, 1925 {reply,From,{ack,Ref}}}; 1926connected(cast, {msg,From,Ref}, Data) -> 1927 From ! {ack,Ref}, 1928 {keep_state,Data}; 1929connected({call,From}, disconnect, Data) -> 1930 {next_state,idle,Data, 1931 [{reply,From,yes}]}; 1932connected(cast, disconnect, Data) -> 1933 {next_state,idle,Data}; 1934connected(cast, {ping,Pid,Tag}, Data) -> 1935 Pid ! {pong,Tag}, 1936 {keep_state,Data}; 1937connected(Type, Content, Data) -> 1938 case handle_common_events(Type, Content, connected, Data) of 1939 undefined -> 1940 case Type of 1941 {call,From} -> 1942 {keep_state,Data, 1943 [{reply,From,'eh?'}]}; 1944 _ -> 1945 {keep_state,Data} 1946 end; 1947 Result -> 1948 Result 1949 end. 1950 1951state0({call,From}, stop, Data) -> 1952 {stop_and_reply,normal,[{reply,From,stopped}],Data}; 1953state0(Type, Content, Data) -> 1954 case handle_common_events(Type, Content, state0, Data) of 1955 undefined -> 1956 {keep_state,Data}; 1957 Result -> 1958 Result 1959 end. 1960 1961hiber_idle({call,From}, 'alive?', Data) -> 1962 {keep_state,Data, 1963 [{reply,From,'alive!'}]}; 1964hiber_idle({call,From}, hibernate_sync, Data) -> 1965 {next_state,hiber_wakeup,Data, 1966 [{reply,From,hibernating}, 1967 hibernate]}; 1968hiber_idle(info, hibernate_later, _) -> 1969 Tref = erlang:start_timer(1000, self(), hibernate), 1970 {keep_state,Tref}; 1971hiber_idle(info, hibernate_now, Data) -> 1972 {keep_state,Data, 1973 [hibernate]}; 1974hiber_idle(info, {timeout,Tref,hibernate}, Tref) -> 1975 {keep_state,[], 1976 [hibernate]}; 1977hiber_idle(cast, hibernate_async, Data) -> 1978 {next_state,hiber_wakeup,Data, 1979 [hibernate]}; 1980hiber_idle(Type, Content, Data) -> 1981 case handle_common_events(Type, Content, hiber_idle, Data) of 1982 undefined -> 1983 {keep_state,Data}; 1984 Result -> 1985 Result 1986 end. 1987 1988hiber_wakeup({call,From}, wakeup_sync, Data) -> 1989 {next_state,hiber_idle,Data, 1990 [{reply,From,good_morning}]}; 1991hiber_wakeup({call,From}, snooze_sync, Data) -> 1992 {keep_state,Data, 1993 [{reply,From,please_just_five_more}, 1994 hibernate]}; 1995hiber_wakeup(cast, wakeup_async, Data) -> 1996 {next_state,hiber_idle,Data}; 1997hiber_wakeup(cast, snooze_async, Data) -> 1998 {keep_state,Data, 1999 [hibernate]}; 2000hiber_wakeup(Type, Content, Data) -> 2001 case handle_common_events(Type, Content, hiber_wakeup, Data) of 2002 undefined -> 2003 {keep_state,Data}; 2004 Result -> 2005 Result 2006 end. 2007 2008next_events(internal, Msg, [Msg|Msgs]) -> 2009 {keep_state,Msgs}; 2010next_events(Type, Content, Data) -> 2011 case handle_common_events(Type, Content, next_events, Data) of 2012 undefined -> 2013 {keep_state,Data}; 2014 Result -> 2015 Result 2016 end. 2017 2018 2019handle_common_events({call,From}, get_callback_mode, _, _) -> 2020 {keep_state_and_data,{reply,From,state_functions}}; 2021handle_common_events({call,From}, get, State, Data) -> 2022 {keep_state,Data, 2023 [{reply,From,{state,State,Data}}]}; 2024handle_common_events(cast, {get,Pid}, State, Data) -> 2025 Pid ! {state,State,Data}, 2026 {keep_state,Data}; 2027handle_common_events({call,From}, stop, _, Data) -> 2028 {stop_and_reply,normal,[{reply,From,stopped}],Data}; 2029handle_common_events(cast, stop, _, _) -> 2030 stop; 2031handle_common_events({call,From}, {stop,Reason}, _, Data) -> 2032 {stop_and_reply,Reason,{reply,From,stopped},Data}; 2033handle_common_events(cast, {stop,Reason}, _, _) -> 2034 {stop,Reason}; 2035handle_common_events({call,From}, 'alive?', _, Data) -> 2036 {keep_state,Data, 2037 [{reply,From,yes}]}; 2038handle_common_events(cast, {'alive?',Pid}, _, Data) -> 2039 Pid ! yes, 2040 {keep_state,Data}; 2041handle_common_events(_, _, _, _) -> 2042 undefined. 2043 2044handle_event({call,From}, get_callback_mode, _, _) -> 2045 {keep_state_and_data,{reply,From,handle_event_function}}; 2046%% Wrapper state machine that uses a map state machine spec 2047handle_event( 2048 Type, Event, State, [Data|Machine]) 2049 when is_map(Machine) -> 2050 #{State := HandleEvent} = Machine, 2051 case 2052 try HandleEvent(Type, Event, Data) of 2053 Result -> 2054 Result 2055 catch 2056 Result -> 2057 Result 2058 end of 2059 {stop,Reason,NewData} -> 2060 {stop,Reason,[NewData|Machine]}; 2061 {next_state,NewState,NewData} -> 2062 {next_state,NewState,[NewData|Machine]}; 2063 {next_state,NewState,NewData,Ops} -> 2064 {next_state,NewState,[NewData|Machine],Ops}; 2065 {keep_state,NewData} -> 2066 {keep_state,[NewData|Machine]}; 2067 {keep_state,NewData,Ops} -> 2068 {keep_state,[NewData|Machine],Ops}; 2069 {repeat_state,NewData} -> 2070 {repeat_state,[NewData|Machine]}; 2071 {repeat_state,NewData,Ops} -> 2072 {repeat_state,[NewData|Machine],Ops}; 2073 Other -> 2074 Other 2075 end; 2076%% 2077%% Dispatcher to test callback_mode handle_event_function 2078%% 2079%% Wrap the state in a 1 element list just to test non-atom 2080%% states. Note that the state from init/1 is not wrapped 2081%% so both atom and non-atom states are tested. 2082handle_event(Type, Event, State, Data) -> 2083 StateName = unwrap_state(State), 2084 try ?MODULE:StateName(Type, Event, Data) of 2085 Result -> 2086 wrap_result(Result) 2087 catch 2088 throw:Result:Stacktrace -> 2089 erlang:raise( 2090 throw, wrap_result(Result), Stacktrace) 2091 end. 2092 2093unwrap_state([State]) -> 2094 State; 2095unwrap_state(State) -> 2096 State. 2097 2098wrap_result(Result) -> 2099 case Result of 2100 {next_state,NewState,NewData} -> 2101 {next_state,[NewState],NewData}; 2102 {next_state,NewState,NewData,StateOps} -> 2103 {next_state,[NewState],NewData,StateOps}; 2104 Other -> 2105 Other 2106 end. 2107 2108 2109 2110code_change(OldVsn, State, Data, CallbackMode) -> 2111 io:format( 2112 "code_change(~p, ~p, ~p, ~p)~n", [OldVsn,State,Data,CallbackMode]), 2113 ets:insert(?MODULE, {callback_mode,CallbackMode}), 2114 io:format( 2115 "code_change(~p, ~p, ~p, ~p)~n", [OldVsn,State,Data,CallbackMode]), 2116 {ok,State,{OldVsn,Data,CallbackMode}}. 2117 2118format_status(terminate, [_Pdict,State,Data]) -> 2119 {formatted,State,Data}; 2120format_status(normal, [_Pdict,_State,_Data]) -> 2121 [format_status_called]. 2122 2123flush() -> 2124 receive 2125 Msg -> 2126 [Msg|flush()] 2127 after 500 -> 2128 [] 2129 end. 2130