Skip to content

Instantly share code, notes, and snippets.

@rlipscombe
Created August 15, 2019 17:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rlipscombe/a0970b147b35cfea6330533431e3de4c to your computer and use it in GitHub Desktop.
Save rlipscombe/a0970b147b35cfea6330533431e3de4c to your computer and use it in GitHub Desktop.
-module(assert_timers).
-export([start_trace/0, stop_trace/0]).
-export([no_timers/1, has_timer/1]).
%% @doc use a dbg handler to track active timers; see http://stackoverflow.com/a/4059587/8446
start_trace() ->
ets:new(timer_trace, [public, named_table, bag]),
Fun = fun({'trace', Pid, 'call', {erlang, send_after, [Time, Dest, Msg]}} = _Msg, State) ->
lager:debug("~p erlang:send_after(~p, ~p, ~p)", [Pid, Time, Dest, Msg]),
State;
({'trace', Pid, 'return_from', {erlang, send_after, 3}, Ref} = _Msg, State) ->
lager:debug("~p erlang:send_after/3 returns ~p", [Pid, Ref]),
ets:insert(timer_trace, {Pid, Ref}),
State;
({'trace', Pid, 'call', {erlang, cancel_timer, [Ref]}} = _Msg, State) ->
lager:debug("~p erlang:cancel_timer(~p)", [Pid, Ref]),
ets:delete_object(timer_trace, {Pid, Ref}),
State;
({'trace', _Pid, 'return_from', {erlang, cancel_timer, 1}, _Result} = _Msg, State) ->
% Ignore it.
State;
(_Msg, State) ->
% We weren't expecting this message...
lager:warning("~p", [_Msg]),
State
end,
{ok, _} = dbg:tracer(process, {Fun, no_state}),
{ok, _} = dbg:p(all, c),
{ok, _} = dbg:tpl(erlang, send_after, [{'_', [], [{'return_trace'}]}]),
{ok, _} = dbg:tpl(erlang, cancel_timer, [{'_', [], [{'return_trace'}]}]),
ok.
stop_trace() ->
dbg:stop_clear().
no_timers(Pid) ->
assert_eventually(fun() ->
[] =:= ets:match(timer_trace, {Pid, '$1'})
end, no_timers).
has_timer(Pid) ->
assert_eventually(fun() ->
[] =/= ets:match(timer_trace, {Pid, '$1'})
end, has_timer).
assert_eventually(Fun, Error) ->
% eunit default test timeout is 5 seconds, so don't wait for *that* long.
assert_eventually(Fun, Error, 10, 400).
assert_eventually(_Fun, Error, 0, _) ->
erlang:error(Error);
assert_eventually(Fun, Error, Count, Interval) ->
case Fun() of
true -> ok;
_ ->
timer:sleep(Interval),
assert_eventually(Fun, Error, Count - 1, Interval)
end.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment