%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2004-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%
%%

%%%-------------------------------------------------------------------
%%% File    : ethread_SUITE.erl
%%% Author  : Rickard Green <rickard.s.green@ericsson.com>
%%% Description : 
%%%
%%% Created : 17 Jun 2004 by Rickard Green <rickard.s.green@ericsson.com>
%%%-------------------------------------------------------------------
-module(ethread_SUITE).
-author('rickard.s.green@ericsson.com').

%-define(line_trace, 1).

-define(DEFAULT_TIMEOUT, ?t:minutes(10)).

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

-export([create_join_thread/1,
	 equal_tids/1,
	 mutex/1,
	 try_lock_mutex/1,
	 recursive_mutex/1,
	 time_now/1,
	 cond_wait/1,
	 cond_timedwait/1,
	 broadcast/1,
	 detached_thread/1,
	 max_threads/1,
	 forksafety/1,
	 vfork/1,
	 tsd/1,
	 spinlock/1,
	 rwspinlock/1,
	 rwmutex/1,
	 atomic/1,
	 gate/1]).

-include_lib("test_server/include/test_server.hrl").

tests() ->
    [create_join_thread,
     equal_tids,
     mutex,
     try_lock_mutex,
     recursive_mutex,
     time_now,
     cond_wait,
     cond_timedwait,
     broadcast,
     detached_thread,
     max_threads,
     forksafety,
     vfork,
     tsd,
     spinlock,
     rwspinlock,
     rwmutex,
     atomic,
     gate].

all(doc) -> [];
all(suite) -> tests().


%%
%%
%% The test-cases
%%
%%

create_join_thread(doc) ->
    ["Tests ethr_thr_create and ethr_thr_join."];
create_join_thread(suite) ->
    [];
create_join_thread(Config) ->
    run_case(Config, "create_join_thread", "").

equal_tids(doc) ->
    ["Tests ethr_equal_tids."];
equal_tids(suite) ->
    [];
equal_tids(Config) ->
    run_case(Config, "equal_tids", "").

mutex(doc) ->
    ["Tests mutexes."];
mutex(suite) ->
    [];
mutex(Config) ->
    run_case(Config, "mutex", "").

try_lock_mutex(doc) ->
    ["Tests try lock on mutex."];
try_lock_mutex(suite) ->
    [];
try_lock_mutex(Config) ->
    run_case(Config, "try_lock_mutex", "").

recursive_mutex(doc) ->
    ["Tests recursive mutexes."];
recursive_mutex(suite) ->
    [];
recursive_mutex(Config) ->
    run_case(Config, "recursive_mutex", "").

time_now(doc) ->
    ["Tests ethr_time_now by comparing time values with Erlang."];
time_now(suite) ->
    [];
time_now(Config) ->
    run_case(Config, "time_now", "", fun (P) ->
					     spawn_link(fun () ->
								watchdog(P)
							end)
				     end).

wd_dispatch(P) ->
    receive
	bye ->
	    ?line true = port_command(P, "-1 "),
	    ?line bye;
	L when is_list(L) ->
	    ?line true = port_command(P, L),
	    ?line wd_dispatch(P)
    end.

watchdog(Port) ->
    ?line process_flag(priority, max),
    ?line receive after 500 -> ok end,

    ?line random:seed(),
    ?line true = port_command(Port, "0 "),
    ?line lists:foreach(fun (T) ->
				erlang:send_after(T,
						  self(),
						  integer_to_list(T)
						  ++ " ")
			end,
			lists:usort(lists:map(fun (_) ->
						      random:uniform(4500)+500
					      end,
					      lists:duplicate(50,0)))),
    ?line erlang:send_after(5100, self(), bye),

    wd_dispatch(Port).

cond_wait(doc) ->
    ["Tests ethr_cond_wait with ethr_cond_signal and ethr_cond_broadcast."];
cond_wait(suite) ->
    [];
cond_wait(Config) ->
    run_case(Config, "cond_wait", "").

cond_timedwait(doc) ->
    ["Tests ethr_cond_timedwait with ethr_cond_signal and ethr_cond_broadcast."];
cond_timedwait(suite) ->
    [];
cond_timedwait(Config) ->
    run_case(Config, "cond_timedwait", "").

broadcast(doc) ->
    ["Tests that a ethr_cond_broadcast really wakes up all waiting threads"];
broadcast(suite) ->
    [];
broadcast(Config) ->
    run_case(Config, "broadcast", "").

detached_thread(doc) ->
    ["Tests detached threads."];
detached_thread(suite) ->
    [];
detached_thread(Config) ->
    run_case(Config, "detached_thread", "").

max_threads(doc) ->
    ["Tests maximum number of threads."];
max_threads(suite) ->
    [];
max_threads(Config) ->
    run_case(Config, "max_threads", "").

forksafety(doc) ->
    ["Tests forksafety."];
forksafety(suite) ->
    [];
forksafety(Config) ->
    run_case(Config, "forksafety", "").

vfork(doc) ->
    ["Tests vfork with threads."];
vfork(suite) ->
    case ?t:os_type() of
	{unix, osf1} ->
	    {skip, "vfork() known to hang multi-threaded applications on osf1"};
	_ ->
	    []
    end;
vfork(Config) ->
    run_case(Config, "vfork", "").

tsd(doc) ->
    ["Tests thread specific data."];
tsd(suite) ->
    [];
tsd(Config) ->
    run_case(Config, "tsd", "").

spinlock(doc) ->
    ["Tests spinlocks."];
spinlock(suite) ->
    [];
spinlock(Config) ->
    run_case(Config, "spinlock", "").

rwspinlock(doc) ->
    ["Tests rwspinlocks."];
rwspinlock(suite) ->
    [];
rwspinlock(Config) ->
    run_case(Config, "rwspinlock", "").

rwmutex(doc) ->
    ["Tests rwmutexes."];
rwmutex(suite) ->
    [];
rwmutex(Config) ->
    run_case(Config, "rwmutex", "").

atomic(doc) ->
    ["Tests atomics."];
atomic(suite) ->
    [];
atomic(Config) ->
    run_case(Config, "atomic", "").

gate(doc) ->
    ["Tests gates."];
gate(suite) ->
    [];
gate(Config) ->
    run_case(Config, "gate", "").

%%
%%
%% Auxiliary functions
%%
%%

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

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

-define(TESTPROG, "ethread_tests").
-define(FAILED_MARKER, $E,$T,$H,$R,$-,$T,$E,$S,$T,$-,$F,$A,$I,$L,$U,$R,$E).
-define(SKIPPED_MARKER, $E,$T,$H,$R,$-,$T,$E,$S,$T,$-,$S,$K,$I,$P).
-define(SUCCESS_MARKER, $E,$T,$H,$R,$-,$T,$E,$S,$T,$-,$S,$U,$C,$C,$E,$S,$S).
-define(PID_MARKER, $E,$T,$H,$R,$-,$T,$E,$S,$T,$-,$P,$I,$D).

port_prog_killer(EProc, OSProc) when is_pid(EProc), is_list(OSProc) ->
    ?line process_flag(trap_exit, true),
    ?line Ref = erlang:monitor(process, EProc),
    ?line receive
	      {'DOWN', Ref, _, _, Reason} when is_tuple(Reason),
					       element(1, Reason)
					       == timetrap_timeout ->
		  ?line Cmd = "kill -9 " ++ OSProc,
		  ?line ?t:format("Test case timed out. "
				  "Trying to kill port program.~n"
				  "  Executing: ~p~n", [Cmd]),
		  ?line case os:cmd(Cmd) of
			    [] ->
				ok;
			    OsCmdRes ->
				?line ?t:format("             ~s", [OsCmdRes])
			end;
	      {'DOWN', Ref, _, _, _} ->
		  %% OSProc is assumed to have terminated by itself
		  ?line ok 
	  end.

get_line(_Port, eol, Data) ->
    ?line Data;
get_line(Port, noeol, Data) ->
    ?line receive
	      {Port, {data, {Flag, NextData}}} ->
		  ?line get_line(Port, Flag, Data ++ NextData);
	      {Port, eof} ->
		  ?line ?t:fail(port_prog_unexpectedly_closed)
	  end.

read_case_data(Port, TestCase) ->
    ?line receive
	      {Port, {data, {eol, [?SUCCESS_MARKER]}}} ->
		  ?line ok;
	      {Port, {data, {Flag, [?SUCCESS_MARKER | CommentStart]}}} ->
		  ?line {comment, get_line(Port, Flag, CommentStart)};
	      {Port, {data, {Flag, [?SKIPPED_MARKER | CommentStart]}}} ->
		  ?line {skipped, get_line(Port, Flag, CommentStart)};
	      {Port, {data, {Flag, [?FAILED_MARKER | ReasonStart]}}} ->
		  ?line ?t:fail(get_line(Port, Flag, ReasonStart));
	      {Port, {data, {eol, [?PID_MARKER | PidStr]}}} ->
		  ?line ?t:format("Port program pid: ~s~n", [PidStr]),
		  ?line CaseProc = self(),
		  ?line list_to_integer(PidStr), % Sanity check
		  spawn_opt(fun () ->
				    port_prog_killer(CaseProc, PidStr)
			    end,
			    [{priority, max}, link]),
		  read_case_data(Port, TestCase);
	      {Port, {data, {Flag, LineStart}}} ->
		  ?line ?t:format("~s~n", [get_line(Port, Flag, LineStart)]),
		  read_case_data(Port, TestCase);
	      {Port, eof} ->
		  ?line ?t:fail(port_prog_unexpectedly_closed)
	  end.

run_case(Config, Test, TestArgs) ->
    run_case(Config, Test, TestArgs, fun (_Port) -> ok end).

run_case(Config, Test, TestArgs, Fun) ->
    TestProg = filename:join([?config(data_dir, Config), ?TESTPROG]),
    Cmd = TestProg ++ " " ++ Test ++ " " ++ TestArgs,
    case catch open_port({spawn, Cmd}, [stream,
					use_stdio,
					stderr_to_stdout,
					eof,
					{line, 1024}]) of
	Port when is_port(Port) ->
	    ?line Fun(Port),
	    ?line CaseResult = read_case_data(Port, Test),
	    ?line receive
		      {Port, eof} ->
			  ?line ok
		  end,
	    ?line CaseResult;
	Error ->
	    ?line ?t:fail({open_port_failed, Error})
    end.