Current section
Files
Jump to
Current section
Files
src/hoax_invocation.erl
-module(hoax_invocation).
-export([handle/3]).
-include("hoax_int.hrl").
handle(M, F, Args) ->
case hoax_tab:lookup_expectations({M, F, length(Args)}) of
[] ->
erlang:error({unexpected_invocation, hoax_fmt:fmt({M, F, Args})});
Records ->
case find_matching_args(Args, Records) of
false ->
unexpected_arguments(M, F, Args, Records);
#expectation{call_count=X,expected_count=X,expected_args=ExpectedArgs} ->
erlang:error({too_many_invocations, X+1, hoax_fmt:fmt({M, F, ExpectedArgs})});
#expectation{action = Action} = Record ->
hoax_tab:record_invocation(Record, Args),
perform(Action, Args)
end
end.
% As the most-common case, handle a single expectation separately to provide better error detail
unexpected_arguments(M, F, Args, [#expectation{expected_args = Expected}]) ->
Arity = length(Args),
FuncRep = flatfmt("~s:~s/~b", [M, F, Arity]),
ArgsNotMatched = lists:foldl(
fun ({_, ExpectedArg, ActualArg}, Acc) when ExpectedArg == ActualArg ->
Acc;
({Seq, ExpectedArg, ActualArg}, Acc) ->
Info = flatfmt("parameter ~b expected ~p but got ~p", [Seq, ExpectedArg, ActualArg]),
[Info | Acc]
end,
[],
lists:zip3(lists:seq(1, Arity), Expected, Args)
),
erlang:error({unexpected_arguments, [FuncRep | lists:reverse(ArgsNotMatched)]});
unexpected_arguments(M, F, Args, Records) when length(Records) > 1 ->
erlang:error({unexpected_arguments, hoax_fmt:fmt({M, F, Args})}).
find_matching_args(Args, Records) ->
keyfind(Args, Records).
keyfind(ActualArgs, [ Expectation = #expectation{expected_args = ExpectedArgs} | Rest ]) ->
case replace_wildcards(ActualArgs, ExpectedArgs) of
ActualArgs -> Expectation;
_ -> keyfind(ActualArgs, Rest)
end;
keyfind(_, []) ->
false.
perform(default, _) -> '$_hoax_default_return_$';
perform(Fun, Args) when is_function(Fun) ->
erlang:apply(Fun, Args);
perform({return, Value}, _) -> Value;
perform({Error, Reason}, _) -> erlang:Error(Reason).
replace_wildcards(ActualArgs, ExpectedArgs) ->
lists:zipwith(fun replace_wildcard/2, ActualArgs, ExpectedArgs).
replace_wildcard(Actual, '_') -> Actual;
replace_wildcard(_, Expected) -> Expected.
flatfmt(Fmt, Args) ->
lists:flatten(io_lib:format(Fmt, Args)).