blob: 04017e5f0bac06b0a6a4b408a44784a4c920429a [file] [log] [blame]
%%%============================================================================
%%% Licensed under the Apache License, Version 2.0 (the "License");
%%% you may not use this file except in compliance with the License.
%%% You may obtain a copy of the License at
%%%
%%% http://www.apache.org/licenses/LICENSE-2.0
%%%
%%% Unless required by applicable law or agreed to in writing, software
%%% distributed under the License is distributed on an "AS IS" BASIS,
%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%%% See the License for the specific language governing permissions and
%%% limitations under the License.
%%%============================================================================
%%% @private
%%% @doc Provides expectation processing functions.
-module(meck_ret_spec).
-export_type([result_spec/0]).
-export_type([ret_spec/0]).
%% API
-export([passthrough/0]).
-export([val/1]).
-export([exec/1]).
-export([seq/1]).
-export([loop/1]).
-export([raise/2]).
-export([is_meck_exception/1]).
-export([retrieve_result/1]).
-export([eval_result/4]).
%%%============================================================================
%%% Types
%%%============================================================================
-opaque result_spec() :: {meck_value, any()} |
{meck_exec, fun()} |
{meck_raise, Class::throw | error | exit, Reason::any()} |
meck_passthrough.
-type ret_spec() :: {meck_seq, [ret_spec()]} |
{meck_loop, [ret_spec()], [ret_spec()]} |
result_spec() |
any().
%%%============================================================================
%%% API
%%%============================================================================
-spec passthrough() -> ret_spec().
passthrough() -> meck_passthrough.
-spec val(any()) -> ret_spec().
val(Value) -> {meck_value, Value}.
-spec exec(fun()) -> ret_spec().
exec(Fun) when is_function(Fun)-> {meck_exec, Fun}.
-spec seq([ret_spec()]) -> ret_spec().
seq(Sequence) when is_list(Sequence) -> {meck_seq, Sequence}.
-spec loop([ret_spec()]) -> ret_spec().
loop(Loop) when is_list(Loop) -> {meck_loop, Loop, Loop}.
-spec raise(Class:: throw | error | exit, Reason::any()) -> ret_spec().
raise(throw, Reason) -> {meck_raise, throw, Reason};
raise(error, Reason) -> {meck_raise, error, Reason};
raise(exit, Reason) -> {meck_raise, exit, Reason}.
-spec is_meck_exception(Reason::any()) -> boolean().
is_meck_exception({meck_raise, MockedClass, MockedReason}) ->
{true, MockedClass, MockedReason};
is_meck_exception(_Reason) ->
false.
-spec retrieve_result(RetSpec::ret_spec()) ->
{result_spec(), NewRetSpec::ret_spec() | unchanged}.
retrieve_result(RetSpec) ->
retrieve_result(RetSpec, []).
-spec eval_result(Mod::atom(), Func::atom(), Args::[any()], result_spec()) ->
Result::any().
eval_result(_Mod, _Func, _Args, {meck_value, Value}) ->
Value;
eval_result(_Mod, _Func, Args, {meck_exec, Fun}) when is_function(Fun) ->
erlang:apply(Fun, Args);
eval_result(_Mod, _Func, _Args, MockedEx = {meck_raise, _Class, _Reason}) ->
erlang:throw(MockedEx);
eval_result(Mod, Func, Args, meck_passthrough) ->
erlang:apply(meck_util:original_name(Mod), Func, Args).
%%%============================================================================
%%% Internal functions
%%%============================================================================
-spec retrieve_result(RetSpec::ret_spec(), ExplodedRs::[ret_spec()]) ->
{result_spec(), NewRetSpec::ret_spec() | unchanged}.
retrieve_result(RetSpec = {meck_seq, [InnerRs | _Rest]}, ExplodedRs) ->
retrieve_result(InnerRs, [RetSpec | ExplodedRs]);
retrieve_result(RetSpec = {meck_loop, [InnerRs | _Rest], _Loop}, ExplodedRs) ->
retrieve_result(InnerRs, [RetSpec | ExplodedRs]);
retrieve_result(RetSpec, ExplodedRs) ->
ResultSpec = case is_result_spec(RetSpec) of
true ->
RetSpec;
_ when erlang:is_function(RetSpec) ->
exec(RetSpec);
_ ->
val(RetSpec)
end,
{ResultSpec, update_rs(RetSpec, ExplodedRs, false)}.
-spec is_result_spec(any()) -> boolean().
is_result_spec({meck_value, _Value}) -> true;
is_result_spec({meck_exec, _Fun}) -> true;
is_result_spec({meck_raise, _Class, _Reason}) -> true;
is_result_spec(meck_passthrough) -> true;
is_result_spec(_Other) -> false.
-spec update_rs(InnerRs::ret_spec(), ExplodedRs::[ret_spec()], Done::boolean()) ->
NewRetSpec::ret_spec() | unchanged.
update_rs(InnerRs, [], true) ->
InnerRs;
update_rs(_InnerRs, [], false) ->
unchanged;
update_rs(InnerRs, [CurrRs = {meck_seq, [InnerRs]} | ExplodedRs], Updated) ->
update_rs(CurrRs, ExplodedRs, Updated);
update_rs(InnerRs, [{meck_seq, [InnerRs | Rest]} | ExplodedRs], _Updated) ->
update_rs({meck_seq, Rest}, ExplodedRs, true);
update_rs(NewInnerRs, [{meck_seq, [_InnerRs | Rest]} | ExplodedRs], _Updated) ->
update_rs({meck_seq, [NewInnerRs | Rest]}, ExplodedRs, true);
update_rs(InnerRs, [{meck_loop, [InnerRs], Loop} | ExplodedRs], _Updated) ->
update_rs({meck_loop, Loop, Loop}, ExplodedRs, true);
update_rs(InnerRs, [{meck_loop, [InnerRs | Rest], Loop} | ExplodedRs],
_Updated) ->
update_rs({meck_loop, Rest, Loop}, ExplodedRs, true);
update_rs(NewInnerRs, [{meck_loop, [_InnerRs | Rest], Loop} | ExplodedRs],
_Updated) ->
update_rs({meck_loop, [NewInnerRs | Rest], Loop}, ExplodedRs, true).