diff options
author | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
---|---|---|
committer | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
commit | 84adefa331c4159d432d22840663c38f155cd4c1 (patch) | |
tree | bff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/eunit/src/eunit_serial.erl | |
download | otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2 otp-84adefa331c4159d432d22840663c38f155cd4c1.zip |
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/eunit/src/eunit_serial.erl')
-rw-r--r-- | lib/eunit/src/eunit_serial.erl | 186 |
1 files changed, 186 insertions, 0 deletions
diff --git a/lib/eunit/src/eunit_serial.erl b/lib/eunit/src/eunit_serial.erl new file mode 100644 index 0000000000..d9ccae86f9 --- /dev/null +++ b/lib/eunit/src/eunit_serial.erl @@ -0,0 +1,186 @@ +%% 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 2006 Richard Carlsson +%% @private +%% @see eunit +%% @doc Event serializing and multiplexing process, to be used as the +%% main "supervisor" process for en EUnit test runner. See eunit_proc +%% for details about the events that will be sent to the listeners +%% (provided to this process at startup). This process guarantees that +%% listeners will receive events in order, even if tests execute in +%% parallel. For every received 'begin' event, there will be exactly one +%% 'end' or 'cancel' event. For a cancelling event with identifier Id, +%% no further events will arrive whose identifiers have Id as prefix. + +-module(eunit_serial). + +-include("eunit.hrl"). +-include("eunit_internal.hrl"). + +-export([start/1]). + +%% Notes: +%% * Due to concurrency, there are no guarantees that we will receive +%% all status messages for the items within a group before we receive +%% the 'end' message of the group itself. +%% +%% * A cancelling event may arrive at any time, and may concern items we +%% are not yet expecting (if tests are executed in parallel), or may +%% concern not only the current item but possibly a group ancestor of +%% the current item (as in the case of a group timeout). +%% +%% * It is not possible to use selective receive to extract only those +%% cancelling messages that affect the current item and its parents; +%% basically, because we cannot have a dynamically computed prefix as a +%% pattern in a receive. Hence, we must extract each cancelling event as +%% it arrives and keep track of them separately. +%% +%% * Before we wait for a new item, we must check whether it (and thus +%% also all its subitems, if any) is already cancelled. +%% +%% * When a new cancelling event arrives, we must either store it for +%% future use, and/or cancel the current item and possibly one or more +%% of its parent groups. + +-record(state, {listeners :: set(), + cancelled = eunit_lib:trie_new() :: gb_tree(), + messages = dict:new() :: dict()}). + +start(Pids) -> + spawn(fun () -> serializer(Pids) end). + +serializer(Pids) -> + St = #state{listeners = sets:from_list(Pids), + cancelled = eunit_lib:trie_new(), + messages = dict:new()}, + expect([], undefined, 0, St), + exit(normal). + +%% collect beginning and end of an expected item; return {Done, NewSt} +%% where Done is true if there are no more items of this group +expect(Id, ParentId, GroupMinSize, St0) -> + case wait(Id, 'begin', ParentId, GroupMinSize, St0) of + {done, St1} -> + {true, St1}; + {cancel, prefix, _Msg, St1} -> + %% if a parent caused the cancel, signal done with group and + %% cast no cancel event (since the item might not exist) + {true, St1}; + {cancel, exact, Msg, St1} -> + cast_cancel(Id, Msg, St1), + {false, St1}; + {ok, Msg, St1} -> + %%?debugVal({got_begin, Id, Msg}), + cast(Msg, St1), + St2 = case Msg of + {status, _, {progress, 'begin', {group, _Info}}} -> + group(Id, 0, St1); + _ -> + St1 + end, + case wait(Id, 'end', ParentId, GroupMinSize, St2) of + {cancel, Why, Msg1, St3} -> + %% we know the item exists, so always cast a cancel + %% event, and signal done with the group if a parent + %% caused the cancel + cast_cancel(Id, Msg1, St3), + {(Why =:= prefix), St3}; + {ok, Msg1, St3} -> + %%?debugVal({got_end, Id, Msg1}), + cast(Msg1, St3), + {false, St3} + end + end. + +%% collect group items in order until group is done +group(ParentId, GroupMinSize, St) -> + N = GroupMinSize + 1, + case expect(ParentId ++ [N], ParentId, GroupMinSize, St) of + {false, St1} -> + group(ParentId, N, St1); + {true, St1} -> + St1 + end. + +cast_cancel(Id, undefined, St) -> + %% reasonable message for implicitly cancelled events + cast({status, Id, {cancel, undefined}}, St); +cast_cancel(_Id, Msg, St) -> + cast(Msg, St). + +cast(Msg, St) -> + sets:fold(fun (L, M) -> L ! M end, Msg, St#state.listeners), + ok. + +%% wait for a particular begin or end event, that might have arrived or +%% been cancelled already, or might become cancelled later, or might not +%% even exist (for the last+1 element of a group) +wait(Id, Type, ParentId, GroupMinSize, St) -> + %%?debugVal({wait, Id, Type}), + case check_cancelled(Id, St) of + no -> + case recall(Id, St) of + undefined -> + wait_1(Id, Type, ParentId, GroupMinSize, St); + Msg -> + {ok, Msg, forget(Id, St)} + end; + Why -> + %%?debugVal({cancelled, Why, Id, ParentId}), + {cancel, Why, recall(Id, St), forget(Id, St)} + end. + +%% the event has not yet arrived or been cancelled - wait for more info +wait_1(Id, Type, ParentId, GroupMinSize, St) -> + receive + {status, Id, {progress, Type, _}}=Msg -> + %%?debugVal({Type, ParentId, Id}), + {ok, Msg, St}; + {status, ParentId, {progress, 'end', {GroupMinSize, _}}}=Msg -> + %% the parent group ended (the final status of a group is + %% the count of its subitems), and we have seen all of its + %% subtests, so the currently expected event does not exist + %%?debugVal({end_group, ParentId, Id, GroupMinSize}), + {done, remember(ParentId, Msg, St)}; + {status, SomeId, {cancel, _Cause}}=Msg -> + %%?debugVal({got_cancel, SomeId, _Cause}), + St1 = set_cancelled(SomeId, Msg, St), + wait(Id, Type, ParentId, GroupMinSize, St1) + end. + +set_cancelled(Id, Msg, St0) -> + St = remember(Id, Msg, St0), + St#state{cancelled = eunit_lib:trie_store(Id, St0#state.cancelled)}. + +check_cancelled(Id, St) -> + %% returns 'no', 'exact', or 'prefix' + eunit_lib:trie_match(Id, St#state.cancelled). + +remember(Id, Msg, St) -> + St#state{messages = dict:store(Id, Msg, St#state.messages)}. + +forget(Id, St) -> + %% this is just to enable garbage collection of old messages + St#state{messages = dict:store(Id, undefined, St#state.messages)}. + +recall(Id, St) -> + case dict:find(Id, St#state.messages) of + {ok, Msg} -> Msg; + error -> undefined + end. |