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
Koichi Sasada 308fe1eb85 Do not create core file if it is intentional abort
Two tests abort intentionally and they create core files if
possible. In these case, we don't need to see core files
so disable by `"Process.setrlimit(Process::RLIMIT_CORE, 0)` for
those cases.
2022-01-19 23:17:14 +09:00

388 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]'
no_core = "Process.setrlimit(Process::RLIMIT_CORE, 0); " if defined?(Process.setrlimit) && defined?(Process::RLIMIT_CORE)
status = assert_in_out_err(['-e', "#{no_core}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