%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1997-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(slave_SUITE).
-include_lib("common_test/include/ct.hrl").
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2, t_start/1, t_start_link/1,
start_link_nodedown/1, errors/1]).
%% Internal exports.
-export([fun_init/1, test_errors/1]).
-export([timeout_test/1, auth_test/1, rsh_test/1, start_a_slave/3]).
suite() ->
[{ct_hooks,[ts_install_cth]},
{timetrap,{minutes,1}}].
all() ->
[t_start_link, start_link_nodedown, t_start, errors].
groups() ->
[].
init_per_suite(Config) ->
Config.
end_per_suite(_Config) ->
ok.
init_per_group(_GroupName, Config) ->
Config.
end_per_group(_GroupName, Config) ->
Config.
t_start_link(Config) when is_list(Config) ->
%% Define useful variables.
Host = host(),
Slave1 = node_name(Host, slave1),
Slave2 = node_name(Host, slave2),
%% Test slave:start_link() with one, two, and three arguments.
ThisNode = node(),
{error, {already_running, ThisNode}} = slave:start_link(Host),
{ok, Slave1} = slave:start_link(Host, slave1),
{ok, Slave2} = slave:start_link(Host, slave2, "-my_option 42"),
{ok, [["42"]]} = rpc:call(Slave2, init, get_argument, [my_option]),
%% Kill the two slave nodes and verify that they are dead.
rpc:cast(Slave1, erlang, halt, []),
rpc:cast(Slave2, erlang, halt, []),
is_dead(Slave1),
is_dead(Slave2),
%% Start two slave nodes from another process and verify that
%% the slaves die when that process terminates.
Parent = self(),
Pid = fun_spawn(fun () ->
{ok, Slave1} = slave:start_link(Host, slave1),
{ok, Slave2} = slave:start_link(Host, slave2),
Parent ! slaves_started,
receive never -> ok end
end),
receive slaves_started -> ok end,
process_flag(trap_exit, true),
wait_alive(Slave1),
wait_alive(Slave2),
exit(Pid, kill),
receive {'EXIT', Pid, killed} -> ok end,
ct:sleep(250),
is_dead(Slave1),
is_dead(Slave2),
ok.
%% Test that slave:start_link() works when the master exits.
start_link_nodedown(Config) when is_list(Config) ->
%% Define useful variables.
Host = host(),
Master = node_name(Host, my_master),
Slave = node_name(Host, my_slave),
Pa = "-pa " ++ filename:dirname(code:which(?MODULE)),
{ok, Master} = slave:start_link(Host, my_master, Pa),
spawn(Master, ?MODULE, start_a_slave, [self(), Host, my_slave]),
{reply, {ok, _Node}} = receive Any -> Any end,
rpc:call(Master, erlang, halt, []),
receive after 200 -> ok end,
pang = net_adm:ping(Slave),
ok.
start_a_slave(ReplyTo, Host, Name) ->
ReplyTo ! {reply, slave:start_link(Host, Name)},
receive never -> ok end.
%% Test slave:start().
t_start(Config) when is_list(Config) ->
%% Define useful variables.
Host = host(),
Slave1 = node_name(Host, slave1),
Slave2 = node_name(Host, slave2),
%% By running all tests from this master node which is linked
%% to this test case, we ensure that all slaves are killed
%% if this test case fails. (If they are not, and therefore further
%% test cases fail, there is a bug in slave.)
{ok, Master} = slave:start_link(Host, master),
%% Test slave:start() with one, two, and three arguments.
ThisNode = node(),
{error, {already_running, ThisNode}} = slave:start(Host),
{ok, Slave1} = rpc:call(Master, slave, start, [Host, slave1]),
{ok, Slave2} = rpc:call(Master, slave, start,
[Host, slave2, "-my_option 42"]),
{ok, [["42"]]} = rpc:call(Slave2, init, get_argument, [my_option]),
%% Test that a slave terminates when its master node terminates.
ok = slave:stop(Slave2),
is_dead(Slave2),
{ok, Slave2} = rpc:call(Slave1, slave, start, [Host, slave2]),
is_alive(Slave2),
rpc:call(Slave1, erlang, halt, []), % Kill master.
receive after 1000 -> ok end, % Make sure slaves have noticed
% their dead master.
is_dead(Slave1),
is_dead(Slave2), % Slave should be dead, too.
%% Kill all slaves and verify that they are dead.
ok = slave:stop(Slave1),
ok = slave:stop(Slave2),
is_dead(Slave1),
is_dead(Slave2),
ok.
%% Test the various error conditions in parallell (since the timeout
%% in slave is 32 seconds).
errors(Config) when is_list(Config) ->
process_flag(trap_exit, true),
Pa = filename:dirname(code:which(?MODULE)),
{ok, Master} = slave_start_link(host(), master,
"-rsh no_rsh_program -pa "++Pa++
" -env ERL_CRASH_DUMP erl_crash_dump.master"),
Pids = rpc:call(Master, ?MODULE, test_errors, [self()]),
wait_for_result(Pids),
ok.
wait_for_result([]) ->
ok;
wait_for_result(Pids) ->
receive
{'EXIT', Pid, normal} ->
io:format("Process ~p terminated", [Pid]),
wait_for_result(lists:delete(Pid, Pids));
{'EXIT', _, Reason} ->
exit(Reason)
end.
show_process_info(Pid) ->
io:format("~p: ~p", [Pid, catch process_info(Pid, initial_call)]).
test_errors(ResultTo) ->
%% Sigh! We use ordinary spawn instead of fun_spawn/1 to be able
%% identify the processes by their initial call.
P1 = spawn(?MODULE, timeout_test, [ResultTo]),
P2 = spawn(?MODULE, auth_test, [ResultTo]),
P3 = spawn(?MODULE, rsh_test, [ResultTo]),
Pids =[P1, P2, P3],
lists:foreach(fun show_process_info/1, Pids),
Pids.
timeout_test(ResultTo) ->
link(ResultTo),
{error, timeout} = slave:start(host(), slave1, "-boot no_boot_script").
auth_test(ResultTo) ->
link(ResultTo),
{error, timeout} = slave:start(host(), slave2,
"-setcookie definitely_not_a_cookie").
rsh_test(ResultTo) ->
link(ResultTo),
{error, no_rsh} = slave:start(super, slave3).
%%% Utilities.
wait_alive(Node) ->
wait_alive_1(10, Node).
wait_alive_1(0, Node) ->
ct:fail({still_not_alive,Node});
wait_alive_1(N, Node) ->
case rpc:call(Node, init, get_status, []) of
{started,_} ->
ok;
{starting,_} ->
receive after 1 -> ok end,
wait_alive_1(N-1, Node)
end.
is_alive(Node) ->
{started, _} = rpc:call(Node, init, get_status, []).
is_dead(Node) ->
{badrpc, nodedown} = rpc:call(Node, init, get_status, []).
node_name(Host, Name) ->
list_to_atom(lists:concat([Name, "@", Host])).
host() ->
from($@, atom_to_list(node())).
from(H, [H | T]) -> T;
from(H, [_ | T]) -> from(H, T);
from(_H, []) -> [].
slave_start_link(Host, Name, Args) ->
case slave:start_link(Host, Name, Args) of
{ok, Node} ->
{ok, Node};
Other ->
io:format("slave:start_link(~p, ~p, ~p) -> ~p",
[Host, Name, Args, Other])
end.
fun_spawn(Fun) ->
spawn_link(?MODULE, fun_init, [Fun]).
fun_init(Fun) ->
Fun().