aboutsummaryrefslogtreecommitdiffstats
path: root/lib/eunit/src/eunit_listener.erl
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/eunit/src/eunit_listener.erl
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/eunit/src/eunit_listener.erl')
-rw-r--r--lib/eunit/src/eunit_listener.erl178
1 files changed, 178 insertions, 0 deletions
diff --git a/lib/eunit/src/eunit_listener.erl b/lib/eunit/src/eunit_listener.erl
new file mode 100644
index 0000000000..20faecbf01
--- /dev/null
+++ b/lib/eunit/src/eunit_listener.erl
@@ -0,0 +1,178 @@
+%% This library is free software; you can redistribute it and/or modify
+%% it under the terms of the GNU Lesser General Public License as
+%% published by the Free Software Foundation; either version 2 of the
+%% License, or (at your option) any later version.
+%%
+%% This library is distributed in the hope that it will be useful, but
+%% WITHOUT ANY WARRANTY; without even the implied warranty of
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%% Lesser General Public License for more details.
+%%
+%% You should have received a copy of the GNU Lesser General Public
+%% License along with this library; if not, write to the Free Software
+%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+%% USA
+%%
+%% $Id$
+%%
+%% @author Richard Carlsson <[email protected]>
+%% @copyright 2009 Richard Carlsson
+%% @private
+%% @see eunit
+%% @doc Generic listener process for eunit.
+
+-module(eunit_listener).
+
+-define(NODEBUG, true).
+-include("eunit.hrl").
+-include("eunit_internal.hrl").
+
+-export([start/1, start/2]).
+
+-export([behaviour_info/1]).
+
+
+behaviour_info(callbacks) ->
+ [{init,1},{handle_begin,3},{handle_end,3},{handle_cancel,3},
+ {terminate,2}];
+behaviour_info(_Other) ->
+ undefined.
+
+
+-record(state, {callback, % callback module
+ pass = 0,
+ fail = 0,
+ skip = 0,
+ cancel = 0,
+ state % substate
+ }).
+
+start(Callback) ->
+ start(Callback, []).
+
+start(Callback, Options) ->
+ St = #state{callback = Callback},
+ spawn_opt(fun () -> init(St, Options) end,
+ proplists:get_all_values(spawn, Options)).
+
+init(St0, Options) ->
+ St1 = call(init, [Options], St0),
+ St2 = expect([], undefined, St1),
+ Data = [{pass, St2#state.pass},
+ {fail, St2#state.fail},
+ {skip, St2#state.skip},
+ {cancel, St2#state.cancel}],
+ call(terminate, [{ok, Data}, St2#state.state], St2),
+ exit(normal).
+
+expect(Id, ParentId, St) ->
+ case wait_for(Id, 'begin', ParentId) of
+ {done, Data} ->
+ {done, Data, St};
+ {ok, Msg} ->
+ case Msg of
+ {group, Data} ->
+ group(Id, Data, St);
+ {test, Data} ->
+ St1 = handle_begin(test, Id, Data, St),
+ case wait_for(Id, 'end', ParentId) of
+ {cancel, Reason} ->
+ handle_cancel(test, Id, Data, Reason, St1);
+ {ok, Result} ->
+ handle_end(test, Id, Data, Result, St1)
+ end
+ end
+ end.
+
+%% collect group items in order until group is done
+group(Id, Data, St) ->
+ St1 = handle_begin(group, Id, Data, St),
+ group_loop(0, Id, Data, St1).
+
+group_loop(N, Id, Data, St) ->
+ N1 = N + 1,
+ case expect(Id ++ [N1], Id, St) of
+ {done, {cancel, Reason}, St1} ->
+ handle_cancel(group, Id, Data, Reason, St1);
+ {done, Result, St1} ->
+ handle_end(group, Id, Data, Result, St1);
+ St1 ->
+ group_loop(N1, Id, Data, St1)
+ end.
+
+%% waiting for [..., M, N] begin
+%% get:
+%% [..., M, N] begin test -> expect [..., M, N] end (test begin)
+%% [..., M, N] begin group -> expect [..., M, N, 1] end (group begin)
+%% [..., M] end -> expect [..., M+1] begin (parent end)
+%% cancel([..., M]) (parent cancel)
+%%
+%% waiting for [..., M, N] end
+%% get:
+%% [..., M, N] end -> expect [..., M, N+1] begin (seen end)
+%% cancel([..., M, N]) (cancelled)
+
+wait_for(Id, Type, ParentId) ->
+ ?debugFmt("waiting for ~w ~w", [Id, Type]),
+ receive
+ {status, Id, {progress, Type, Data}} ->
+ ?debugFmt("got status ~w ~w", [Id, Data]),
+ {ok, Data};
+ {status, ParentId, {progress, 'end', Data}} when Type =:= 'begin' ->
+ ?debugFmt("got parent end ~w ~w", [ParentId, Data]),
+ {done, Data};
+ {status, Id, {cancel, Reason}} when Type =:= 'end' ->
+ ?debugFmt("got cancel ~w ~w", [Id, Reason]),
+ {cancel, Reason};
+ {status, ParentId, {cancel, _Reason}} ->
+ ?debugFmt("got parent cancel ~w ~w", [ParentId, _Reason]),
+ {done, {cancel, _Reason}}
+ end.
+
+call(F, As, St) when is_atom(F) ->
+ try apply(St#state.callback, F, As) of
+ Substate -> St#state{state = Substate}
+ catch
+ Class:Term ->
+ Trace = erlang:get_stacktrace(),
+ if F =/= terminate ->
+ call(terminate, [{error, {Class, Term, Trace}},
+ St#state.state], St);
+ true -> ok
+ end,
+ erlang:raise(Class, Term, Trace)
+ end.
+
+handle_begin(group, Id, Data0, St) ->
+ Data = [{id, Id} | Data0],
+ ?debugFmt("handle_begin group ~w ~w", [Id, Data0]),
+ call(handle_begin, [group, Data, St#state.state], St);
+handle_begin(test, Id, Data0, St) ->
+ Data = [{id, Id} | Data0],
+ ?debugFmt("handle_begin test ~w ~w", [Id, Data0]),
+ call(handle_begin, [test, Data, St#state.state], St).
+
+handle_end(group, Id, Data0, {Count, Data1}, St) ->
+ Data = [{id, Id}, {size, Count} | Data0 ++ Data1],
+ ?debugFmt("handle_end group ~w ~w", [Id, {Count, Data1}]),
+ call(handle_end, [group, Data, St#state.state], St);
+handle_end(test, Id, Data0, {Status, Data1}, St) ->
+ Data = [{id, Id}, {status, Status} | Data0 ++ Data1],
+ ?debugFmt("handle_end test ~w ~w", [Id, {Status, Data1}]),
+ St1 = case Status of
+ ok -> St#state{pass = St#state.pass + 1};
+ {skipped,_} -> St#state{skip = St#state.skip + 1};
+ {error,_} -> St#state{fail = St#state.fail + 1}
+ end,
+ call(handle_end, [test, Data, St#state.state], St1).
+
+handle_cancel(group, Id, Data0, Reason, St) ->
+ Data = [{id, Id}, {reason, Reason} | Data0],
+ ?debugFmt("handle_cancel group ~w ~w", [Id, Reason]),
+ call(handle_cancel, [group, Data, St#state.state],
+ St#state{cancel = St#state.cancel + 1});
+handle_cancel(test, Id, Data0, Reason, St) ->
+ Data = [{id, Id}, {reason, Reason} | Data0],
+ ?debugFmt("handle_cancel test ~w ~w", [Id, Reason]),
+ call(handle_cancel, [test, Data, St#state.state],
+ St#state{cancel = St#state.cancel + 1}).