%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2012-2016. 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.