From 9b434d8c7b8284b940b5f28fb6917bbf3abf38a9 Mon Sep 17 00:00:00 2001 From: Maxim Vladimirsky Date: Tue, 19 Feb 2013 11:28:38 +0400 Subject: [PATCH 1/5] Broaden the notion of `args_spec()` --- src/meck.erl | 78 +++++++++++++++++++++---------------- src/meck_args_matcher.erl | 57 +++++++++++++++------------ src/meck_expect.erl | 9 ++--- test/meck_expect_tests.erl | 8 ++++ test/meck_history_tests.erl | 37 ++++++++++++++++++ test/meck_tests.erl | 8 ++++ 6 files changed, 133 insertions(+), 64 deletions(-) create mode 100644 test/meck_history_tests.erl diff --git a/src/meck.erl b/src/meck.erl index c72d41d3..cec41208 100644 --- a/src/meck.erl +++ b/src/meck.erl @@ -85,14 +85,26 @@ %% An instance of this type may be specified in any or even all positions of an %% {@link arg_spec()}. --type args_spec() :: [any() | '_' | matcher()]. -%% It is used in {@link expect/3} and {@link expect/4} to define an expectation -%% by an argument pattern. The length of the list defines the arity of the -%% function an expectation is created for. Every list element corresponds to a -%% function argument at the respective position. '_' is a wildcard that matches -%% any value. Instead of exact values or '_' wildcards, you can also specify -%% a {@link matcher()} created by {@link is/1} from a predicate function or a -%% hamcrest matcher. +-type args_spec() :: [any() | '_' | matcher()] | non_neg_integer(). +%% Argument specification is used to specify argument patterns throughout Meck. +%% In particular it is used in definition of expectation clauses by +%% {@link expect/3}, {@link expect/4}, and by history digging functions +%% {@link num_called/3}, {@link called/3} to specify what arguments of a +%% function call of interest should look like. +%% +%% An argument specification can be given as a argument pattern list or +%% as a non-negative integer that represents function clause/call arity. +%% +%% If an argument specification is given as an argument pattern, then every +%% pattern element corresponds to a function argument at the respective +%% position. '_' is a wildcard that matches any value. In fact you can specify +%% atom wildcard '_' at any level in the value structure. +%% (E.g.: {1, [blah, {'_', "bar", 2} | '_'], 3}). It is also possible to use a +%% {@link matcher()} created by {@link is/1} in-place of a value pattern. +%% +%% If an argument specification is given by an arity, then it is equivalent to +%% a pattern based argument specification that consists solely of wildcards, +%% and has the length of arity (e.g.: 3 is equivalent to ['_', '_', '_']). -opaque ret_spec() :: meck_ret_spec:ret_spec(). %% Opaque data structure that specifies a value or a set of values to be returned @@ -214,21 +226,20 @@ expect(Mod, Func, Expectation) when is_atom(Mod), is_atom(Func) -> %% @doc Adds an expectation with the supplied arity and return value. %% -%% This creates an expectation which takes `Arity' number of functions -%% and always returns `Result'. +%% This creates an expectation that has the only clause {`ArgsSpec', `RetSpec'}. %% -%% @see expect/3. --spec expect(Mods, Func, AriOrArgs, RetSpec) -> ok when +%% @equiv expect(Mod, Func, [{ArgsSpec, RetSpec}]) +-spec expect(Mods, Func, ArgsSpec, RetSpec) -> ok when Mods :: Mod | [Mod], Mod :: atom(), Func :: atom(), - AriOrArgs :: byte() | args_spec(), + ArgsSpec :: args_spec(), RetSpec :: ret_spec(). -expect(Mod, Func, AriOrArgs, RetSpec) when is_list(Mod) -> - lists:foreach(fun(M) -> expect(M, Func, AriOrArgs, RetSpec) end, Mod), +expect(Mod, Func, ArgsSpec, RetSpec) when is_list(Mod) -> + lists:foreach(fun(M) -> expect(M, Func, ArgsSpec, RetSpec) end, Mod), ok; -expect(Mod, Func, AriOrArgs, RetSpec) when is_atom(Mod), is_atom(Func) -> - Expect = meck_expect:new(Func, AriOrArgs, RetSpec), +expect(Mod, Func, ArgsSpec, RetSpec) when is_atom(Mod), is_atom(Func) -> + Expect = meck_expect:new(Func, ArgsSpec, RetSpec), check_expect_result(meck_proc:set_expect(Mod, Expect)). %% @equiv expect(Mod, Func, Ari, seq(Sequence)) @@ -373,12 +384,12 @@ unload(Mods) when is_list(Mods) -> %% @doc Returns whether `Mod:Func' has been called with `Args'. %% %% @equiv called(Mod, Fun, Args, '_') --spec called(Mod, OptFun, OptArgs) -> boolean() when +-spec called(Mod, OptFun, OptArgsSpec) -> boolean() when Mod :: atom(), OptFun :: '_' | atom(), - OptArgs :: '_' | args_spec(). -called(Mod, OptFun, OptArgs) -> - meck_history:num_calls('_', Mod, OptFun, OptArgs) > 0. + OptArgsSpec :: '_' | args_spec(). +called(Mod, OptFun, OptArgsSpec) -> + meck_history:num_calls('_', Mod, OptFun, OptArgsSpec) > 0. %% @doc Returns whether `Pid' has called `Mod:Func' with `Args'. %% @@ -390,23 +401,23 @@ called(Mod, OptFun, OptArgs) -> %% atom: ``'_' '' %% %% @see called/3 --spec called(Mod, OptFun, OptArgs, OptCallerPid) -> boolean() when +-spec called(Mod, OptFun, OptArgsSpec, OptCallerPid) -> boolean() when Mod :: atom(), OptFun :: '_' | atom(), - OptArgs :: '_' | args_spec(), + OptArgsSpec :: '_' | args_spec(), OptCallerPid :: '_' | pid(). -called(Mod, OptFun, OptArgs, OptPid) -> - meck_history:num_calls(OptPid, Mod, OptFun, OptArgs) > 0. +called(Mod, OptFun, OptArgsSpec, OptPid) -> + meck_history:num_calls(OptPid, Mod, OptFun, OptArgsSpec) > 0. %% @doc Returns the number of times `Mod:Func' has been called with `Args'. %% %% @equiv num_calls(Mod, Fun, Args, '_') --spec num_calls(Mod, OptFun, OptArgs) -> non_neg_integer() when +-spec num_calls(Mod, OptFun, OptArgsSpec) -> non_neg_integer() when Mod :: atom(), OptFun :: '_' | atom(), - OptArgs :: '_' | args_spec(). -num_calls(Mod, OptFun, OptArgs) -> - meck_history:num_calls('_', Mod, OptFun, OptArgs). + OptArgsSpec :: '_' | args_spec(). +num_calls(Mod, OptFun, OptArgsSpec) -> + meck_history:num_calls('_', Mod, OptFun, OptArgsSpec). %% @doc Returns the number of times process `Pid' has called `Mod:Func' %% with `Args'. @@ -416,13 +427,14 @@ num_calls(Mod, OptFun, OptArgs) -> %% arguments, `Args' and returns the result. %% %% @see num_calls/3 --spec num_calls(Mod, OptFun, OptArgs, OptCallerPid) -> non_neg_integer() when +-spec num_calls(Mod, OptFun, OptArgsSpec, OptCallerPid) -> + non_neg_integer() when Mod :: atom(), OptFun :: '_' | atom(), - OptArgs :: '_' | args_spec(), + OptArgsSpec :: '_' | args_spec(), OptCallerPid :: '_' | pid(). -num_calls(Mod, OptFun, OptArgs, OptPid) -> - meck_history:num_calls(OptPid, Mod, OptFun, OptArgs). +num_calls(Mod, OptFun, OptArgsSpec, OptPid) -> + meck_history:num_calls(OptPid, Mod, OptFun, OptArgsSpec). %% @doc Erases the call history for a mocked module or a list of mocked modules. %% diff --git a/src/meck_args_matcher.erl b/src/meck_args_matcher.erl index b8952d39..758888cd 100644 --- a/src/meck_args_matcher.erl +++ b/src/meck_args_matcher.erl @@ -21,13 +21,14 @@ %% API -export([new/1, + arity/1, match/2]). %%%============================================================================ %%% Definitions %%%============================================================================ --record(args_matcher, {args_spec :: opt_args_spec(), +-record(args_matcher, {args_pattern :: opt_args_spec(), comp_match_spec :: ets:comp_match_spec(), has_matchers = false :: boolean()}). @@ -35,46 +36,52 @@ %%% Types %%%============================================================================ --type args_spec() :: [any()]. -type opt_args_spec() :: args_spec() | '_'. +-type args_spec() :: args_pattern() | non_neg_integer(). +-type args_pattern() :: [any() | '_' | meck_matcher:matcher()]. + -opaque args_matcher() :: #args_matcher{}. %%%============================================================================ %%% API %%%============================================================================ --spec new(byte() | opt_args_spec()) -> args_matcher(). -new(ArgsSpec = '_') -> - MatchSpecItem = meck_util:match_spec_item({ArgsSpec}), +-spec new(opt_args_spec()) -> args_matcher(). +new('_') -> + MatchSpecItem = meck_util:match_spec_item({'_'}), CompMatchSpec = ets:match_spec_compile([MatchSpecItem]), - #args_matcher{args_spec = ArgsSpec, comp_match_spec = CompMatchSpec}; -new(Ari) when is_number(Ari) -> - ArgsSpec = lists:duplicate(Ari, '_'), - MatchSpecItem = meck_util:match_spec_item({ArgsSpec}), + #args_matcher{args_pattern = '_', comp_match_spec = CompMatchSpec}; +new(Arity) when is_number(Arity) -> + ArgsPattern = lists:duplicate(Arity, '_'), + MatchSpecItem = meck_util:match_spec_item({ArgsPattern}), CompMatchSpec = ets:match_spec_compile([MatchSpecItem]), - #args_matcher{args_spec = ArgsSpec, comp_match_spec = CompMatchSpec}; -new(ArgsSpec) when is_list(ArgsSpec) -> - {HasMatchers, Pattern} = case strip_off_matchers(ArgsSpec) of + #args_matcher{args_pattern = ArgsPattern, comp_match_spec = CompMatchSpec}; +new(ArgsPattern) when is_list(ArgsPattern) -> + {HasMatchers, Pattern} = case strip_off_matchers(ArgsPattern) of unchanged -> - {false, ArgsSpec}; + {false, ArgsPattern}; StrippedArgsSpec -> {true, StrippedArgsSpec} end, MatchSpecItem = meck_util:match_spec_item({Pattern}), CompMatchSpec = ets:match_spec_compile([MatchSpecItem]), - #args_matcher{args_spec = ArgsSpec, + #args_matcher{args_pattern = ArgsPattern, comp_match_spec = CompMatchSpec, has_matchers = HasMatchers}. +-spec arity(args_matcher()) -> Arity::non_neg_integer(). +arity(#args_matcher{args_pattern = ArgsPattern}) -> + erlang:length(ArgsPattern). + -spec match(Args::any(), args_matcher()) -> boolean(). -match(Args, #args_matcher{args_spec = ArgsSpec, +match(Args, #args_matcher{args_pattern = ArgsPattern, comp_match_spec = CompMatchSpec, has_matchers = HasMatchers}) -> case ets:match_spec_run([{Args}], CompMatchSpec) of [] -> false; _Matches when HasMatchers -> - check_by_matchers(Args, ArgsSpec); + check_by_matchers(Args, ArgsPattern); _Matches -> true end. @@ -83,19 +90,19 @@ match(Args, #args_matcher{args_spec = ArgsSpec, %%% Internal functions %%%============================================================================ --spec strip_off_matchers(args_spec()) -> - NewArgsSpec::args_spec() | unchanged. -strip_off_matchers(ArgsSpec) -> - strip_off_matchers(ArgsSpec, [], false). +-spec strip_off_matchers(args_pattern()) -> + NewArgsPattern::args_pattern() | unchanged. +strip_off_matchers(ArgsPattern) -> + strip_off_matchers(ArgsPattern, [], false). --spec strip_off_matchers(args_spec(), Stripped::[any() | '_'], boolean()) -> - NewArgsSpec::args_spec() | unchanged. -strip_off_matchers([ArgSpec | Rest], Stripped, HasMatchers) -> - case meck_matcher:is_matcher(ArgSpec) of +-spec strip_off_matchers(args_pattern(), Stripped::[any() | '_'], boolean()) -> + NewArgsPattern::args_pattern() | unchanged. +strip_off_matchers([ArgPattern | Rest], Stripped, HasMatchers) -> + case meck_matcher:is_matcher(ArgPattern) of true -> strip_off_matchers(Rest, ['_' | Stripped], true); _ -> - strip_off_matchers(Rest, [ArgSpec | Stripped], HasMatchers) + strip_off_matchers(Rest, [ArgPattern | Stripped], HasMatchers) end; strip_off_matchers([], Stripped, true) -> lists:reverse(Stripped); diff --git a/src/meck_expect.erl b/src/meck_expect.erl index b033d2a5..3de4ed9b 100644 --- a/src/meck_expect.erl +++ b/src/meck_expect.erl @@ -54,13 +54,10 @@ new(Func, ClauseSpecs) when is_list(ClauseSpecs) -> {{Func, Arity}, Clauses}. -spec new(Func::atom(), - byte() | meck_args_matcher:args_spec(), + meck_args_matcher:args_spec(), meck_ret_spec:ret_spec()) -> expect(). -new(Func, Ari, RetSpec) when is_integer(Ari), Ari >= 0 -> - Clause = {meck_args_matcher:new(Ari), RetSpec}, - {{Func, Ari}, [Clause]}; -new(Func, ArgsSpec, RetSpec) when is_list(ArgsSpec) -> +new(Func, ArgsSpec, RetSpec) -> {Ari, Clause} = parse_clause_spec({ArgsSpec, RetSpec}), {{Func, Ari}, [Clause]}. @@ -124,8 +121,8 @@ parse_clause_specs([], FirstClauseAri, Clauses) -> -spec parse_clause_spec(meck:func_clause_spec()) -> {Ari::byte(), func_clause()}. parse_clause_spec({ArgsSpec, RetSpec}) -> - Ari = length(ArgsSpec), ArgsMatcher = meck_args_matcher:new(ArgsSpec), + Ari = meck_args_matcher:arity(ArgsMatcher), Clause = {ArgsMatcher, RetSpec}, {Ari, Clause}. diff --git a/test/meck_expect_tests.erl b/test/meck_expect_tests.erl index 238c0e31..a18bf390 100644 --- a/test/meck_expect_tests.erl +++ b/test/meck_expect_tests.erl @@ -97,3 +97,11 @@ expect_with_matchers_masked_clause_test() -> %% Then V2001 = meck_ret_spec:val(2001), ?assertMatch({V2001, _}, meck_expect:fetch_result([1003, 1003], E)). + +expect_with_arity_test() -> + %% When + E = meck_expect:new(foo, [{2, 2001}]), + %% Then + V2001 = meck_ret_spec:val(2001), + ?assertMatch({V2001, _}, meck_expect:fetch_result([1, 2], E)), + ?assertMatch({undefined, _}, meck_expect:fetch_result([1, 2, 3], E)). diff --git a/test/meck_history_tests.erl b/test/meck_history_tests.erl new file mode 100644 index 00000000..8c7a2480 --- /dev/null +++ b/test/meck_history_tests.erl @@ -0,0 +1,37 @@ +%%%============================================================================ +%%% Copyright 2013 Maxim Vladimirsky +%%% +%%% 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. +%%%============================================================================ + +-module(meck_history_tests). + +-include_lib("eunit/include/eunit.hrl"). + +num_calls_with_arity_test() -> + %% Given + meck:new(test, [non_strict]), + meck:expect(test, foo, 2, ok), + meck:expect(test, foo, 3, ok), + %% When + test:foo(1, 2, 3), + test:foo(1, 2), + test:foo(1, 2, 3), + test:foo(1, 2, 3), + test:foo(1, 2), + %% Then + ?assertMatch(2, meck:num_calls(test, foo, 2)), + ?assertMatch(3, meck:num_calls(test, foo, 3)), + ?assertMatch(0, meck:num_calls(test, foo, 4)), + %% Clean + meck:unload(). diff --git a/test/meck_tests.erl b/test/meck_tests.erl index b6d237fc..9b6d4f47 100644 --- a/test/meck_tests.erl +++ b/test/meck_tests.erl @@ -92,6 +92,7 @@ meck_test_() -> fun ?MODULE:expect_loop_in_seq_/1, fun ?MODULE:expect_args_exception_/1, fun ?MODULE:expect_arity_exception_/1, + fun ?MODULE:expect_arity_clause_/1, fun ?MODULE:loop_multi_/1, fun ?MODULE:expect_args_pattern_override_/1, fun ?MODULE:expect_args_pattern_shadow_/1, @@ -695,6 +696,13 @@ expect_arity_exception_(Mod) -> %% When/Then ?assertError(a, Mod:f(1001)). +expect_arity_clause_(Mod) -> + %% Given + meck:expect(Mod, foo, [{2, blah}]), + %% When/Then + ?assertMatch(blah, Mod:foo(1, 2)), + ?assertError(_, Mod:foo(1, 2, 3)). + loop_multi_(Mod) -> meck:new(mymod2, [non_strict]), Mods = [Mod, mymod2], From 37a558c78607e3e253fe2c1c064418a0df1af82b Mon Sep 17 00:00:00 2001 From: Maxim Vladimirsky Date: Tue, 19 Feb 2013 12:21:21 +0400 Subject: [PATCH 2/5] Fix function spec --- src/meck_expect.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/meck_expect.erl b/src/meck_expect.erl index 3de4ed9b..0acf93f5 100644 --- a/src/meck_expect.erl +++ b/src/meck_expect.erl @@ -101,7 +101,7 @@ parse_clause_specs([ClauseSpec | Rest]) -> {Ari, Clause} = parse_clause_spec(ClauseSpec), parse_clause_specs(Rest, Ari, [Clause]). --spec parse_clause_specs([meck:func_clause_spec()], +-spec parse_clause_specs([func_clause_spec()], FirstClauseAri::byte(), Clauses::[func_clause()]) -> {Ari::byte(), [func_clause()]}. @@ -118,7 +118,7 @@ parse_clause_specs([ClauseSpec | Rest], FirstClauseAri, Clauses) -> parse_clause_specs([], FirstClauseAri, Clauses) -> {FirstClauseAri, lists:reverse(Clauses)}. --spec parse_clause_spec(meck:func_clause_spec()) -> +-spec parse_clause_spec(func_clause_spec()) -> {Ari::byte(), func_clause()}. parse_clause_spec({ArgsSpec, RetSpec}) -> ArgsMatcher = meck_args_matcher:new(ArgsSpec), From 8e673ed719dcf4f1c4c1ac2586a0409bc5bb92be Mon Sep 17 00:00:00 2001 From: Maxim Vladimirsky Date: Fri, 8 Feb 2013 14:39:18 +0400 Subject: [PATCH 3/5] Add argument capture capability (fixes #86) --- src/meck.erl | 51 +++++++++++++++++++++++++++++++++++++ src/meck_history.erl | 36 +++++++++++++++++++++++++- test/meck_history_tests.erl | 46 +++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 1 deletion(-) diff --git a/src/meck.erl b/src/meck.erl index cec41208..ba21b3a8 100644 --- a/src/meck.erl +++ b/src/meck.erl @@ -46,6 +46,8 @@ -export([num_calls/3]). -export([num_calls/4]). -export([reset/1]). +-export([capture/5]). +-export([capture/6]). %% Syntactic sugar -export([loop/1]). @@ -518,6 +520,55 @@ exec(Fun) -> meck_ret_spec:exec(Fun). is(MatcherImpl) -> meck_matcher:new(MatcherImpl). +%% @doc Returns the value of an argument as it was passed to a particular +%% function call made by a particular process. +%% +%% It retrieves the value of argument at `ArgNum' position as it was passed +%% to function call `Mod:Func' with arguments that match `OptArgsSpec' made by +%% process `CallerPid' that occurred `Occur''th according to the call history. +%% +%% Atoms `first' and `last' can be used in place of the occurrence number to +%% retrieve the argument value passed when the function was called the first +%% or the last time respectively. +%% +%% If an occurrence of a function call irrespective of the calling process needs +%% to be captured then `_' might be passed as `OptCallerPid', but it is better +%% to use {@link capture/3} instead. +-spec capture(Occur, Mod, Func, OptArgsSpec, ArgNum, OptCallerPid) -> + {ok, ArgValue} | not_found when + Occur :: first | last | pos_integer(), + Mod :: atom(), + Func :: atom(), + OptArgsSpec :: '_' | args_spec(), + ArgNum :: pos_integer(), + OptCallerPid :: '_' | pid(), + ArgValue :: any(). +capture(Occur, Mod, Func, OptArgsSpec, ArgNum, OptCallerPid) -> + meck_history:capture(Occur, OptCallerPid, Mod, Func, OptArgsSpec, ArgNum). + +%% @doc Returns the value of an argument as it was passed to a particular +%% function call. +%% +%% It retrieves the value of argument at `ArgNum' position as it was passed +%% to function call `Mod:Func' with arguments that match `OptArgsSpec' that +%% occurred `Occur''th according to the call history. +%% +%% Atoms `first' and `last' can be used in place of the occurrence number to +%% retrieve the argument value passed when the function was called the first +%% or the last time respectively. +%% +%% @equiv capture(Occur, '_', Mod, Func, OptArgsSpec, ArgNum) +-spec capture(Occur, Mod, Func, OptArgsSpec, ArgNum) -> + {ok, ArgValue} | not_found when + Occur :: first | last | pos_integer(), + Mod::atom(), + Func::atom(), + OptArgsSpec :: args_spec(), + ArgNum :: pos_integer(), + ArgValue :: any(). +capture(Occur, Mod, Func, OptArgsSpec, ArgNum) -> + meck_history:capture(Occur, '_', Mod, Func, OptArgsSpec, ArgNum). + %%%============================================================================ %%% Internal functions %%%============================================================================ diff --git a/src/meck_history.erl b/src/meck_history.erl index 8d4d9aea..323f89c4 100644 --- a/src/meck_history.erl +++ b/src/meck_history.erl @@ -27,7 +27,8 @@ history/0]). -export([get_history/2, - num_calls/4]). + num_calls/4, + capture/6]). %%%============================================================================ %%% Types @@ -76,6 +77,22 @@ num_calls(CallerPid, Mod, OptFunc, OptArgsSpec) -> Filtered = lists:filter(Filter, meck_proc:get_history(Mod)), length(Filtered). +-spec capture(Occur::pos_integer(), opt_pid(), Mod::atom(), Func::atom(), + meck_args_matcher:opt_args_spec(), ArgNum::pos_integer()) -> + {ok, ArgValue::any()} | not_found. +capture(Occur, OptCallerPid, Mod, Func, OptArgsSpec, ArgNum) -> + ArgsMatcher = meck_args_matcher:new(OptArgsSpec), + Filter = new_filter(OptCallerPid, Func, ArgsMatcher), + Filtered = lists:filter(Filter, meck_proc:get_history(Mod)), + case nth_record(Occur, Filtered) of + not_found -> + not_found; + {_CallerPid, {_Mod, _Func, Args}, _Result} -> + {ok, lists:nth(ArgNum, Args)}; + {_CallerPid, {_Mod, Func, Args}, _Class, _Reason, _Trace} -> + {ok, lists:nth(ArgNum, Args)} + end. + %%%============================================================================ %%% Internal functions %%%============================================================================ @@ -94,3 +111,20 @@ new_filter(TheCallerPid, TheFunc, ArgsMatcher) -> (_Other) -> false end. + +-spec nth_record(Occur::pos_integer(), history()) -> history_record() | + not_found. +nth_record(Occur, History) -> + try + case Occur of + first -> + lists:nth(1, History); + last -> + lists:last(History); + _Else -> + lists:nth(Occur, History) + end + catch + error:_Reason -> + not_found + end. diff --git a/test/meck_history_tests.erl b/test/meck_history_tests.erl index 8c7a2480..3b1cf088 100644 --- a/test/meck_history_tests.erl +++ b/test/meck_history_tests.erl @@ -35,3 +35,49 @@ num_calls_with_arity_test() -> ?assertMatch(0, meck:num_calls(test, foo, 4)), %% Clean meck:unload(). + +capture_different_positions_test() -> + %% Given + meck:new(test, [non_strict]), + meck:expect(test, foo, 3, ok), + meck:expect(test, foo, 4, ok), + meck:expect(test, bar, 3, ok), + test:foo(1001, 2001, 3001, 4001), + test:bar(1002, 2002, 3002), + test:foo(1003, 2003, 3003), + test:bar(1004, 2004, 3004), + test:foo(1005, 2005, 3005), + test:foo(1006, 2006, 3006), + test:bar(1007, 2007, 3007), + test:foo(1008, 2008, 3008), + %% When/Then + ?assertMatch({ok, 2003}, meck:capture(first, test, foo, ['_', '_', '_'], 2)), + ?assertMatch({ok, 2008}, meck:capture(last, test, foo, ['_', '_', '_'], 2)), + ?assertMatch({ok, 2006}, meck:capture(3, test, foo, ['_', '_', '_'], 2)), + ?assertMatch(not_found, meck:capture(5, test, foo, ['_', '_', '_'], 2)), + %% Clean + meck:unload(). + +capture_different_args_specs_test() -> + %% Given + meck:new(test, [non_strict]), + meck:expect(test, foo, 2, ok), + meck:expect(test, foo, 3, ok), + meck:expect(test, foo, 4, ok), + meck:expect(test, bar, 3, ok), + test:foo(1001, 2001, 3001, 4001), + test:bar(1002, 2002, 3002), + test:foo(1003, 2003, 3003), + test:bar(1004, 2004, 3004), + test:foo(1005, 2005), + test:foo(1006, 2006, 3006), + test:bar(1007, 2007, 3007), + test:foo(1008, 2008, 3008), + %% When/Then + ?assertMatch({ok, 2001}, meck:capture(first, test, foo, '_', 2)), + ?assertMatch({ok, 2003}, meck:capture(first, test, foo, 3, 2)), + ?assertMatch({ok, 2005}, meck:capture(first, test, foo, ['_', '_'], 2)), + ?assertMatch({ok, 2006}, meck:capture(first, test, foo, [1006, '_', '_'], 2)), + ?assertMatch({ok, 2008}, meck:capture(first, test, foo, ['_', '_', meck:is(hamcrest_matchers:greater_than(3006))], 2)), + %% Clean + meck:unload(). From d714ef3207e31bdb8d10b60eb2248ad037c6fdac Mon Sep 17 00:00:00 2001 From: Maxim Vladimirsky Date: Tue, 26 Mar 2013 13:02:18 +0400 Subject: [PATCH 4/5] Fix `args_matcher.args_pattern` type spec --- src/meck_args_matcher.erl | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/meck_args_matcher.erl b/src/meck_args_matcher.erl index 758888cd..77ef3baa 100644 --- a/src/meck_args_matcher.erl +++ b/src/meck_args_matcher.erl @@ -28,9 +28,9 @@ %%% Definitions %%%============================================================================ --record(args_matcher, {args_pattern :: opt_args_spec(), +-record(args_matcher, {opt_args_pattern :: opt_args_pattern(), comp_match_spec :: ets:comp_match_spec(), - has_matchers = false :: boolean()}). + has_matchers :: boolean()}). %%%============================================================================ %%% Types @@ -38,6 +38,7 @@ -type opt_args_spec() :: args_spec() | '_'. -type args_spec() :: args_pattern() | non_neg_integer(). +-type opt_args_pattern() :: args_pattern() | '_'. -type args_pattern() :: [any() | '_' | meck_matcher:matcher()]. -opaque args_matcher() :: #args_matcher{}. @@ -50,12 +51,16 @@ new('_') -> MatchSpecItem = meck_util:match_spec_item({'_'}), CompMatchSpec = ets:match_spec_compile([MatchSpecItem]), - #args_matcher{args_pattern = '_', comp_match_spec = CompMatchSpec}; + #args_matcher{opt_args_pattern = '_', + comp_match_spec = CompMatchSpec, + has_matchers = false}; new(Arity) when is_number(Arity) -> ArgsPattern = lists:duplicate(Arity, '_'), MatchSpecItem = meck_util:match_spec_item({ArgsPattern}), CompMatchSpec = ets:match_spec_compile([MatchSpecItem]), - #args_matcher{args_pattern = ArgsPattern, comp_match_spec = CompMatchSpec}; + #args_matcher{opt_args_pattern = ArgsPattern, + comp_match_spec = CompMatchSpec, + has_matchers = false}; new(ArgsPattern) when is_list(ArgsPattern) -> {HasMatchers, Pattern} = case strip_off_matchers(ArgsPattern) of unchanged -> @@ -65,23 +70,23 @@ new(ArgsPattern) when is_list(ArgsPattern) -> end, MatchSpecItem = meck_util:match_spec_item({Pattern}), CompMatchSpec = ets:match_spec_compile([MatchSpecItem]), - #args_matcher{args_pattern = ArgsPattern, + #args_matcher{opt_args_pattern = ArgsPattern, comp_match_spec = CompMatchSpec, has_matchers = HasMatchers}. -spec arity(args_matcher()) -> Arity::non_neg_integer(). -arity(#args_matcher{args_pattern = ArgsPattern}) -> +arity(#args_matcher{opt_args_pattern = ArgsPattern}) -> erlang:length(ArgsPattern). -spec match(Args::any(), args_matcher()) -> boolean(). -match(Args, #args_matcher{args_pattern = ArgsPattern, +match(Args, #args_matcher{opt_args_pattern = OptArgsPattern, comp_match_spec = CompMatchSpec, has_matchers = HasMatchers}) -> case ets:match_spec_run([{Args}], CompMatchSpec) of [] -> false; - _Matches when HasMatchers -> - check_by_matchers(Args, ArgsPattern); + _Matches when HasMatchers andalso erlang:is_list(OptArgsPattern) -> + check_by_matchers(Args, OptArgsPattern); _Matches -> true end. From 34de17c3eb0f7218359e8e09fc6012b28ad3221c Mon Sep 17 00:00:00 2001 From: Maxim Vladimirsky Date: Tue, 26 Mar 2013 13:18:33 +0400 Subject: [PATCH 5/5] Change `meck:capture` return value It returns just value (not wrapped in {ok, ...} and fails with error:not_found if the expected call has not happened. --- src/meck.erl | 12 ++++++------ src/meck_history.erl | 8 ++++---- test/meck_history_tests.erl | 18 +++++++++--------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/meck.erl b/src/meck.erl index ba21b3a8..a8184400 100644 --- a/src/meck.erl +++ b/src/meck.erl @@ -521,7 +521,8 @@ is(MatcherImpl) -> meck_matcher:new(MatcherImpl). %% @doc Returns the value of an argument as it was passed to a particular -%% function call made by a particular process. +%% function call made by a particular process. It fails with `not_found' error +%% if a function call of interest has never been made. %% %% It retrieves the value of argument at `ArgNum' position as it was passed %% to function call `Mod:Func' with arguments that match `OptArgsSpec' made by @@ -534,8 +535,7 @@ is(MatcherImpl) -> %% If an occurrence of a function call irrespective of the calling process needs %% to be captured then `_' might be passed as `OptCallerPid', but it is better %% to use {@link capture/3} instead. --spec capture(Occur, Mod, Func, OptArgsSpec, ArgNum, OptCallerPid) -> - {ok, ArgValue} | not_found when +-spec capture(Occur, Mod, Func, OptArgsSpec, ArgNum, OptCallerPid) -> ArgValue when Occur :: first | last | pos_integer(), Mod :: atom(), Func :: atom(), @@ -547,7 +547,8 @@ capture(Occur, Mod, Func, OptArgsSpec, ArgNum, OptCallerPid) -> meck_history:capture(Occur, OptCallerPid, Mod, Func, OptArgsSpec, ArgNum). %% @doc Returns the value of an argument as it was passed to a particular -%% function call. +%% function call, It fails with `not_found' error if a function call of +%% interest has never been made. %% %% It retrieves the value of argument at `ArgNum' position as it was passed %% to function call `Mod:Func' with arguments that match `OptArgsSpec' that @@ -558,8 +559,7 @@ capture(Occur, Mod, Func, OptArgsSpec, ArgNum, OptCallerPid) -> %% or the last time respectively. %% %% @equiv capture(Occur, '_', Mod, Func, OptArgsSpec, ArgNum) --spec capture(Occur, Mod, Func, OptArgsSpec, ArgNum) -> - {ok, ArgValue} | not_found when +-spec capture(Occur, Mod, Func, OptArgsSpec, ArgNum) -> ArgValue when Occur :: first | last | pos_integer(), Mod::atom(), Func::atom(), diff --git a/src/meck_history.erl b/src/meck_history.erl index 323f89c4..489e87bd 100644 --- a/src/meck_history.erl +++ b/src/meck_history.erl @@ -79,18 +79,18 @@ num_calls(CallerPid, Mod, OptFunc, OptArgsSpec) -> -spec capture(Occur::pos_integer(), opt_pid(), Mod::atom(), Func::atom(), meck_args_matcher:opt_args_spec(), ArgNum::pos_integer()) -> - {ok, ArgValue::any()} | not_found. + ArgValue::any(). capture(Occur, OptCallerPid, Mod, Func, OptArgsSpec, ArgNum) -> ArgsMatcher = meck_args_matcher:new(OptArgsSpec), Filter = new_filter(OptCallerPid, Func, ArgsMatcher), Filtered = lists:filter(Filter, meck_proc:get_history(Mod)), case nth_record(Occur, Filtered) of not_found -> - not_found; + erlang:error(not_found); {_CallerPid, {_Mod, _Func, Args}, _Result} -> - {ok, lists:nth(ArgNum, Args)}; + lists:nth(ArgNum, Args); {_CallerPid, {_Mod, Func, Args}, _Class, _Reason, _Trace} -> - {ok, lists:nth(ArgNum, Args)} + lists:nth(ArgNum, Args) end. %%%============================================================================ diff --git a/test/meck_history_tests.erl b/test/meck_history_tests.erl index 3b1cf088..104a6429 100644 --- a/test/meck_history_tests.erl +++ b/test/meck_history_tests.erl @@ -51,10 +51,10 @@ capture_different_positions_test() -> test:bar(1007, 2007, 3007), test:foo(1008, 2008, 3008), %% When/Then - ?assertMatch({ok, 2003}, meck:capture(first, test, foo, ['_', '_', '_'], 2)), - ?assertMatch({ok, 2008}, meck:capture(last, test, foo, ['_', '_', '_'], 2)), - ?assertMatch({ok, 2006}, meck:capture(3, test, foo, ['_', '_', '_'], 2)), - ?assertMatch(not_found, meck:capture(5, test, foo, ['_', '_', '_'], 2)), + ?assertMatch(2003, meck:capture(first, test, foo, ['_', '_', '_'], 2)), + ?assertMatch(2008, meck:capture(last, test, foo, ['_', '_', '_'], 2)), + ?assertMatch(2006, meck:capture(3, test, foo, ['_', '_', '_'], 2)), + ?assertError(not_found, meck:capture(5, test, foo, ['_', '_', '_'], 2)), %% Clean meck:unload(). @@ -74,10 +74,10 @@ capture_different_args_specs_test() -> test:bar(1007, 2007, 3007), test:foo(1008, 2008, 3008), %% When/Then - ?assertMatch({ok, 2001}, meck:capture(first, test, foo, '_', 2)), - ?assertMatch({ok, 2003}, meck:capture(first, test, foo, 3, 2)), - ?assertMatch({ok, 2005}, meck:capture(first, test, foo, ['_', '_'], 2)), - ?assertMatch({ok, 2006}, meck:capture(first, test, foo, [1006, '_', '_'], 2)), - ?assertMatch({ok, 2008}, meck:capture(first, test, foo, ['_', '_', meck:is(hamcrest_matchers:greater_than(3006))], 2)), + ?assertMatch(2001, meck:capture(first, test, foo, '_', 2)), + ?assertMatch(2003, meck:capture(first, test, foo, 3, 2)), + ?assertMatch(2005, meck:capture(first, test, foo, ['_', '_'], 2)), + ?assertMatch(2006, meck:capture(first, test, foo, [1006, '_', '_'], 2)), + ?assertMatch(2008, meck:capture(first, test, foo, ['_', '_', meck:is(hamcrest_matchers:greater_than(3006))], 2)), %% Clean meck:unload().