summaryrefslogtreecommitdiff
path: root/test/ruby/test_process.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/ruby/test_process.rb')
-rw-r--r--test/ruby/test_process.rb235
1 files changed, 80 insertions, 155 deletions
diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb
index 38a29f8332..30427aeec1 100644
--- a/test/ruby/test_process.rb
+++ b/test/ruby/test_process.rb
@@ -3,6 +3,7 @@
require 'test/unit'
require 'tempfile'
require 'timeout'
+require 'io/wait'
require 'rbconfig'
class TestProcess < Test::Unit::TestCase
@@ -168,7 +169,7 @@ class TestProcess < Test::Unit::TestCase
end
def test_execopts_pgroup
- omit "system(:pgroup) is not supported" if windows?
+ skip "system(:pgroup) is not supported" if windows?
assert_nothing_raised { system(*TRUECOMMAND, :pgroup=>false) }
io = IO.popen([RUBY, "-e", "print Process.getpgrp"])
@@ -272,7 +273,7 @@ class TestProcess < Test::Unit::TestCase
end;
end
- MANDATORY_ENVS = %w[RUBYLIB RJIT_SEARCH_BUILD_DIR]
+ MANDATORY_ENVS = %w[RUBYLIB MJIT_SEARCH_BUILD_DIR]
case RbConfig::CONFIG['target_os']
when /linux/
MANDATORY_ENVS << 'LD_PRELOAD'
@@ -510,7 +511,7 @@ class TestProcess < Test::Unit::TestCase
UMASK = [RUBY, '-e', 'printf "%04o\n", File.umask']
def test_execopts_umask
- omit "umask is not supported" if windows?
+ skip "umask is not supported" if windows?
IO.popen([*UMASK, :umask => 0]) {|io|
assert_equal("0000", io.read.chomp)
}
@@ -665,7 +666,6 @@ class TestProcess < Test::Unit::TestCase
end unless windows? # does not support fifo
def test_execopts_redirect_open_fifo_interrupt_raise
- pid = nil
with_tmpchdir {|d|
begin
File.mkfifo("fifo")
@@ -683,21 +683,15 @@ class TestProcess < Test::Unit::TestCase
puts "ok"
end
EOS
- pid = io.pid
assert_equal("start\n", io.gets)
sleep 0.5
Process.kill(:USR1, io.pid)
assert_equal("ok\n", io.read)
}
- assert_equal(pid, $?.pid)
- assert_predicate($?, :success?)
}
- ensure
- assert_raise(Errno::ESRCH) {Process.kill(:KILL, pid)} if pid
end unless windows? # does not support fifo
def test_execopts_redirect_open_fifo_interrupt_print
- pid = nil
with_tmpchdir {|d|
begin
File.mkfifo("fifo")
@@ -710,7 +704,6 @@ class TestProcess < Test::Unit::TestCase
puts "start"
system("cat", :in => "fifo")
EOS
- pid = io.pid
assert_equal("start\n", io.gets)
sleep 0.2 # wait for the child to stop at opening "fifo"
Process.kill(:USR1, io.pid)
@@ -718,13 +711,7 @@ class TestProcess < Test::Unit::TestCase
File.write("fifo", "ok\n")
assert_equal("ok\n", io.read)
}
- assert_equal(pid, $?.pid)
- assert_predicate($?, :success?)
}
- ensure
- if pid
- assert_raise(Errno::ESRCH) {Process.kill(:KILL, pid)}
- end
end unless windows? # does not support fifo
def test_execopts_redirect_pipe
@@ -850,7 +837,7 @@ class TestProcess < Test::Unit::TestCase
STDERR=>"out", STDOUT=>[:child, STDERR])
assert_equal("errout", File.read("out"))
- omit "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
+ skip "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
Process.wait spawn(RUBY, "-e", "STDERR.print 'err'; STDOUT.print 'out'",
STDOUT=>"out",
STDERR=>[:child, 3],
@@ -902,7 +889,7 @@ class TestProcess < Test::Unit::TestCase
end
def test_execopts_popen_extra_fd
- omit "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
+ skip "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
with_tmpchdir {|d|
with_pipe {|r, w|
IO.popen([RUBY, '-e', 'IO.new(3, "w").puts("a"); puts "b"', 3=>w]) {|io|
@@ -931,7 +918,7 @@ class TestProcess < Test::Unit::TestCase
end
def test_fd_inheritance
- omit "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
+ skip "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
with_pipe {|r, w|
system(RUBY, '-e', 'IO.new(ARGV[0].to_i, "w").puts(:ba)', w.fileno.to_s, w=>w)
w.close
@@ -977,7 +964,7 @@ class TestProcess < Test::Unit::TestCase
end
def test_execopts_close_others
- omit "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
+ skip "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
with_tmpchdir {|d|
with_pipe {|r, w|
system(RUBY, '-e', 'STDERR.reopen("err", "w"); IO.new(ARGV[0].to_i, "w").puts("ma")', w.fileno.to_s, :close_others=>true)
@@ -1071,7 +1058,7 @@ class TestProcess < Test::Unit::TestCase
}
}
rescue NotImplementedError
- omit "IO#close_on_exec= is not supported"
+ skip "IO#close_on_exec= is not supported"
end
end unless windows? # passing non-stdio fds is not supported on Windows
@@ -1438,11 +1425,6 @@ class TestProcess < Test::Unit::TestCase
REPRO
end
- def test_argv0_frozen
- assert_predicate Process.argv0, :frozen?
- assert_predicate $0, :frozen?
- end
-
def test_status
with_tmpchdir do
s = run_in_child("exit 1")
@@ -1565,8 +1547,6 @@ class TestProcess < Test::Unit::TestCase
assert_operator(diff, :<, sec,
->{"#{bug11340}: #{diff} seconds to interrupt Process.wait"})
f.puts
- rescue Errno::EPIPE
- omit "child process exited already in #{diff} seconds"
end
end
@@ -1630,7 +1610,7 @@ class TestProcess < Test::Unit::TestCase
else
assert_kind_of(Integer, max)
assert_predicate(max, :positive?)
- omit "not limited to NGROUPS_MAX" if /darwin/ =~ RUBY_PLATFORM
+ skip "not limited to NGROUPS_MAX" if /darwin/ =~ RUBY_PLATFORM
gs = Process.groups
assert_operator(gs.size, :<=, max)
gs[0] ||= 0
@@ -1657,7 +1637,7 @@ class TestProcess < Test::Unit::TestCase
end
def test_setegid
- omit "root can use Process.egid on Android platform" if RUBY_PLATFORM =~ /android/
+ skip "root can use Process.egid on Android platform" if RUBY_PLATFORM =~ /android/
assert_nothing_raised(TypeError) {Process.egid += 0}
rescue NotImplementedError
end
@@ -1715,6 +1695,11 @@ class TestProcess < Test::Unit::TestCase
end
def test_wait_and_sigchild
+ if /freebsd|openbsd/ =~ RUBY_PLATFORM
+ # this relates #4173
+ # When ruby can use 2 cores, signal and wait4 may miss the signal.
+ skip "this fails on FreeBSD and OpenBSD on multithreaded environment"
+ end
signal_received = []
IO.pipe do |sig_r, sig_w|
Signal.trap(:CHLD) do
@@ -1733,7 +1718,7 @@ class TestProcess < Test::Unit::TestCase
Process.wait pid
assert_send [sig_r, :wait_readable, 5], 'self-pipe not readable'
end
- if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? # checking -DRJIT_FORCE_ENABLE. It may trigger extra SIGCHLD.
+ if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # checking -DMJIT_FORCE_ENABLE. It may trigger extra SIGCHLD.
assert_equal [true], signal_received.uniq, "[ruby-core:19744]"
else
assert_equal [true], signal_received, "[ruby-core:19744]"
@@ -1748,7 +1733,7 @@ class TestProcess < Test::Unit::TestCase
def test_no_curdir
if /solaris/i =~ RUBY_PLATFORM
- omit "Temporary omit to avoid CI failures after commit to use realpath on required files"
+ skip "Temporary skip to avoid CI failures after commit to use realpath on required files"
end
with_tmpchdir {|d|
Dir.mkdir("vd")
@@ -1790,7 +1775,7 @@ class TestProcess < Test::Unit::TestCase
def test_aspawn_too_long_path
if /solaris/i =~ RUBY_PLATFORM && !defined?(Process::RLIMIT_NPROC)
- omit "Too exhaustive test on platforms without Process::RLIMIT_NPROC such as Solaris 10"
+ skip "Too exhaustive test on platforms without Process::RLIMIT_NPROC such as Solaris 10"
end
bug4315 = '[ruby-core:34833] #7904 [ruby-core:52628] #11613'
assert_fail_too_long_path(%w"echo |", bug4315)
@@ -1804,20 +1789,14 @@ class TestProcess < Test::Unit::TestCase
exs << Errno::EINVAL if windows?
exs << Errno::E2BIG if defined?(Errno::E2BIG)
opts = {[STDOUT, STDERR]=>File::NULL}
- if defined?(Process::RLIMIT_NPROC)
- opts[:rlimit_nproc] = /openbsd/i =~ RUBY_PLATFORM ? 64 : 128
- end
+ opts[:rlimit_nproc] = 128 if defined?(Process::RLIMIT_NPROC)
EnvUtil.suppress_warning do
assert_raise(*exs, mesg) do
begin
loop do
Process.spawn(cmds.join(sep), opts)
min = [cmds.size, min].max
- begin
- cmds *= 100
- rescue ArgumentError
- raise NoMemoryError
- end
+ cmds *= 100
end
rescue NoMemoryError
size = cmds.size
@@ -1838,7 +1817,7 @@ class TestProcess < Test::Unit::TestCase
with_tmpchdir do
assert_nothing_raised('[ruby-dev:12261]') do
- EnvUtil.timeout(10) do
+ EnvUtil.timeout(3) do
pid = spawn('yes | ls')
Process.waitpid pid
end
@@ -1859,6 +1838,8 @@ class TestProcess < Test::Unit::TestCase
end
def test_daemon_noclose
+ pend "macOS 15 beta is not working with this test" if /darwin/ =~ RUBY_PLATFORM && /15/ =~ `sw_vers -productVersion`
+
data = IO.popen("-", "r+") do |f|
break f.read if f
Process.daemon(false, true)
@@ -1897,28 +1878,6 @@ class TestProcess < Test::Unit::TestCase
assert_not_equal(cpid, dpid)
end
- def test_daemon_detached
- IO.popen("-", "r+") do |f|
- if f
- assert_equal(f.pid, Process.wait(f.pid))
-
- dpid, ppid, dsid = 3.times.map {Integer(f.gets)}
-
- message = "daemon #{dpid} should be detached"
- assert_not_equal($$, ppid, message) # would be 1 almost always
- assert_raise(Errno::ECHILD, message) {Process.wait(dpid)}
- assert_kind_of(Integer, Process.kill(0, dpid), message)
- assert_equal(dpid, dsid)
-
- break # close f, and let the daemon resume and exit
- end
- Process.setsid rescue nil
- Process.daemon(false, true)
- puts $$, Process.ppid, Process.getsid
- $stdin.gets # wait for the above assertions using signals
- end
- end
-
if File.directory?("/proc/self/task") && /netbsd[a-z]*[1-6]/ !~ RUBY_PLATFORM
def test_daemon_no_threads
pid, data = IO.popen("-", "r+") do |f|
@@ -2001,7 +1960,7 @@ class TestProcess < Test::Unit::TestCase
end
def test_execopts_uid
- omit "root can use uid option of Kernel#system on Android platform" if RUBY_PLATFORM =~ /android/
+ skip "root can use uid option of Kernel#system on Android platform" if RUBY_PLATFORM =~ /android/
feature6975 = '[ruby-core:47414]'
[30000, [Process.uid, ENV["USER"]]].each do |uid, user|
@@ -2032,8 +1991,8 @@ class TestProcess < Test::Unit::TestCase
end
def test_execopts_gid
- omit "Process.groups not implemented on Windows platform" if windows?
- omit "root can use Process.groups on Android platform" if RUBY_PLATFORM =~ /android/
+ skip "Process.groups not implemented on Windows platform" if windows?
+ skip "root can use Process.groups on Android platform" if RUBY_PLATFORM =~ /android/
feature6975 = '[ruby-core:47414]'
groups = Process.groups.map do |g|
@@ -2177,9 +2136,7 @@ EOS
t3 = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
assert_operator(t1, :<=, t2)
assert_operator(t2, :<=, t3)
- assert_raise_with_message(Errno::EINVAL, /:foo/) do
- Process.clock_gettime(:foo)
- end
+ assert_raise(Errno::EINVAL) { Process.clock_gettime(:foo) }
end
def test_clock_gettime_unit
@@ -2284,9 +2241,7 @@ EOS
rescue Errno::EINVAL
else
assert_kind_of(Integer, r)
- assert_raise_with_message(Errno::EINVAL, /:foo/) do
- Process.clock_getres(:foo)
- end
+ assert_raise(Errno::EINVAL) { Process.clock_getres(:foo) }
end
def test_clock_getres_constants
@@ -2373,7 +2328,7 @@ EOS
end
def test_deadlock_by_signal_at_forking
- assert_separately(%W(- #{RUBY}), <<-INPUT, timeout: 100)
+ assert_separately(%W(--disable=gems - #{RUBY}), <<-INPUT, timeout: 100)
ruby = ARGV.shift
GC.start # reduce garbage
GC.disable # avoid triggering CoW after forks
@@ -2558,7 +2513,7 @@ EOS
end
def test_forked_child_handles_signal
- omit "fork not supported" unless Process.respond_to?(:fork)
+ skip "fork not supported" unless Process.respond_to?(:fork)
assert_normal_exit(<<-"end;", '[ruby-core:82883] [Bug #13916]')
require 'timeout'
pid = fork { sleep }
@@ -2612,26 +2567,6 @@ EOS
end
end if Process.respond_to?(:_fork)
- def test__fork_pid_cache
- _parent_pid = Process.pid
- r, w = IO.pipe
- pid = Process._fork
- if pid == 0
- begin
- r.close
- w << "ok: #{Process.pid}"
- w.close
- ensure
- exit!
- end
- else
- w.close
- assert_equal("ok: #{pid}", r.read)
- r.close
- Process.waitpid(pid)
- end
- end if Process.respond_to?(:_fork)
-
def test__fork_hook
%w(fork Process.fork).each do |method|
feature17795 = '[ruby-core:103400] [Feature #17795]'
@@ -2705,68 +2640,58 @@ EOS
end;
end if Process.respond_to?(:_fork)
- def test_warmup_promote_all_objects_to_oldgen
- assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
- require 'objspace'
- begin;
- obj = Object.new
-
- assert_not_include(ObjectSpace.dump(obj), '"old":true')
- Process.warmup
- assert_include(ObjectSpace.dump(obj), '"old":true')
- end;
- end
-
- def test_warmup_run_major_gc_and_compact
- assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
- begin;
- # Run a GC to ensure that we are not in the middle of a GC run
- GC.start
-
- major_gc_count = GC.stat(:major_gc_count)
- compact_count = GC.stat(:compact_count)
- Process.warmup
- assert_equal major_gc_count + 1, GC.stat(:major_gc_count)
- assert_equal compact_count + 1, GC.stat(:compact_count)
- end;
- end
-
- def test_warmup_precompute_string_coderange
- assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
- require 'objspace'
- begin;
- obj = "a" * 12
- obj.force_encoding(Encoding::BINARY)
- assert_include(ObjectSpace.dump(obj), '"coderange":"unknown"')
- Process.warmup
- assert_include(ObjectSpace.dump(obj), '"coderange":"7bit"')
- end;
- end
-
- def test_warmup_frees_pages
- assert_separately([{"RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO" => "1.0"}, "-W0"], "#{<<~"begin;"}\n#{<<~'end;'}")
- begin;
- GC.start
-
- TIMES = 10_000
- ary = Array.new(TIMES)
- TIMES.times do |i|
- ary[i] = Object.new
+ def test_concurrent_group_and_pid_wait
+ # Use a pair of pipes that will make long_pid exit when this test exits, to avoid
+ # leaking temp processes.
+ long_rpipe, long_wpipe = IO.pipe
+ short_rpipe, short_wpipe = IO.pipe
+ # This process should run forever
+ long_pid = fork do
+ [short_rpipe, short_wpipe, long_wpipe].each(&:close)
+ long_rpipe.read
+ end
+ # This process will exit
+ short_pid = fork do
+ [long_rpipe, long_wpipe, short_wpipe].each(&:close)
+ short_rpipe.read
+ end
+ t1, t2, t3 = nil
+ EnvUtil.timeout(5) do
+ t1 = Thread.new do
+ Process.waitpid long_pid
end
- ary.clear
- ary = nil
+ # Wait for us to be blocking in a call to waitpid2
+ Thread.pass until t1.stop?
+ short_wpipe.close # Make short_pid exit
- # Disable GC so we can make sure GC only runs in Process.warmup
- GC.disable
+ # The short pid has exited, so -1 should pick that up.
+ assert_equal short_pid, Process.waitpid(-1)
- total_pages_before = GC.stat(:heap_eden_pages) + GC.stat(:heap_allocatable_pages)
+ # Terminate t1 for the next phase of the test.
+ t1.kill
+ t1.join
- Process.warmup
+ t2 = Thread.new do
+ Process.waitpid -1
+ rescue Errno::ECHILD
+ nil
+ end
+ Thread.pass until t2.stop?
+ t3 = Thread.new do
+ Process.waitpid long_pid
+ rescue Errno::ECHILD
+ nil
+ end
+ Thread.pass until t3.stop?
- # Number of pages freed should cause equal increase in number of allocatable pages.
- assert_equal(total_pages_before, GC.stat(:heap_eden_pages) + GC.stat(:heap_allocatable_pages))
- assert_equal(0, GC.stat(:heap_tomb_pages), GC.stat)
- assert_operator(GC.stat(:total_freed_pages), :>, 0)
- end;
- end
+ # it's actually nondeterministic which of t2 or t3 will receive the wait (this
+ # nondeterminism comes from the behaviour of the underlying system calls)
+ long_wpipe.close
+ assert_equal [long_pid], [t2, t3].map(&:value).compact
+ end
+ ensure
+ [t1, t2, t3].each { _1&.kill rescue nil }
+ [t1, t2, t3].each { _1&.join rescue nil }
+ [long_rpipe, long_wpipe, short_rpipe, short_wpipe].each { _1&.close rescue nil }
+ end if defined?(fork)
end