diff options
Diffstat (limited to 'test/ruby/test_process.rb')
| -rw-r--r-- | test/ruby/test_process.rb | 328 |
1 files changed, 217 insertions, 111 deletions
diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb index dc0ccd1f77..b3a88b664c 100644 --- a/test/ruby/test_process.rb +++ b/test/ruby/test_process.rb @@ -23,12 +23,6 @@ class TestProcess < Test::Unit::TestCase return /mswin|mingw|bccwin/ =~ RUBY_PLATFORM end - def write_file(filename, content) - File.open(filename, "w") {|f| - f << content - } - end - def with_tmpchdir Dir.mktmpdir {|d| d = File.realpath(d) @@ -39,7 +33,7 @@ class TestProcess < Test::Unit::TestCase end def run_in_child(str) # should be called in a temporary directory - write_file("test-script", str) + File.write("test-script", str) Process.wait spawn(RUBY, "test-script") $? end @@ -64,8 +58,10 @@ class TestProcess < Test::Unit::TestCase def test_rlimit_nofile return unless rlimit_exist? + omit "LSAN needs to open proc file" if Test::Sanitizers.lsan_enabled? + with_tmpchdir { - write_file 's', <<-"End" + File.write 's', <<-"End" # Too small RLIMIT_NOFILE, such as zero, causes problems. # [OpenBSD] Setting to zero freezes this test. # [GNU/Linux] EINVAL on poll(). EINVAL on ruby's internal poll() ruby with "[ASYNC BUG] thread_timer: select". @@ -120,14 +116,19 @@ class TestProcess < Test::Unit::TestCase } assert_raise(ArgumentError) { Process.getrlimit(:FOO) } assert_raise(ArgumentError) { Process.getrlimit("FOO") } - assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Process.getrlimit("\u{30eb 30d3 30fc}") } + + EnvUtil.with_default_internal(Encoding::UTF_8) do + assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Process.getrlimit("\u{30eb 30d3 30fc}") } + end end def test_rlimit_value return unless rlimit_exist? assert_raise(ArgumentError) { Process.setrlimit(:FOO, 0) } assert_raise(ArgumentError) { Process.setrlimit(:CORE, :FOO) } - assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Process.setrlimit("\u{30eb 30d3 30fc}", 0) } + EnvUtil.with_default_internal(Encoding::UTF_8) do + assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Process.setrlimit("\u{30eb 30d3 30fc}", 0) } + end assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Process.setrlimit(:CORE, "\u{30eb 30d3 30fc}") } with_tmpchdir do s = run_in_child(<<-'End') @@ -205,58 +206,67 @@ class TestProcess < Test::Unit::TestCase max = Process.getrlimit(:CORE).last + # When running under ASAN, we need to set disable_coredump=0 for this test; by default + # the ASAN runtime library sets RLIMIT_CORE to 0, "to avoid dumping a 16T+ core file", and + # that inteferes with this test. + asan_options = ENV['ASAN_OPTIONS'] || '' + asan_options << ':' unless asan_options.empty? + env = { + 'ASAN_OPTIONS' => "#{asan_options}disable_coredump=0" + } + n = max - IO.popen([RUBY, "-e", + IO.popen([env, RUBY, "-e", "puts Process.getrlimit(:CORE)", :rlimit_core=>n]) {|io| assert_equal("#{n}\n#{n}\n", io.read) } n = 0 - IO.popen([RUBY, "-e", + IO.popen([env, RUBY, "-e", "puts Process.getrlimit(:CORE)", :rlimit_core=>n]) {|io| assert_equal("#{n}\n#{n}\n", io.read) } n = max - IO.popen([RUBY, "-e", + IO.popen([env, RUBY, "-e", "puts Process.getrlimit(:CORE)", :rlimit_core=>[n]]) {|io| assert_equal("#{n}\n#{n}\n", io.read) } m, n = 0, max - IO.popen([RUBY, "-e", + IO.popen([env, RUBY, "-e", "puts Process.getrlimit(:CORE)", :rlimit_core=>[m,n]]) {|io| assert_equal("#{m}\n#{n}\n", io.read) } m, n = 0, 0 - IO.popen([RUBY, "-e", + IO.popen([env, RUBY, "-e", "puts Process.getrlimit(:CORE)", :rlimit_core=>[m,n]]) {|io| assert_equal("#{m}\n#{n}\n", io.read) } n = max - IO.popen([RUBY, "-e", + IO.popen([env, RUBY, "-e", "puts Process.getrlimit(:CORE), Process.getrlimit(:CPU)", :rlimit_core=>n, :rlimit_cpu=>3600]) {|io| assert_equal("#{n}\n#{n}\n""3600\n3600\n", io.read) } assert_raise(ArgumentError) do - system(RUBY, '-e', 'exit', 'rlimit_bogus'.to_sym => 123) + system(env, RUBY, '-e', 'exit', 'rlimit_bogus'.to_sym => 123) end - assert_separately([],"#{<<~"begin;"}\n#{<<~'end;'}", 'rlimit_cpu'.to_sym => 3600) + assert_separately([env],"#{<<~"begin;"}\n#{<<~'end;'}", 'rlimit_cpu'.to_sym => 3600) BUG = "[ruby-core:82033] [Bug #13744]" begin; assert_equal([3600,3600], Process.getrlimit(:CPU), BUG) end; assert_raise_with_message(ArgumentError, /bogus/) do - system(RUBY, '-e', 'exit', :rlimit_bogus => 123) + system(env, RUBY, '-e', 'exit', :rlimit_bogus => 123) end assert_raise_with_message(ArgumentError, /rlimit_cpu/) { - system(RUBY, '-e', 'exit', "rlimit_cpu\0".to_sym => 3600) + system(env, RUBY, '-e', 'exit', "rlimit_cpu\0".to_sym => 3600) } end @@ -272,21 +282,22 @@ class TestProcess < Test::Unit::TestCase end; end - MANDATORY_ENVS = %w[RUBYLIB RJIT_SEARCH_BUILD_DIR] - case RbConfig::CONFIG['target_os'] - when /linux/ - MANDATORY_ENVS << 'LD_PRELOAD' - when /mswin|mingw/ - MANDATORY_ENVS.concat(%w[HOME USER TMPDIR]) - when /darwin/ - MANDATORY_ENVS.concat(ENV.keys.grep(/\A__CF_/)) - end + MANDATORY_ENVS = %w[RUBYLIB GEM_HOME GEM_PATH RUBY_FREE_AT_EXIT] if e = RbConfig::CONFIG['LIBPATHENV'] MANDATORY_ENVS << e end if e = RbConfig::CONFIG['PRELOADENV'] and !e.empty? MANDATORY_ENVS << e end + case RbConfig::CONFIG['target_os'] + when /mswin|mingw/ + MANDATORY_ENVS.concat(%w[HOME USER TMPDIR PROCESSOR_ARCHITECTURE]) + when /darwin/ + MANDATORY_ENVS.concat(%w[TMPDIR], ENV.keys.grep(/\A__CF_/)) + # IO.popen([ENV.keys.to_h {|e| [e, nil]}, + # RUBY, "-e", %q[print ENV.keys.join(?\0)]], + # &:read).split(?\0) + end PREENVARG = ['-e', "%w[#{MANDATORY_ENVS.join(' ')}].each{|e|ENV.delete(e)}"] ENVARG = ['-e', 'ENV.each {|k,v| puts "#{k}=#{v}" }'] ENVCOMMAND = [RUBY].concat(PREENVARG).concat(ENVARG) @@ -361,7 +372,7 @@ class TestProcess < Test::Unit::TestCase def test_execopt_env_path bug8004 = '[ruby-core:53103] [Bug #8004]' Dir.mktmpdir do |d| - open("#{d}/tmp_script.cmd", "w") {|f| f.puts ": ;"; f.chmod(0755)} + File.write("#{d}/tmp_script.cmd", ": ;\n", perm: 0o755) assert_not_nil(pid = Process.spawn({"PATH" => d}, "tmp_script.cmd"), bug8004) wpid, st = Process.waitpid2(pid) assert_equal([pid, true], [wpid, st.success?], bug8004) @@ -399,7 +410,7 @@ class TestProcess < Test::Unit::TestCase def test_execopts_env_popen_string with_tmpchdir do |d| - open('test-script', 'w') do |f| + File.open('test-script', 'w') do |f| ENVCOMMAND.each_with_index do |cmd, i| next if i.zero? or cmd == "-e" f.puts cmd @@ -411,16 +422,14 @@ class TestProcess < Test::Unit::TestCase def test_execopts_preserve_env_on_exec_failure with_tmpchdir {|d| - write_file 's', <<-"End" + File.write 's', <<-"End" ENV["mgg"] = nil prog = "./nonexistent" begin Process.exec({"mgg" => "mggoo"}, [prog, prog]) rescue Errno::ENOENT end - open('out', 'w') {|f| - f.print ENV["mgg"].inspect - } + File.write('out', ENV["mgg"].inspect) End system(RUBY, 's') assert_equal(nil.inspect, File.read('out'), @@ -430,9 +439,7 @@ class TestProcess < Test::Unit::TestCase def test_execopts_env_single_word with_tmpchdir {|d| - open("test_execopts_env_single_word.rb", "w") {|f| - f.puts "print ENV['hgga']" - } + File.write("test_execopts_env_single_word.rb", "print ENV['hgga']\n") system({"hgga"=>"ugu"}, RUBY, :in => 'test_execopts_env_single_word.rb', :out => 'test_execopts_env_single_word.out') @@ -554,7 +561,7 @@ class TestProcess < Test::Unit::TestCase assert_equal("a", File.read("out").chomp) if windows? # currently telling to child the file modes is not supported. - open("out", "a") {|f| f.write "0\n"} + File.write("out", "0\n", mode: "a") else Process.wait Process.spawn(*ECHO["0"], STDOUT=>["out", File::WRONLY|File::CREAT|File::APPEND, 0644]) assert_equal("a\n0\n", File.read("out")) @@ -665,6 +672,7 @@ 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") @@ -682,15 +690,21 @@ 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") @@ -703,14 +717,25 @@ 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) assert_equal("trap\n", io.readpartial(8)) + sleep 0.2 # wait for the child to return to opening "fifo". + # On arm64-darwin22, often deadlocks while the child is + # opening "fifo". Not sure to where "ok" line being written + # at the next has gone. 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 @@ -858,7 +883,7 @@ class TestProcess < Test::Unit::TestCase def test_execopts_exec with_tmpchdir {|d| - write_file("s", 'exec "echo aaa", STDOUT=>"foo"') + File.write("s", 'exec "echo aaa", STDOUT=>"foo"') pid = spawn RUBY, 's' Process.wait pid assert_equal("aaa\n", File.read("foo")) @@ -905,15 +930,29 @@ class TestProcess < Test::Unit::TestCase } end - def test_popen_fork - IO.popen("-") {|io| - if !io - puts "fooo" - else - assert_equal("fooo\n", io.read) + if Process.respond_to?(:fork) + def test_popen_fork + IO.popen("-") do |io| + if !io + puts "fooo" + else + assert_equal("fooo\n", io.read) + end end - } - rescue NotImplementedError + end + + def test_popen_fork_ensure + IO.popen("-") do |io| + if !io + STDERR.reopen(STDOUT) + raise "fooo" + else + assert_empty io.read + end + end + rescue RuntimeError + abort "[Bug #20995] should not reach here" + end end def test_fd_inheritance @@ -932,7 +971,7 @@ class TestProcess < Test::Unit::TestCase } with_pipe {|r, w| with_tmpchdir {|d| - write_file("s", <<-"End") + File.write("s", <<-"End") exec(#{RUBY.dump}, '-e', 'IO.new(ARGV[0].to_i, "w").puts("bu") rescue nil', #{w.fileno.to_s.dump}, :close_others=>false) @@ -986,7 +1025,7 @@ class TestProcess < Test::Unit::TestCase assert_equal("bi\n", r.read) } with_pipe {|r, w| - write_file("s", <<-"End") + File.write("s", <<-"End") exec(#{RUBY.dump}, '-e', 'STDERR.reopen("err", "w"); IO.new(ARGV[0].to_i, "w").puts("mu")', #{w.fileno.to_s.dump}, @@ -1112,7 +1151,7 @@ class TestProcess < Test::Unit::TestCase def test_exec_noshell with_tmpchdir {|d| - write_file("s", <<-"End") + File.write("s", <<-"End") str = "echo non existing command name which contains spaces" STDERR.reopen(STDOUT) begin @@ -1128,7 +1167,7 @@ class TestProcess < Test::Unit::TestCase def test_system_wordsplit with_tmpchdir {|d| - write_file("script", <<-'End') + File.write("script", <<-'End') File.open("result", "w") {|t| t << "haha pid=#{$$} ppid=#{Process.ppid}" } exit 5 End @@ -1144,7 +1183,7 @@ class TestProcess < Test::Unit::TestCase def test_spawn_wordsplit with_tmpchdir {|d| - write_file("script", <<-'End') + File.write("script", <<-'End') File.open("result", "w") {|t| t << "hihi pid=#{$$} ppid=#{Process.ppid}" } exit 6 End @@ -1161,7 +1200,7 @@ class TestProcess < Test::Unit::TestCase def test_popen_wordsplit with_tmpchdir {|d| - write_file("script", <<-'End') + File.write("script", <<-'End') print "fufu pid=#{$$} ppid=#{Process.ppid}" exit 7 End @@ -1180,7 +1219,7 @@ class TestProcess < Test::Unit::TestCase def test_popen_wordsplit_beginning_and_trailing_spaces with_tmpchdir {|d| - write_file("script", <<-'End') + File.write("script", <<-'End') print "fufumm pid=#{$$} ppid=#{Process.ppid}" exit 7 End @@ -1199,7 +1238,7 @@ class TestProcess < Test::Unit::TestCase def test_exec_wordsplit with_tmpchdir {|d| - write_file("script", <<-'End') + File.write("script", <<-'End') File.open("result", "w") {|t| if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM t << "hehe ppid=#{Process.ppid}" @@ -1209,7 +1248,7 @@ class TestProcess < Test::Unit::TestCase } exit 6 End - write_file("s", <<-"End") + File.write("s", <<-"End") ruby = #{RUBY.dump} exec "\#{ruby} script" End @@ -1230,11 +1269,11 @@ class TestProcess < Test::Unit::TestCase def test_system_shell with_tmpchdir {|d| - write_file("script1", <<-'End') + File.write("script1", <<-'End') File.open("result1", "w") {|t| t << "taka pid=#{$$} ppid=#{Process.ppid}" } exit 7 End - write_file("script2", <<-'End') + File.write("script2", <<-'End') File.open("result2", "w") {|t| t << "taki pid=#{$$} ppid=#{Process.ppid}" } exit 8 End @@ -1250,7 +1289,7 @@ class TestProcess < Test::Unit::TestCase if windows? Dir.mkdir(path = "path with space") - write_file(bat = path + "/bat test.bat", "@echo %1>out") + File.write(bat = path + "/bat test.bat", "@echo %1>out") system(bat, "foo 'bar'") assert_equal(%["foo 'bar'"\n], File.read("out"), '[ruby-core:22960]') system(%[#{bat.dump} "foo 'bar'"]) @@ -1261,11 +1300,11 @@ class TestProcess < Test::Unit::TestCase def test_spawn_shell with_tmpchdir {|d| - write_file("script1", <<-'End') + File.write("script1", <<-'End') File.open("result1", "w") {|t| t << "taku pid=#{$$} ppid=#{Process.ppid}" } exit 7 End - write_file("script2", <<-'End') + File.write("script2", <<-'End') File.open("result2", "w") {|t| t << "take pid=#{$$} ppid=#{Process.ppid}" } exit 8 End @@ -1282,7 +1321,7 @@ class TestProcess < Test::Unit::TestCase if windows? Dir.mkdir(path = "path with space") - write_file(bat = path + "/bat test.bat", "@echo %1>out") + File.write(bat = path + "/bat test.bat", "@echo %1>out") pid = spawn(bat, "foo 'bar'") Process.wait pid status = $? @@ -1301,11 +1340,11 @@ class TestProcess < Test::Unit::TestCase def test_popen_shell with_tmpchdir {|d| - write_file("script1", <<-'End') + File.write("script1", <<-'End') puts "tako pid=#{$$} ppid=#{Process.ppid}" exit 7 End - write_file("script2", <<-'End') + File.write("script2", <<-'End') puts "tika pid=#{$$} ppid=#{Process.ppid}" exit 8 End @@ -1320,7 +1359,7 @@ class TestProcess < Test::Unit::TestCase if windows? Dir.mkdir(path = "path with space") - write_file(bat = path + "/bat test.bat", "@echo %1") + File.write(bat = path + "/bat test.bat", "@echo %1") r = IO.popen([bat, "foo 'bar'"]) {|f| f.read} assert_equal(%["foo 'bar'"\n], r, '[ruby-core:22960]') r = IO.popen(%[#{bat.dump} "foo 'bar'"]) {|f| f.read} @@ -1331,15 +1370,15 @@ class TestProcess < Test::Unit::TestCase def test_exec_shell with_tmpchdir {|d| - write_file("script1", <<-'End') + File.write("script1", <<-'End') File.open("result1", "w") {|t| t << "tiki pid=#{$$} ppid=#{Process.ppid}" } exit 7 End - write_file("script2", <<-'End') + File.write("script2", <<-'End') File.open("result2", "w") {|t| t << "tiku pid=#{$$} ppid=#{Process.ppid}" } exit 8 End - write_file("s", <<-"End") + File.write("s", <<-"End") ruby = #{RUBY.dump} exec("\#{ruby} script1 || \#{ruby} script2") End @@ -1366,7 +1405,7 @@ class TestProcess < Test::Unit::TestCase assert_equal("1", IO.popen([[RUBY, "qwerty"], "-e", "print 1"]) {|f| f.read }) - write_file("s", <<-"End") + File.write("s", <<-"End") exec([#{RUBY.dump}, "lkjh"], "-e", "exit 5") End pid = spawn RUBY, "s" @@ -1376,7 +1415,7 @@ class TestProcess < Test::Unit::TestCase end def with_stdin(filename) - open(filename) {|f| + File.open(filename) {|f| begin old = STDIN.dup begin @@ -1393,8 +1432,8 @@ class TestProcess < Test::Unit::TestCase def test_argv0_noarg with_tmpchdir {|d| - open("t", "w") {|f| f.print "exit true" } - open("f", "w") {|f| f.print "exit false" } + File.write("t", "exit true") + File.write("f", "exit false") with_stdin("t") { assert_equal(true, system([RUBY, "qaz"])) } with_stdin("f") { assert_equal(false, system([RUBY, "wsx"])) } @@ -1437,8 +1476,6 @@ class TestProcess < Test::Unit::TestCase assert_equal(s, s) assert_equal(s, s.to_i) - assert_equal(s.to_i & 0x55555555, s & 0x55555555) - assert_equal(s.to_i >> 1, s >> 1) assert_equal(false, s.stopped?) assert_equal(nil, s.stopsig) @@ -1454,7 +1491,7 @@ class TestProcess < Test::Unit::TestCase expected = Signal.list.include?("QUIT") ? [false, true, false, nil] : [true, false, false, true] with_tmpchdir do - write_file("foo", "Process.kill(:KILL, $$); exit(42)") + File.write("foo", "Process.kill(:KILL, $$); exit(42)") system(RUBY, "foo") s = $? assert_equal(expected, @@ -1502,7 +1539,7 @@ class TestProcess < Test::Unit::TestCase def test_wait_without_arg with_tmpchdir do - write_file("foo", "sleep 0.1") + File.write("foo", "sleep 0.1") pid = spawn(RUBY, "foo") assert_equal(pid, Process.wait) end @@ -1510,7 +1547,7 @@ class TestProcess < Test::Unit::TestCase def test_wait2 with_tmpchdir do - write_file("foo", "sleep 0.1") + File.write("foo", "sleep 0.1") pid = spawn(RUBY, "foo") assert_equal([pid, 0], Process.wait2) end @@ -1518,7 +1555,7 @@ class TestProcess < Test::Unit::TestCase def test_waitall with_tmpchdir do - write_file("foo", "sleep 0.1") + File.write("foo", "sleep 0.1") ps = (0...3).map { spawn(RUBY, "foo") }.sort ss = Process.waitall.sort ps.zip(ss) do |p1, (p2, s)| @@ -1560,7 +1597,7 @@ class TestProcess < Test::Unit::TestCase with_tmpchdir do s = run_in_child("abort") assert_not_predicate(s, :success?) - write_file("test-script", "#{<<~"begin;"}\n#{<<~'end;'}") + File.write("test-script", "#{<<~"begin;"}\n#{<<~'end;'}") begin; STDERR.reopen(STDOUT) begin @@ -1653,9 +1690,10 @@ class TestProcess < Test::Unit::TestCase if u = Etc.getpwuid(Process.uid) assert_equal(Process.uid, Process::UID.from_name(u.name), u.name) end - assert_raise_with_message(ArgumentError, /\u{4e0d 5b58 5728}/) { + exc = assert_raise_kind_of(ArgumentError, SystemCallError) { Process::UID.from_name("\u{4e0d 5b58 5728}") } + assert_match(/\u{4e0d 5b58 5728}/, exc.message) if exc.is_a?(ArgumentError) end end @@ -1664,12 +1702,7 @@ class TestProcess < Test::Unit::TestCase if g = Etc.getgrgid(Process.gid) assert_equal(Process.gid, Process::GID.from_name(g.name), g.name) end - expected_excs = [ArgumentError] - expected_excs << Errno::ENOENT if defined?(Errno::ENOENT) - expected_excs << Errno::ESRCH if defined?(Errno::ESRCH) # WSL 2 actually raises Errno::ESRCH - expected_excs << Errno::EBADF if defined?(Errno::EBADF) - expected_excs << Errno::EPERM if defined?(Errno::EPERM) - exc = assert_raise(*expected_excs) do + exc = assert_raise_kind_of(ArgumentError, SystemCallError) do Process::GID.from_name("\u{4e0d 5b58 5728}") # fu son zai ("absent" in Kanji) end assert_match(/\u{4e0d 5b58 5728}/, exc.message) if exc.is_a?(ArgumentError) @@ -1708,22 +1741,23 @@ class TestProcess < Test::Unit::TestCase sig_w.write('?') end pid = nil + th = nil IO.pipe do |r, w| pid = fork { r.read(1); exit } - Thread.start { + th = Thread.start { Thread.current.report_on_exception = false raise } w.puts end Process.wait pid + begin + th.join + rescue Exception + end 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. - assert_equal [true], signal_received.uniq, "[ruby-core:19744]" - else - assert_equal [true], signal_received, "[ruby-core:19744]" - end + assert_equal [true], signal_received, "[ruby-core:19744]" rescue NotImplementedError, ArgumentError ensure begin @@ -1733,15 +1767,12 @@ class TestProcess < Test::Unit::TestCase end def test_no_curdir - if /solaris/i =~ RUBY_PLATFORM - omit "Temporary omit to avoid CI failures after commit to use realpath on required files" - end with_tmpchdir {|d| Dir.mkdir("vd") status = nil Dir.chdir("vd") { dir = "#{d}/vd" - # OpenSolaris cannot remove the current directory. + # Windows cannot remove the current directory with permission issues. system(RUBY, "--disable-gems", "-e", "Dir.chdir '..'; Dir.rmdir #{dir.dump}", err: File::NULL) system({"RUBYLIB"=>nil}, RUBY, "--disable-gems", "-e", "exit true") status = $? @@ -1753,16 +1784,16 @@ class TestProcess < Test::Unit::TestCase def test_fallback_to_sh feature = '[ruby-core:32745]' with_tmpchdir do |d| - open("tmp_script.#{$$}", "w") {|f| f.puts ": ;"; f.chmod(0755)} + File.write("tmp_script.#{$$}", ": ;\n", perm: 0o755) assert_not_nil(pid = Process.spawn("./tmp_script.#{$$}"), feature) wpid, st = Process.waitpid2(pid) assert_equal([pid, true], [wpid, st.success?], feature) - open("tmp_script.#{$$}", "w") {|f| f.puts "echo $#: $@"; f.chmod(0755)} + File.write("tmp_script.#{$$}", "echo $#: $@", perm: 0o755) result = IO.popen(["./tmp_script.#{$$}", "a b", "c"]) {|f| f.read} assert_equal("2: a b c\n", result, feature) - open("tmp_script.#{$$}", "w") {|f| f.puts "echo $hghg"; f.chmod(0755)} + File.write("tmp_script.#{$$}", "echo $hghg", perm: 0o755) result = IO.popen([{"hghg" => "mogomogo"}, "./tmp_script.#{$$}", "a b", "c"]) {|f| f.read} assert_equal("mogomogo\n", result, feature) @@ -1775,9 +1806,6 @@ class TestProcess < Test::Unit::TestCase end 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" - end bug4315 = '[ruby-core:34833] #7904 [ruby-core:52628] #11613' assert_fail_too_long_path(%w"echo |", bug4315) end @@ -1968,7 +1996,7 @@ class TestProcess < Test::Unit::TestCase end def test_popen_reopen - assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; io = File.open(IO::NULL) io2 = io.dup @@ -2145,7 +2173,7 @@ EOS "c\u{1EE7}a", ].each do |arg| begin - arg = arg.encode(Encoding.find("locale")) + arg = arg.encode(Encoding.local_charmap) rescue else assert_in_out_err([], "#{<<-"begin;"}\n#{<<-"end;"}", [arg], [], bug12841) @@ -2359,7 +2387,7 @@ EOS end def test_deadlock_by_signal_at_forking - assert_separately(%W(- #{RUBY}), <<-INPUT, timeout: 100) + assert_ruby_status(%W(- #{RUBY}), <<-INPUT, timeout: 100) ruby = ARGV.shift GC.start # reduce garbage GC.disable # avoid triggering CoW after forks @@ -2722,6 +2750,7 @@ EOS require 'objspace' begin; obj = "a" * 12 + obj.force_encoding(Encoding::UTF_16LE) obj.force_encoding(Encoding::BINARY) assert_include(ObjectSpace.dump(obj), '"coderange":"unknown"') Process.warmup @@ -2734,7 +2763,7 @@ EOS begin; GC.start - TIMES = 10_000 + TIMES = 100_000 ary = Array.new(TIMES) TIMES.times do |i| ary[i] = Object.new @@ -2745,14 +2774,91 @@ EOS # Disable GC so we can make sure GC only runs in Process.warmup GC.disable - total_pages_before = GC.stat(:heap_eden_pages) + GC.stat(:heap_allocatable_pages) + total_slots_before = GC.stat(:heap_available_slots) + GC.stat(:heap_allocatable_slots) Process.warmup - # 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) + # TODO: flaky + # assert_equal(total_slots_before, GC.stat(:heap_available_slots) + GC.stat(:heap_allocatable_slots)) + + assert_equal(0, GC.stat(:heap_empty_pages)) assert_operator(GC.stat(:total_freed_pages), :>, 0) end; end + + 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 + # Wait for us to be blocking in a call to waitpid2 + Thread.pass until t1.stop? + short_wpipe.close # Make short_pid exit + + # The short pid has exited, so -1 should pick that up. + assert_equal short_pid, Process.waitpid(-1) + + # Terminate t1 for the next phase of the test. + t1.kill + t1.join + + 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? + + # 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) + + def test_handle_interrupt_with_fork + Thread.handle_interrupt(RuntimeError => :never) do + Thread.current.raise(RuntimeError, "Queued error") + + assert_predicate Thread, :pending_interrupt? + + pid = Process.fork do + if Thread.pending_interrupt? + exit 1 + end + end + + _, status = Process.waitpid2(pid) + assert_predicate status, :success? + + assert_predicate Thread, :pending_interrupt? + end + rescue RuntimeError + # Ignore. + end if defined?(fork) end |
