summaryrefslogtreecommitdiff
path: root/test/mock_genserver.erl
diff options
context:
space:
mode:
Diffstat (limited to 'test/mock_genserver.erl')
-rw-r--r--test/mock_genserver.erl209
1 files changed, 209 insertions, 0 deletions
diff --git a/test/mock_genserver.erl b/test/mock_genserver.erl
new file mode 100644
index 00000000..cde41ff5
--- /dev/null
+++ b/test/mock_genserver.erl
@@ -0,0 +1,209 @@
+%%%-------------------------------------------------------------------
+%%% File: mock_genserver.erl
+%%% @author Cliff Moon <> []
+%%% @copyright 2009 Cliff Moon
+%%% @doc
+%%%
+%%% @end
+%%%
+%%% @since 2009-01-02 by Cliff Moon
+%%%-------------------------------------------------------------------
+-module(mock_genserver).
+-author('cliff@powerset.com').
+
+-behaviour(gen_server).
+
+-include_lib("eunit/include/eunit.hrl").
+
+%% API
+-export([start_link/1, stub_call/3, expects_call/3, expects_call/4, stop/1]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-record(state, {call_stubs=[], call_expects=[], cast_expectations, info_expectations}).
+
+%%====================================================================
+%% API
+%%====================================================================
+%%--------------------------------------------------------------------
+%% @spec start_link(Reference::atom()) -> {ok,Pid} | ignore | {error,Error}
+%% @doc Starts the server
+%% @end
+%%--------------------------------------------------------------------
+start_link(Reference) ->
+ gen_server:start_link(Reference, ?MODULE, [], []).
+
+stub_call(Server, Sym, Fun) when is_function(Fun) ->
+ gen_server:call(Server, {mock_stub_call, Sym, Fun}).
+
+expects_call(Server, Args, Fun) when is_function(Fun) ->
+ gen_server:call(Server, {mock_expects_call, Args, Fun}).
+
+expects_call(Server, Args, Fun, Times) when is_function(Fun) ->
+ gen_server:call(Server, {mock_expects_call, Args, Fun, Times}).
+
+stop(Server) ->
+ gen_server:call(Server, mock_stop).
+
+%%====================================================================
+%% gen_server callbacks
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% @spec init(Args) -> {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%% @doc Initiates the server
+%% @end
+%%--------------------------------------------------------------------
+init([]) ->
+ {ok, #state{}}.
+
+%%--------------------------------------------------------------------
+%% @spec
+%% handle_call(Request, From, State) -> {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} |
+%% {stop, Reason, State}
+%% @doc Handling call messages
+%% @end
+%%--------------------------------------------------------------------
+handle_call({mock_stub_call, Sym, Fun}, _From, State = #state{call_stubs=Stubs}) ->
+ {reply, ok, State#state{call_stubs=[{Sym, Fun}|Stubs]}};
+
+handle_call({mock_expects_call, Args, Fun}, _From, State = #state{call_expects=Expects}) ->
+ {reply, ok, State#state{call_expects=add_expectation(Args, Fun, at_least_once, Expects)}};
+
+handle_call({mock_expects_call, Args, Fun, Times}, _From, State = #state{call_expects=Expects}) ->
+ {reply, ok, State#state{call_expects=add_expectation(Args, Fun, Times, Expects)}};
+
+handle_call(mock_stop, _From, State) ->
+ {stop, normal, ok, State};
+
+handle_call(Request, _From, State = #state{call_stubs=Stubs,call_expects=Expects}) ->
+ % expectations have a higher priority
+ case find_expectation(Request, Expects) of
+ {found, {_, Fun, Time}, NewExpects} -> {reply, Fun(Request, Time), State#state{call_expects=NewExpects}};
+ not_found -> % look for a stub
+ case find_stub(Request, Stubs) of
+ {found, {_, Fun}} -> {reply, Fun(Request), State};
+ not_found ->
+ {stop, {unexpected_call, Request}, State}
+ end
+ end.
+
+%%--------------------------------------------------------------------
+%% @spec handle_cast(Msg, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @doc Handling cast messages
+%% @end
+%%--------------------------------------------------------------------
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% @spec handle_info(Info, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @doc Handling all non call/cast messages
+%% @end
+%%--------------------------------------------------------------------
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% @spec terminate(Reason, State) -> void()
+%% @doc This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any necessary
+%% cleaning up. When it returns, the gen_server terminates with Reason.
+%% The return value is ignored.
+%% @end
+%%--------------------------------------------------------------------
+terminate(_Reason, _State) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
+%% @doc Convert process state when code is changed
+%% @end
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+
+
+add_expectation(Args, Fun, Times, Expects) ->
+ Expects ++ [{Args, Fun, Times}].
+
+find_expectation(Request, Expects) ->
+ find_expectation(Request, Expects, []).
+
+find_expectation(_Request, [], _Rest) ->
+ not_found;
+
+find_expectation(Request, [{Args, Fun, Times}|Expects], Rest) ->
+ MatchFun = generate_match_fun(Args),
+ case MatchFun(Request) of
+ true ->
+ if
+ Times == at_least_once -> {found, {Args, Fun, Times}, lists:reverse(Rest) ++ [{Args, Fun, Times}] ++ Expects};
+ Times == 1 -> {found, {Args, Fun, Times}, lists:reverse(Rest) ++ Expects};
+ true -> {found, {Args, Fun, Times}, lists:reverse(Rest) ++ [{Args, Fun, Times-1}] ++ Expects}
+ end;
+ false -> find_expectation(Request, Expects, [{Args, Fun, Times}|Rest])
+ end.
+
+find_stub(Request, Stub) when is_tuple(Request) ->
+ Sym = element(1, Request),
+ find_stub(Sym, Stub);
+
+find_stub(_Sym, []) ->
+ not_found;
+
+find_stub(Sym, _Stubs) when not is_atom(Sym) ->
+ not_found;
+
+find_stub(Sym, [{Sym, Fun}|_Stubs]) ->
+ {found, {Sym, Fun}};
+
+find_stub(Sym, [_Stub|Stubs]) ->
+ find_stub(Sym, Stubs).
+
+generate_match_fun(Args) when is_tuple(Args) ->
+ generate_match_fun(tuple_to_list(Args));
+
+generate_match_fun(Args) when not is_list(Args) ->
+ generate_match_fun([Args]);
+
+generate_match_fun(Args) when is_list(Args) ->
+ Src = generate_match_fun("fun({", Args),
+ {ok, Tokens, _} = erl_scan:string(Src),
+ {ok, [Form]} = erl_parse:parse_exprs(Tokens),
+ {value, Fun, _} = erl_eval:expr(Form, erl_eval:new_bindings()),
+ Fun.
+
+generate_match_fun(Src, []) ->
+ Src ++ "}) -> true; (_) -> false end.";
+
+% unbound atom means you don't care about an arg
+generate_match_fun(Src, [unbound|Args]) ->
+ if
+ length(Args) > 0 -> generate_match_fun(Src ++ "_,", Args);
+ true -> generate_match_fun(Src ++ "_", Args)
+ end;
+
+generate_match_fun(Src, [Bound|Args]) ->
+ Term = lists:flatten(io_lib:format("~w", [Bound])),
+ if
+ length(Args) > 0 -> generate_match_fun(Src ++ Term ++ ",", Args);
+ true -> generate_match_fun(Src ++ Term, Args)
+ end.