mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
ffd0820ab3
This removes the related tests, and puts the related specs behind version guards. This affects all code in lib, including some libraries that may want to support older versions of Ruby.
387 lines
11 KiB
Ruby
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
|
|
skip '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
|