%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
%% 
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%% 
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%% 
%% %CopyrightEnd%
%%

-module(after_SUITE).

%% Tests receive after.

-include("test_server.hrl").

-export([all/1, t_after/1, receive_after/1, receive_after_big/1,
	 receive_after_errors/1, receive_var_zero/1, receive_zero/1,
	 multi_timeout/1, receive_after_32bit/1]).

-export([init_per_testcase/2, fin_per_testcase/2]).

%% Internal exports.

-export([timeout_g/0]).

all(suite) ->
    [t_after, receive_after, receive_after_big, receive_after_errors,
     receive_var_zero, receive_zero, multi_timeout, receive_after_32bit].

init_per_testcase(Func, Config) when is_atom(Func), is_list(Config) ->
    Dog=?t:timetrap(?t:minutes(3)),
    [{watchdog, Dog}|Config].

fin_per_testcase(_Func, Config) ->
    Dog=?config(watchdog, Config),
    ?t:timetrap_cancel(Dog).

%% Tests for an old round-off error in 'receive after'."
t_after(Config) when is_list(Config) ->
    ?line spawn(fun frequent_process/0),
    ?line Period = test_server:minutes(1),
    ?line Before = erlang:now(),
    receive
	after Period ->
		?line After = erlang:now(),
		?line report(Period, Before, After)
	end.


report(Period, Before, After) ->
    ?line Elapsed = (element(1, After)*1000000000
		     +element(2, After)*1000
		     +element(3, After) div 1000) -
	(element(1,Before)*1000000000
	 + element(2,Before)*1000 + element(3,Before) div 1000),
    ?line case Elapsed*100 / Period of
	      Percent when Percent > 100.10 ->
		  ?line test_server:fail({too_inaccurate, Percent});
	      Percent when Percent < 100.0 ->
		  ?line test_server:fail({too_early, Percent});
	      Percent ->
		  ?line Comment = io_lib:format("Elapsed/expected: ~.2f %",
						[Percent]),
		  {comment, lists:flatten(Comment)}
	  end.

frequent_process() ->
    receive
	after 100 ->
		?line frequent_process()
	end.

receive_after(doc) ->
    "Test that 'receive after' works (doesn't hang). "
    "The test takes 10 seconds to complete.";
receive_after(Config) when is_list(Config) ->
    ?line receive_after1(5000).

receive_after1(1) ->
    ?line io:format("Testing: receive after ~p~n", [1]), 
    ?line receive after 1 -> ok end;
receive_after1(N) -> 
    ?line io:format("Testing: receive after ~p~n", [N]), 
    ?line receive after N -> receive_after1(N div 2) end.

receive_after_big(Config) when is_list(Config) ->
    %% Test that 'receive after' with a 32 bit number works.
    receive_after_big1(16#f7654321),
    receive_after_big2().

receive_after_big1(Timeout) ->
    Self = self(),
    erlang:yield(),
    spawn(fun() -> Self ! here_is_a_message end),
    ok = receive
	     here_is_a_message ->
		 ok
	 after Timeout ->
		 %% We test that the timeout can be set,
		 %% not that an timeout occurs after the appropriate delay
		 %% (48 days, 56 minutes, 48 seconds)!
		 timeout
	 end.

receive_after_big2() ->
    Self = self(),
    erlang:yield(),
    spawn(fun() -> Self ! here_is_a_message end),
    ok = receive
	     here_is_a_message ->
		 ok
	 after 16#f7999977 ->
		 %% We only test that the timeout can be set.
		 timeout
	 end.

-define(TryAfter(Timeout), 
	{'EXIT',{timeout_value,_}} = (catch receive mission -> exit(impossible) after Timeout -> ok end),
	{'EXIT',{timeout_value,_}} = (catch receive after Timeout -> ok end),
	try_after(Timeout)).

%% Test error cases for 'receive after'.
receive_after_errors(Config) when is_list(Config) ->
    ?line ?TryAfter(-1),
    ?line ?TryAfter(0.0),
    ?line ?TryAfter(3.14),
    ?line ?TryAfter(16#100000000),
    ?line ?TryAfter(392347129847294724972398472984729847129874),
    ?line ?TryAfter(16#3fffffffffffffff),
    ?line ?TryAfter(16#ffffffffffffffff),
    ?line ?TryAfter(-16#100000000),
    ?line ?TryAfter(-3891278094774921784123987129848),
    ?line ?TryAfter(xxx),
    ok.

try_after(Timeout) ->
    {'EXIT',{timeout_value,_}} = (catch receive after Timeout -> ok end).

receive_var_zero(doc) -> "Test 'after Z', when Z == 0.";
receive_var_zero(Config) when is_list(Config) ->
    self() ! x,
    self() ! y,
    Z = zero(),
    timeout = receive
		  z -> ok
	      after Z -> timeout
	      end,
    timeout = receive
	      after Z -> timeout
	      end,
    self() ! w,
    receive
	x -> ok;
	Other ->
	    ?line ?t:fail({bad_message,Other})
    end.

zero() -> 0.

%% Test 'after 0'.
receive_zero(Config) when is_list(Config) ->
    self() ! x,
    self() ! y,
    timeout = receive
		  z -> ok
	      after 0 ->
		      timeout
	      end,
    self() ! w,
    timeout = receive
              after 0 -> timeout
              end,
    receive
	x -> ok;
	Other ->
	    ?line ?t:fail({bad_message,Other})
    end.

multi_timeout(doc) ->
    "Test for catching invalid assertion in erl_message.c (in queue_message)."
    "This failed (dumped core) with debug-compiled emulator.";
multi_timeout(Config) when is_list(Config) ->
    ?line P = spawn(?MODULE, timeout_g, []),
    ?line P ! a,
    ?line P ! b,
    ?line receive
	  after 1000 -> ok
	  end,
    ?line P ! c,
    ?line receive
	  after 1000 -> ok
	  end,
    ?line P ! d,
    ok.

timeout_g() ->
    ?line receive
	a -> ok
    end,
    ?line receive
	  after 100000 -> ok
	  end,
    ok.

%% OTP-7493: Timeout for 32 bit numbers (such as 16#ffffFFFF) could
%% timeout at once.
receive_after_32bit(Config) when is_list(Config) ->
    T = 16#ffffFFFF,
    Pids = [spawn_link(fun() -> recv_after_32bit(I, T) end) ||
	       I <- lists:seq(1, 2048)],

    %% Wait two seconds for any of the processes to timeout too early.
    receive after 2000 -> ok end,

    %% Kill the processes.
    [begin unlink(Pid), exit(Pid, kill) end || Pid <- Pids],
    ok.

recv_after_32bit(I, T) when I rem 2 =:= 0 ->
    receive after T -> exit(timeout) end;
recv_after_32bit(_, _) ->
    receive after 16#ffffFFFF -> exit(timeout) end.