-module(wait_helper).
-export([wait_until/2, wait_until/3]).
%% From mongoose_helper
%% @see wait_until/3
wait_until(Fun, ExpectedValue) ->
wait_until(Fun, ExpectedValue, #{}).
%% @doc Waits `TimeLeft' for `Fun' to return `ExpectedValue'
%%
%% If the result of `Fun' matches `ExpectedValue', returns `{ok, ExpectedValue}'
%%
%% If no value is returned or the result doesn't match `ExpectedValue',
%% returns one of the following:
%% <li>`{Name, History}', if `#{name => Name}' is is passed in the `Opts'</li>
%% <li>`{timeout, History}' otherwise</li>
%%
%% Example: wait_until(fun () -> ... end, SomeVal, #{time_left => timer:seconds(2)})
wait_until(Fun, ExpectedValue, Opts) ->
Defaults = #{
validator => fun(NewValue) -> ExpectedValue =:= NewValue end,
expected_value => ExpectedValue,
time_left => timer:seconds(5),
sleep_time => 100,
history => [],
name => timeout
},
do_wait_until(Fun, maps:merge(Defaults, Opts)).
do_wait_until(
_Fun,
#{
expected_value := ExpectedValue,
time_left := TimeLeft,
history := History,
name := Name
} = Opts
) when TimeLeft =< 0 ->
error({Name, ExpectedValue, simplify_history(lists:reverse(History), 1), on_error(Opts)});
do_wait_until(Fun, #{validator := Validator} = Opts) ->
try Fun() of
Value ->
case Validator(Value) of
true -> {ok, Value};
_ -> wait_and_continue(Fun, Value, Opts)
end
catch
Error:Reason:Stacktrace ->
wait_and_continue(Fun, {Error, Reason, Stacktrace}, Opts)
end.
on_error(#{on_error := F}) ->
F();
on_error(_Opts) ->
ok.
simplify_history([H | [H | _] = T], Times) ->
simplify_history(T, Times + 1);
simplify_history([H | T], Times) ->
[{times, Times, H} | simplify_history(T, 1)];
simplify_history([], 1) ->
[].
wait_and_continue(
Fun,
FunResult,
#{
time_left := TimeLeft,
sleep_time := SleepTime,
history := History
} = Opts
) ->
timer:sleep(SleepTime),
do_wait_until(Fun, Opts#{
time_left => TimeLeft - SleepTime,
history => [FunResult | History]
}).