1: %%
    2: %% %CopyrightBegin%
    3: %% 
    4: %% Copyright Ericsson AB 1998-2013. All Rights Reserved.
    5: %% 
    6: %% The contents of this file are subject to the Erlang Public License,
    7: %% Version 1.1, (the "License"); you may not use this file except in
    8: %% compliance with the License. You should have received a copy of the
    9: %% Erlang Public License along with this software. If not, it can be
   10: %% retrieved online at http://www.erlang.org/.
   11: %% 
   12: %% Software distributed under the License is distributed on an "AS IS"
   13: %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
   14: %% the License for the specific language governing rights and limitations
   15: %% under the License.
   16: %% 
   17: %% %CopyrightEnd%
   18: %%
   19: 
   20: -module(timer_bif_SUITE).
   21: 
   22: -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, 
   23: 	 init_per_group/2,end_per_group/2,
   24: 	 init_per_testcase/2,end_per_testcase/2]).
   25: -export([start_timer_1/1, send_after_1/1, send_after_2/1, send_after_3/1,
   26: 	 cancel_timer_1/1,
   27: 	 start_timer_big/1, send_after_big/1,
   28: 	 start_timer_e/1, send_after_e/1, cancel_timer_e/1,
   29: 	 read_timer_trivial/1, read_timer/1,
   30: 	 cleanup/1, evil_timers/1, registered_process/1]).
   31: 
   32: -include_lib("test_server/include/test_server.hrl").
   33: 
   34: init_per_testcase(_Case, Config) ->
   35:     ?line Dog=test_server:timetrap(test_server:seconds(30)),
   36:     case catch erts_debug:get_internal_state(available_internal_state) of
   37: 	true -> ok;
   38: 	_ -> erts_debug:set_internal_state(available_internal_state, true)
   39:     end,
   40:     [{watchdog, Dog}|Config].
   41: 
   42: end_per_testcase(_Case, Config) ->
   43:     Dog = ?config(watchdog, Config),
   44:     test_server:timetrap_cancel(Dog),
   45:     ok.
   46: 
   47: init_per_suite(Config) ->
   48:     Config.
   49: 
   50: end_per_suite(_Config) ->
   51:     catch erts_debug:set_internal_state(available_internal_state, false).
   52: 
   53: suite() -> [{ct_hooks,[ts_install_cth]}].
   54: 
   55: all() -> 
   56:     [start_timer_1, send_after_1, send_after_2,
   57:      cancel_timer_1, start_timer_e, send_after_e,
   58:      cancel_timer_e, start_timer_big, send_after_big,
   59:      read_timer_trivial, read_timer, cleanup, evil_timers,
   60:      registered_process].
   61: 
   62: groups() -> 
   63:     [].
   64: 
   65: init_per_group(_GroupName, Config) ->
   66:     Config.
   67: 
   68: end_per_group(_GroupName, Config) ->
   69:     Config.
   70: 
   71: 
   72: start_timer_1(doc) -> ["Basic start_timer/3 functionality"];
   73: start_timer_1(Config) when is_list(Config) ->
   74:     Ref1 = erlang:start_timer(1000, self(), plopp),
   75:     ok   = get(1100, {timeout, Ref1, plopp}),
   76: 
   77:     false = erlang:read_timer(Ref1),
   78:     false = erlang:cancel_timer(Ref1),
   79:     false = erlang:read_timer(Ref1),
   80: 
   81:     Ref2  = erlang:start_timer(1000, self(), plapp),
   82:     Left2 = erlang:cancel_timer(Ref2),
   83:     UpperLimit = 1000,
   84:     true = (Left2 > 900) and (Left2 =< UpperLimit),
   85:     empty = get_msg(),
   86:     false = erlang:cancel_timer(Ref2),
   87: 
   88:     Ref3 = erlang:start_timer(1000, self(), plopp),
   89:     no_message = get(900, {timeout, Ref3, plopp}),
   90:     ok.
   91: 
   92: send_after_1(doc) -> ["Basic send_after/3 functionality"];
   93: send_after_1(Config) when is_list(Config) ->
   94:     ?line Ref3 = erlang:send_after(1000, self(), plipp),
   95:     ?line ok = get(1500, plipp),
   96:     ?line false = erlang:read_timer(Ref3),
   97:     ok.
   98: 
   99: start_timer_big(doc) -> ["Big timeouts for start_timer/3"];
  100: start_timer_big(Config) when is_list(Config) ->
  101:     ?line Big = 1 bsl 31,
  102:     ?line R = erlang:start_timer(Big, self(), hej),
  103:     ?line timer:sleep(200),
  104:     ?line Left = erlang:cancel_timer(R),
  105:     ?line case Big - Left of
  106: 	      Diff when Diff >= 200, Diff < 10000 ->
  107: 		  ok;
  108: 	      _Diff ->
  109: 		  test_server:fail({big, Big, Left})
  110: 	  end,
  111:     ok.
  112: 
  113: send_after_big(doc) -> ["Big timeouts for send_after/3"];
  114: send_after_big(Config) when is_list(Config) ->
  115:     ?line Big = 1 bsl 31,
  116:     ?line R = erlang:send_after(Big, self(), hej),
  117:     ?line timer:sleep(200),
  118:     ?line Left = erlang:cancel_timer(R),
  119:     ?line case Big - Left of
  120: 	      Diff when Diff >= 200, Diff < 10000 ->
  121: 		  ok;
  122: 	      _Diff ->
  123: 		  test_server:fail({big, Big, Left})
  124: 	  end,
  125:     ok.
  126: 
  127: send_after_2(doc) -> ["send_after/3: messages in the right order, kind version"];
  128: send_after_2(Config) when is_list(Config) ->
  129:     ?line _ = erlang:send_after(5000, self(), last),
  130:     ?line _ = erlang:send_after(0, self(), a0),
  131:     ?line _ = erlang:send_after(200, self(), a2),
  132:     ?line _ = erlang:send_after(100, self(), a1),
  133:     ?line _ = erlang:send_after(500, self(), a5),
  134:     ?line _ = erlang:send_after(300, self(), a3),
  135:     ?line _ = erlang:send_after(400, self(), a4),
  136:     ?line [a0,a1,a2,a3,a4,a5,last] = collect(last),
  137:     ok.
  138: 
  139: send_after_3(doc) -> ["send_after/3: messages in the right order, worse than send_after_2"];
  140: send_after_3(Config) when is_list(Config) ->
  141:     _ = erlang:send_after(100, self(), b1),
  142:     _ = erlang:send_after(101, self(), b2),
  143:     _ = erlang:send_after(102, self(), b3),
  144:     _ = erlang:send_after(103, self(), last),
  145:     [b1, b2, b3, last] = collect(last),
  146: 
  147: % This behaviour is not guaranteed:
  148: %    ?line _ = erlang:send_after(100, self(), c1),
  149: %    ?line _ = erlang:send_after(100, self(), c2),
  150: %    ?line _ = erlang:send_after(100, self(), c3),
  151: %    ?line _ = erlang:send_after(100, self(), last),
  152: %    ?line [c1, c2, c3, last] = collect(last),
  153: 
  154:     ok.
  155: 
  156: cancel_timer_1(doc) -> ["Check trivial cancel_timer/1 behaviour"];
  157: cancel_timer_1(Config) when is_list(Config) ->
  158:     ?line false = erlang:cancel_timer(make_ref()),
  159: 
  160:     ok.
  161: 
  162: start_timer_e(doc) -> ["Error cases for start_timer/3"];
  163: start_timer_e(Config) when is_list(Config) ->
  164:     ?line {'EXIT', _} = (catch erlang:start_timer(-4, self(), hej)),
  165:     ?line {'EXIT', _} = (catch erlang:start_timer(4728472847827482,
  166: 						  self(), hej)),
  167: 
  168:     ?line {'EXIT', _} = (catch erlang:start_timer(4.5, self(), hej)),
  169:     ?line {'EXIT', _} = (catch erlang:start_timer(a, self(), hej)),
  170: 
  171:     ?line Node = start_slave(),
  172:     ?line Pid = spawn(Node, timer, sleep, [10000]),
  173:     ?line {'EXIT', _} = (catch erlang:start_timer(1000, Pid, hej)),
  174:     ?line stop_slave(Node),
  175: 
  176: 
  177:     ok.
  178: 
  179: send_after_e(doc) -> ["Error cases for send_after/3"];
  180: send_after_e(suite) -> [];
  181: send_after_e(Config) when is_list(Config) ->
  182:     ?line {'EXIT', _} = (catch erlang:send_after(-4, self(), hej)),
  183:     ?line {'EXIT', _} = (catch erlang:send_after(4728472847827482,
  184: 						 self(), hej)),
  185: 
  186:     ?line {'EXIT', _} = (catch erlang:send_after(4.5, self(), hej)),
  187:     ?line {'EXIT', _} = (catch erlang:send_after(a, self(), hej)),
  188: 
  189:     ?line Node = start_slave(),
  190:     ?line Pid = spawn(Node, timer, sleep, [10000]),
  191:     ?line {'EXIT', _} = (catch erlang:send_after(1000, Pid, hej)),
  192:     ?line stop_slave(Node),
  193:     ok.
  194: 
  195: cancel_timer_e(doc) -> ["Error cases for cancel_timer/1"];
  196: cancel_timer_e(suite) -> [];
  197: cancel_timer_e(Config) when is_list(Config) ->
  198:     ?line {'EXIT', _} = (catch erlang:cancel_timer(1)),
  199:     ?line {'EXIT', _} = (catch erlang:cancel_timer(self())),
  200:     ?line {'EXIT', _} = (catch erlang:cancel_timer(a)),
  201:     ok.
  202: 
  203: read_timer_trivial(doc) -> ["Trivial and error test cases for read_timer/1."];
  204: read_timer_trivial(suite) -> [];
  205: read_timer_trivial(Config) when is_list(Config) ->
  206:     ?line false = erlang:read_timer(make_ref()),
  207:     ?line {'EXIT', _} = (catch erlang:read_timer(42)),
  208:     ?line {'EXIT', _} = (catch erlang:read_timer(423497834744444444457667444444)),
  209:     ?line {'EXIT', _} = (catch erlang:read_timer(self())),
  210:     ?line {'EXIT', _} = (catch erlang:read_timer(ab)),
  211:     ok.
  212: 
  213: read_timer(doc) -> ["Test that read_timer/1 seems to return the correct values."];
  214: read_timer(suite) -> [];
  215: read_timer(Config) when is_list(Config) ->
  216:     ?line Big = 1 bsl 31,
  217:     ?line R = erlang:send_after(Big, self(), hej_hopp),
  218: 
  219:     ?line receive after 200 -> ok end,		% Delay and clear reductions.
  220:     ?line Left = erlang:read_timer(R),
  221:     ?line Left = erlang:cancel_timer(R),
  222:     ?line false = erlang:read_timer(R),
  223: 
  224:     ?line case Big - Left of
  225: 	      Diff when Diff >= 200, Diff < 10000 ->
  226: 		  ok;
  227: 	      _Diff ->
  228: 		  test_server:fail({big, Big, Left})
  229: 	  end,
  230:     ok.
  231: 
  232: cleanup(doc) -> [];
  233: cleanup(suite) -> [];
  234: cleanup(Config) when is_list(Config) ->
  235:     ?line Mem = mem(),
  236:     %% Timer on dead process
  237:     ?line P1 = spawn(fun () -> ok end),
  238:     ?line wait_until(fun () -> process_is_cleaned_up(P1) end),
  239:     ?line T1 = erlang:start_timer(10000, P1, "hej"),
  240:     ?line T2 = erlang:send_after(10000, P1, "hej"),
  241:     ?line Mem = mem(),
  242:     ?line false = erlang:read_timer(T1),
  243:     ?line false = erlang:read_timer(T2),
  244:     ?line Mem = mem(),
  245:     %% Process dies before timeout
  246:     ?line P2 = spawn(fun () -> receive after 500 -> ok end end),
  247:     ?line T3 = erlang:start_timer(10000, P2, "hej"),
  248:     ?line T4 = erlang:send_after(10000, P2, "hej"),
  249:     ?line true = Mem < mem(),
  250:     ?line true = is_integer(erlang:read_timer(T3)),
  251:     ?line true = is_integer(erlang:read_timer(T4)),
  252:     ?line wait_until(fun () -> process_is_cleaned_up(P2) end),
  253:     ?line false = erlang:read_timer(T3),
  254:     ?line false = erlang:read_timer(T4),
  255:     ?line Mem = mem(),
  256:     %% Cancel timer
  257:     ?line P3 = spawn(fun () -> receive after 20000 -> ok end end),
  258:     ?line T5 = erlang:start_timer(10000, P3, "hej"),
  259:     ?line T6 = erlang:send_after(10000, P3, "hej"),
  260:     ?line true = Mem < mem(),
  261:     ?line true = is_integer(erlang:cancel_timer(T5)),
  262:     ?line true = is_integer(erlang:cancel_timer(T6)),
  263:     ?line false = erlang:read_timer(T5),
  264:     ?line false = erlang:read_timer(T6),
  265:     ?line exit(P3, kill),
  266:     ?line Mem = mem(),
  267:     %% Timeout
  268:     ?line Ref = make_ref(),
  269:     ?line T7 = erlang:start_timer(500, self(), Ref),
  270:     ?line T8 = erlang:send_after(500, self(), Ref),
  271:     ?line true = Mem < mem(),
  272:     ?line true = is_integer(erlang:read_timer(T7)),
  273:     ?line true = is_integer(erlang:read_timer(T8)),
  274:     ?line receive {timeout, T7, Ref} -> ok end,
  275:     ?line receive Ref -> ok end,
  276:     ?line Mem = mem(),
  277:     ?line ok.
  278: 
  279: 
  280: evil_timers(doc) -> [];
  281: evil_timers(suite) -> [];
  282: evil_timers(Config) when is_list(Config) ->
  283:     %% Create a composite term consisting of at least:
  284:     %% * externals (remote pids, ports, and refs)
  285:     %% * large (off heap) binaries
  286:     %% * small (heap) binaries
  287:     %% * funs
  288:     %% * bignums
  289:     %% * tuples
  290:     %% * lists
  291:     %% since data of these types have to be adjusted if moved
  292:     %% in memory
  293:     ?line Self = self(),
  294:     ?line R1 = make_ref(),
  295:     ?line Node = start_slave(),
  296:     ?line spawn_link(Node,
  297: 		     fun () ->
  298: 			     Self ! {R1,
  299: 				     [lists:sublist(erlang:ports(), 3),
  300: 				      [make_ref(), make_ref(), make_ref()],
  301: 				      lists:sublist(processes(), 3),
  302: 				      [fun () -> gurka end,
  303: 				       fun (A) -> A + 1 end,
  304: 				       fun (A, B) -> A + B end]]}
  305: 		     end),
  306:     ?line ExtList = receive {R1, L} -> L end,
  307:     ?line stop_slave(Node),
  308:     ?line BinList = [<<"bla">>,
  309: 		     <<"blipp">>,
  310: 		     <<"blupp">>,
  311: 		     list_to_binary(lists:duplicate(1000000,$a)),
  312: 		     list_to_binary(lists:duplicate(1000000,$b)),
  313: 		     list_to_binary(lists:duplicate(1000000,$c))],
  314:     ?line FunList = [fun () -> gurka end,
  315: 		     fun (A) -> A + 1 end,
  316: 		     fun (A, B) -> A + B end],
  317:     ?line PidList = lists:sublist(processes(), 3),
  318:     ?line PortList = lists:sublist(erlang:ports(), 3),
  319:     ?line RefList = [make_ref(), make_ref(), make_ref()],
  320:     ?line BigList = [111111111111, 22222222222222, 333333333333333333],
  321:     ?line Msg = {BinList,[FunList,{RefList,ExtList,PidList,PortList,BigList}]},
  322:     %% ?line ?t:format("Msg=~p~n",[Msg]),
  323: 
  324:     ?line Prio = process_flag(priority, max),
  325:     %%
  326:     %% In the smp case there are four major cases we want to test:
  327:     %%
  328:     %% 1. A timer started with erlang:start_timer(Time, Receiver, Msg),
  329:     %%    where Msg is a composite term, expires, and the receivers main
  330:     %%    lock *can not* be acquired immediately (typically when the
  331:     %%    receiver *is* running).
  332:     %%
  333:     %%    The wrap tuple ({timeout, TRef, Msg}) will in this case
  334:     %%    be allocated in the previously allocated message buffer along
  335:     %%    with Msg, i.e. the previously allocated message buffer will be
  336:     %%    reallocated and potentially moved.
  337:     ?line TimeOutMsgs0 = evil_setup_timers(200, Self, Msg),
  338:     ?line RecvTimeOutMsgs0 = evil_recv_timeouts(200),
  339:     %% 2. A timer started with erlang:start_timer(Time, Receiver, Msg),
  340:     %%    where Msg is an immediate term, expires, and the receivers main
  341:     %%    lock *can not* be acquired immediately (typically when the
  342:     %%    receiver *is* running).
  343:     %%
  344:     %%    The wrap tuple will in this case be allocated in a new
  345:     %%    message buffer.
  346:     ?line TimeOutMsgs1 = evil_setup_timers(200, Self, immediate),
  347:     ?line RecvTimeOutMsgs1 = evil_recv_timeouts(200),
  348:     %% 3. A timer started with erlang:start_timer(Time, Receiver, Msg),
  349:     %%    where Msg is a composite term, expires, and the receivers main
  350:     %%    lock *can* be acquired immediately (typically when the receiver
  351:     %%    *is not* running).
  352:     %%
  353:     %%    The wrap tuple will in this case be allocated on the receivers
  354:     %%    heap, and Msg is passed in the previously allocated message
  355:     %%    buffer.
  356:     ?line R2 = make_ref(),
  357:     ?line spawn_link(fun () ->
  358: 			     Self ! {R2, evil_setup_timers(200, Self, Msg)}
  359: 		     end),
  360:     ?line receive after 1000 -> ok end,
  361:     ?line TimeOutMsgs2 = receive {R2, TOM2} -> TOM2 end,
  362:     ?line RecvTimeOutMsgs2 = evil_recv_timeouts(200),
  363:     %% 4. A timer started with erlang:start_timer(Time, Receiver, Msg),
  364:     %%    where Msg is an immediate term, expires, and the Receivers main
  365:     %%    lock *can* be acquired immediately (typically when the receiver
  366:     %%    *is not* running).
  367:     %%
  368:     %%    The wrap tuple will in this case be allocated on the receivers
  369:     %%    heap.
  370:     ?line R3 = make_ref(),
  371:     ?line spawn_link(fun () ->
  372: 			     Self ! {R3, evil_setup_timers(200,Self,immediate)}
  373: 		     end),
  374:     ?line receive after 1000 -> ok end,
  375:     ?line TimeOutMsgs3 = receive {R3, TOM3} -> TOM3 end,
  376:     ?line RecvTimeOutMsgs3 = evil_recv_timeouts(200),
  377: 
  378:     %% Garge collection will hopefully crash the emulator if something
  379:     %% is wrong...
  380:     ?line garbage_collect(),
  381:     ?line garbage_collect(),
  382:     ?line garbage_collect(),
  383: 
  384:     %% Make sure we got the timeouts we expected
  385:     %%
  386:     %% Note timeouts are *not* guaranteed to be delivered in order
  387:     ?line ok = match(lists:sort(RecvTimeOutMsgs0), lists:sort(TimeOutMsgs0)),
  388:     ?line ok = match(lists:sort(RecvTimeOutMsgs1), lists:sort(TimeOutMsgs1)),
  389:     ?line ok = match(lists:sort(RecvTimeOutMsgs2), lists:sort(TimeOutMsgs2)),
  390:     ?line ok = match(lists:sort(RecvTimeOutMsgs3), lists:sort(TimeOutMsgs3)),
  391: 
  392:     ?line process_flag(priority, Prio),
  393:     ?line ok.
  394: 
  395: evil_setup_timers(N, Receiver, Msg) ->
  396:     ?line evil_setup_timers(0, N, Receiver, Msg, []).
  397: 
  398: evil_setup_timers(N, N, _Receiver, _Msg, TOs) ->
  399:     ?line TOs;
  400: evil_setup_timers(N, Max, Receiver, Msg, TOs) ->
  401:     ?line TRef = erlang:start_timer(N, Receiver, Msg),
  402:     ?line evil_setup_timers(N+1, Max, Receiver, Msg, [{timeout,TRef,Msg}|TOs]).
  403: 
  404: 
  405: evil_recv_timeouts(M) ->
  406:     ?line evil_recv_timeouts([], 0, M).
  407: 
  408: evil_recv_timeouts(TOs, N, N) ->
  409:     ?line TOs;
  410: evil_recv_timeouts(TOs, N, M) ->
  411:     ?line receive
  412: 	      {timeout, _, _} = TO ->
  413: 		  ?line evil_recv_timeouts([TO|TOs], N+1, M)
  414: 	  after 0 ->
  415: 		  ?line evil_recv_timeouts(TOs, N, M)
  416: 	  end.
  417: 
  418: registered_process(doc) -> [];
  419: registered_process(suite) -> [];
  420: registered_process(Config) when is_list(Config) ->
  421:     ?line Mem = mem(),
  422:     %% Cancel
  423:     ?line T1 = erlang:start_timer(500, ?MODULE, "hej"),
  424:     ?line T2 = erlang:send_after(500, ?MODULE, "hej"),
  425:     ?line undefined = whereis(?MODULE),
  426:     ?line true = Mem < mem(),
  427:     ?line true = is_integer(erlang:cancel_timer(T1)),
  428:     ?line true = is_integer(erlang:cancel_timer(T2)),
  429:     ?line false = erlang:read_timer(T1),
  430:     ?line false = erlang:read_timer(T2),
  431:     ?line Mem = mem(),
  432:     %% Timeout register after start
  433:     ?line Ref1 = make_ref(),
  434:     ?line T3 = erlang:start_timer(500, ?MODULE, Ref1),
  435:     ?line T4 = erlang:send_after(500, ?MODULE, Ref1),
  436:     ?line undefined = whereis(?MODULE),
  437:     ?line true = Mem < mem(),
  438:     ?line true = is_integer(erlang:read_timer(T3)),
  439:     ?line true = is_integer(erlang:read_timer(T4)),
  440:     ?line true = register(?MODULE, self()),
  441:     ?line receive {timeout, T3, Ref1} -> ok end,
  442:     ?line receive Ref1 -> ok end,
  443:     ?line Mem = mem(),
  444:     %% Timeout register before start
  445:     ?line Ref2 = make_ref(),
  446:     ?line T5 = erlang:start_timer(500, ?MODULE, Ref2),
  447:     ?line T6 = erlang:send_after(500, ?MODULE, Ref2),
  448:     ?line true = Mem < mem(),
  449:     ?line true = is_integer(erlang:read_timer(T5)),
  450:     ?line true = is_integer(erlang:read_timer(T6)),
  451:     ?line receive {timeout, T5, Ref2} -> ok end,
  452:     ?line receive Ref2 -> ok end,
  453:     ?line Mem = mem(),
  454:     ?line true = unregister(?MODULE),
  455:     ?line ok.
  456: 
  457: mem() ->
  458:     AA = erlang:system_info(allocated_areas),
  459:     {value,{bif_timer,Mem}} = lists:keysearch(bif_timer, 1, AA),
  460:     Mem.
  461: 
  462: process_is_cleaned_up(P) when is_pid(P) ->
  463:     undefined == erts_debug:get_internal_state({process_status, P}).
  464: 
  465: wait_until(Pred) when is_function(Pred) ->
  466:     case catch Pred() of
  467: 	true -> ok;
  468: 	_ -> receive after 50 -> ok end, wait_until(Pred)
  469:     end.
  470: 
  471: get(Time, Msg) ->
  472:     receive 
  473: 	Msg ->
  474: 	    ok
  475:     after Time
  476: 	  ->
  477: 	    no_message
  478:     end.
  479: 
  480: get_msg() ->
  481:     receive
  482: 	Msg ->
  483: 	    {ok, Msg}
  484:     after 0 ->
  485: 	    empty
  486:     end.
  487: 
  488: start_slave() ->
  489:     ?line {A, B, C} = now(),
  490:     ?line Pa = filename:dirname(code:which(?MODULE)),
  491:     ?line Name = atom_to_list(?MODULE) ++ "-" ++ integer_to_list(A+B+C),
  492:     {ok, Node} = ?t:start_node(Name, slave, [{args, "-pa " ++ Pa}]),
  493:     Node.
  494: 
  495: stop_slave(Node) ->
  496:     test_server:stop_node(Node).
  497: 
  498: collect(Last) ->
  499:     collect(Last, []).
  500: 
  501: receive_one() ->
  502:     receive
  503: 	Msg ->
  504: 	    Msg
  505:     end.
  506: 
  507: collect(Last, Msgs0) ->
  508:     Msg = receive_one(),
  509:     Msgs = Msgs0 ++ [Msg],
  510:     case Msg of
  511: 	Last ->
  512: 	    Msgs;
  513: 	_ ->
  514: 	    collect(Last, Msgs)
  515:     end.
  516: 
  517: match(X, X) ->
  518:     %erlang:display({match, X}),
  519:     ok;
  520: match(X, Y) ->
  521:     %erlang:display({mismatch, X, Y}),
  522:     match_aux(X, Y).
  523: 
  524: match_aux(X, X) ->
  525:     unexpected_error;
  526: match_aux(X, Y) when is_list(X), is_list(Y), length(X) =/= length(Y) ->
  527:     %% erlang:display({mismatch, X, Y}),
  528:     {list_length_mismatch, length(X), length(Y)};
  529: match_aux([X|Xs], [X|Ys]) ->
  530:     match_aux(Xs, Ys);
  531: match_aux([X|_], [Y|_]) ->
  532:     match_aux(X, Y);
  533: match_aux(X, Y) when is_tuple(X), is_tuple(Y), size(X) =/= size(Y) ->
  534:     %% erlang:display({mismatch, X, Y}),
  535:     {tuple_size_mismatch, size(X), size(Y)};
  536: match_aux(X, Y) when is_tuple(X), is_tuple(Y) ->
  537:     match_aux(tuple_to_list(X), tuple_to_list(Y));
  538: match_aux(X, Y) ->
  539:     %% erlang:display({mismatch, X, Y}),
  540:     {mismatch, type(X), type(Y)}.
  541: 
  542: type(X) when is_list(X) -> list;
  543: type(X) when is_tuple(X) -> tuple;
  544: type(X) when is_float(X) -> float;
  545: type(X) when is_integer(X) -> integer;
  546: type(X) when is_pid(X) -> {pid, node(X)};
  547: type(X) when is_reference(X) -> {reference, node(X)};
  548: type(X) when is_port(X) -> {port, node(X)};
  549: type(X) when is_binary(X) -> binary;
  550: type(X) when is_atom(X) -> atom;
  551: type(_) -> unknown.
  552:     
  553: