%%%============================================================================ %%% 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).