mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
229cb0fcdb
Implement long path support on Windows by applying Microsoft's recommended application manifest. To make this work on both Visual C++ and MinGW, include the manifest as a resource when generating the resource files. This way it will be embedded into the executables generated by both compilers. It's important for the manifest resource to have ID 1, otherwise GCC will embed a default manifest. Note that in addition to this, the user needs to have [long paths enabled] either by modifying the registry or by enabling a group policy. [long paths enabled]: https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later Co-authored-by: Nobuyoshi Nakada <nobu@ruby-lang.org>
2533 lines
75 KiB
Ruby
2533 lines
75 KiB
Ruby
# coding: utf-8
|
|
# frozen_string_literal: false
|
|
require 'test/unit'
|
|
require 'tempfile'
|
|
require 'timeout'
|
|
require 'io/wait'
|
|
require 'rbconfig'
|
|
|
|
class TestProcess < Test::Unit::TestCase
|
|
RUBY = EnvUtil.rubybin
|
|
|
|
def setup
|
|
Process.waitall
|
|
end
|
|
|
|
def teardown
|
|
Process.waitall
|
|
end
|
|
|
|
def windows?
|
|
self.class.windows?
|
|
end
|
|
def self.windows?
|
|
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)
|
|
Dir.chdir(d) {
|
|
yield d
|
|
}
|
|
}
|
|
end
|
|
|
|
def run_in_child(str) # should be called in a temporary directory
|
|
write_file("test-script", str)
|
|
Process.wait spawn(RUBY, "test-script")
|
|
$?
|
|
end
|
|
|
|
def test_rlimit_availability
|
|
begin
|
|
Process.getrlimit(nil)
|
|
rescue NotImplementedError
|
|
assert_raise(NotImplementedError) { Process.setrlimit }
|
|
rescue TypeError
|
|
assert_raise(ArgumentError) { Process.setrlimit }
|
|
end
|
|
end
|
|
|
|
def rlimit_exist?
|
|
Process.getrlimit(nil)
|
|
rescue NotImplementedError
|
|
return false
|
|
rescue TypeError
|
|
return true
|
|
end
|
|
|
|
def test_rlimit_nofile
|
|
return unless rlimit_exist?
|
|
with_tmpchdir {
|
|
write_file '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".
|
|
pipes = IO.pipe
|
|
limit = pipes.map {|io| io.fileno }.min
|
|
result = 1
|
|
begin
|
|
Process.setrlimit(Process::RLIMIT_NOFILE, limit)
|
|
rescue Errno::EINVAL
|
|
result = 0
|
|
end
|
|
if result == 1
|
|
begin
|
|
IO.pipe
|
|
rescue Errno::EMFILE
|
|
result = 0
|
|
end
|
|
end
|
|
exit result
|
|
End
|
|
pid = spawn RUBY, "s"
|
|
Process.wait pid
|
|
assert_equal(0, $?.to_i, "#{$?}")
|
|
}
|
|
end
|
|
|
|
def test_rlimit_name
|
|
return unless rlimit_exist?
|
|
[
|
|
:AS, "AS",
|
|
:CORE, "CORE",
|
|
:CPU, "CPU",
|
|
:DATA, "DATA",
|
|
:FSIZE, "FSIZE",
|
|
:MEMLOCK, "MEMLOCK",
|
|
:MSGQUEUE, "MSGQUEUE",
|
|
:NICE, "NICE",
|
|
:NOFILE, "NOFILE",
|
|
:NPROC, "NPROC",
|
|
:RSS, "RSS",
|
|
:RTPRIO, "RTPRIO",
|
|
:RTTIME, "RTTIME",
|
|
:SBSIZE, "SBSIZE",
|
|
:SIGPENDING, "SIGPENDING",
|
|
:STACK, "STACK",
|
|
].each {|name|
|
|
if Process.const_defined? "RLIMIT_#{name}"
|
|
assert_nothing_raised { Process.getrlimit(name) }
|
|
else
|
|
assert_raise(ArgumentError) { Process.getrlimit(name) }
|
|
end
|
|
}
|
|
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}") }
|
|
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) }
|
|
assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Process.setrlimit(:CORE, "\u{30eb 30d3 30fc}") }
|
|
with_tmpchdir do
|
|
s = run_in_child(<<-'End')
|
|
cur, max = Process.getrlimit(:NOFILE)
|
|
Process.setrlimit(:NOFILE, [max-10, cur].min)
|
|
begin
|
|
Process.setrlimit(:NOFILE, :INFINITY)
|
|
rescue Errno::EPERM
|
|
exit false
|
|
end
|
|
End
|
|
assert_not_predicate(s, :success?)
|
|
s = run_in_child(<<-'End')
|
|
cur, max = Process.getrlimit(:NOFILE)
|
|
Process.setrlimit(:NOFILE, [max-10, cur].min)
|
|
begin
|
|
Process.setrlimit(:NOFILE, "INFINITY")
|
|
rescue Errno::EPERM
|
|
exit false
|
|
end
|
|
End
|
|
assert_not_predicate(s, :success?)
|
|
end
|
|
end
|
|
|
|
TRUECOMMAND = [RUBY, '-e', '']
|
|
|
|
def test_execopts_opts
|
|
assert_nothing_raised {
|
|
Process.wait Process.spawn(*TRUECOMMAND, {})
|
|
}
|
|
assert_raise(ArgumentError) {
|
|
Process.wait Process.spawn(*TRUECOMMAND, :foo => 100)
|
|
}
|
|
assert_raise(ArgumentError) {
|
|
Process.wait Process.spawn(*TRUECOMMAND, Process => 100)
|
|
}
|
|
end
|
|
|
|
def test_execopts_pgroup
|
|
skip "system(:pgroup) is not supported" if windows?
|
|
assert_nothing_raised { system(*TRUECOMMAND, :pgroup=>false) }
|
|
|
|
io = IO.popen([RUBY, "-e", "print Process.getpgrp"])
|
|
assert_equal(Process.getpgrp.to_s, io.read)
|
|
io.close
|
|
|
|
io = IO.popen([RUBY, "-e", "print Process.getpgrp", :pgroup=>true])
|
|
assert_equal(io.pid.to_s, io.read)
|
|
io.close
|
|
|
|
assert_raise(ArgumentError) { system(*TRUECOMMAND, :pgroup=>-1) }
|
|
IO.popen([RUBY, '-egets'], 'w') do |f|
|
|
assert_raise(Errno::EPERM) {
|
|
Process.wait spawn(*TRUECOMMAND, :pgroup=>f.pid)
|
|
}
|
|
end
|
|
|
|
io1 = IO.popen([RUBY, "-e", "print Process.getpgrp", :pgroup=>true])
|
|
io2 = IO.popen([RUBY, "-e", "print Process.getpgrp", :pgroup=>io1.pid])
|
|
assert_equal(io1.pid.to_s, io1.read)
|
|
assert_equal(io1.pid.to_s, io2.read)
|
|
Process.wait io1.pid
|
|
Process.wait io2.pid
|
|
io1.close
|
|
io2.close
|
|
end
|
|
|
|
def test_execopts_rlimit
|
|
return unless rlimit_exist?
|
|
assert_raise(ArgumentError) { system(*TRUECOMMAND, :rlimit_foo=>0) }
|
|
assert_raise(ArgumentError) { system(*TRUECOMMAND, :rlimit_NOFILE=>0) }
|
|
assert_raise(ArgumentError) { system(*TRUECOMMAND, :rlimit_nofile=>[]) }
|
|
assert_raise(ArgumentError) { system(*TRUECOMMAND, :rlimit_nofile=>[1,2,3]) }
|
|
|
|
max = Process.getrlimit(:CORE).last
|
|
|
|
n = max
|
|
IO.popen([RUBY, "-e",
|
|
"p Process.getrlimit(:CORE)", :rlimit_core=>n]) {|io|
|
|
assert_equal("[#{n}, #{n}]\n", io.read)
|
|
}
|
|
|
|
n = 0
|
|
IO.popen([RUBY, "-e",
|
|
"p Process.getrlimit(:CORE)", :rlimit_core=>n]) {|io|
|
|
assert_equal("[#{n}, #{n}]\n", io.read)
|
|
}
|
|
|
|
n = max
|
|
IO.popen([RUBY, "-e",
|
|
"p Process.getrlimit(:CORE)", :rlimit_core=>[n]]) {|io|
|
|
assert_equal("[#{n}, #{n}]", io.read.chomp)
|
|
}
|
|
|
|
m, n = 0, max
|
|
IO.popen([RUBY, "-e",
|
|
"p Process.getrlimit(:CORE)", :rlimit_core=>[m,n]]) {|io|
|
|
assert_equal("[#{m}, #{n}]", io.read.chomp)
|
|
}
|
|
|
|
m, n = 0, 0
|
|
IO.popen([RUBY, "-e",
|
|
"p Process.getrlimit(:CORE)", :rlimit_core=>[m,n]]) {|io|
|
|
assert_equal("[#{m}, #{n}]", io.read.chomp)
|
|
}
|
|
|
|
n = max
|
|
IO.popen([RUBY, "-e",
|
|
"p Process.getrlimit(:CORE), Process.getrlimit(:CPU)",
|
|
:rlimit_core=>n, :rlimit_cpu=>3600]) {|io|
|
|
assert_equal("[#{n}, #{n}]\n[3600, 3600]", io.read.chomp)
|
|
}
|
|
|
|
assert_raise(ArgumentError) do
|
|
system(RUBY, '-e', 'exit', 'rlimit_bogus'.to_sym => 123)
|
|
end
|
|
assert_separately([],"#{<<~"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)
|
|
end
|
|
|
|
assert_raise_with_message(ArgumentError, /rlimit_cpu/) {
|
|
system(RUBY, '-e', 'exit', "rlimit_cpu\0".to_sym => 3600)
|
|
}
|
|
end
|
|
|
|
MANDATORY_ENVS = %w[RUBYLIB MJIT_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
|
|
if e = RbConfig::CONFIG['LIBPATHENV']
|
|
MANDATORY_ENVS << e
|
|
end
|
|
if e = RbConfig::CONFIG['PRELOADENV'] and !e.empty?
|
|
MANDATORY_ENVS << e
|
|
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)
|
|
|
|
def test_execopts_env
|
|
assert_raise(ArgumentError) {
|
|
system({"F=O"=>"BAR"}, *TRUECOMMAND)
|
|
}
|
|
|
|
with_tmpchdir {|d|
|
|
prog = "#{d}/notexist"
|
|
e = assert_raise(Errno::ENOENT) {
|
|
Process.wait Process.spawn({"FOO"=>"BAR"}, prog)
|
|
}
|
|
assert_equal(prog, e.message.sub(/.* - /, ''))
|
|
e = assert_raise(Errno::ENOENT) {
|
|
Process.wait Process.spawn({"FOO"=>"BAR"}, [prog, "blar"])
|
|
}
|
|
assert_equal(prog, e.message.sub(/.* - /, ''))
|
|
}
|
|
h = {}
|
|
cmd = [h, RUBY]
|
|
(ENV.keys + MANDATORY_ENVS).each do |k|
|
|
case k
|
|
when /\APATH\z/i
|
|
when *MANDATORY_ENVS
|
|
cmd << '-e' << "ENV.delete('#{k}')"
|
|
else
|
|
h[k] = nil
|
|
end
|
|
end
|
|
cmd << '-e' << 'puts ENV.keys.map{|e|e.upcase}'
|
|
IO.popen(cmd) {|io|
|
|
assert_equal("PATH\n", io.read)
|
|
}
|
|
|
|
IO.popen([{"FOO"=>"BAR"}, *ENVCOMMAND]) {|io|
|
|
assert_match(/^FOO=BAR$/, io.read)
|
|
}
|
|
|
|
with_tmpchdir {|d|
|
|
system({"fofo"=>"haha"}, *ENVCOMMAND, STDOUT=>"out")
|
|
assert_match(/^fofo=haha$/, File.read("out").chomp)
|
|
}
|
|
|
|
old = ENV["hmm"]
|
|
begin
|
|
ENV["hmm"] = "fufu"
|
|
IO.popen(ENVCOMMAND) {|io| assert_match(/^hmm=fufu$/, io.read) }
|
|
IO.popen([{"hmm"=>""}, *ENVCOMMAND]) {|io| assert_match(/^hmm=$/, io.read) }
|
|
IO.popen([{"hmm"=>nil}, *ENVCOMMAND]) {|io| assert_not_match(/^hmm=/, io.read) }
|
|
ENV["hmm"] = ""
|
|
IO.popen(ENVCOMMAND) {|io| assert_match(/^hmm=$/, io.read) }
|
|
IO.popen([{"hmm"=>""}, *ENVCOMMAND]) {|io| assert_match(/^hmm=$/, io.read) }
|
|
IO.popen([{"hmm"=>nil}, *ENVCOMMAND]) {|io| assert_not_match(/^hmm=/, io.read) }
|
|
ENV["hmm"] = nil
|
|
IO.popen(ENVCOMMAND) {|io| assert_not_match(/^hmm=/, io.read) }
|
|
IO.popen([{"hmm"=>""}, *ENVCOMMAND]) {|io| assert_match(/^hmm=$/, io.read) }
|
|
IO.popen([{"hmm"=>nil}, *ENVCOMMAND]) {|io| assert_not_match(/^hmm=/, io.read) }
|
|
ensure
|
|
ENV["hmm"] = old
|
|
end
|
|
|
|
assert_raise_with_message(ArgumentError, /fo=fo/) {
|
|
system({"fo=fo"=>"ha"}, *ENVCOMMAND)
|
|
}
|
|
assert_raise_with_message(ArgumentError, /\u{30c0}=\u{30e1}/) {
|
|
system({"\u{30c0}=\u{30e1}"=>"ha"}, *ENVCOMMAND)
|
|
}
|
|
end
|
|
|
|
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)}
|
|
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)
|
|
end
|
|
end
|
|
|
|
def _test_execopts_env_popen(cmd)
|
|
message = cmd.inspect
|
|
IO.popen({"FOO"=>"BAR"}, cmd) {|io|
|
|
assert_equal('FOO=BAR', io.read[/^FOO=.*/], message)
|
|
}
|
|
|
|
old = ENV["hmm"]
|
|
begin
|
|
ENV["hmm"] = "fufu"
|
|
IO.popen(cmd) {|io| assert_match(/^hmm=fufu$/, io.read, message)}
|
|
IO.popen({"hmm"=>""}, cmd) {|io| assert_match(/^hmm=$/, io.read, message)}
|
|
IO.popen({"hmm"=>nil}, cmd) {|io| assert_not_match(/^hmm=/, io.read, message)}
|
|
ENV["hmm"] = ""
|
|
IO.popen(cmd) {|io| assert_match(/^hmm=$/, io.read, message)}
|
|
IO.popen({"hmm"=>""}, cmd) {|io| assert_match(/^hmm=$/, io.read, message)}
|
|
IO.popen({"hmm"=>nil}, cmd) {|io| assert_not_match(/^hmm=/, io.read, message)}
|
|
ENV["hmm"] = nil
|
|
IO.popen(cmd) {|io| assert_not_match(/^hmm=/, io.read, message)}
|
|
IO.popen({"hmm"=>""}, cmd) {|io| assert_match(/^hmm=$/, io.read, message)}
|
|
IO.popen({"hmm"=>nil}, cmd) {|io| assert_not_match(/^hmm=/, io.read, message)}
|
|
ensure
|
|
ENV["hmm"] = old
|
|
end
|
|
end
|
|
|
|
def test_execopts_env_popen_vector
|
|
_test_execopts_env_popen(ENVCOMMAND)
|
|
end
|
|
|
|
def test_execopts_env_popen_string
|
|
with_tmpchdir do |d|
|
|
open('test-script', 'w') do |f|
|
|
ENVCOMMAND.each_with_index do |cmd, i|
|
|
next if i.zero? or cmd == "-e"
|
|
f.puts cmd
|
|
end
|
|
end
|
|
_test_execopts_env_popen("#{RUBY} test-script")
|
|
end
|
|
end
|
|
|
|
def test_execopts_preserve_env_on_exec_failure
|
|
with_tmpchdir {|d|
|
|
write_file '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
|
|
}
|
|
End
|
|
system(RUBY, 's')
|
|
assert_equal(nil.inspect, File.read('out'),
|
|
"[ruby-core:44093] [ruby-trunk - Bug #6249]")
|
|
}
|
|
end
|
|
|
|
def test_execopts_env_single_word
|
|
with_tmpchdir {|d|
|
|
open("test_execopts_env_single_word.rb", "w") {|f|
|
|
f.puts "print ENV['hgga']"
|
|
}
|
|
system({"hgga"=>"ugu"}, RUBY,
|
|
:in => 'test_execopts_env_single_word.rb',
|
|
:out => 'test_execopts_env_single_word.out')
|
|
assert_equal('ugu', File.read('test_execopts_env_single_word.out'))
|
|
}
|
|
end
|
|
|
|
def test_execopts_unsetenv_others
|
|
h = {}
|
|
MANDATORY_ENVS.each {|k| e = ENV[k] and h[k] = e}
|
|
IO.popen([h, *ENVCOMMAND, :unsetenv_others=>true]) {|io|
|
|
assert_equal("", io.read)
|
|
}
|
|
IO.popen([h.merge("A"=>"B"), *ENVCOMMAND, :unsetenv_others=>true]) {|io|
|
|
assert_equal("A=B\n", io.read)
|
|
}
|
|
end
|
|
|
|
PWD = [RUBY, '-e', 'puts Dir.pwd']
|
|
|
|
def test_execopts_chdir
|
|
with_tmpchdir {|d|
|
|
IO.popen([*PWD, :chdir => d]) {|io|
|
|
assert_equal(d, io.read.chomp)
|
|
}
|
|
assert_raise_with_message(Errno::ENOENT, %r"d/notexist") {
|
|
Process.wait Process.spawn(*PWD, :chdir => "d/notexist")
|
|
}
|
|
n = "d/\u{1F37A}"
|
|
assert_raise_with_message(Errno::ENOENT, /#{n}/) {
|
|
Process.wait Process.spawn(*PWD, :chdir => n)
|
|
}
|
|
}
|
|
end
|
|
|
|
def test_execopts_open_chdir
|
|
with_tmpchdir {|d|
|
|
Dir.mkdir "foo"
|
|
system(*PWD, :chdir => "foo", :out => "open_chdir_test")
|
|
assert_file.exist?("open_chdir_test")
|
|
assert_file.not_exist?("foo/open_chdir_test")
|
|
assert_equal("#{d}/foo", File.read("open_chdir_test").chomp)
|
|
}
|
|
end
|
|
|
|
def test_execopts_open_chdir_m17n_path
|
|
with_tmpchdir {|d|
|
|
Dir.mkdir "テスト"
|
|
(pwd = PWD.dup).insert(1, '-EUTF-8:UTF-8')
|
|
system(*pwd, :chdir => "テスト", :out => "open_chdir_テスト")
|
|
assert_file.exist?("open_chdir_テスト")
|
|
assert_file.not_exist?("テスト/open_chdir_テスト")
|
|
assert_equal("#{d}/テスト", File.read("open_chdir_テスト", encoding: "UTF-8").chomp)
|
|
}
|
|
end if windows? || Encoding.find('locale') == Encoding::UTF_8
|
|
|
|
def test_execopts_open_failure
|
|
with_tmpchdir {|d|
|
|
assert_raise_with_message(Errno::ENOENT, %r"d/notexist") {
|
|
Process.wait Process.spawn(*PWD, :in => "d/notexist")
|
|
}
|
|
assert_raise_with_message(Errno::ENOENT, %r"d/notexist") {
|
|
Process.wait Process.spawn(*PWD, :out => "d/notexist")
|
|
}
|
|
n = "d/\u{1F37A}"
|
|
assert_raise_with_message(Errno::ENOENT, /#{n}/) {
|
|
Process.wait Process.spawn(*PWD, :in => n)
|
|
}
|
|
assert_raise_with_message(Errno::ENOENT, /#{n}/) {
|
|
Process.wait Process.spawn(*PWD, :out => n)
|
|
}
|
|
}
|
|
end
|
|
|
|
UMASK = [RUBY, '-e', 'printf "%04o\n", File.umask']
|
|
|
|
def test_execopts_umask
|
|
skip "umask is not supported" if windows?
|
|
IO.popen([*UMASK, :umask => 0]) {|io|
|
|
assert_equal("0000", io.read.chomp)
|
|
}
|
|
IO.popen([*UMASK, :umask => 0777]) {|io|
|
|
assert_equal("0777", io.read.chomp)
|
|
}
|
|
end
|
|
|
|
def with_pipe
|
|
begin
|
|
r, w = IO.pipe
|
|
yield r, w
|
|
ensure
|
|
r.close unless r.closed?
|
|
w.close unless w.closed?
|
|
end
|
|
end
|
|
|
|
def with_pipes(n)
|
|
ary = []
|
|
begin
|
|
n.times {
|
|
ary << IO.pipe
|
|
}
|
|
yield ary
|
|
ensure
|
|
ary.each {|r, w|
|
|
r.close unless r.closed?
|
|
w.close unless w.closed?
|
|
}
|
|
end
|
|
end
|
|
|
|
ECHO = lambda {|arg| [RUBY, '-e', "puts #{arg.dump}; STDOUT.flush"] }
|
|
SORT = [RUBY, '-e', "puts ARGF.readlines.sort"]
|
|
CAT = [RUBY, '-e', "IO.copy_stream STDIN, STDOUT"]
|
|
|
|
def test_execopts_redirect_fd
|
|
with_tmpchdir {|d|
|
|
Process.wait Process.spawn(*ECHO["a"], STDOUT=>["out", File::WRONLY|File::CREAT|File::TRUNC, 0644])
|
|
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"}
|
|
else
|
|
Process.wait Process.spawn(*ECHO["0"], STDOUT=>["out", File::WRONLY|File::CREAT|File::APPEND, 0644])
|
|
assert_equal("a\n0\n", File.read("out"))
|
|
end
|
|
Process.wait Process.spawn(*SORT, STDIN=>["out", File::RDONLY, 0644],
|
|
STDOUT=>["out2", File::WRONLY|File::CREAT|File::TRUNC, 0644])
|
|
assert_equal("0\na\n", File.read("out2"))
|
|
Process.wait Process.spawn(*ECHO["b"], [STDOUT, STDERR]=>["out", File::WRONLY|File::CREAT|File::TRUNC, 0644])
|
|
assert_equal("b", File.read("out").chomp)
|
|
# problem occur with valgrind
|
|
#Process.wait Process.spawn(*ECHO["a"], STDOUT=>:close, STDERR=>["out", File::WRONLY|File::CREAT|File::TRUNC, 0644])
|
|
#p File.read("out")
|
|
#assert_not_empty(File.read("out")) # error message such as "-e:1:in `flush': Bad file descriptor (Errno::EBADF)"
|
|
Process.wait Process.spawn(*ECHO["c"], STDERR=>STDOUT, STDOUT=>["out", File::WRONLY|File::CREAT|File::TRUNC, 0644])
|
|
assert_equal("c", File.read("out").chomp)
|
|
File.open("out", "w") {|f|
|
|
Process.wait Process.spawn(*ECHO["d"], STDOUT=>f)
|
|
assert_equal("d", File.read("out").chomp)
|
|
}
|
|
opts = {STDOUT=>["out", File::WRONLY|File::CREAT|File::TRUNC, 0644]}
|
|
opts.merge(3=>STDOUT, 4=>STDOUT, 5=>STDOUT, 6=>STDOUT, 7=>STDOUT) unless windows?
|
|
Process.wait Process.spawn(*ECHO["e"], opts)
|
|
assert_equal("e", File.read("out").chomp)
|
|
opts = {STDOUT=>["out", File::WRONLY|File::CREAT|File::TRUNC, 0644]}
|
|
opts.merge(3=>0, 4=>:in, 5=>STDIN, 6=>1, 7=>:out, 8=>STDOUT, 9=>2, 10=>:err, 11=>STDERR) unless windows?
|
|
Process.wait Process.spawn(*ECHO["ee"], opts)
|
|
assert_equal("ee", File.read("out").chomp)
|
|
unless windows?
|
|
# passing non-stdio fds is not supported on Windows
|
|
File.open("out", "w") {|f|
|
|
h = {STDOUT=>f, f=>STDOUT}
|
|
3.upto(30) {|i| h[i] = STDOUT if f.fileno != i }
|
|
Process.wait Process.spawn(*ECHO["f"], h)
|
|
assert_equal("f", File.read("out").chomp)
|
|
}
|
|
end
|
|
assert_raise(ArgumentError) {
|
|
Process.wait Process.spawn(*ECHO["f"], 1=>Process)
|
|
}
|
|
assert_raise(ArgumentError) {
|
|
Process.wait Process.spawn(*ECHO["f"], [Process]=>1)
|
|
}
|
|
assert_raise(ArgumentError) {
|
|
Process.wait Process.spawn(*ECHO["f"], [1, STDOUT]=>2)
|
|
}
|
|
assert_raise(ArgumentError) {
|
|
Process.wait Process.spawn(*ECHO["f"], -1=>2)
|
|
}
|
|
Process.wait Process.spawn(*ECHO["hhh\nggg\n"], STDOUT=>"out")
|
|
assert_equal("hhh\nggg\n", File.read("out"))
|
|
Process.wait Process.spawn(*SORT, STDIN=>"out", STDOUT=>"out2")
|
|
assert_equal("ggg\nhhh\n", File.read("out2"))
|
|
|
|
unless windows?
|
|
# passing non-stdio fds is not supported on Windows
|
|
assert_raise(Errno::ENOENT) {
|
|
Process.wait Process.spawn("non-existing-command", (3..60).to_a=>["err", File::WRONLY|File::CREAT])
|
|
}
|
|
assert_equal("", File.read("err"))
|
|
end
|
|
|
|
system(*ECHO["bb\naa\n"], STDOUT=>["out", "w"])
|
|
assert_equal("bb\naa\n", File.read("out"))
|
|
system(*SORT, STDIN=>["out"], STDOUT=>"out2")
|
|
assert_equal("aa\nbb\n", File.read("out2"))
|
|
}
|
|
end
|
|
|
|
def test_execopts_redirect_open_order_normal
|
|
minfd = 3
|
|
maxfd = 20
|
|
with_tmpchdir {|d|
|
|
opts = {}
|
|
minfd.upto(maxfd) {|fd| opts[fd] = ["out#{fd}", "w"] }
|
|
system RUBY, "-e", "#{minfd}.upto(#{maxfd}) {|fd| IO.new(fd).print fd.to_s }", opts
|
|
minfd.upto(maxfd) {|fd| assert_equal(fd.to_s, File.read("out#{fd}")) }
|
|
}
|
|
end unless windows? # passing non-stdio fds is not supported on Windows
|
|
|
|
def test_execopts_redirect_open_order_reverse
|
|
minfd = 3
|
|
maxfd = 20
|
|
with_tmpchdir {|d|
|
|
opts = {}
|
|
maxfd.downto(minfd) {|fd| opts[fd] = ["out#{fd}", "w"] }
|
|
system RUBY, "-e", "#{minfd}.upto(#{maxfd}) {|fd| IO.new(fd).print fd.to_s }", opts
|
|
minfd.upto(maxfd) {|fd| assert_equal(fd.to_s, File.read("out#{fd}")) }
|
|
}
|
|
end unless windows? # passing non-stdio fds is not supported on Windows
|
|
|
|
def test_execopts_redirect_open_fifo
|
|
with_tmpchdir {|d|
|
|
begin
|
|
File.mkfifo("fifo")
|
|
rescue NotImplementedError
|
|
return
|
|
end
|
|
assert_file.pipe?("fifo")
|
|
t1 = Thread.new {
|
|
system(*ECHO["output to fifo"], :out=>"fifo")
|
|
}
|
|
t2 = Thread.new {
|
|
IO.popen([*CAT, :in=>"fifo"]) {|f| f.read }
|
|
}
|
|
_, v2 = assert_join_threads([t1, t2])
|
|
assert_equal("output to fifo\n", v2)
|
|
}
|
|
end unless windows? # does not support fifo
|
|
|
|
def test_execopts_redirect_open_fifo_interrupt_raise
|
|
with_tmpchdir {|d|
|
|
begin
|
|
File.mkfifo("fifo")
|
|
rescue NotImplementedError
|
|
return
|
|
end
|
|
IO.popen([RUBY, '-e', <<-'EOS']) {|io|
|
|
class E < StandardError; end
|
|
trap(:USR1) { raise E }
|
|
begin
|
|
puts "start"
|
|
STDOUT.flush
|
|
system("cat", :in => "fifo")
|
|
rescue E
|
|
puts "ok"
|
|
end
|
|
EOS
|
|
assert_equal("start\n", io.gets)
|
|
sleep 0.5
|
|
Process.kill(:USR1, io.pid)
|
|
assert_equal("ok\n", io.read)
|
|
}
|
|
}
|
|
end unless windows? # does not support fifo
|
|
|
|
def test_execopts_redirect_open_fifo_interrupt_print
|
|
with_tmpchdir {|d|
|
|
begin
|
|
File.mkfifo("fifo")
|
|
rescue NotImplementedError
|
|
return
|
|
end
|
|
IO.popen([RUBY, '-e', <<-'EOS']) {|io|
|
|
STDOUT.sync = true
|
|
trap(:USR1) { print "trap\n" }
|
|
puts "start"
|
|
system("cat", :in => "fifo")
|
|
EOS
|
|
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))
|
|
File.write("fifo", "ok\n")
|
|
assert_equal("ok\n", io.read)
|
|
}
|
|
}
|
|
end unless windows? # does not support fifo
|
|
|
|
def test_execopts_redirect_pipe
|
|
with_pipe {|r1, w1|
|
|
with_pipe {|r2, w2|
|
|
opts = {STDIN=>r1, STDOUT=>w2}
|
|
opts.merge(w1=>:close, r2=>:close) unless windows?
|
|
pid = spawn(*SORT, opts)
|
|
r1.close
|
|
w2.close
|
|
w1.puts "c"
|
|
w1.puts "a"
|
|
w1.puts "b"
|
|
w1.close
|
|
assert_equal("a\nb\nc\n", r2.read)
|
|
r2.close
|
|
Process.wait(pid)
|
|
}
|
|
}
|
|
|
|
unless windows?
|
|
# passing non-stdio fds is not supported on Windows
|
|
with_pipes(5) {|pipes|
|
|
ios = pipes.flatten
|
|
h = {}
|
|
ios.length.times {|i| h[ios[i]] = ios[(i-1)%ios.length] }
|
|
h2 = h.invert
|
|
_rios = pipes.map {|r, w| r }
|
|
wios = pipes.map {|r, w| w }
|
|
child_wfds = wios.map {|w| h2[w].fileno }
|
|
pid = spawn(RUBY, "-e",
|
|
"[#{child_wfds.join(',')}].each {|fd| IO.new(fd, 'w').puts fd }", h)
|
|
pipes.each {|r, w|
|
|
assert_equal("#{h2[w].fileno}\n", r.gets)
|
|
}
|
|
Process.wait pid;
|
|
}
|
|
|
|
with_pipes(5) {|pipes|
|
|
ios = pipes.flatten
|
|
h = {}
|
|
ios.length.times {|i| h[ios[i]] = ios[(i+1)%ios.length] }
|
|
h2 = h.invert
|
|
_rios = pipes.map {|r, w| r }
|
|
wios = pipes.map {|r, w| w }
|
|
child_wfds = wios.map {|w| h2[w].fileno }
|
|
pid = spawn(RUBY, "-e",
|
|
"[#{child_wfds.join(',')}].each {|fd| IO.new(fd, 'w').puts fd }", h)
|
|
pipes.each {|r, w|
|
|
assert_equal("#{h2[w].fileno}\n", r.gets)
|
|
}
|
|
Process.wait pid
|
|
}
|
|
|
|
closed_fd = nil
|
|
with_pipes(5) {|pipes|
|
|
io = pipes.last.last
|
|
closed_fd = io.fileno
|
|
}
|
|
assert_raise(Errno::EBADF) { Process.wait spawn(*TRUECOMMAND, closed_fd=>closed_fd) }
|
|
|
|
with_pipe {|r, w|
|
|
if w.respond_to?(:"close_on_exec=")
|
|
w.close_on_exec = true
|
|
pid = spawn(RUBY, "-e", "IO.new(#{w.fileno}, 'w').print 'a'", w=>w)
|
|
w.close
|
|
assert_equal("a", r.read)
|
|
Process.wait pid
|
|
end
|
|
}
|
|
|
|
# ensure standard FDs we redirect to are blocking for compatibility
|
|
with_pipes(3) do |pipes|
|
|
src = 'p [STDIN,STDOUT,STDERR].map(&:nonblock?)'
|
|
rdr = { 0 => pipes[0][0], 1 => pipes[1][1], 2 => pipes[2][1] }
|
|
pid = spawn(RUBY, '-rio/nonblock', '-e', src, rdr)
|
|
assert_equal("[false, false, false]\n", pipes[1][0].gets)
|
|
Process.wait pid
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_execopts_redirect_symbol
|
|
with_tmpchdir {|d|
|
|
system(*ECHO["funya"], :out=>"out")
|
|
assert_equal("funya\n", File.read("out"))
|
|
system(RUBY, '-e', 'STDOUT.reopen(STDERR); puts "henya"', :err=>"out")
|
|
assert_equal("henya\n", File.read("out"))
|
|
IO.popen([*CAT, :in=>"out"]) {|io|
|
|
assert_equal("henya\n", io.read)
|
|
}
|
|
}
|
|
end
|
|
|
|
def test_execopts_redirect_nonascii_path
|
|
bug9946 = '[ruby-core:63185] [Bug #9946]'
|
|
with_tmpchdir {|d|
|
|
path = "t-\u{30c6 30b9 30c8 f6}.txt"
|
|
system(*ECHO["a"], out: path)
|
|
assert_file.for(bug9946).exist?(path)
|
|
assert_equal("a\n", File.read(path), bug9946)
|
|
}
|
|
end
|
|
|
|
def test_execopts_redirect_to_out_and_err
|
|
with_tmpchdir {|d|
|
|
ret = system(RUBY, "-e", 'STDERR.print "e"; STDOUT.print "o"', [:out, :err] => "foo")
|
|
assert_equal(true, ret)
|
|
assert_equal("eo", File.read("foo"))
|
|
ret = system(RUBY, "-e", 'STDERR.print "E"; STDOUT.print "O"', [:err, :out] => "bar")
|
|
assert_equal(true, ret)
|
|
assert_equal("EO", File.read("bar"))
|
|
}
|
|
end
|
|
|
|
def test_execopts_redirect_dup2_child
|
|
with_tmpchdir {|d|
|
|
Process.wait spawn(RUBY, "-e", "STDERR.print 'err'; STDOUT.print 'out'",
|
|
STDOUT=>"out", STDERR=>[:child, STDOUT])
|
|
assert_equal("errout", File.read("out"))
|
|
|
|
Process.wait spawn(RUBY, "-e", "STDERR.print 'err'; STDOUT.print 'out'",
|
|
STDERR=>"out", STDOUT=>[:child, STDERR])
|
|
assert_equal("errout", File.read("out"))
|
|
|
|
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],
|
|
3=>[:child, 4],
|
|
4=>[:child, STDOUT]
|
|
)
|
|
assert_equal("errout", File.read("out"))
|
|
|
|
IO.popen([RUBY, "-e", "STDERR.print 'err'; STDOUT.print 'out'", STDERR=>[:child, STDOUT]]) {|io|
|
|
assert_equal("errout", io.read)
|
|
}
|
|
|
|
assert_raise(ArgumentError) { Process.wait spawn(*TRUECOMMAND, STDOUT=>[:child, STDOUT]) }
|
|
assert_raise(ArgumentError) { Process.wait spawn(*TRUECOMMAND, 3=>[:child, 4], 4=>[:child, 3]) }
|
|
assert_raise(ArgumentError) { Process.wait spawn(*TRUECOMMAND, 3=>[:child, 4], 4=>[:child, 5], 5=>[:child, 3]) }
|
|
assert_raise(ArgumentError) { Process.wait spawn(*TRUECOMMAND, STDOUT=>[:child, 3]) }
|
|
}
|
|
end
|
|
|
|
def test_execopts_exec
|
|
with_tmpchdir {|d|
|
|
write_file("s", 'exec "echo aaa", STDOUT=>"foo"')
|
|
pid = spawn RUBY, 's'
|
|
Process.wait pid
|
|
assert_equal("aaa\n", File.read("foo"))
|
|
}
|
|
end
|
|
|
|
def test_execopts_popen
|
|
with_tmpchdir {|d|
|
|
IO.popen("#{RUBY} -e 'puts :foo'") {|io| assert_equal("foo\n", io.read) }
|
|
assert_raise(Errno::ENOENT) { IO.popen(["echo bar"]) {} } # assuming "echo bar" command not exist.
|
|
IO.popen(ECHO["baz"]) {|io| assert_equal("baz\n", io.read) }
|
|
}
|
|
end
|
|
|
|
def test_execopts_popen_stdio
|
|
with_tmpchdir {|d|
|
|
assert_raise(ArgumentError) {
|
|
IO.popen([*ECHO["qux"], STDOUT=>STDOUT]) {|io| }
|
|
}
|
|
IO.popen([*ECHO["hoge"], STDERR=>STDOUT]) {|io|
|
|
assert_equal("hoge\n", io.read)
|
|
}
|
|
assert_raise(ArgumentError) {
|
|
IO.popen([*ECHO["fuga"], STDOUT=>"out"]) {|io| }
|
|
}
|
|
}
|
|
end
|
|
|
|
def test_execopts_popen_extra_fd
|
|
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|
|
|
assert_equal("b\n", io.read)
|
|
}
|
|
w.close
|
|
assert_equal("a\n", r.read)
|
|
}
|
|
IO.popen([RUBY, '-e', "IO.new(9, 'w').puts(:b)",
|
|
9=>["out2", File::WRONLY|File::CREAT|File::TRUNC]]) {|io|
|
|
assert_equal("", io.read)
|
|
}
|
|
assert_equal("b\n", File.read("out2"))
|
|
}
|
|
end
|
|
|
|
def test_popen_fork
|
|
IO.popen("-") {|io|
|
|
if !io
|
|
puts "fooo"
|
|
else
|
|
assert_equal("fooo\n", io.read)
|
|
end
|
|
}
|
|
rescue NotImplementedError
|
|
end
|
|
|
|
def test_fd_inheritance
|
|
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
|
|
assert_equal("ba\n", r.read)
|
|
}
|
|
with_pipe {|r, w|
|
|
Process.wait spawn(RUBY, '-e',
|
|
'IO.new(ARGV[0].to_i, "w").puts("bi") rescue nil',
|
|
w.fileno.to_s)
|
|
w.close
|
|
assert_equal("", r.read)
|
|
}
|
|
with_pipe {|r, w|
|
|
with_tmpchdir {|d|
|
|
write_file("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)
|
|
End
|
|
w.close_on_exec = false
|
|
Process.wait spawn(RUBY, "s", :close_others=>false)
|
|
w.close
|
|
assert_equal("bu\n", r.read)
|
|
}
|
|
}
|
|
with_pipe {|r, w|
|
|
io = IO.popen([RUBY, "-e", "STDERR.reopen(STDOUT); IO.new(#{w.fileno}, 'w').puts('me')"])
|
|
begin
|
|
w.close
|
|
errmsg = io.read
|
|
assert_equal("", r.read)
|
|
assert_not_equal("", errmsg)
|
|
ensure
|
|
io.close
|
|
end
|
|
}
|
|
with_pipe {|r, w|
|
|
errmsg = `#{RUBY} -e "STDERR.reopen(STDOUT); IO.new(#{w.fileno}, 'w').puts(123)"`
|
|
w.close
|
|
assert_equal("", r.read)
|
|
assert_not_equal("", errmsg)
|
|
}
|
|
end
|
|
|
|
def test_execopts_close_others
|
|
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)
|
|
w.close
|
|
assert_equal("", r.read)
|
|
assert_not_equal("", File.read("err"))
|
|
File.unlink("err")
|
|
}
|
|
with_pipe {|r, w|
|
|
Process.wait spawn(RUBY, '-e', 'STDERR.reopen("err", "w"); IO.new(ARGV[0].to_i, "w").puts("mi")', w.fileno.to_s, :close_others=>true)
|
|
w.close
|
|
assert_equal("", r.read)
|
|
assert_not_equal("", File.read("err"))
|
|
File.unlink("err")
|
|
}
|
|
with_pipe {|r, w|
|
|
w.close_on_exec = false
|
|
Process.wait spawn(RUBY, '-e', 'IO.new(ARGV[0].to_i, "w").puts("bi")', w.fileno.to_s, :close_others=>false)
|
|
w.close
|
|
assert_equal("bi\n", r.read)
|
|
}
|
|
with_pipe {|r, w|
|
|
write_file("s", <<-"End")
|
|
exec(#{RUBY.dump}, '-e',
|
|
'STDERR.reopen("err", "w"); IO.new(ARGV[0].to_i, "w").puts("mu")',
|
|
#{w.fileno.to_s.dump},
|
|
:close_others=>true)
|
|
End
|
|
Process.wait spawn(RUBY, "s", :close_others=>false)
|
|
w.close
|
|
assert_equal("", r.read)
|
|
assert_not_equal("", File.read("err"))
|
|
File.unlink("err")
|
|
}
|
|
with_pipe {|r, w|
|
|
io = IO.popen([RUBY, "-e", "STDERR.reopen(STDOUT); IO.new(#{w.fileno}, 'w').puts('me')", :close_others=>true])
|
|
begin
|
|
w.close
|
|
errmsg = io.read
|
|
assert_equal("", r.read)
|
|
assert_not_equal("", errmsg)
|
|
ensure
|
|
io.close
|
|
end
|
|
}
|
|
with_pipe {|r, w|
|
|
w.close_on_exec = false
|
|
io = IO.popen([RUBY, "-e", "STDERR.reopen(STDOUT); IO.new(#{w.fileno}, 'w').puts('mo')", :close_others=>false])
|
|
begin
|
|
w.close
|
|
errmsg = io.read
|
|
assert_equal("mo\n", r.read)
|
|
assert_equal("", errmsg)
|
|
ensure
|
|
io.close
|
|
end
|
|
}
|
|
with_pipe {|r, w|
|
|
w.close_on_exec = false
|
|
io = IO.popen([RUBY, "-e", "STDERR.reopen(STDOUT); IO.new(#{w.fileno}, 'w').puts('mo')", :close_others=>nil])
|
|
begin
|
|
w.close
|
|
errmsg = io.read
|
|
assert_equal("mo\n", r.read)
|
|
assert_equal("", errmsg)
|
|
ensure
|
|
io.close
|
|
end
|
|
}
|
|
|
|
}
|
|
end
|
|
|
|
def test_close_others_default_false
|
|
IO.pipe do |r,w|
|
|
w.close_on_exec = false
|
|
src = "IO.new(#{w.fileno}).puts(:hi)"
|
|
assert_equal true, system(*%W(#{RUBY} --disable=gems -e #{src}))
|
|
assert_equal "hi\n", r.gets
|
|
end
|
|
end unless windows? # passing non-stdio fds is not supported on Windows
|
|
|
|
def test_execopts_redirect_self
|
|
begin
|
|
with_pipe {|r, w|
|
|
w << "haha\n"
|
|
w.close
|
|
r.close_on_exec = true
|
|
IO.popen([RUBY, "-e", "print IO.new(#{r.fileno}, 'r').read", r.fileno=>r.fileno, :close_others=>false]) {|io|
|
|
assert_equal("haha\n", io.read)
|
|
}
|
|
}
|
|
rescue NotImplementedError
|
|
skip "IO#close_on_exec= is not supported"
|
|
end
|
|
end unless windows? # passing non-stdio fds is not supported on Windows
|
|
|
|
def test_execopts_redirect_tempfile
|
|
bug6269 = '[ruby-core:44181]'
|
|
Tempfile.create("execopts") do |tmp|
|
|
pid = assert_nothing_raised(ArgumentError, bug6269) do
|
|
break spawn(RUBY, "-e", "print $$", out: tmp)
|
|
end
|
|
Process.wait(pid)
|
|
tmp.rewind
|
|
assert_equal(pid.to_s, tmp.read)
|
|
end
|
|
end
|
|
|
|
def test_execopts_duplex_io
|
|
IO.popen("#{RUBY} -e ''", "r+") {|duplex|
|
|
assert_raise(ArgumentError) { system("#{RUBY} -e ''", duplex=>STDOUT) }
|
|
assert_raise(ArgumentError) { system("#{RUBY} -e ''", STDOUT=>duplex) }
|
|
}
|
|
end
|
|
|
|
def test_execopts_modification
|
|
h = {}
|
|
Process.wait spawn(*TRUECOMMAND, h)
|
|
assert_equal({}, h)
|
|
|
|
h = {}
|
|
system(*TRUECOMMAND, h)
|
|
assert_equal({}, h)
|
|
|
|
h = {}
|
|
io = IO.popen([*TRUECOMMAND, h])
|
|
io.close
|
|
assert_equal({}, h)
|
|
end
|
|
|
|
def test_system_noshell
|
|
str = "echo non existing command name which contains spaces"
|
|
assert_nil(system([str, str]))
|
|
end
|
|
|
|
def test_spawn_noshell
|
|
str = "echo non existing command name which contains spaces"
|
|
assert_raise(Errno::ENOENT) { spawn([str, str]) }
|
|
end
|
|
|
|
def test_popen_noshell
|
|
str = "echo non existing command name which contains spaces"
|
|
assert_raise(Errno::ENOENT) { IO.popen([str, str]) }
|
|
end
|
|
|
|
def test_exec_noshell
|
|
with_tmpchdir {|d|
|
|
write_file("s", <<-"End")
|
|
str = "echo non existing command name which contains spaces"
|
|
STDERR.reopen(STDOUT)
|
|
begin
|
|
exec [str, str]
|
|
rescue Errno::ENOENT
|
|
print "Errno::ENOENT success"
|
|
end
|
|
End
|
|
r = IO.popen([RUBY, "s", :close_others=>false], "r") {|f| f.read}
|
|
assert_equal("Errno::ENOENT success", r)
|
|
}
|
|
end
|
|
|
|
def test_system_wordsplit
|
|
with_tmpchdir {|d|
|
|
write_file("script", <<-'End')
|
|
File.open("result", "w") {|t| t << "haha pid=#{$$} ppid=#{Process.ppid}" }
|
|
exit 5
|
|
End
|
|
str = "#{RUBY} script"
|
|
ret = system(str)
|
|
status = $?
|
|
assert_equal(false, ret)
|
|
assert_predicate(status, :exited?)
|
|
assert_equal(5, status.exitstatus)
|
|
assert_equal("haha pid=#{status.pid} ppid=#{$$}", File.read("result"))
|
|
}
|
|
end
|
|
|
|
def test_spawn_wordsplit
|
|
with_tmpchdir {|d|
|
|
write_file("script", <<-'End')
|
|
File.open("result", "w") {|t| t << "hihi pid=#{$$} ppid=#{Process.ppid}" }
|
|
exit 6
|
|
End
|
|
str = "#{RUBY} script"
|
|
pid = spawn(str)
|
|
Process.wait pid
|
|
status = $?
|
|
assert_equal(pid, status.pid)
|
|
assert_predicate(status, :exited?)
|
|
assert_equal(6, status.exitstatus)
|
|
assert_equal("hihi pid=#{status.pid} ppid=#{$$}", File.read("result"))
|
|
}
|
|
end
|
|
|
|
def test_popen_wordsplit
|
|
with_tmpchdir {|d|
|
|
write_file("script", <<-'End')
|
|
print "fufu pid=#{$$} ppid=#{Process.ppid}"
|
|
exit 7
|
|
End
|
|
str = "#{RUBY} script"
|
|
io = IO.popen(str)
|
|
pid = io.pid
|
|
result = io.read
|
|
io.close
|
|
status = $?
|
|
assert_equal(pid, status.pid)
|
|
assert_predicate(status, :exited?)
|
|
assert_equal(7, status.exitstatus)
|
|
assert_equal("fufu pid=#{status.pid} ppid=#{$$}", result)
|
|
}
|
|
end
|
|
|
|
def test_popen_wordsplit_beginning_and_trailing_spaces
|
|
with_tmpchdir {|d|
|
|
write_file("script", <<-'End')
|
|
print "fufumm pid=#{$$} ppid=#{Process.ppid}"
|
|
exit 7
|
|
End
|
|
str = " #{RUBY} script "
|
|
io = IO.popen(str)
|
|
pid = io.pid
|
|
result = io.read
|
|
io.close
|
|
status = $?
|
|
assert_equal(pid, status.pid)
|
|
assert_predicate(status, :exited?)
|
|
assert_equal(7, status.exitstatus)
|
|
assert_equal("fufumm pid=#{status.pid} ppid=#{$$}", result)
|
|
}
|
|
end
|
|
|
|
def test_exec_wordsplit
|
|
with_tmpchdir {|d|
|
|
write_file("script", <<-'End')
|
|
File.open("result", "w") {|t|
|
|
if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
|
|
t << "hehe ppid=#{Process.ppid}"
|
|
else
|
|
t << "hehe pid=#{$$} ppid=#{Process.ppid}"
|
|
end
|
|
}
|
|
exit 6
|
|
End
|
|
write_file("s", <<-"End")
|
|
ruby = #{RUBY.dump}
|
|
exec "\#{ruby} script"
|
|
End
|
|
pid = spawn(RUBY, "s")
|
|
Process.wait pid
|
|
status = $?
|
|
assert_equal(pid, status.pid)
|
|
assert_predicate(status, :exited?)
|
|
assert_equal(6, status.exitstatus)
|
|
if windows?
|
|
expected = "hehe ppid=#{status.pid}"
|
|
else
|
|
expected = "hehe pid=#{status.pid} ppid=#{$$}"
|
|
end
|
|
assert_equal(expected, File.read("result"))
|
|
}
|
|
end
|
|
|
|
def test_system_shell
|
|
with_tmpchdir {|d|
|
|
write_file("script1", <<-'End')
|
|
File.open("result1", "w") {|t| t << "taka pid=#{$$} ppid=#{Process.ppid}" }
|
|
exit 7
|
|
End
|
|
write_file("script2", <<-'End')
|
|
File.open("result2", "w") {|t| t << "taki pid=#{$$} ppid=#{Process.ppid}" }
|
|
exit 8
|
|
End
|
|
ret = system("#{RUBY} script1 || #{RUBY} script2")
|
|
status = $?
|
|
assert_equal(false, ret)
|
|
assert_predicate(status, :exited?)
|
|
result1 = File.read("result1")
|
|
result2 = File.read("result2")
|
|
assert_match(/\Ataka pid=\d+ ppid=\d+\z/, result1)
|
|
assert_match(/\Ataki pid=\d+ ppid=\d+\z/, result2)
|
|
assert_not_equal(result1[/\d+/].to_i, status.pid)
|
|
|
|
if windows?
|
|
Dir.mkdir(path = "path with space")
|
|
write_file(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'"])
|
|
assert_equal(%["foo 'bar'"\n], File.read("out"), '[ruby-core:22960]')
|
|
end
|
|
}
|
|
end
|
|
|
|
def test_spawn_shell
|
|
with_tmpchdir {|d|
|
|
write_file("script1", <<-'End')
|
|
File.open("result1", "w") {|t| t << "taku pid=#{$$} ppid=#{Process.ppid}" }
|
|
exit 7
|
|
End
|
|
write_file("script2", <<-'End')
|
|
File.open("result2", "w") {|t| t << "take pid=#{$$} ppid=#{Process.ppid}" }
|
|
exit 8
|
|
End
|
|
pid = spawn("#{RUBY} script1 || #{RUBY} script2")
|
|
Process.wait pid
|
|
status = $?
|
|
assert_predicate(status, :exited?)
|
|
assert_not_predicate(status, :success?)
|
|
result1 = File.read("result1")
|
|
result2 = File.read("result2")
|
|
assert_match(/\Ataku pid=\d+ ppid=\d+\z/, result1)
|
|
assert_match(/\Atake pid=\d+ ppid=\d+\z/, result2)
|
|
assert_not_equal(result1[/\d+/].to_i, status.pid)
|
|
|
|
if windows?
|
|
Dir.mkdir(path = "path with space")
|
|
write_file(bat = path + "/bat test.bat", "@echo %1>out")
|
|
pid = spawn(bat, "foo 'bar'")
|
|
Process.wait pid
|
|
status = $?
|
|
assert_predicate(status, :exited?)
|
|
assert_predicate(status, :success?)
|
|
assert_equal(%["foo 'bar'"\n], File.read("out"), '[ruby-core:22960]')
|
|
pid = spawn(%[#{bat.dump} "foo 'bar'"])
|
|
Process.wait pid
|
|
status = $?
|
|
assert_predicate(status, :exited?)
|
|
assert_predicate(status, :success?)
|
|
assert_equal(%["foo 'bar'"\n], File.read("out"), '[ruby-core:22960]')
|
|
end
|
|
}
|
|
end
|
|
|
|
def test_popen_shell
|
|
with_tmpchdir {|d|
|
|
write_file("script1", <<-'End')
|
|
puts "tako pid=#{$$} ppid=#{Process.ppid}"
|
|
exit 7
|
|
End
|
|
write_file("script2", <<-'End')
|
|
puts "tika pid=#{$$} ppid=#{Process.ppid}"
|
|
exit 8
|
|
End
|
|
io = IO.popen("#{RUBY} script1 || #{RUBY} script2")
|
|
result = io.read
|
|
io.close
|
|
status = $?
|
|
assert_predicate(status, :exited?)
|
|
assert_not_predicate(status, :success?)
|
|
assert_match(/\Atako pid=\d+ ppid=\d+\ntika pid=\d+ ppid=\d+\n\z/, result)
|
|
assert_not_equal(result[/\d+/].to_i, status.pid)
|
|
|
|
if windows?
|
|
Dir.mkdir(path = "path with space")
|
|
write_file(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}
|
|
assert_equal(%["foo 'bar'"\n], r, '[ruby-core:22960]')
|
|
end
|
|
}
|
|
end
|
|
|
|
def test_exec_shell
|
|
with_tmpchdir {|d|
|
|
write_file("script1", <<-'End')
|
|
File.open("result1", "w") {|t| t << "tiki pid=#{$$} ppid=#{Process.ppid}" }
|
|
exit 7
|
|
End
|
|
write_file("script2", <<-'End')
|
|
File.open("result2", "w") {|t| t << "tiku pid=#{$$} ppid=#{Process.ppid}" }
|
|
exit 8
|
|
End
|
|
write_file("s", <<-"End")
|
|
ruby = #{RUBY.dump}
|
|
exec("\#{ruby} script1 || \#{ruby} script2")
|
|
End
|
|
pid = spawn RUBY, "s"
|
|
Process.wait pid
|
|
status = $?
|
|
assert_predicate(status, :exited?)
|
|
assert_not_predicate(status, :success?)
|
|
result1 = File.read("result1")
|
|
result2 = File.read("result2")
|
|
assert_match(/\Atiki pid=\d+ ppid=\d+\z/, result1)
|
|
assert_match(/\Atiku pid=\d+ ppid=\d+\z/, result2)
|
|
assert_not_equal(result1[/\d+/].to_i, status.pid)
|
|
}
|
|
end
|
|
|
|
def test_argv0
|
|
with_tmpchdir {|d|
|
|
assert_equal(false, system([RUBY, "asdfg"], "-e", "exit false"))
|
|
assert_equal(true, system([RUBY, "zxcvb"], "-e", "exit true"))
|
|
|
|
Process.wait spawn([RUBY, "poiu"], "-e", "exit 4")
|
|
assert_equal(4, $?.exitstatus)
|
|
|
|
assert_equal("1", IO.popen([[RUBY, "qwerty"], "-e", "print 1"]) {|f| f.read })
|
|
|
|
write_file("s", <<-"End")
|
|
exec([#{RUBY.dump}, "lkjh"], "-e", "exit 5")
|
|
End
|
|
pid = spawn RUBY, "s"
|
|
Process.wait pid
|
|
assert_equal(5, $?.exitstatus)
|
|
}
|
|
end
|
|
|
|
def with_stdin(filename)
|
|
open(filename) {|f|
|
|
begin
|
|
old = STDIN.dup
|
|
begin
|
|
STDIN.reopen(filename)
|
|
yield
|
|
ensure
|
|
STDIN.reopen(old)
|
|
end
|
|
ensure
|
|
old.close
|
|
end
|
|
}
|
|
end
|
|
|
|
def test_argv0_noarg
|
|
with_tmpchdir {|d|
|
|
open("t", "w") {|f| f.print "exit true" }
|
|
open("f", "w") {|f| f.print "exit false" }
|
|
|
|
with_stdin("t") { assert_equal(true, system([RUBY, "qaz"])) }
|
|
with_stdin("f") { assert_equal(false, system([RUBY, "wsx"])) }
|
|
|
|
with_stdin("t") { Process.wait spawn([RUBY, "edc"]) }
|
|
assert_predicate($?, :success?)
|
|
with_stdin("f") { Process.wait spawn([RUBY, "rfv"]) }
|
|
assert_not_predicate($?, :success?)
|
|
|
|
with_stdin("t") { IO.popen([[RUBY, "tgb"]]) {|io| assert_equal("", io.read) } }
|
|
assert_predicate($?, :success?)
|
|
with_stdin("f") { IO.popen([[RUBY, "yhn"]]) {|io| assert_equal("", io.read) } }
|
|
assert_not_predicate($?, :success?)
|
|
|
|
status = run_in_child "STDIN.reopen('t'); exec([#{RUBY.dump}, 'ujm'])"
|
|
assert_predicate(status, :success?)
|
|
status = run_in_child "STDIN.reopen('f'); exec([#{RUBY.dump}, 'ik,'])"
|
|
assert_not_predicate(status, :success?)
|
|
}
|
|
end
|
|
|
|
def test_argv0_keep_alive
|
|
assert_in_out_err([], <<~REPRO, ['-'], [], "[Bug #15887]")
|
|
$0 = "diverge"
|
|
4.times { GC.start }
|
|
puts Process.argv0
|
|
REPRO
|
|
end
|
|
|
|
def test_status
|
|
with_tmpchdir do
|
|
s = run_in_child("exit 1")
|
|
assert_equal("#<Process::Status: pid #{ s.pid } exit #{ s.exitstatus }>", s.inspect)
|
|
|
|
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)
|
|
|
|
assert_equal(s, Marshal.load(Marshal.dump(s)))
|
|
end
|
|
end
|
|
|
|
def test_status_kill
|
|
return unless Process.respond_to?(:kill)
|
|
return unless Signal.list.include?("KILL")
|
|
|
|
# assume the system supports signal if SIGQUIT is available
|
|
expected = Signal.list.include?("QUIT") ? [false, true, false, nil] : [true, false, false, true]
|
|
|
|
with_tmpchdir do
|
|
write_file("foo", "Process.kill(:KILL, $$); exit(42)")
|
|
system(RUBY, "foo")
|
|
s = $?
|
|
assert_equal(expected,
|
|
[s.exited?, s.signaled?, s.stopped?, s.success?],
|
|
"[s.exited?, s.signaled?, s.stopped?, s.success?]")
|
|
|
|
assert_equal(s, Marshal.load(Marshal.dump(s)))
|
|
end
|
|
end
|
|
|
|
def test_status_quit
|
|
return unless Process.respond_to?(:kill)
|
|
return unless Signal.list.include?("QUIT")
|
|
|
|
with_tmpchdir do
|
|
s = assert_in_out_err([], "Signal.trap(:QUIT,'DEFAULT'); Process.kill(:SIGQUIT, $$);sleep 30", //, //, rlimit_core: 0)
|
|
assert_equal([false, true, false, nil],
|
|
[s.exited?, s.signaled?, s.stopped?, s.success?],
|
|
"[s.exited?, s.signaled?, s.stopped?, s.success?]")
|
|
assert_equal("#<Process::Status: pid #{ s.pid } SIGQUIT (signal #{ s.termsig })>",
|
|
s.inspect.sub(/ \(core dumped\)(?=>\z)/, ''))
|
|
|
|
assert_equal(s, Marshal.load(Marshal.dump(s)))
|
|
end
|
|
end
|
|
|
|
def test_status_fail
|
|
ret = Process::Status.wait($$)
|
|
assert_instance_of(Process::Status, ret)
|
|
assert_equal(-1, ret.pid)
|
|
end
|
|
|
|
|
|
def test_status_wait
|
|
IO.popen([RUBY, "-e", "gets"], "w") do |io|
|
|
pid = io.pid
|
|
assert_nil(Process::Status.wait(pid, Process::WNOHANG))
|
|
io.puts
|
|
ret = Process::Status.wait(pid)
|
|
assert_instance_of(Process::Status, ret)
|
|
assert_equal(pid, ret.pid)
|
|
assert_predicate(ret, :exited?)
|
|
end
|
|
end
|
|
|
|
def test_wait_without_arg
|
|
with_tmpchdir do
|
|
write_file("foo", "sleep 0.1")
|
|
pid = spawn(RUBY, "foo")
|
|
assert_equal(pid, Process.wait)
|
|
end
|
|
end
|
|
|
|
def test_wait2
|
|
with_tmpchdir do
|
|
write_file("foo", "sleep 0.1")
|
|
pid = spawn(RUBY, "foo")
|
|
assert_equal([pid, 0], Process.wait2)
|
|
end
|
|
end
|
|
|
|
def test_waitall
|
|
with_tmpchdir do
|
|
write_file("foo", "sleep 0.1")
|
|
ps = (0...3).map { spawn(RUBY, "foo") }.sort
|
|
ss = Process.waitall.sort
|
|
ps.zip(ss) do |p1, (p2, s)|
|
|
assert_equal(p1, p2)
|
|
assert_equal(p1, s.pid)
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_wait_exception
|
|
bug11340 = '[ruby-dev:49176] [Bug #11340]'
|
|
t0 = t1 = nil
|
|
sec = 3
|
|
code = "puts;STDOUT.flush;Thread.start{gets;exit};sleep(#{sec})"
|
|
IO.popen([RUBY, '-e', code], 'r+') do |f|
|
|
pid = f.pid
|
|
f.gets
|
|
t0 = Time.now
|
|
th = Thread.start(Thread.current) do |main|
|
|
Thread.pass until main.stop?
|
|
main.raise Interrupt
|
|
end
|
|
begin
|
|
assert_raise(Interrupt) {Process.wait(pid)}
|
|
ensure
|
|
th.kill.join
|
|
end
|
|
t1 = Time.now
|
|
diff = t1 - t0
|
|
assert_operator(diff, :<, sec,
|
|
->{"#{bug11340}: #{diff} seconds to interrupt Process.wait"})
|
|
f.puts
|
|
end
|
|
end
|
|
|
|
def test_abort
|
|
with_tmpchdir do
|
|
s = run_in_child("abort")
|
|
assert_not_predicate(s, :success?)
|
|
write_file("test-script", "#{<<~"begin;"}\n#{<<~'end;'}")
|
|
begin;
|
|
STDERR.reopen(STDOUT)
|
|
begin
|
|
raise "[Bug #16424]"
|
|
rescue
|
|
abort
|
|
end
|
|
end;
|
|
assert_include(IO.popen([RUBY, "test-script"], &:read), "[Bug #16424]")
|
|
end
|
|
end
|
|
|
|
def test_sleep
|
|
assert_raise(ArgumentError) { sleep(1, 1) }
|
|
[-1, -1.0, -1r].each do |sec|
|
|
assert_raise_with_message(ArgumentError, /not.*negative/) { sleep(sec) }
|
|
end
|
|
end
|
|
|
|
def test_getpgid
|
|
assert_kind_of(Integer, Process.getpgid(Process.ppid))
|
|
rescue NotImplementedError
|
|
end
|
|
|
|
def test_getpriority
|
|
assert_kind_of(Integer, Process.getpriority(Process::PRIO_PROCESS, $$))
|
|
rescue NameError, NotImplementedError
|
|
end
|
|
|
|
def test_setpriority
|
|
if defined? Process::PRIO_USER
|
|
assert_nothing_raised do
|
|
pr = Process.getpriority(Process::PRIO_PROCESS, $$)
|
|
Process.setpriority(Process::PRIO_PROCESS, $$, pr)
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_getuid
|
|
assert_kind_of(Integer, Process.uid)
|
|
end
|
|
|
|
def test_groups
|
|
gs = Process.groups
|
|
assert_instance_of(Array, gs)
|
|
gs.each {|g| assert_kind_of(Integer, g) }
|
|
rescue NotImplementedError
|
|
end
|
|
|
|
def test_maxgroups
|
|
max = Process.maxgroups
|
|
rescue NotImplementedError
|
|
else
|
|
assert_kind_of(Integer, max)
|
|
assert_predicate(max, :positive?)
|
|
skip "not limited to NGROUPS_MAX" if /darwin/ =~ RUBY_PLATFORM
|
|
gs = Process.groups
|
|
assert_operator(gs.size, :<=, max)
|
|
gs[0] ||= 0
|
|
assert_raise(ArgumentError) {Process.groups = gs * (max / gs.size + 1)}
|
|
end
|
|
|
|
def test_geteuid
|
|
assert_kind_of(Integer, Process.euid)
|
|
end
|
|
|
|
def test_seteuid
|
|
assert_nothing_raised(TypeError) {Process.euid += 0}
|
|
rescue NotImplementedError
|
|
end
|
|
|
|
def test_seteuid_name
|
|
user = (Etc.getpwuid(Process.euid).name rescue ENV["USER"]) or return
|
|
assert_nothing_raised(TypeError) {Process.euid = user}
|
|
rescue NotImplementedError
|
|
end
|
|
|
|
def test_getegid
|
|
assert_kind_of(Integer, Process.egid)
|
|
end
|
|
|
|
def test_setegid
|
|
skip "root can use Process.egid on Android platform" if RUBY_PLATFORM =~ /android/
|
|
assert_nothing_raised(TypeError) {Process.egid += 0}
|
|
rescue NotImplementedError
|
|
end
|
|
|
|
if Process::UID.respond_to?(:from_name)
|
|
def test_uid_from_name
|
|
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}/) {
|
|
Process::UID.from_name("\u{4e0d 5b58 5728}")
|
|
}
|
|
end
|
|
end
|
|
|
|
if Process::GID.respond_to?(:from_name) && !RUBY_PLATFORM.include?("android")
|
|
def test_gid_from_name
|
|
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
|
|
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)
|
|
end
|
|
end
|
|
|
|
def test_uid_re_exchangeable_p
|
|
r = Process::UID.re_exchangeable?
|
|
assert_include([true, false], r)
|
|
end
|
|
|
|
def test_gid_re_exchangeable_p
|
|
r = Process::GID.re_exchangeable?
|
|
assert_include([true, false], r)
|
|
end
|
|
|
|
def test_uid_sid_available?
|
|
r = Process::UID.sid_available?
|
|
assert_include([true, false], r)
|
|
end
|
|
|
|
def test_gid_sid_available?
|
|
r = Process::GID.sid_available?
|
|
assert_include([true, false], r)
|
|
end
|
|
|
|
def test_pst_inspect
|
|
assert_nothing_raised { Process::Status.allocate.inspect }
|
|
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
|
|
signal_received << true
|
|
sig_w.write('?')
|
|
end
|
|
pid = nil
|
|
IO.pipe do |r, w|
|
|
pid = fork { r.read(1); exit }
|
|
Thread.start {
|
|
Thread.current.report_on_exception = false
|
|
raise
|
|
}
|
|
w.puts
|
|
end
|
|
Process.wait pid
|
|
assert_send [sig_r, :wait_readable, 5], 'self-pipe not readable'
|
|
end
|
|
if defined?(RubyVM::JIT) && RubyVM::JIT.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]"
|
|
end
|
|
rescue NotImplementedError, ArgumentError
|
|
ensure
|
|
begin
|
|
Signal.trap(:CHLD, 'DEFAULT')
|
|
rescue ArgumentError
|
|
end
|
|
end
|
|
|
|
def test_no_curdir
|
|
with_tmpchdir {|d|
|
|
Dir.mkdir("vd")
|
|
status = nil
|
|
Dir.chdir("vd") {
|
|
dir = "#{d}/vd"
|
|
# OpenSolaris cannot remove the current directory.
|
|
system(RUBY, "--disable-gems", "-e", "Dir.chdir '..'; Dir.rmdir #{dir.dump}", err: File::NULL)
|
|
system({"RUBYLIB"=>nil}, RUBY, "--disable-gems", "-e", "exit true")
|
|
status = $?
|
|
}
|
|
assert_predicate(status, :success?, "[ruby-dev:38105]")
|
|
}
|
|
end
|
|
|
|
def test_fallback_to_sh
|
|
feature = '[ruby-core:32745]'
|
|
with_tmpchdir do |d|
|
|
open("tmp_script.#{$$}", "w") {|f| f.puts ": ;"; f.chmod(0755)}
|
|
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)}
|
|
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)}
|
|
result = IO.popen([{"hghg" => "mogomogo"}, "./tmp_script.#{$$}", "a b", "c"]) {|f| f.read}
|
|
assert_equal("mogomogo\n", result, feature)
|
|
|
|
end
|
|
end if File.executable?("/bin/sh")
|
|
|
|
def test_spawn_too_long_path
|
|
bug4314 = '[ruby-core:34842]'
|
|
assert_fail_too_long_path(%w"echo", bug4314)
|
|
end
|
|
|
|
def test_aspawn_too_long_path
|
|
if /solaris/i =~ RUBY_PLATFORM && !defined?(Process::RLIMIT_NPROC)
|
|
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)
|
|
end
|
|
|
|
def assert_fail_too_long_path((cmd, sep), mesg)
|
|
sep ||= ""
|
|
min = 1_000 / (cmd.size + sep.size)
|
|
cmds = Array.new(min, cmd)
|
|
exs = [Errno::ENOENT]
|
|
exs << Errno::EINVAL if windows?
|
|
exs << Errno::E2BIG if defined?(Errno::E2BIG)
|
|
opts = {[STDOUT, STDERR]=>File::NULL}
|
|
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
|
|
cmds *= 100
|
|
end
|
|
rescue NoMemoryError
|
|
size = cmds.size
|
|
raise if min >= size - 1
|
|
min = [min, size /= 2].max
|
|
cmds[size..-1] = []
|
|
raise if size < 250
|
|
retry
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_system_sigpipe
|
|
return if windows?
|
|
|
|
pid = 0
|
|
|
|
with_tmpchdir do
|
|
assert_nothing_raised('[ruby-dev:12261]') do
|
|
EnvUtil.timeout(3) do
|
|
pid = spawn('yes | ls')
|
|
Process.waitpid pid
|
|
end
|
|
end
|
|
end
|
|
ensure
|
|
Process.kill(:KILL, pid) if (pid != 0) rescue false
|
|
end
|
|
|
|
if Process.respond_to?(:daemon)
|
|
def test_daemon_default
|
|
data = IO.popen("-", "r+") do |f|
|
|
break f.read if f
|
|
Process.daemon
|
|
puts "ng"
|
|
end
|
|
assert_equal("", data)
|
|
end
|
|
|
|
def test_daemon_noclose
|
|
data = IO.popen("-", "r+") do |f|
|
|
break f.read if f
|
|
Process.daemon(false, true)
|
|
puts "ok", Dir.pwd
|
|
end
|
|
assert_equal("ok\n/\n", data)
|
|
end
|
|
|
|
def test_daemon_nochdir_noclose
|
|
data = IO.popen("-", "r+") do |f|
|
|
break f.read if f
|
|
Process.daemon(true, true)
|
|
puts "ok", Dir.pwd
|
|
end
|
|
assert_equal("ok\n#{Dir.pwd}\n", data)
|
|
end
|
|
|
|
def test_daemon_readwrite
|
|
data = IO.popen("-", "r+") do |f|
|
|
if f
|
|
f.puts "ok?"
|
|
break f.read
|
|
end
|
|
Process.daemon(true, true)
|
|
puts STDIN.gets
|
|
end
|
|
assert_equal("ok?\n", data)
|
|
end
|
|
|
|
def test_daemon_pid
|
|
cpid, dpid = IO.popen("-", "r+") do |f|
|
|
break f.pid, Integer(f.read) if f
|
|
Process.daemon(false, true)
|
|
puts $$
|
|
end
|
|
assert_not_equal(cpid, dpid)
|
|
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|
|
|
break f.pid, f.readlines if f
|
|
Process.daemon(true, true)
|
|
puts Dir.entries("/proc/self/task") - %W[. ..]
|
|
end
|
|
bug4920 = '[ruby-dev:43873]'
|
|
assert_include(1..2, data.size, bug4920)
|
|
assert_not_include(data.map(&:to_i), pid)
|
|
end
|
|
else # darwin
|
|
def test_daemon_no_threads
|
|
data = EnvUtil.timeout(3) do
|
|
IO.popen("-") do |f|
|
|
break f.readlines.map(&:chomp) if f
|
|
th = Thread.start {sleep 3}
|
|
Process.daemon(true, true)
|
|
puts Thread.list.size, th.status.inspect
|
|
end
|
|
end
|
|
assert_equal(["1", "false"], data)
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_popen_cloexec
|
|
return unless defined? Fcntl::FD_CLOEXEC
|
|
IO.popen([RUBY, "-e", ""]) {|io|
|
|
assert_predicate(io, :close_on_exec?)
|
|
}
|
|
end
|
|
|
|
def test_popen_exit
|
|
bug11510 = '[ruby-core:70671] [Bug #11510]'
|
|
pid = nil
|
|
opt = {timeout: 10, stdout_filter: ->(s) {pid = s}}
|
|
if windows?
|
|
opt[:new_pgroup] = true
|
|
else
|
|
opt[:pgroup] = true
|
|
end
|
|
assert_ruby_status(["-", RUBY], <<-'end;', bug11510, **opt)
|
|
RUBY = ARGV[0]
|
|
th = Thread.start {
|
|
Thread.current.abort_on_exception = true
|
|
IO.popen([RUBY, "-esleep 15", err: [:child, :out]]) {|f|
|
|
STDOUT.puts f.pid
|
|
STDOUT.flush
|
|
sleep(2)
|
|
}
|
|
}
|
|
sleep(0.001) until th.stop?
|
|
end;
|
|
assert_match(/\A\d+\Z/, pid)
|
|
ensure
|
|
if pid
|
|
pid = pid.to_i
|
|
[:TERM, :KILL].each {|sig| Process.kill(sig, pid) rescue break}
|
|
end
|
|
end
|
|
|
|
def test_popen_reopen
|
|
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
|
|
begin;
|
|
io = File.open(IO::NULL)
|
|
io2 = io.dup
|
|
IO.popen("echo") {|f| io.reopen(f)}
|
|
io.reopen(io2)
|
|
end;
|
|
end
|
|
|
|
def test_execopts_new_pgroup
|
|
return unless windows?
|
|
|
|
assert_nothing_raised { system(*TRUECOMMAND, :new_pgroup=>true) }
|
|
assert_nothing_raised { system(*TRUECOMMAND, :new_pgroup=>false) }
|
|
assert_nothing_raised { spawn(*TRUECOMMAND, :new_pgroup=>true) }
|
|
assert_nothing_raised { IO.popen([*TRUECOMMAND, :new_pgroup=>true]) {} }
|
|
end
|
|
|
|
def test_execopts_uid
|
|
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|
|
|
if user
|
|
assert_nothing_raised(feature6975) do
|
|
begin
|
|
system(*TRUECOMMAND, uid: user, exception: true)
|
|
rescue Errno::EPERM, Errno::EACCES, NotImplementedError
|
|
end
|
|
end
|
|
end
|
|
|
|
assert_nothing_raised(feature6975) do
|
|
begin
|
|
system(*TRUECOMMAND, uid: uid, exception: true)
|
|
rescue Errno::EPERM, Errno::EACCES, NotImplementedError
|
|
end
|
|
end
|
|
|
|
assert_nothing_raised(feature6975) do
|
|
begin
|
|
u = IO.popen([RUBY, "-e", "print Process.uid", uid: user||uid], &:read)
|
|
assert_equal(uid.to_s, u, feature6975)
|
|
rescue Errno::EPERM, Errno::EACCES, NotImplementedError
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_execopts_gid
|
|
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|
|
|
g = Etc.getgrgid(g) rescue next
|
|
[g.name, g.gid]
|
|
end
|
|
groups.compact!
|
|
[30000, *groups].each do |group, gid|
|
|
assert_nothing_raised(feature6975) do
|
|
begin
|
|
system(*TRUECOMMAND, gid: group)
|
|
rescue Errno::EPERM, NotImplementedError
|
|
end
|
|
end
|
|
|
|
gid = "#{gid || group}"
|
|
assert_nothing_raised(feature6975) do
|
|
begin
|
|
g = IO.popen([RUBY, "-e", "print Process.gid", gid: group], &:read)
|
|
# AIX allows a non-root process to setgid to its supplementary group,
|
|
# while other UNIXes do not. (This might be AIX's violation of the POSIX standard.)
|
|
# However, Ruby does not allow a setgid'ed Ruby process to use the -e option.
|
|
# As a result, the Ruby process invoked by "IO.popen([RUBY, "-e", ..." above fails
|
|
# with a message like "no -e allowed while running setgid (SecurityError)" to stderr,
|
|
# the exis status is set to 1, and the variable "g" is set to an empty string.
|
|
# To conclude, on AIX, if the "gid" variable is a supplementary group,
|
|
# the assert_equal next can fail, so skip it.
|
|
assert_equal(gid, g, feature6975) unless $?.exitstatus == 1 && /aix/ =~ RUBY_PLATFORM && gid != Process.gid
|
|
rescue Errno::EPERM, NotImplementedError
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_sigpipe
|
|
system(RUBY, "-e", "")
|
|
with_pipe {|r, w|
|
|
r.close
|
|
assert_raise(Errno::EPIPE) { w.print "a" }
|
|
}
|
|
end
|
|
|
|
def test_sh_comment
|
|
IO.popen("echo a # fofoof") {|f|
|
|
assert_equal("a\n", f.read)
|
|
}
|
|
end if File.executable?("/bin/sh")
|
|
|
|
def test_sh_env
|
|
IO.popen("foofoo=barbar env") {|f|
|
|
lines = f.readlines
|
|
assert_operator(lines, :include?, "foofoo=barbar\n")
|
|
}
|
|
end if File.executable?("/bin/sh")
|
|
|
|
def test_sh_exec
|
|
IO.popen("exec echo exexexec") {|f|
|
|
assert_equal("exexexec\n", f.read)
|
|
}
|
|
end if File.executable?("/bin/sh")
|
|
|
|
def test_setsid
|
|
return unless Process.respond_to?(:setsid)
|
|
return unless Process.respond_to?(:getsid)
|
|
# OpenBSD and AIX don't allow Process::getsid(pid) when pid is in
|
|
# different session.
|
|
return if /openbsd|aix/ =~ RUBY_PLATFORM
|
|
|
|
IO.popen([RUBY, "-e", <<EOS]) do|io|
|
|
Marshal.dump(Process.getsid, STDOUT)
|
|
newsid = Process.setsid
|
|
Marshal.dump(newsid, STDOUT)
|
|
STDOUT.flush
|
|
# getsid() on MacOS X return ESRCH when target process is zombie
|
|
# even if it is valid process id.
|
|
sleep
|
|
EOS
|
|
begin
|
|
# test Process.getsid() w/o arg
|
|
assert_equal(Marshal.load(io), Process.getsid)
|
|
|
|
# test Process.setsid return value and Process::getsid(pid)
|
|
assert_equal(Marshal.load(io), Process.getsid(io.pid))
|
|
ensure
|
|
Process.kill(:KILL, io.pid) rescue nil
|
|
Process.wait(io.pid)
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_spawn_nonascii
|
|
bug1771 = '[ruby-core:24309] [Bug #1771]'
|
|
|
|
with_tmpchdir do
|
|
[
|
|
"\u{7d05 7389}",
|
|
"zuf\u{00E4}llige_\u{017E}lu\u{0165}ou\u{010D}k\u{00FD}_\u{10D2 10D0 10DB 10D4 10DD 10E0 10D4 10D1}_\u{0440 0430 0437 043B 043E 0433 0430}_\u{548C 65B0 52A0 5761 4EE5 53CA 4E1C}",
|
|
"c\u{1EE7}a",
|
|
].each do |name|
|
|
msg = "#{bug1771} #{name}"
|
|
exename = "./#{name}.exe"
|
|
FileUtils.cp(ENV["COMSPEC"], exename)
|
|
assert_equal(true, system("#{exename} /c exit"), msg)
|
|
system("#{exename} /c exit 12")
|
|
assert_equal(12, $?.exitstatus, msg)
|
|
_, status = Process.wait2(Process.spawn("#{exename} /c exit 42"))
|
|
assert_equal(42, status.exitstatus, msg)
|
|
assert_equal("ok\n", `#{exename} /c echo ok`, msg)
|
|
assert_equal("ok\n", IO.popen("#{exename} /c echo ok", &:read), msg)
|
|
assert_equal("ok\n", IO.popen(%W"#{exename} /c echo ok", &:read), msg)
|
|
File.binwrite("#{name}.txt", "ok")
|
|
assert_equal("ok", `type #{name}.txt`)
|
|
end
|
|
end
|
|
end if windows?
|
|
|
|
def test_exec_nonascii
|
|
bug12841 = '[ruby-dev:49838] [Bug #12841]'
|
|
|
|
[
|
|
"\u{7d05 7389}",
|
|
"zuf\u{00E4}llige_\u{017E}lu\u{0165}ou\u{010D}k\u{00FD}_\u{10D2 10D0 10DB 10D4 10DD 10E0 10D4 10D1}_\u{0440 0430 0437 043B 043E 0433 0430}_\u{548C 65B0 52A0 5761 4EE5 53CA 4E1C}",
|
|
"c\u{1EE7}a",
|
|
].each do |arg|
|
|
begin
|
|
arg = arg.encode(Encoding.find("locale"))
|
|
rescue
|
|
else
|
|
assert_in_out_err([], "#{<<-"begin;"}\n#{<<-"end;"}", [arg], [], bug12841)
|
|
begin;
|
|
arg = "#{arg.b}".force_encoding("#{arg.encoding.name}")
|
|
exec(ENV["COMSPEC"]||"cmd.exe", "/c", "echo", arg)
|
|
end;
|
|
end
|
|
end
|
|
end if windows?
|
|
|
|
def test_clock_gettime
|
|
t1 = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
|
|
t2 = Time.now; t2 = t2.tv_sec * 1000000000 + t2.tv_nsec
|
|
t3 = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
|
|
assert_operator(t1, :<=, t2)
|
|
assert_operator(t2, :<=, t3)
|
|
assert_raise(Errno::EINVAL) { Process.clock_gettime(:foo) }
|
|
end
|
|
|
|
def test_clock_gettime_unit
|
|
t0 = Time.now.to_f
|
|
[
|
|
[:nanosecond, 1_000_000_000],
|
|
[:microsecond, 1_000_000],
|
|
[:millisecond, 1_000],
|
|
[:second, 1],
|
|
[:float_microsecond, 1_000_000.0],
|
|
[:float_millisecond, 1_000.0],
|
|
[:float_second, 1.0],
|
|
[nil, 1.0],
|
|
[:foo],
|
|
].each do |unit, num|
|
|
unless num
|
|
assert_raise(ArgumentError){ Process.clock_gettime(Process::CLOCK_REALTIME, unit) }
|
|
next
|
|
end
|
|
t1 = Process.clock_gettime(Process::CLOCK_REALTIME, unit)
|
|
assert_kind_of num.integer? ? Integer : num.class, t1, [unit, num].inspect
|
|
assert_in_delta t0, t1/num, 1, [unit, num].inspect
|
|
end
|
|
end
|
|
|
|
def test_clock_gettime_constants
|
|
Process.constants.grep(/\ACLOCK_/).each {|n|
|
|
c = Process.const_get(n)
|
|
begin
|
|
t = Process.clock_gettime(c)
|
|
rescue Errno::EINVAL
|
|
next
|
|
end
|
|
assert_kind_of(Float, t, "Process.clock_gettime(Process::#{n})")
|
|
}
|
|
end
|
|
|
|
def test_clock_gettime_GETTIMEOFDAY_BASED_CLOCK_REALTIME
|
|
n = :GETTIMEOFDAY_BASED_CLOCK_REALTIME
|
|
begin
|
|
t = Process.clock_gettime(n)
|
|
rescue Errno::EINVAL
|
|
return
|
|
end
|
|
assert_kind_of(Float, t, "Process.clock_gettime(:#{n})")
|
|
end
|
|
|
|
def test_clock_gettime_TIME_BASED_CLOCK_REALTIME
|
|
n = :TIME_BASED_CLOCK_REALTIME
|
|
t = Process.clock_gettime(n)
|
|
assert_kind_of(Float, t, "Process.clock_gettime(:#{n})")
|
|
end
|
|
|
|
def test_clock_gettime_TIMES_BASED_CLOCK_MONOTONIC
|
|
n = :TIMES_BASED_CLOCK_MONOTONIC
|
|
begin
|
|
t = Process.clock_gettime(n)
|
|
rescue Errno::EINVAL
|
|
return
|
|
end
|
|
assert_kind_of(Float, t, "Process.clock_gettime(:#{n})")
|
|
end
|
|
|
|
def test_clock_gettime_GETRUSAGE_BASED_CLOCK_PROCESS_CPUTIME_ID
|
|
n = :GETRUSAGE_BASED_CLOCK_PROCESS_CPUTIME_ID
|
|
begin
|
|
t = Process.clock_gettime(n)
|
|
rescue Errno::EINVAL
|
|
return
|
|
end
|
|
assert_kind_of(Float, t, "Process.clock_gettime(:#{n})")
|
|
end
|
|
|
|
def test_clock_gettime_TIMES_BASED_CLOCK_PROCESS_CPUTIME_ID
|
|
n = :TIMES_BASED_CLOCK_PROCESS_CPUTIME_ID
|
|
begin
|
|
t = Process.clock_gettime(n)
|
|
rescue Errno::EINVAL
|
|
return
|
|
end
|
|
assert_kind_of(Float, t, "Process.clock_gettime(:#{n})")
|
|
end
|
|
|
|
def test_clock_gettime_CLOCK_BASED_CLOCK_PROCESS_CPUTIME_ID
|
|
n = :CLOCK_BASED_CLOCK_PROCESS_CPUTIME_ID
|
|
t = Process.clock_gettime(n)
|
|
assert_kind_of(Float, t, "Process.clock_gettime(:#{n})")
|
|
end
|
|
|
|
def test_clock_gettime_MACH_ABSOLUTE_TIME_BASED_CLOCK_MONOTONIC
|
|
n = :MACH_ABSOLUTE_TIME_BASED_CLOCK_MONOTONIC
|
|
begin
|
|
t = Process.clock_gettime(n)
|
|
rescue Errno::EINVAL
|
|
return
|
|
end
|
|
assert_kind_of(Float, t, "Process.clock_gettime(:#{n})")
|
|
end
|
|
|
|
def test_clock_getres
|
|
r = Process.clock_getres(Process::CLOCK_REALTIME, :nanosecond)
|
|
rescue Errno::EINVAL
|
|
else
|
|
assert_kind_of(Integer, r)
|
|
assert_raise(Errno::EINVAL) { Process.clock_getres(:foo) }
|
|
end
|
|
|
|
def test_clock_getres_constants
|
|
Process.constants.grep(/\ACLOCK_/).each {|n|
|
|
c = Process.const_get(n)
|
|
begin
|
|
t = Process.clock_getres(c)
|
|
rescue Errno::EINVAL
|
|
next
|
|
end
|
|
assert_kind_of(Float, t, "Process.clock_getres(Process::#{n})")
|
|
}
|
|
end
|
|
|
|
def test_clock_getres_GETTIMEOFDAY_BASED_CLOCK_REALTIME
|
|
n = :GETTIMEOFDAY_BASED_CLOCK_REALTIME
|
|
begin
|
|
t = Process.clock_getres(n)
|
|
rescue Errno::EINVAL
|
|
return
|
|
end
|
|
assert_kind_of(Float, t, "Process.clock_getres(:#{n})")
|
|
assert_equal(1000, Process.clock_getres(n, :nanosecond))
|
|
end
|
|
|
|
def test_clock_getres_TIME_BASED_CLOCK_REALTIME
|
|
n = :TIME_BASED_CLOCK_REALTIME
|
|
t = Process.clock_getres(n)
|
|
assert_kind_of(Float, t, "Process.clock_getres(:#{n})")
|
|
assert_equal(1000000000, Process.clock_getres(n, :nanosecond))
|
|
end
|
|
|
|
def test_clock_getres_TIMES_BASED_CLOCK_MONOTONIC
|
|
n = :TIMES_BASED_CLOCK_MONOTONIC
|
|
begin
|
|
t = Process.clock_getres(n)
|
|
rescue Errno::EINVAL
|
|
return
|
|
end
|
|
assert_kind_of(Float, t, "Process.clock_getres(:#{n})")
|
|
f = Process.clock_getres(n, :hertz)
|
|
assert_equal(0, f - f.floor)
|
|
end
|
|
|
|
def test_clock_getres_GETRUSAGE_BASED_CLOCK_PROCESS_CPUTIME_ID
|
|
n = :GETRUSAGE_BASED_CLOCK_PROCESS_CPUTIME_ID
|
|
begin
|
|
t = Process.clock_getres(n)
|
|
rescue Errno::EINVAL
|
|
return
|
|
end
|
|
assert_kind_of(Float, t, "Process.clock_getres(:#{n})")
|
|
assert_equal(1000, Process.clock_getres(n, :nanosecond))
|
|
end
|
|
|
|
def test_clock_getres_TIMES_BASED_CLOCK_PROCESS_CPUTIME_ID
|
|
n = :TIMES_BASED_CLOCK_PROCESS_CPUTIME_ID
|
|
begin
|
|
t = Process.clock_getres(n)
|
|
rescue Errno::EINVAL
|
|
return
|
|
end
|
|
assert_kind_of(Float, t, "Process.clock_getres(:#{n})")
|
|
f = Process.clock_getres(n, :hertz)
|
|
assert_equal(0, f - f.floor)
|
|
end
|
|
|
|
def test_clock_getres_CLOCK_BASED_CLOCK_PROCESS_CPUTIME_ID
|
|
n = :CLOCK_BASED_CLOCK_PROCESS_CPUTIME_ID
|
|
t = Process.clock_getres(n)
|
|
assert_kind_of(Float, t, "Process.clock_getres(:#{n})")
|
|
f = Process.clock_getres(n, :hertz)
|
|
assert_equal(0, f - f.floor)
|
|
end
|
|
|
|
def test_clock_getres_MACH_ABSOLUTE_TIME_BASED_CLOCK_MONOTONIC
|
|
n = :MACH_ABSOLUTE_TIME_BASED_CLOCK_MONOTONIC
|
|
begin
|
|
t = Process.clock_getres(n)
|
|
rescue Errno::EINVAL
|
|
return
|
|
end
|
|
assert_kind_of(Float, t, "Process.clock_getres(:#{n})")
|
|
end
|
|
|
|
def test_deadlock_by_signal_at_forking
|
|
assert_separately(%W(--disable=gems - #{RUBY}), <<-INPUT, timeout: 100)
|
|
ruby = ARGV.shift
|
|
GC.start # reduce garbage
|
|
GC.disable # avoid triggering CoW after forks
|
|
trap(:QUIT) {}
|
|
parent = $$
|
|
100.times do |i|
|
|
pid = fork {Process.kill(:QUIT, parent)}
|
|
IO.popen([ruby, -'--disable=gems'], -'r+'){}
|
|
Process.wait(pid)
|
|
end
|
|
INPUT
|
|
end if defined?(fork)
|
|
|
|
def test_process_detach
|
|
pid = fork {}
|
|
th = Process.detach(pid)
|
|
assert_equal pid, th.pid
|
|
status = th.value
|
|
assert_predicate status, :success?
|
|
end if defined?(fork)
|
|
|
|
def test_kill_at_spawn_failure
|
|
bug11166 = '[ruby-core:69304] [Bug #11166]'
|
|
th = nil
|
|
x = with_tmpchdir {|d|
|
|
prog = "#{d}/notexist"
|
|
q = Thread::Queue.new
|
|
th = Thread.start {system(prog);q.push(nil);sleep}
|
|
q.pop
|
|
th.kill
|
|
th.join(0.1)
|
|
}
|
|
assert_equal(th, x, bug11166)
|
|
end if defined?(fork)
|
|
|
|
def test_exec_fd_3_redirect
|
|
# ensure we can redirect anything to fd=3 in a child process.
|
|
# fd=3 is a commonly reserved FD for the timer thread pipe in the
|
|
# parent, but fd=3 is the first FD used by the sd_listen_fds function
|
|
# for systemd
|
|
assert_separately(['-', RUBY], <<-INPUT, timeout: 60)
|
|
ruby = ARGV.shift
|
|
begin
|
|
a = IO.pipe
|
|
b = IO.pipe
|
|
pid = fork do
|
|
exec ruby, '-e', 'print IO.for_fd(3).read(1)', 3 => a[0], 1 => b[1]
|
|
end
|
|
b[1].close
|
|
a[0].close
|
|
a[1].write('.')
|
|
assert_equal ".", b[0].read(1)
|
|
ensure
|
|
Process.wait(pid) if pid
|
|
a.each(&:close) if a
|
|
b.each(&:close) if b
|
|
end
|
|
INPUT
|
|
end if defined?(fork)
|
|
|
|
def test_exec_close_reserved_fd
|
|
cmd = ".#{File::ALT_SEPARATOR || File::SEPARATOR}bug11353"
|
|
with_tmpchdir {
|
|
(3..6).each do |i|
|
|
ret = run_in_child(<<-INPUT)
|
|
begin
|
|
$VERBOSE = nil
|
|
Process.exec('#{cmd}', 'dummy', #{i} => :close)
|
|
rescue SystemCallError
|
|
end
|
|
INPUT
|
|
assert_equal(0, ret)
|
|
end
|
|
}
|
|
end
|
|
|
|
def test_signals_work_after_exec_fail
|
|
r, w = IO.pipe
|
|
pid = status = nil
|
|
EnvUtil.timeout(30) do
|
|
pid = fork do
|
|
r.close
|
|
begin
|
|
trap(:USR1) { w.syswrite("USR1\n"); exit 0 }
|
|
exec "/path/to/non/existent/#$$/#{rand}.ex"
|
|
rescue SystemCallError
|
|
w.syswrite("exec failed\n")
|
|
end
|
|
sleep
|
|
exit 1
|
|
end
|
|
w.close
|
|
assert_equal "exec failed\n", r.gets
|
|
Process.kill(:USR1, pid)
|
|
assert_equal "USR1\n", r.gets
|
|
assert_nil r.gets
|
|
_, status = Process.waitpid2(pid)
|
|
end
|
|
assert_predicate status, :success?
|
|
rescue Timeout::Error
|
|
begin
|
|
Process.kill(:KILL, pid)
|
|
rescue Errno::ESRCH
|
|
end
|
|
raise
|
|
ensure
|
|
w.close if w
|
|
r.close if r
|
|
end if defined?(fork)
|
|
|
|
def test_threading_works_after_exec_fail
|
|
r, w = IO.pipe
|
|
pid = status = nil
|
|
EnvUtil.timeout(90) do
|
|
pid = fork do
|
|
r.close
|
|
begin
|
|
exec "/path/to/non/existent/#$$/#{rand}.ex"
|
|
rescue SystemCallError
|
|
w.syswrite("exec failed\n")
|
|
end
|
|
q = Queue.new
|
|
th1 = Thread.new { i = 0; i += 1 while q.empty?; i }
|
|
th2 = Thread.new { j = 0; j += 1 while q.empty? && Thread.pass.nil?; j }
|
|
sleep 0.5
|
|
q << true
|
|
w.syswrite "#{th1.value} #{th2.value}\n"
|
|
end
|
|
w.close
|
|
assert_equal "exec failed\n", r.gets
|
|
vals = r.gets.split.map!(&:to_i)
|
|
assert_operator vals[0], :>, vals[1], vals.inspect
|
|
_, status = Process.waitpid2(pid)
|
|
end
|
|
assert_predicate status, :success?
|
|
rescue Timeout::Error
|
|
begin
|
|
Process.kill(:KILL, pid)
|
|
rescue Errno::ESRCH
|
|
end
|
|
raise
|
|
ensure
|
|
w.close if w
|
|
r.close if r
|
|
end if defined?(fork)
|
|
|
|
def test_rescue_exec_fail
|
|
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
|
|
begin;
|
|
assert_raise(Errno::ENOENT) do
|
|
exec("", in: "")
|
|
end
|
|
end;
|
|
end
|
|
|
|
def test_many_args
|
|
bug11418 = '[ruby-core:70251] [Bug #11418]'
|
|
assert_in_out_err([], <<-"end;", ["x"]*256, [], bug11418, timeout: 60)
|
|
bin = "#{EnvUtil.rubybin}"
|
|
args = Array.new(256) {"x"}
|
|
GC.stress = true
|
|
system(bin, "--disable=gems", "-w", "-e", "puts ARGV", *args)
|
|
end;
|
|
end
|
|
|
|
def test_to_hash_on_arguments
|
|
all_assertions do |a|
|
|
%w[Array String].each do |type|
|
|
a.for(type) do
|
|
assert_separately(['-', EnvUtil.rubybin], <<~"END;")
|
|
class #{type}
|
|
def to_hash
|
|
raise "[Bug-12355]: #{type}#to_hash is called"
|
|
end
|
|
end
|
|
ex = ARGV[0]
|
|
assert_equal(true, system([ex, ex], "-e", ""))
|
|
END;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_forked_child_handles_signal
|
|
skip "fork not supported" unless Process.respond_to?(:fork)
|
|
assert_normal_exit(<<-"end;", '[ruby-core:82883] [Bug #13916]')
|
|
require 'timeout'
|
|
pid = fork { sleep }
|
|
Process.kill(:TERM, pid)
|
|
assert_equal pid, Timeout.timeout(30) { Process.wait(pid) }
|
|
end;
|
|
end
|
|
|
|
if Process.respond_to?(:initgroups)
|
|
def test_initgroups
|
|
assert_raise(ArgumentError) do
|
|
Process.initgroups("\0", 0)
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_last_status
|
|
Process.wait spawn(RUBY, "-e", "exit 13")
|
|
assert_same(Process.last_status, $?)
|
|
end
|
|
|
|
def test_last_status_failure
|
|
assert_nil system("sad")
|
|
assert_not_predicate $?, :success?
|
|
assert_equal $?.exitstatus, 127
|
|
end
|
|
|
|
def test_exec_failure_leaves_no_child
|
|
assert_raise(Errno::ENOENT) do
|
|
spawn('inexistent_command')
|
|
end
|
|
assert_empty(Process.waitall)
|
|
end
|
|
end
|