%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2002-2010. 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(cpu_sup_SUITE).
-include("test_server.hrl").

%% Test server specific exports
-export([all/1]).
-export([init_per_suite/1, end_per_suite/1]).
-export([init_per_testcase/2, end_per_testcase/2]).

%% Test cases
-export([load_api/1]).
-export([util_api/1, util_values/1]).
-export([port/1]).
-export([terminate/1, unavailable/1, restart/1]).

%% Default timetrap timeout (set in init_per_testcase)
-define(default_timeout, ?t:minutes(1)).

init_per_suite(Config) when is_list(Config) ->
    ?line ok = application:start(os_mon),
    Config.

end_per_suite(Config) when is_list(Config) ->
    ?line ok = application:stop(os_mon),
    Config.

init_per_testcase(_Case, Config) ->
    Dog = ?t:timetrap(?default_timeout),
    [{watchdog, Dog} | Config].

end_per_testcase(_Case, Config) ->
    Dog = ?config(watchdog, Config),
    ?t:timetrap_cancel(Dog),
    ok.

all(suite) ->
    case ?t:os_type() of
	{unix, sunos} ->
	    [load_api, util_api, util_values, port,
	     {conf, terminate, [unavailable], restart}];
	{unix, linux} ->
	    [load_api, util_api, util_values, port,
	     {conf, terminate, [unavailable], restart}];
	{unix, _OSname} ->
	    [load_api];
	_OS ->
	    [unavailable]
    end.

load_api(suite) ->
    [];
load_api(doc) ->
    ["Test of load API functions"];
load_api(Config) when is_list(Config) ->

    %% nprocs()
    ?line N = cpu_sup:nprocs(),
    ?line true = is_integer(N),
    ?line true = N>0,

    %% avg1()
    ?line Load1 = cpu_sup:avg1(),
    ?line true = is_integer(Load1),
    ?line true = Load1>0,

    %% avg5()
    ?line Load5 = cpu_sup:avg5(),
    ?line true = is_integer(Load5),
    ?line true = Load5>0,

    %% avg15()
    ?line Load15 = cpu_sup:avg15(),
    ?line true = is_integer(Load15),
    ?line true = Load15>0,

    ok.

util_api(suite) ->
    [];
util_api(doc) ->
    ["Test of utilization API functions"];
util_api(Config) when is_list(Config) ->
    %% Some useful funs when testing util/1
    BusyP = fun({user, _Share}) -> true;
	       ({nice_user, _Share}) -> true;
	       ({kernel, _Share}) -> true;
	       ({hard_irq, _Share}) -> true;
	       ({soft_irq, _Share}) -> true;
	       (_) -> false
	    end,
    NonBusyP = fun({wait, _Share}) -> true;
		  ({idle, _Share}) -> true;
		  ({steal, _Share}) -> true;
		  (_) -> false
	       end,
    Sum = fun({_Tag, X}, Acc) -> Acc+X end,

    %% util()
    ?line Util1 = cpu_sup:util(),
    ?line true = is_number(Util1),
    ?line true = Util1>0,
    ?line Util2 = cpu_sup:util(),
    ?line true = is_number(Util2),
    ?line true = Util2>0,

    %% util([])
    ?line {all, Busy1, NonBusy1, []} = cpu_sup:util([]),
    ?line 100.00 = Busy1 + NonBusy1,

    %% util([detailed])
    ?line {Cpus2, Busy2, NonBusy2, []} = cpu_sup:util([detailed]),
    ?line true = lists:all(fun(X) -> is_integer(X) end, Cpus2),
    ?line true = lists:all(BusyP, Busy2),
    ?line true = lists:all(NonBusyP, NonBusy2),
    ?line 100.00 = lists:foldl(Sum,0,Busy2)+lists:foldl(Sum,0,NonBusy2),

    %% util([per_cpu])
    ?line [{Cpu3, Busy3, NonBusy3, []}|_] = cpu_sup:util([per_cpu]),
    ?line true = is_integer(Cpu3),
    ?line 100.00 = Busy3 + NonBusy3,

    %% util([detailed, per_cpu])
    ?line [{Cpu4, Busy4, NonBusy4, []}|_] =
	cpu_sup:util([detailed, per_cpu]),
    ?line true = is_integer(Cpu4),
    ?line true = lists:all(BusyP, Busy2),
    ?line true = lists:all(NonBusyP, NonBusy2),
    ?line 100.00 = lists:foldl(Sum,0,Busy4)+lists:foldl(Sum,0,NonBusy4),

    %% bad util/1 calls
    ?line {'EXIT',{badarg,_}} = (catch cpu_sup:util(detailed)),
    ?line {'EXIT',{badarg,_}} = (catch cpu_sup:util([detialed])),

    ok.

-define(SPIN_TIME, 1000).

util_values(suite) ->
    [];
util_values(doc) ->
    ["Test utilization values"];
util_values(Config) when is_list(Config) ->

    Tester = self(),
    Ref = make_ref(),
    Loop = fun (L) -> L(L) end,
    Spinner = fun () ->
		      Looper = spawn_link(fun () -> Loop(Loop) end),
		      receive after ?SPIN_TIME -> ok end,
		      unlink(Looper),
		      exit(Looper, kill),
		      Tester ! Ref
	      end,

    ?line cpu_sup:util(),

    ?line spawn_link(Spinner),
    ?line receive Ref -> ok end,
    ?line HighUtil1 = cpu_sup:util(),

    ?line receive after ?SPIN_TIME -> ok end,
    ?line LowUtil1 = cpu_sup:util(),

    ?line spawn_link(Spinner),
    ?line receive Ref -> ok end,
    ?line HighUtil2 = cpu_sup:util(),

    ?line receive after ?SPIN_TIME -> ok end,
    ?line LowUtil2 = cpu_sup:util(),

    Utils = [{high1,HighUtil1}, {low1,LowUtil1},
	     {high2,HighUtil2}, {low2,LowUtil2}],
    ?t:format("Utils: ~p~n", [Utils]),

    ?line false = LowUtil1 > HighUtil1,
    ?line false = LowUtil1 > HighUtil2,
    ?line false = LowUtil2 > HighUtil1,
    ?line false = LowUtil2 > HighUtil2,

    ok.


% Outdated
% The portprogram is now restarted if killed, and not by os_mon...

port(suite) ->
    [];
port(doc) ->
    ["Test that cpu_sup handles a terminating port program"];
port(Config) when is_list(Config) ->
    case cpu_sup_os_pid() of
	{ok, PidStr} ->
	    %% Monitor cpu_sup
	    ?line MonRef = erlang:monitor(process, cpu_sup),
	    ?line N1 = cpu_sup:nprocs(),
	    ?line true = N1>0,

	    %% Kill the port program
	    case os:cmd("kill -9 " ++ PidStr) of
		[] ->
		    %% cpu_sup should not terminate
		    receive
			{'DOWN', MonRef, _, _, Reason} ->
			    ?line ?t:fail({unexpected_exit_reason, Reason})
		    after 3000 ->
			ok
		    end,

		    %% Give cpu_sup time to restart cpu_sup port
		    ?t:sleep(?t:seconds(3)),
		    ?line N2 = cpu_sup:nprocs(),
		    ?line true = N2>0,

		    erlang:demonitor(MonRef),
		    ok;

		Line ->
		    erlang:demonitor(MonRef),
		    {skip, {not_killed, Line}}
	    end;
	_ ->
	    {skip, os_pid_not_found }
    end.

terminate(suite) ->
    [];
terminate(Config) when is_list(Config) ->
    ?line ok = application:set_env(os_mon, start_cpu_sup, false),
    ?line ok = supervisor:terminate_child(os_mon_sup, cpu_sup),
    ok.

unavailable(suite) ->
    [];
unavailable(doc) ->
    ["Test correct behaviour when service is unavailable"];
unavailable(Config) when is_list(Config) ->

    %% Make sure all API functions return their dummy values
    ?line 0 = cpu_sup:nprocs(),
    ?line 0 = cpu_sup:avg1(),
    ?line 0 = cpu_sup:avg5(),
    ?line 0 = cpu_sup:avg15(),
    ?line 0 = cpu_sup:util(),
    ?line {all,0,0,[]} = cpu_sup:util([]),
    ?line {all,0,0,[]} = cpu_sup:util([detailed]),
    ?line {all,0,0,[]} = cpu_sup:util([per_cpu]),
    ?line {all,0,0,[]} = cpu_sup:util([detailed,per_cpu]),

    ok.

restart(suite) ->
    [];
restart(Config) when is_list(Config) ->
    ?line ok = application:set_env(os_mon, start_cpu_sup, true),
    ?line {ok, _Pid} = supervisor:restart_child(os_mon_sup, cpu_sup),
    ok.

%% Aux

cpu_sup_os_pid() ->
    Str = os:cmd("ps -e | grep '[c]pu_sup'"),
    case io_lib:fread("~s", Str) of
	{ok, [Pid], _Rest} -> {ok, Pid};
	_ -> {error, pid_not_found}
    end.
