diff options
Diffstat (limited to 'test/ruby/test_process.rb')
| -rw-r--r-- | test/ruby/test_process.rb | 235 |
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 |
