%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2012-2013. 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(group_leader_SUITE).

-compile(export_all).

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

%%--------------------------------------------------------------------
%% @spec suite() -> Info
%% Info = [tuple()]
%% @end
%%--------------------------------------------------------------------
suite() ->
    [{timetrap,{seconds,10}}].

%%--------------------------------------------------------------------
%% @spec init_per_suite(Config0) ->
%%     Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
%% Config0 = Config1 = [tuple()]
%% Reason = term()
%% @end
%%--------------------------------------------------------------------
init_per_suite(Config) ->
    start_my_io_server(),
    Config.

%%--------------------------------------------------------------------
%% @spec end_per_suite(Config0) -> void() | {save_config,Config1}
%% Config0 = Config1 = [tuple()]
%% @end
%%--------------------------------------------------------------------
end_per_suite(_Config) ->
    my_io_server ! die,
    ok.

%%--------------------------------------------------------------------
%% @spec init_per_group(GroupName, Config0) ->
%%               Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
%% GroupName = atom()
%% Config0 = Config1 = [tuple()]
%% Reason = term()
%% @end
%%--------------------------------------------------------------------
init_per_group(_GroupName, Config) ->
    Config.

%%--------------------------------------------------------------------
%% @spec end_per_group(GroupName, Config0) ->
%%               void() | {save_config,Config1}
%% GroupName = atom()
%% Config0 = Config1 = [tuple()]
%% @end
%%--------------------------------------------------------------------
end_per_group(_GroupName, _Config) ->
    ok.

%%--------------------------------------------------------------------
%% @spec init_per_testcase(TestCase, Config0) ->
%%               Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
%% TestCase = atom()
%% Config0 = Config1 = [tuple()]
%% Reason = term()
%% @end
%%--------------------------------------------------------------------
init_per_testcase(_TestCase, Config) ->
    Config.

%%--------------------------------------------------------------------
%% @spec end_per_testcase(TestCase, Config0) ->
%%               void() | {save_config,Config1} | {fail,Reason}
%% TestCase = atom()
%% Config0 = Config1 = [tuple()]
%% Reason = term()
%% @end
%%--------------------------------------------------------------------
end_per_testcase(_TestCase, _Config) ->
    ok.

%%--------------------------------------------------------------------
%% @spec groups() -> [Group]
%% Group = {GroupName,Properties,GroupsAndTestCases}
%% GroupName = atom()
%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}]
%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase]
%% TestCase = atom()
%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}}
%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail |
%%              repeat_until_any_ok | repeat_until_any_fail
%% N = integer() | forever
%% @end
%%--------------------------------------------------------------------
groups() ->
    [{p,[parallel],[p1,p2]},
     {p_restart,[parallel],[p_restart_my_io_server]},
     {seq,[],[s1,s2,s3]},
     {seq2,[],[s4,s5]},
     {seq_in_par,[parallel],[p10,p11,{group,seq},p12,{group,seq2},p13]},
     {capture_io,[parallel],[cap1,cap2]},
     {unexpected_io,[parallel],[unexp1,unexp2]}].

%%--------------------------------------------------------------------
%% @spec all() -> GroupsAndTestCases | {skip,Reason}
%% GroupsAndTestCases = [{group,GroupName} | TestCase]
%% GroupName = atom()
%% TestCase = atom()
%% Reason = term()
%% @end
%%--------------------------------------------------------------------
all() ->
    [tc1,{group,p},{group,p_restart},p3,
     {group,seq_in_par},
     cap1,cap2,
     {group,capture_io},
     {group,unexpected_io}].

tc1(_C) ->
    ok.

p1(_) ->
    %% OTP-10101:
    %%
    %% External apps/processes started by init_per_suite (common operation),
    %% will inherit the group leader of the init_per_suite process, i.e. the
    %% test_server test case control process (executing run_test_case_msgloop/7).
    %% If, later, a parallel test case triggers the external app to print with
    %% e.g. io:format() (also common operation), the calling process will hang!
    %% The reason for this is that a parallel test case has a dedicated IO
    %% server process, other than the central test case control process. The
    %% latter process is not executing run_test_case_msgloop/7 and will not
    %% respond to IO messages. The process is still group leader for the
    %% external app, however, which is wrong. It's the IO process for the
    %% parallel test case that should be group leader - but only for the
    %% particular invokation, since other parallel test cases could be
    %% invoking the external app too.
    print("hej\n").

p2(_) ->
    print("hopp\n").

p_restart_my_io_server(_) ->
    %% Restart the IO server and change its group leader. This used
    %% to set to the group leader to a process that would soon die.
    Ref = erlang:monitor(process, my_io_server),
    my_io_server ! die,
    receive
	{'DOWN',Ref,_,_,_} ->
	    start_my_io_server()
    end.

p3(_) ->
    %% OTP-10125. This would crash since the group leader process
    %% for the my_io_server had died.
    print("hoppsan\n").

print(String) ->
    my_io_server ! {print,self(),String},
    receive
	{printed,String} ->
	    ok
    end.

start_my_io_server() ->
    Parent = self(),
    Pid = spawn(fun() -> my_io_server(Parent) end),
    receive
	{Pid,started} ->
	    io:format("~p\n", [process_info(Pid)]),
	    ok
    end.

my_io_server(Parent) ->
    register(my_io_server, self()),
    Parent ! {self(),started},
    my_io_server_loop().

my_io_server_loop() ->
    receive
	{print,From,String} ->
	    io:put_chars(String),
	    From ! {printed,String},
	    my_io_server_loop();
	die ->
	    ok
    end.

p10(_) ->
    receive after 1 -> ok end.

p11(_) ->
    ok.

p12(_) ->
    ok.

p13(_) ->
    ok.

s1(_) ->
    ok.

s2(_) ->
    ok.

s3(_) ->
    ok.

s4(_) ->
    ok.

s5(_) ->
    ok.

cap1(_) ->
    ct:capture_start(),
    IO = gen_io(cap1, 10, []),
    ct:capture_stop(),
    IO = ct:capture_get(),
    ok.

cap2(_) ->
    ct:capture_start(),
    {Pid,Ref} = spawn_monitor(fun() ->
				      exit(gen_io(cap2, 42, []))
			      end),
    receive
	{'DOWN',Ref,process,Pid,IO} ->
	    ct:capture_stop(),
	    IO = ct:capture_get(),
	    ok
    end.

gen_io(_, 0, Acc) ->
    lists:reverse(Acc);
gen_io(Label, N, Acc) ->
    S = lists:flatten(io_lib:format("~s: ~p\n", [Label,N])),
    io:put_chars(S),
    gen_io(Label, N-1, [S|Acc]).

%% Test that unexpected I/O is sent to test_server's unexpeced_io log.
%% To trigger this, run two test cases in parallel and send a printout
%% (via ct logging functions) from an external process which has a
%% different group leader than the test cases.
unexp1(Config) ->
    ct:sleep(1000),
    gen_unexp_io(),
    ct:sleep(1000),
    check_unexp_io(Config),
    ok.

unexp2(_) ->
    ct:sleep(2000),
    ok.

gen_unexp_io() ->
    spawn(fun() ->
		  group_leader(whereis(user),self()),
		  ct:log("-x- Unexpected io ct:log -x-",[]),
		  ct:pal("-x- Unexpected io ct:pal -x-",[]),
		  ok
	  end).

check_unexp_io(Config) ->
    SuiteLog = ?config(tc_logfile,Config),
    Dir = filename:dirname(SuiteLog),
    UnexpLog = filename:join(Dir,"unexpected_io.log.html"),
    {ok,SuiteBin} = file:read_file(SuiteLog),
    nomatch = re:run(SuiteBin,"-x- Unexpected io ",[global,{capture,none}]),
    {ok,UnexpBin} = file:read_file(UnexpLog),
    {match,[_,_]} = re:run(UnexpBin,"-x- Unexpected io ",[global]),
    ok.
