%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2017. 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% %% %% %% File: os_signal_SUITE.erl %% Author: Björn-Egil Dahlberg %% Created: 2017-01-13 %% -module(os_signal_SUITE). -include_lib("common_test/include/ct.hrl"). -export([all/0, suite/0]). -export([init_per_testcase/2, end_per_testcase/2]). -export([init_per_suite/1, end_per_suite/1]). -export([set_alarm/1, fork/0, get_exit_code/1]). % Test cases -export([set_unset/1, t_sighup/1, t_sigusr1/1, t_sigusr2/1, t_sigterm/1, t_sigalrm/1, t_sigchld/1, t_sigchld_fork/1]). -define(signal_server, erl_signal_server). suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap, {minutes, 2}}]. all() -> case os:type() of {win32, _} -> []; _ -> [set_unset, t_sighup, t_sigusr1, t_sigusr2, t_sigterm, t_sigalrm, t_sigchld, t_sigchld_fork] end. init_per_testcase(Func, Config) when is_atom(Func), is_list(Config) -> Pid = erlang:whereis(?signal_server), true = erlang:unregister(?signal_server), [{signal_server, Pid}|Config]. end_per_testcase(_Func, Config) -> case proplists:get_value(signal_server, Config) of undefined -> ok; Pid -> true = erlang:register(?signal_server, Pid), ok end. init_per_suite(Config) -> load_nif(Config), Config. end_per_suite(_Config) -> ok. %% tests set_unset(_Config) -> Signals = [sighup, %sigint, sigquit, %sigill, sigabrt, sigalrm, sigterm, sigusr1, sigusr2, sigchld, sigstop, sigtstp], F1 = fun(Sig) -> ok = os:set_signal(Sig,handle) end, F2 = fun(Sig) -> ok = os:set_signal(Sig,default) end, F3 = fun(Sig) -> ok = os:set_signal(Sig,ignore) end, %% set handle ok = lists:foreach(F1, Signals), %% set ignore ok = lists:foreach(F2, Signals), %% set default ok = lists:foreach(F3, Signals), ok. t_sighup(_Config) -> Pid1 = setup_service(), OsPid = os:getpid(), os:set_signal(sighup, handle), ok = kill("HUP", OsPid), ok = kill("HUP", OsPid), ok = kill("HUP", OsPid), Msgs1 = fetch_msgs(Pid1), io:format("Msgs1: ~p~n", [Msgs1]), [{notify,sighup}, {notify,sighup}, {notify,sighup}] = Msgs1, %% no proc ok = kill("HUP", OsPid), ok = kill("HUP", OsPid), ok = kill("HUP", OsPid), %% ignore Pid2 = setup_service(), os:set_signal(sighup, ignore), ok = kill("HUP", OsPid), ok = kill("HUP", OsPid), ok = kill("HUP", OsPid), Msgs2 = fetch_msgs(Pid2), io:format("Msgs2: ~p~n", [Msgs2]), [] = Msgs2, %% reset to handle (it's the default) os:set_signal(sighup, handle), ok. t_sigusr1(_Config) -> Pid1 = setup_service(), OsPid = os:getpid(), os:set_signal(sigusr1, handle), ok = kill("USR1", OsPid), ok = kill("USR1", OsPid), ok = kill("USR1", OsPid), Msgs1 = fetch_msgs(Pid1), io:format("Msgs1: ~p~n", [Msgs1]), [{notify,sigusr1}, {notify,sigusr1}, {notify,sigusr1}] = Msgs1, %% no proc ok = kill("USR1", OsPid), ok = kill("USR1", OsPid), ok = kill("USR1", OsPid), %% ignore Pid2 = setup_service(), os:set_signal(sigusr1, ignore), ok = kill("USR1", OsPid), ok = kill("USR1", OsPid), ok = kill("USR1", OsPid), Msgs2 = fetch_msgs(Pid2), io:format("Msgs2: ~p~n", [Msgs2]), [] = Msgs2, %% reset to ignore (it's the default) os:set_signal(sigusr1, handle), ok. t_sigusr2(_Config) -> Pid1 = setup_service(), OsPid = os:getpid(), os:set_signal(sigusr2, handle), ok = kill("USR2", OsPid), ok = kill("USR2", OsPid), ok = kill("USR2", OsPid), Msgs1 = fetch_msgs(Pid1), io:format("Msgs1: ~p~n", [Msgs1]), [{notify,sigusr2}, {notify,sigusr2}, {notify,sigusr2}] = Msgs1, %% no proc ok = kill("USR2", OsPid), ok = kill("USR2", OsPid), ok = kill("USR2", OsPid), %% ignore Pid2 = setup_service(), os:set_signal(sigusr2, ignore), ok = kill("USR2", OsPid), ok = kill("USR2", OsPid), ok = kill("USR2", OsPid), Msgs2 = fetch_msgs(Pid2), io:format("Msgs2: ~p~n", [Msgs2]), [] = Msgs2, %% reset to ignore (it's the default) os:set_signal(sigusr2, ignore), ok. t_sigterm(_Config) -> Pid1 = setup_service(), OsPid = os:getpid(), os:set_signal(sigterm, handle), ok = kill("TERM", OsPid), ok = kill("TERM", OsPid), ok = kill("TERM", OsPid), Msgs1 = fetch_msgs(Pid1), io:format("Msgs1: ~p~n", [Msgs1]), [{notify,sigterm}, {notify,sigterm}, {notify,sigterm}] = Msgs1, %% no proc ok = kill("TERM", OsPid), ok = kill("TERM", OsPid), ok = kill("TERM", OsPid), %% ignore Pid2 = setup_service(), os:set_signal(sigterm, ignore), ok = kill("TERM", OsPid), ok = kill("TERM", OsPid), ok = kill("TERM", OsPid), Msgs2 = fetch_msgs(Pid2), io:format("Msgs2: ~p~n", [Msgs2]), [] = Msgs2, %% reset to handle (it's the default) os:set_signal(sigterm, handle), ok. t_sigchld(_Config) -> Pid1 = setup_service(), OsPid = os:getpid(), os:set_signal(sigchld, handle), ok = kill("CHLD", OsPid), ok = kill("CHLD", OsPid), ok = kill("CHLD", OsPid), Msgs1 = fetch_msgs(Pid1), io:format("Msgs1: ~p~n", [Msgs1]), [{notify,sigchld}, {notify,sigchld}, {notify,sigchld}] = Msgs1, %% no proc ok = kill("CHLD", OsPid), ok = kill("CHLD", OsPid), ok = kill("CHLD", OsPid), %% ignore Pid2 = setup_service(), os:set_signal(sigchld, ignore), ok = kill("CHLD", OsPid), ok = kill("CHLD", OsPid), ok = kill("CHLD", OsPid), Msgs2 = fetch_msgs(Pid2), io:format("Msgs2: ~p~n", [Msgs2]), [] = Msgs2, %% reset to handle (it's the default) os:set_signal(sigchld, ignore), ok. t_sigalrm(_Config) -> Pid1 = setup_service(), ok = os:set_signal(sigalrm, handle), ok = os_signal_SUITE:set_alarm(1), receive after 3000 -> ok end, Msgs1 = fetch_msgs(Pid1), [{notify,sigalrm}] = Msgs1, io:format("Msgs1: ~p~n", [Msgs1]), os:set_signal(sigalrm, ignore), Pid2 = setup_service(), ok = os_signal_SUITE:set_alarm(1), receive after 3000 -> ok end, Msgs2 = fetch_msgs(Pid2), [] = Msgs2, io:format("Msgs2: ~p~n", [Msgs2]), Pid3 = setup_service(), os:set_signal(sigalrm, handle), ok = os_signal_SUITE:set_alarm(1), receive after 3000 -> ok end, Msgs3 = fetch_msgs(Pid3), [{notify,sigalrm}] = Msgs3, io:format("Msgs3: ~p~n", [Msgs3]), os:set_signal(sigalrm, ignore), ok. t_sigchld_fork(_Config) -> Pid1 = setup_service(), ok = os:set_signal(sigchld, handle), {ok,OsPid} = os_signal_SUITE:fork(), receive after 3000 -> ok end, Msgs1 = fetch_msgs(Pid1), io:format("Msgs1: ~p~n", [Msgs1]), [{notify,sigchld}] = Msgs1, {ok,Status} = os_signal_SUITE:get_exit_code(OsPid), io:format("exit status from ~w : ~w~n", [OsPid,Status]), 42 = Status, %% reset to ignore (it's the default) os:set_signal(sigchld, ignore), ok. %% nif stubs set_alarm(_Secs) -> no. fork() -> no. get_exit_code(_OsPid) -> no. %% aux setup_service() -> Pid = spawn_link(fun msgs/0), true = erlang:register(?signal_server, Pid), Pid. msgs() -> msgs([]). msgs(Ms) -> receive {Pid, fetch_msgs} -> Pid ! {self(), lists:reverse(Ms)}; Msg -> msgs([Msg|Ms]) end. fetch_msgs(Pid) -> Pid ! {self(), fetch_msgs}, receive {Pid, Msgs} -> Msgs end. kill(Signal, Pid) -> {0,_} = run("kill", ["-s", Signal, Pid]), receive after 200 -> ok end, ok. load_nif(Config) -> Path = proplists:get_value(data_dir, Config), case erlang:load_nif(filename:join(Path,"os_signal_nif"), 0) of ok -> ok; {error,{reload,_}} -> ok end. run(Program0, Args) -> run(".", Program0, Args). run(Cwd, Program0, Args) when is_list(Cwd) -> Program = case os:find_executable(Program0) of Path when is_list(Path) -> Path; false -> exit(no) end, Options = [{args,Args},binary,exit_status,stderr_to_stdout, {line,4096}, {cd, Cwd}], try open_port({spawn_executable,Program}, Options) of Port -> run_loop(Port, []) catch error:_ -> exit(no) end. run_loop(Port, Output) -> receive {Port,{exit_status,Status}} -> {Status,lists:reverse(Output)}; {Port,{data,{eol,Bin}}} -> run_loop(Port, [Bin|Output]); _Msg -> run_loop(Port, Output) end.