From 3784ea746710fd7a1c9647f9ee97e903322baf1b Mon Sep 17 00:00:00 2001 From: Mark Allen Date: Fri, 24 Feb 2017 15:25:54 -0600 Subject: [PATCH] Add unit tests for gen_fsm/statem crash msgs Don't use dirty timeout for gen_statem That was introduced in OTP 19.2, and causes failures in tests on OTP 19 and 19.1 Fix statem for all 19.x releases --- rebar.config | 3 +- test/crash_fsm.erl | 34 ++++++++++++++ test/crash_statem.erl | 42 +++++++++++++++++ test/lager_test_backend.erl | 93 +++++++++++++++++++++++++++++++++++++ 4 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 test/crash_fsm.erl create mode 100644 test/crash_statem.erl diff --git a/rebar.config b/rebar.config index d6c23a1..62bceca 100644 --- a/rebar.config +++ b/rebar.config @@ -21,6 +21,7 @@ {erl_opts, [ {lager_extra_sinks, ['__lager_test_sink']}, + {platform_define, "19", test_statem}, debug_info, report, verbose, @@ -47,7 +48,7 @@ ]}. {deps, [ - {goldrush, ".*", {git, "https://github.com/basho/goldrush.git", {tag, "0.1.9"}}} + {goldrush, ".*", {git, "https://github.com/DeadZen/goldrush.git", {tag, "0.1.9"}}} ]}. {xref_checks, []}. diff --git a/test/crash_fsm.erl b/test/crash_fsm.erl new file mode 100644 index 0000000..dc46e7a --- /dev/null +++ b/test/crash_fsm.erl @@ -0,0 +1,34 @@ +-module(crash_fsm). +-behaviour(gen_fsm). + +-export([start/0, crash/0, state1/2]). + +%% gen_fsm callbacks +-export([init/1, handle_event/3, handle_sync_event/4, handle_info/3, + terminate/3, code_change/4]). + +-record(state, {}). + +start() -> + gen_fsm:start({local, ?MODULE}, ?MODULE, [], []). + +crash() -> + gen_fsm:sync_send_event(?MODULE, crash). + +%% gen_fsm callbacks +init([]) -> + {ok, state1, #state{}}. + +handle_event(_Event, StateName, State) -> + {next_state, StateName, State}. +handle_sync_event(_Event, _From, StateName, State) -> + Reply = ok, + {reply, Reply, StateName, State}. +handle_info(_Info, StateName, State) -> + {next_state, StateName, State}. +terminate(_Reason, _StateName, _State) -> + ok. +code_change(_OldVersion, StateName, State, _Extra) -> + {ok, StateName, State}. + +state1(_Event, S) -> {next_state, state1, S}. diff --git a/test/crash_statem.erl b/test/crash_statem.erl new file mode 100644 index 0000000..a6216d8 --- /dev/null +++ b/test/crash_statem.erl @@ -0,0 +1,42 @@ +-module(crash_statem). +%% we're only going to compile this on OTP 19+ +-ifdef(test_statem). +-behaviour(gen_statem). + +-export([ + start/0, + crash/0, + handle_event/4 +]). + +-export([terminate/3,code_change/4,init/1,callback_mode/0]). + +start() -> + gen_statem:start({local,?MODULE}, ?MODULE, [], []). + +crash() -> + gen_statem:call(?MODULE, boom). + +%% Mandatory callback functions +terminate(_Reason, _State, _Data) -> ok. +code_change(_Vsn, State, Data, _Extra) -> {ok,State,Data}. +init([]) -> + %% insert rant here about breaking changes in minor versions... + case erlang:system_info(version) of + "8.0" -> {callback_mode(),state1,undefined}; + _ -> {ok, state1, undefined} + end. +callback_mode() -> handle_event_function. + +%%% state callback(s) + +handle_event({call, _From}, state1, _Arg, Data) -> + {next_state, state1, Data}. + +-else. +-export([start/0, crash/0]). + +start() -> ok. +crash() -> ok. + +-endif. diff --git a/test/lager_test_backend.erl b/test/lager_test_backend.erl index d1370fe..c9102b8 100644 --- a/test/lager_test_backend.erl +++ b/test/lager_test_backend.erl @@ -913,6 +913,99 @@ error_logger_redirect_crash_cleanup(_Sink) -> end, error_logger:tty(true). +crash_fsm_setup() -> + error_logger:tty(false), + application:load(lager), + application:set_env(lager, error_logger_redirect, true), + application:set_env(lager, handlers, [{?MODULE, error}]), + lager:start(), + crash_fsm:start(), + crash_statem:start(), + lager:log(error, self(), "flush flush"), + timer:sleep(100), + gen_event:call(lager_event, ?MODULE, flush), + lager_event. + +crash_fsm_sink_setup() -> + ErrorSink = error_logger_lager_event, + error_logger:tty(false), + application:load(lager), + application:set_env(lager, error_logger_redirect, true), + application:set_env(lager, handlers, []), + application:set_env(lager, extra_sinks, [{ErrorSink, [{handlers, [{?MODULE, error}]}]}]), + lager:start(), + crash_fsm:start(), + crash_statem:start(), + lager:log(ErrorSink, error, self(), "flush flush", []), + timer:sleep(100), + flush(ErrorSink), + ErrorSink. + +crash_fsm_cleanup(_Sink) -> + application:stop(lager), + application:stop(goldrush), + application:unset_env(lager, extra_sinks), + lists:foreach(fun(N) -> kill_crasher(N) end, [crash_fsm, crash_statem]), + error_logger:tty(true). + +kill_crasher(RegName) -> + case whereis(RegName) of + undefined -> ok; + Pid -> exit(Pid, kill) + end. + +spawn_fsm_crash(Module) -> + spawn(fun() -> Module:crash() end), + timer:sleep(100), + _ = gen_event:which_handlers(error_logger), + ok. + +crash_fsm_test_() -> + TestBody = fun(Name, FsmModule, Expected) -> + fun(Sink) -> + {Name, + fun() -> + case {FsmModule =:= crash_statem, lager_util:otp_version() < 19} of + {true, true} -> ok; + _ -> + Pid = whereis(FsmModule), + spawn_fsm_crash(FsmModule), + {Level, _, Msg, Metadata} = pop(Sink), + test_body(Expected, lists:flatten(Msg)), + ?assertEqual(Pid, proplists:get_value(pid, Metadata)), + ?assertEqual(lager_util:level_to_num(error), Level) + end + end + } + end + end, + Tests = [ + fun(Sink) -> + {"again, there is nothing up my sleeve", + fun() -> + ?assertEqual(undefined, pop(Sink)), + ?assertEqual(0, count(Sink)) + end + } + end, + + TestBody("gen_fsm crash", crash_fsm, "gen_fsm crash_fsm in state state1 terminated with reason: call to undefined function crash_fsm:state1/3 from gen_fsm:handle_msg/7"), + TestBody("gen_statem crash", crash_statem, "gen_statem crash_statem in state state1 terminated with reason: no function clause matching crash_statem:handle") + ], + + {"FSM crash output tests", [ + {"Default sink", + {foreach, + fun crash_fsm_setup/0, + fun crash_fsm_cleanup/1, + Tests}}, + {"Error logger sink", + {foreach, + fun crash_fsm_sink_setup/0, + fun crash_fsm_cleanup/1, + Tests}} + ]}. + error_logger_redirect_crash_test_() -> TestBody=fun(Name,CrashReason,Expected) -> fun(Sink) ->