mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
93bb48995c
This is accepted in the meeting: https://bugs.ruby-lang.org/projects/ruby/wiki/DevelopersMeeting20130809 This method is accepted as a CRuby feature. I.e. Other Ruby implementations don't need to implement it. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@42504 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
1672 lines
49 KiB
Ruby
1672 lines
49 KiB
Ruby
require 'test/unit'
|
|
require 'tempfile'
|
|
require 'timeout'
|
|
require_relative 'envutil'
|
|
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"
|
|
# if limit=0, this test freeze pn OpenBSD
|
|
limit = /openbsd/ =~ RUBY_PLATFORM ? 1 : 0
|
|
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") }
|
|
end
|
|
|
|
def test_rlimit_value
|
|
return unless rlimit_exist?
|
|
assert_raise(ArgumentError) { Process.setrlimit(:CORE, :FOO) }
|
|
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_equal(0, s.exitstatus)
|
|
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_equal(0, s.exitstatus)
|
|
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) }
|
|
assert_raise(Errno::EPERM) { Process.wait spawn(*TRUECOMMAND, :pgroup=>2) }
|
|
|
|
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)
|
|
}
|
|
end
|
|
|
|
MANDATORY_ENVS = %w[RUBYLIB]
|
|
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
|
|
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
|
|
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(Errno::ENOENT) {
|
|
Process.wait Process.spawn(*PWD, :chdir => "d/notexist")
|
|
}
|
|
}
|
|
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
|
|
|
|
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
|
|
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(!File.read("out").empty?) # 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"))
|
|
|
|
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
|
|
}
|
|
end
|
|
|
|
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_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) }
|
|
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| }
|
|
}
|
|
skip "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
|
|
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')"])
|
|
w.close
|
|
errmsg = io.read
|
|
assert_equal("", r.read)
|
|
assert_not_equal("", errmsg)
|
|
Process.wait
|
|
}
|
|
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])
|
|
w.close
|
|
errmsg = io.read
|
|
assert_equal("", r.read)
|
|
assert_not_equal("", errmsg)
|
|
Process.wait
|
|
}
|
|
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])
|
|
w.close
|
|
errmsg = io.read
|
|
assert_equal("mo\n", r.read)
|
|
assert_equal("", errmsg)
|
|
Process.wait
|
|
}
|
|
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])
|
|
w.close
|
|
errmsg = io.read
|
|
assert_equal("mo\n", r.read)
|
|
assert_equal("", errmsg)
|
|
Process.wait
|
|
}
|
|
|
|
}
|
|
end
|
|
|
|
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
|
|
|
|
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(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(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(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(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(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(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(status.exited?)
|
|
assert(!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(status.exited?)
|
|
assert(status.success?)
|
|
assert_equal(%["foo 'bar'"\n], File.read("out"), '[ruby-core:22960]')
|
|
pid = spawn(%[#{bat.dump} "foo 'bar'"])
|
|
Process.wait pid
|
|
status = $?
|
|
assert(status.exited?)
|
|
assert(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(status.exited?)
|
|
assert(!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(status.exited?)
|
|
assert(!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"]).read)
|
|
Process.wait
|
|
|
|
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($?.success?)
|
|
with_stdin("f") { Process.wait spawn([RUBY, "rfv"]) }
|
|
assert(!$?.success?)
|
|
|
|
with_stdin("t") { IO.popen([[RUBY, "tgb"]]) {|io| assert_equal("", io.read) } }
|
|
assert($?.success?)
|
|
with_stdin("f") { IO.popen([[RUBY, "yhn"]]) {|io| assert_equal("", io.read) } }
|
|
assert(!$?.success?)
|
|
|
|
status = run_in_child "STDIN.reopen('t'); exec([#{RUBY.dump}, 'ujm'])"
|
|
assert(status.success?)
|
|
status = run_in_child "STDIN.reopen('f'); exec([#{RUBY.dump}, 'ik,'])"
|
|
assert(!status.success?)
|
|
}
|
|
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)
|
|
end
|
|
end
|
|
|
|
def test_status_kill
|
|
return unless Process.respond_to?(:kill)
|
|
return unless Signal.list.include?("QUIT")
|
|
|
|
with_tmpchdir do
|
|
write_file("foo", "sleep 30")
|
|
pid = spawn(RUBY, "foo")
|
|
Thread.new { sleep 1; Process.kill(:SIGQUIT, pid) }
|
|
Process.wait(pid)
|
|
s = $?
|
|
assert_equal([false, true, false],
|
|
[s.exited?, s.signaled?, s.stopped?],
|
|
"[s.exited?, s.signaled?, s.stopped?]")
|
|
assert_send(
|
|
[["#<Process::Status: pid #{ s.pid } SIGQUIT (signal #{ s.termsig })>",
|
|
"#<Process::Status: pid #{ s.pid } SIGQUIT (signal #{ s.termsig }) (core dumped)>"],
|
|
:include?,
|
|
s.inspect])
|
|
assert_equal(false, s.exited?)
|
|
assert_equal(nil, s.success?)
|
|
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_abort
|
|
with_tmpchdir do
|
|
s = run_in_child("abort")
|
|
assert_not_equal(0, s.exitstatus)
|
|
end
|
|
end
|
|
|
|
def test_sleep
|
|
assert_raise(ArgumentError) { sleep(1, 1) }
|
|
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
|
|
assert_kind_of(Integer, Process.maxgroups)
|
|
rescue NotImplementedError
|
|
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 = 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
|
|
assert_nothing_raised(TypeError) {Process.egid += 0}
|
|
rescue NotImplementedError
|
|
end
|
|
|
|
def test_uid_re_exchangeable_p
|
|
r = Process::UID.re_exchangeable?
|
|
assert(true == r || false == r)
|
|
end
|
|
|
|
def test_gid_re_exchangeable_p
|
|
r = Process::GID.re_exchangeable?
|
|
assert(true == r || false == r)
|
|
end
|
|
|
|
def test_uid_sid_available?
|
|
r = Process::UID.sid_available?
|
|
assert(true == r || false == r)
|
|
end
|
|
|
|
def test_gid_sid_available?
|
|
r = Process::GID.sid_available?
|
|
assert(true == r || 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 = []
|
|
Signal.trap(:CHLD) { signal_received << true }
|
|
pid = fork { sleep 0.1; exit }
|
|
Thread.start { raise }
|
|
Process.wait pid
|
|
sleep 0.1
|
|
assert_equal [true], signal_received, " [ruby-core:19744]"
|
|
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(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
|
|
bug4315 = '[ruby-core:34833]'
|
|
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::E2BIG if defined?(Errno::E2BIG)
|
|
EnvUtil.suppress_warning do
|
|
assert_raise(*exs, mesg) do
|
|
begin
|
|
loop do
|
|
Process.spawn(cmds.join(sep), [STDOUT, STDERR]=>File::NULL)
|
|
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
|
|
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_equal(2, data.size, bug4920)
|
|
assert_not_include(data.map(&:to_i), pid)
|
|
end
|
|
else # darwin
|
|
def test_daemon_no_threads
|
|
data = Timeout.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(io.close_on_exec?)
|
|
}
|
|
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
|
|
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)
|
|
rescue Errno::EPERM, NotImplementedError
|
|
end
|
|
end
|
|
end
|
|
|
|
assert_nothing_raised(feature6975) do
|
|
begin
|
|
system(*TRUECOMMAND, uid: uid)
|
|
rescue Errno::EPERM, 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, NotImplementedError
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_execopts_gid
|
|
skip "Process.groups not implemented on Windows platform" if windows?
|
|
feature6975 = '[ruby-core:47414]'
|
|
|
|
[30000, *Process.groups.map {|g| g = Etc.getgrgid(g); [g.name, g.gid]}].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)
|
|
assert_equal(gid, g, feature6975)
|
|
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 doesn't allow Process::getsid(pid) when pid is in
|
|
# different session.
|
|
return if /openbsd/ =~ 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_clock_gettime
|
|
t1 = Process.clock_gettime(Process::CLOCK_REALTIME, :nanoseconds)
|
|
t2 = Time.now; t2 = t2.tv_sec * 1000000000 + t2.tv_nsec
|
|
t3 = Process.clock_gettime(Process::CLOCK_REALTIME, :nanoseconds)
|
|
assert_operator(t1, :<=, t2)
|
|
assert_operator(t2, :<=, t3)
|
|
assert_raise(Errno::EINVAL) { Process.clock_gettime(:foo) }
|
|
end
|
|
|
|
end
|