1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/test/ruby/test_signal.rb
2022-01-04 17:25:30 +09:00

387 lines
11 KiB
Ruby

# frozen_string_literal: false
require 'test/unit'
require 'timeout'
require 'tempfile'
class TestSignal < Test::Unit::TestCase
def test_signal
begin
x = 0
oldtrap = Signal.trap(:INT) {|sig| x = 2 }
Process.kill :INT, Process.pid
10.times do
break if 2 == x
sleep 0.1
end
assert_equal 2, x
Signal.trap(:INT) { raise "Interrupt" }
assert_raise_with_message(RuntimeError, /Interrupt/) {
Process.kill :INT, Process.pid
sleep 0.1
}
ensure
Signal.trap :INT, oldtrap if oldtrap
end
end if Process.respond_to?(:kill)
def test_signal_process_group
bug4362 = '[ruby-dev:43169]'
assert_nothing_raised(bug4362) do
cmd = [ EnvUtil.rubybin, '--disable=gems' '-e', 'sleep 10' ]
pid = Process.spawn(*cmd, :pgroup => true)
Process.kill(:"-TERM", pid)
Process.waitpid(pid)
assert_equal(true, $?.signaled?)
assert_equal(Signal.list["TERM"], $?.termsig)
end
end if Process.respond_to?(:kill) and
Process.respond_to?(:pgroup) # for mswin32
def test_exit_action
if Signal.list[sig = "USR1"]
term = :TERM
else
sig = "INT"
term = :KILL
end
IO.popen([EnvUtil.rubybin, '--disable=gems', '-e', <<-"End"], 'r+') do |io|
Signal.trap(:#{sig}, "EXIT")
STDOUT.syswrite("a")
Thread.start { sleep(2) }
STDIN.sysread(4096)
End
pid = io.pid
io.sysread(1)
sleep 0.1
assert_nothing_raised("[ruby-dev:26128]") {
Process.kill(term, pid)
begin
Timeout.timeout(3) {
Process.waitpid pid
}
rescue Timeout::Error
if term
Process.kill(term, pid)
term = (:KILL if term != :KILL)
retry
end
raise
end
}
end
end if Process.respond_to?(:kill)
def test_invalid_signal_name
assert_raise(ArgumentError) { Process.kill(:XXXXXXXXXX, $$) }
assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Process.kill("\u{30eb 30d3 30fc}", $$) }
end if Process.respond_to?(:kill)
def test_signal_exception
assert_raise(ArgumentError) { SignalException.new }
assert_raise(ArgumentError) { SignalException.new(-1) }
assert_raise(ArgumentError) { SignalException.new(:XXXXXXXXXX) }
assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { SignalException.new("\u{30eb 30d3 30fc}") }
Signal.list.each do |signm, signo|
next if signm == "EXIT"
assert_equal(signo, SignalException.new(signm).signo, signm)
assert_equal(signo, SignalException.new(signm.to_sym).signo, signm)
assert_equal(signo, SignalException.new(signo).signo, signo)
end
e = assert_raise(ArgumentError) {SignalException.new("-SIGEXIT")}
assert_not_match(/SIG-SIG/, e.message)
end
def test_interrupt
assert_raise(Interrupt) { raise Interrupt.new }
end
def test_signal2
begin
x = false
oldtrap = Signal.trap(:INT) {|sig| x = true }
GC.start
assert_raise(ArgumentError) { Process.kill }
Timeout.timeout(10) do
x = false
Process.kill(SignalException.new(:INT).signo, $$)
sleep(0.01) until x
x = false
Process.kill("INT", $$)
sleep(0.01) until x
x = false
Process.kill("SIGINT", $$)
sleep(0.01) until x
x = false
o = Object.new
def o.to_str; "SIGINT"; end
Process.kill(o, $$)
sleep(0.01) until x
end
assert_raise(ArgumentError) { Process.kill(Object.new, $$) }
ensure
Signal.trap(:INT, oldtrap) if oldtrap
end
end if Process.respond_to?(:kill)
def test_trap
begin
oldtrap = Signal.trap(:INT) {|sig| }
assert_raise(ArgumentError) { Signal.trap }
# FIXME!
Signal.trap(:INT, nil)
Signal.trap(:INT, "")
Signal.trap(:INT, "SIG_IGN")
Signal.trap(:INT, "IGNORE")
Signal.trap(:INT, "SIG_DFL")
Signal.trap(:INT, "SYSTEM_DEFAULT")
Signal.trap(:INT, "EXIT")
Signal.trap(:INT, "xxxxxx")
Signal.trap(:INT, "xxxx")
Signal.trap(SignalException.new(:INT).signo, "SIG_DFL")
assert_raise(ArgumentError) { Signal.trap(-1, "xxxx") }
o = Object.new
def o.to_str; "SIGINT"; end
Signal.trap(o, "SIG_DFL")
assert_raise(ArgumentError) { Signal.trap("XXXXXXXXXX", "SIG_DFL") }
assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Signal.trap("\u{30eb 30d3 30fc}", "SIG_DFL") }
assert_raise(ArgumentError) { Signal.trap("EXIT\0") {} }
ensure
Signal.trap(:INT, oldtrap) if oldtrap
end
end if Process.respond_to?(:kill)
%w"KILL STOP".each do |sig|
if Signal.list.key?(sig)
define_method("test_trap_uncatchable_#{sig}") do
assert_raise(Errno::EINVAL, "SIG#{sig} is not allowed to be caught") { Signal.trap(sig) {} }
end
end
end
def test_sigexit
assert_in_out_err([], 'Signal.trap(:EXIT) {print "OK"}', ["OK"])
assert_in_out_err([], 'Signal.trap("EXIT") {print "OK"}', ["OK"])
assert_in_out_err([], 'Signal.trap(:SIGEXIT) {print "OK"}', ["OK"])
assert_in_out_err([], 'Signal.trap("SIGEXIT") {print "OK"}', ["OK"])
assert_in_out_err([], 'Signal.trap(0) {print "OK"}', ["OK"])
end
def test_kill_immediately_before_termination
Signal.list[sig = "USR1"] or sig = "INT"
assert_in_out_err(["-e", <<-"end;"], "", %w"foo")
Signal.trap(:#{sig}) { STDOUT.syswrite("foo") }
Process.kill :#{sig}, $$
end;
end if Process.respond_to?(:kill)
def test_trap_system_default
assert_separately([], <<-End)
trap(:QUIT, "SYSTEM_DEFAULT")
assert_equal("SYSTEM_DEFAULT", trap(:QUIT, "DEFAULT"))
End
end if Signal.list.key?('QUIT')
def test_reserved_signal
assert_raise(ArgumentError) {
Signal.trap(:SEGV) {}
}
assert_raise(ArgumentError) {
Signal.trap(:BUS) {}
}
assert_raise(ArgumentError) {
Signal.trap(:ILL) {}
}
assert_raise(ArgumentError) {
Signal.trap(:FPE) {}
}
assert_raise(ArgumentError) {
Signal.trap(:VTALRM) {}
}
end
def test_signame
Signal.list.each do |name, num|
assert_equal(num, Signal.list[Signal.signame(num)], name)
end
assert_nil(Signal.signame(-1))
signums = Signal.list.invert
assert_nil(Signal.signame((1..1000).find {|num| !signums[num]}))
end
def test_signame_delivered
args = [EnvUtil.rubybin, "--disable=gems", "-e", <<"", :err => File::NULL]
Signal.trap("INT") do |signo|
signame = Signal.signame(signo)
Marshal.dump(signame, STDOUT)
STDOUT.flush
exit 0
end
Process.kill("INT", $$)
sleep 1 # wait signal deliver
10.times do
IO.popen(args) do |child|
signame = Marshal.load(child)
assert_equal("INT", signame)
end
end
end if Process.respond_to?(:kill)
def test_trap_puts
assert_in_out_err([], <<-INPUT, ["a"*10000], [])
Signal.trap(:INT) {
# for enable internal io mutex
STDOUT.sync = false
# larger than internal io buffer
print "a"*10000
}
Process.kill :INT, $$
sleep 0.1
INPUT
end if Process.respond_to?(:kill)
def test_hup_me
# [Bug #7951] [ruby-core:52864]
# This is MRI specific spec. ruby has no guarantee
# that signal will be deliverd synchronously.
# This ugly workaround was introduced to don't break
# compatibility against silly example codes.
assert_separately([], <<-RUBY)
trap(:HUP, "DEFAULT")
assert_raise(SignalException) {
Process.kill('HUP', Process.pid)
}
RUBY
bug8137 = '[ruby-dev:47182] [Bug #8137]'
assert_nothing_raised(bug8137) {
Timeout.timeout(1) {
Process.kill(0, Process.pid)
}
}
end if Process.respond_to?(:kill) and Signal.list.key?('HUP')
def test_ignored_interrupt
bug9820 = '[ruby-dev:48203] [Bug #9820]'
assert_separately(['-', bug9820], <<-'end;') # begin
bug = ARGV.shift
trap(:INT, "IGNORE")
assert_nothing_raised(SignalException, bug) do
Process.kill(:INT, $$)
end
end;
if trap = Signal.list['TRAP']
bug9820 = '[ruby-dev:48592] [Bug #9820]'
status = assert_in_out_err(['-e', 'Process.kill(:TRAP, $$)'])
assert_predicate(status, :signaled?, bug9820)
assert_equal(trap, status.termsig, bug9820)
end
if Signal.list['CONT']
bug9820 = '[ruby-dev:48606] [Bug #9820]'
assert_ruby_status(['-e', 'Process.kill(:CONT, $$)'])
end
end if Process.respond_to?(:kill)
def test_signal_list_dedupe_keys
a = Signal.list.keys.map(&:object_id).sort
b = Signal.list.keys.map(&:object_id).sort
assert_equal a, b
end
def test_self_stop
assert_ruby_status([], <<-'end;')
begin
fork{
sleep 1
Process.kill(:CONT, Process.ppid)
}
Process.kill(:STOP, Process.pid)
rescue NotImplementedError
# ok
end
end;
end
def test_sigchld_ignore
omit 'no SIGCHLD' unless Signal.list['CHLD']
old = trap(:CHLD, 'IGNORE')
cmd = [ EnvUtil.rubybin, '--disable=gems', '-e' ]
assert(system(*cmd, 'exit!(0)'), 'no ECHILD')
IO.pipe do |r, w|
pid = spawn(*cmd, "STDIN.read", in: r)
nb = Process.wait(pid, Process::WNOHANG)
th = Thread.new(Thread.current) do |parent|
Thread.pass until parent.stop? # wait for parent to Process.wait
w.close
end
assert_raise(Errno::ECHILD) { Process.wait(pid) }
th.join
assert_nil nb
end
IO.pipe do |r, w|
pids = 3.times.map { spawn(*cmd, 'exit!', out: w) }
w.close
zombies = pids.dup
assert_nil r.read(1), 'children dead'
Timeout.timeout(10) do
zombies.delete_if do |pid|
begin
Process.kill(0, pid)
false
rescue Errno::ESRCH
true
end
end while zombies[0]
end
assert_predicate zombies, :empty?, 'zombies leftover'
pids.each do |pid|
assert_raise(Errno::ECHILD) { Process.waitpid(pid) }
end
end
ensure
trap(:CHLD, old) if Signal.list['CHLD']
end
def test_sigwait_fd_unused
t = EnvUtil.apply_timeout_scale(0.1)
assert_separately([], <<-End)
tgt = $$
trap(:TERM) { exit(0) }
e = "Process.daemon; sleep #{t * 2}; Process.kill(:TERM,\#{tgt})"
term = [ '#{EnvUtil.rubybin}', '--disable=gems', '-e', e ]
t2 = Thread.new { sleep } # grab sigwait_fd
Thread.pass until t2.stop?
Thread.new do
sleep #{t}
t2.kill
t2.join
end
Process.spawn(*term)
# last thread remaining, ensure it can react to SIGTERM
loop { sleep }
End
end if Process.respond_to?(:kill) && Process.respond_to?(:daemon)
end