%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 1997-2017. All Rights Reserved.
%% 
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%% 
%% %CopyrightEnd%
%%

-module(after_SUITE).

%% Tests receive after.

-include_lib("common_test/include/ct.hrl").

-export([all/0, suite/0,
	 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,
	 receive_after_blast/1]).

%% Internal exports.

-export([timeout_g/0]).

suite() ->
    [{ct_hooks,[ts_install_cth]},
     {timetrap, {minutes, 4}}].

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

%% Tests for an old round-off error in 'receive after'."
t_after(Config) when is_list(Config) ->
    Frequent = spawn_link(fun frequent_process/0),
    Period = test_server:minutes(1),
    Before = erlang:monotonic_time(),
    receive
    after Period ->
            After = erlang:monotonic_time(),
            unlink(Frequent),
            exit(Frequent, die),
            report(Period, Before, After)
    end.

report(Period, Before, After) ->
    case erlang:convert_time_unit(After - Before, native, 100*1000) / Period of
        Percent when Percent > 100.10 ->
            ct:fail({too_inaccurate, Percent});
        Percent when Percent < 100.0 ->
            ct:fail({too_early, Percent});
        Percent ->
            Comment = io_lib:format("Elapsed/expected: ~.2f %", [Percent]),
            {comment, lists:flatten(Comment)}
    end.

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

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

receive_after1(1) ->
    io:format("Testing: receive after ~p~n", [1]), 
    receive after 1 -> ok end;
receive_after1(N) -> 
    io:format("Testing: receive after ~p~n", [N]), 
    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) ->
    ?TryAfter(-1),
    ?TryAfter(0.0),
    ?TryAfter(3.14),
    ?TryAfter(16#100000000),
    ?TryAfter(392347129847294724972398472984729847129874),
    ?TryAfter(16#3fffffffffffffff),
    ?TryAfter(16#ffffffffffffffff),
    ?TryAfter(-16#100000000),
    ?TryAfter(-3891278094774921784123987129848),
    ?TryAfter(xxx),
    ok.

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

%% 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 ->
	    ct: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 ->
            ct:fail({bad_message,Other})
    end.

%% 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) ->
    P = spawn(?MODULE, timeout_g, []),
    P ! a,
    P ! b,
    receive
    after 1000 -> ok
    end,
    P ! c,
    receive
    after 1000 -> ok
    end,
    P ! d,
    ok.

timeout_g() ->
    receive
        a -> ok
    end,
    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.

blaster() ->
    receive
	{go, TimeoutTime} ->
	    Tmo = TimeoutTime - erlang:monotonic_time(millisecond),
	    receive after Tmo -> ok end
    end.

spawn_blasters(0) ->
    [];
spawn_blasters(N) ->
    [spawn_monitor(fun () -> blaster() end)|spawn_blasters(N-1)].

receive_after_blast(Config) when is_list(Config) ->
    PMs = spawn_blasters(10000),
    TimeoutTime = erlang:monotonic_time(millisecond) + 5000,
    lists:foreach(fun ({P, _}) -> P ! {go, TimeoutTime} end, PMs),
    lists:foreach(fun ({P, M}) ->
                          receive
                              {'DOWN', M, process, P, normal} ->
                                  ok
                          end
                  end, PMs).
