1require 'test/unit'
2require 'tempfile'
3require 'pathname'
4require 'timeout'
5require_relative 'envutil'
6require 'rbconfig'
7
8class TestProcess < Test::Unit::TestCase
9  RUBY = EnvUtil.rubybin
10
11  def setup
12    Process.waitall
13  end
14
15  def teardown
16    Process.waitall
17  end
18
19  def windows?
20    return /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
21  end
22
23  def write_file(filename, content)
24    File.open(filename, "w") {|f|
25      f << content
26    }
27  end
28
29  def with_tmpchdir
30    Dir.mktmpdir {|d|
31      d = Pathname.new(d).realpath.to_s
32      Dir.chdir(d) {
33        yield d
34      }
35    }
36  end
37
38  def run_in_child(str) # should be called in a temporary directory
39    write_file("test-script", str)
40    Process.wait spawn(RUBY, "test-script")
41    $?
42  end
43
44  def test_rlimit_availability
45    begin
46      Process.getrlimit(nil)
47    rescue NotImplementedError
48      assert_raise(NotImplementedError) { Process.setrlimit }
49    rescue TypeError
50      assert_raise(ArgumentError) { Process.setrlimit }
51    end
52  end
53
54  def rlimit_exist?
55    Process.getrlimit(nil)
56  rescue NotImplementedError
57    return false
58  rescue TypeError
59    return true
60  end
61
62  def test_rlimit_nofile
63    return unless rlimit_exist?
64    with_tmpchdir {
65      write_file 's', <<-"End"
66	# if limit=0, this test freeze pn OpenBSD
67	limit = /openbsd/ =~ RUBY_PLATFORM ? 1 : 0
68	result = 1
69	begin
70	  Process.setrlimit(Process::RLIMIT_NOFILE, limit)
71	rescue Errno::EINVAL
72	  result = 0
73	end
74	if result == 1
75	  begin
76	    IO.pipe
77	  rescue Errno::EMFILE
78	   result = 0
79	  end
80	end
81	exit result
82      End
83      pid = spawn RUBY, "s"
84      Process.wait pid
85      assert_equal(0, $?.to_i, "#{$?}")
86    }
87  end
88
89  def test_rlimit_name
90    return unless rlimit_exist?
91    [
92      :AS, "AS",
93      :CORE, "CORE",
94      :CPU, "CPU",
95      :DATA, "DATA",
96      :FSIZE, "FSIZE",
97      :MEMLOCK, "MEMLOCK",
98      :MSGQUEUE, "MSGQUEUE",
99      :NICE, "NICE",
100      :NOFILE, "NOFILE",
101      :NPROC, "NPROC",
102      :RSS, "RSS",
103      :RTPRIO, "RTPRIO",
104      :RTTIME, "RTTIME",
105      :SBSIZE, "SBSIZE",
106      :SIGPENDING, "SIGPENDING",
107      :STACK, "STACK",
108    ].each {|name|
109      if Process.const_defined? "RLIMIT_#{name}"
110        assert_nothing_raised { Process.getrlimit(name) }
111      else
112        assert_raise(ArgumentError) { Process.getrlimit(name) }
113      end
114    }
115    assert_raise(ArgumentError) { Process.getrlimit(:FOO) }
116    assert_raise(ArgumentError) { Process.getrlimit("FOO") }
117  end
118
119  def test_rlimit_value
120    return unless rlimit_exist?
121    assert_raise(ArgumentError) { Process.setrlimit(:CORE, :FOO) }
122    with_tmpchdir do
123      s = run_in_child(<<-'End')
124        cur, max = Process.getrlimit(:NOFILE)
125        Process.setrlimit(:NOFILE, [max-10, cur].min)
126        begin
127          Process.setrlimit(:NOFILE, :INFINITY)
128        rescue Errno::EPERM
129          exit false
130        end
131      End
132      assert_not_equal(0, s.exitstatus)
133      s = run_in_child(<<-'End')
134        cur, max = Process.getrlimit(:NOFILE)
135        Process.setrlimit(:NOFILE, [max-10, cur].min)
136        begin
137          Process.setrlimit(:NOFILE, "INFINITY")
138        rescue Errno::EPERM
139          exit false
140        end
141      End
142      assert_not_equal(0, s.exitstatus)
143    end
144  end
145
146  TRUECOMMAND = [RUBY, '-e', '']
147
148  def test_execopts_opts
149    assert_nothing_raised {
150      Process.wait Process.spawn(*TRUECOMMAND, {})
151    }
152    assert_raise(ArgumentError) {
153      Process.wait Process.spawn(*TRUECOMMAND, :foo => 100)
154    }
155    assert_raise(ArgumentError) {
156      Process.wait Process.spawn(*TRUECOMMAND, Process => 100)
157    }
158  end
159
160  def test_execopts_pgroup
161    skip "system(:pgroup) is not supported" if windows?
162    assert_nothing_raised { system(*TRUECOMMAND, :pgroup=>false) }
163
164    io = IO.popen([RUBY, "-e", "print Process.getpgrp"])
165    assert_equal(Process.getpgrp.to_s, io.read)
166    io.close
167
168    io = IO.popen([RUBY, "-e", "print Process.getpgrp", :pgroup=>true])
169    assert_equal(io.pid.to_s, io.read)
170    io.close
171
172    assert_raise(ArgumentError) { system(*TRUECOMMAND, :pgroup=>-1) }
173    assert_raise(Errno::EPERM) { Process.wait spawn(*TRUECOMMAND, :pgroup=>2) }
174
175    io1 = IO.popen([RUBY, "-e", "print Process.getpgrp", :pgroup=>true])
176    io2 = IO.popen([RUBY, "-e", "print Process.getpgrp", :pgroup=>io1.pid])
177    assert_equal(io1.pid.to_s, io1.read)
178    assert_equal(io1.pid.to_s, io2.read)
179    Process.wait io1.pid
180    Process.wait io2.pid
181    io1.close
182    io2.close
183  end
184
185  def test_execopts_rlimit
186    return unless rlimit_exist?
187    assert_raise(ArgumentError) { system(*TRUECOMMAND, :rlimit_foo=>0) }
188    assert_raise(ArgumentError) { system(*TRUECOMMAND, :rlimit_NOFILE=>0) }
189    assert_raise(ArgumentError) { system(*TRUECOMMAND, :rlimit_nofile=>[]) }
190    assert_raise(ArgumentError) { system(*TRUECOMMAND, :rlimit_nofile=>[1,2,3]) }
191
192    max = Process.getrlimit(:CORE).last
193
194    n = max
195    IO.popen([RUBY, "-e",
196             "p Process.getrlimit(:CORE)", :rlimit_core=>n]) {|io|
197      assert_equal("[#{n}, #{n}]\n", io.read)
198    }
199
200    n = 0
201    IO.popen([RUBY, "-e",
202             "p Process.getrlimit(:CORE)", :rlimit_core=>n]) {|io|
203      assert_equal("[#{n}, #{n}]\n", io.read)
204    }
205
206    n = max
207    IO.popen([RUBY, "-e",
208             "p Process.getrlimit(:CORE)", :rlimit_core=>[n]]) {|io|
209      assert_equal("[#{n}, #{n}]", io.read.chomp)
210    }
211
212    m, n = 0, max
213    IO.popen([RUBY, "-e",
214             "p Process.getrlimit(:CORE)", :rlimit_core=>[m,n]]) {|io|
215      assert_equal("[#{m}, #{n}]", io.read.chomp)
216    }
217
218    m, n = 0, 0
219    IO.popen([RUBY, "-e",
220             "p Process.getrlimit(:CORE)", :rlimit_core=>[m,n]]) {|io|
221      assert_equal("[#{m}, #{n}]", io.read.chomp)
222    }
223
224    n = max
225    IO.popen([RUBY, "-e",
226      "p Process.getrlimit(:CORE), Process.getrlimit(:CPU)",
227      :rlimit_core=>n, :rlimit_cpu=>3600]) {|io|
228      assert_equal("[#{n}, #{n}]\n[3600, 3600]", io.read.chomp)
229    }
230  end
231
232  MANDATORY_ENVS = %w[RUBYLIB]
233  case RbConfig::CONFIG['target_os']
234  when /linux/
235    MANDATORY_ENVS << 'LD_PRELOAD'
236  when /mswin|mingw/
237    MANDATORY_ENVS.concat(%w[HOME USER TMPDIR])
238  end
239  if e = RbConfig::CONFIG['LIBPATHENV']
240    MANDATORY_ENVS << e
241  end
242  PREENVARG = ['-e', "%w[#{MANDATORY_ENVS.join(' ')}].each{|e|ENV.delete(e)}"]
243  ENVARG = ['-e', 'ENV.each {|k,v| puts "#{k}=#{v}" }']
244  ENVCOMMAND = [RUBY].concat(PREENVARG).concat(ENVARG)
245
246  def test_execopts_env
247    assert_raise(ArgumentError) {
248      system({"F=O"=>"BAR"}, *TRUECOMMAND)
249    }
250
251    with_tmpchdir {|d|
252      prog = "#{d}/notexist"
253      e = assert_raise(Errno::ENOENT) {
254        Process.wait Process.spawn({"FOO"=>"BAR"}, prog)
255      }
256      assert_equal(prog, e.message.sub(/.* - /, ''))
257      e = assert_raise(Errno::ENOENT) {
258        Process.wait Process.spawn({"FOO"=>"BAR"}, [prog, "blar"])
259      }
260      assert_equal(prog, e.message.sub(/.* - /, ''))
261    }
262    h = {}
263    cmd = [h, RUBY]
264    (ENV.keys + MANDATORY_ENVS).each do |k|
265      case k
266      when /\APATH\z/i
267      when *MANDATORY_ENVS
268        cmd << '-e' << "ENV.delete('#{k}')"
269      else
270        h[k] = nil
271      end
272    end
273    cmd << '-e' << 'puts ENV.keys.map{|e|e.upcase}'
274    IO.popen(cmd) {|io|
275      assert_equal("PATH\n", io.read)
276    }
277
278    IO.popen([{"FOO"=>"BAR"}, *ENVCOMMAND]) {|io|
279      assert_match(/^FOO=BAR$/, io.read)
280    }
281
282    with_tmpchdir {|d|
283      system({"fofo"=>"haha"}, *ENVCOMMAND, STDOUT=>"out")
284      assert_match(/^fofo=haha$/, File.read("out").chomp)
285    }
286
287    old = ENV["hmm"]
288    begin
289      ENV["hmm"] = "fufu"
290      IO.popen(ENVCOMMAND) {|io| assert_match(/^hmm=fufu$/, io.read) }
291      IO.popen([{"hmm"=>""}, *ENVCOMMAND]) {|io| assert_match(/^hmm=$/, io.read) }
292      IO.popen([{"hmm"=>nil}, *ENVCOMMAND]) {|io| assert_not_match(/^hmm=/, io.read) }
293      ENV["hmm"] = ""
294      IO.popen(ENVCOMMAND) {|io| assert_match(/^hmm=$/, io.read) }
295      IO.popen([{"hmm"=>""}, *ENVCOMMAND]) {|io| assert_match(/^hmm=$/, io.read) }
296      IO.popen([{"hmm"=>nil}, *ENVCOMMAND]) {|io| assert_not_match(/^hmm=/, io.read) }
297      ENV["hmm"] = nil
298      IO.popen(ENVCOMMAND) {|io| assert_not_match(/^hmm=/, io.read) }
299      IO.popen([{"hmm"=>""}, *ENVCOMMAND]) {|io| assert_match(/^hmm=$/, io.read) }
300      IO.popen([{"hmm"=>nil}, *ENVCOMMAND]) {|io| assert_not_match(/^hmm=/, io.read) }
301    ensure
302      ENV["hmm"] = old
303    end
304  end
305
306  def _test_execopts_env_popen(cmd)
307    message = cmd.inspect
308    IO.popen({"FOO"=>"BAR"}, cmd) {|io|
309      assert_equal('FOO=BAR', io.read[/^FOO=.*/], message)
310    }
311
312    old = ENV["hmm"]
313    begin
314      ENV["hmm"] = "fufu"
315      IO.popen(cmd) {|io| assert_match(/^hmm=fufu$/, io.read, message)}
316      IO.popen({"hmm"=>""}, cmd) {|io| assert_match(/^hmm=$/, io.read, message)}
317      IO.popen({"hmm"=>nil}, cmd) {|io| assert_not_match(/^hmm=/, io.read, message)}
318      ENV["hmm"] = ""
319      IO.popen(cmd) {|io| assert_match(/^hmm=$/, io.read, message)}
320      IO.popen({"hmm"=>""}, cmd) {|io| assert_match(/^hmm=$/, io.read, message)}
321      IO.popen({"hmm"=>nil}, cmd) {|io| assert_not_match(/^hmm=/, io.read, message)}
322      ENV["hmm"] = nil
323      IO.popen(cmd) {|io| assert_not_match(/^hmm=/, io.read, message)}
324      IO.popen({"hmm"=>""}, cmd) {|io| assert_match(/^hmm=$/, io.read, message)}
325      IO.popen({"hmm"=>nil}, cmd) {|io| assert_not_match(/^hmm=/, io.read, message)}
326    ensure
327      ENV["hmm"] = old
328    end
329  end
330
331  def test_execopts_env_popen_vector
332    _test_execopts_env_popen(ENVCOMMAND)
333  end
334
335  def test_execopts_env_popen_string
336    with_tmpchdir do |d|
337      open('test-script', 'w') do |f|
338        ENVCOMMAND.each_with_index do |cmd, i|
339          next if i.zero? or cmd == "-e"
340          f.puts cmd
341        end
342      end
343      _test_execopts_env_popen("#{RUBY} test-script")
344    end
345  end
346
347  def test_execopts_preserve_env_on_exec_failure
348    with_tmpchdir {|d|
349      write_file 's', <<-"End"
350        ENV["mgg"] = nil
351        prog = "./nonexistent"
352        begin
353          Process.exec({"mgg" => "mggoo"}, [prog, prog])
354        rescue Errno::ENOENT
355        end
356        open('out', 'w') {|f|
357          f.print ENV["mgg"].inspect
358        }
359      End
360      system(RUBY, 's')
361      assert_equal(nil.inspect, File.read('out'),
362        "[ruby-core:44093] [ruby-trunk - Bug #6249]")
363    }
364  end
365
366  def test_execopts_env_single_word
367    with_tmpchdir {|d|
368      open("test_execopts_env_single_word.rb", "w") {|f|
369        f.puts "print ENV['hgga']"
370      }
371      system({"hgga"=>"ugu"}, RUBY,
372             :in => 'test_execopts_env_single_word.rb',
373             :out => 'test_execopts_env_single_word.out')
374      assert_equal('ugu', File.read('test_execopts_env_single_word.out'))
375    }
376  end
377
378  def test_execopts_unsetenv_others
379    h = {}
380    MANDATORY_ENVS.each {|k| e = ENV[k] and h[k] = e}
381    IO.popen([h, *ENVCOMMAND, :unsetenv_others=>true]) {|io|
382      assert_equal("", io.read)
383    }
384    IO.popen([h.merge("A"=>"B"), *ENVCOMMAND, :unsetenv_others=>true]) {|io|
385      assert_equal("A=B\n", io.read)
386    }
387  end
388
389  PWD = [RUBY, '-e', 'puts Dir.pwd']
390
391  def test_execopts_chdir
392    with_tmpchdir {|d|
393      IO.popen([*PWD, :chdir => d]) {|io|
394        assert_equal(d, io.read.chomp)
395      }
396      assert_raise(Errno::ENOENT) {
397        Process.wait Process.spawn(*PWD, :chdir => "d/notexist")
398      }
399    }
400  end
401
402  def test_execopts_open_chdir
403    with_tmpchdir {|d|
404      Dir.mkdir "foo"
405      system(*PWD, :chdir => "foo", :out => "open_chdir_test")
406      assert_file.exist?("open_chdir_test")
407      assert_file.not_exist?("foo/open_chdir_test")
408      assert_equal("#{d}/foo", File.read("open_chdir_test").chomp)
409    }
410  end
411
412  UMASK = [RUBY, '-e', 'printf "%04o\n", File.umask']
413
414  def test_execopts_umask
415    skip "umask is not supported" if windows?
416    IO.popen([*UMASK, :umask => 0]) {|io|
417      assert_equal("0000", io.read.chomp)
418    }
419    IO.popen([*UMASK, :umask => 0777]) {|io|
420      assert_equal("0777", io.read.chomp)
421    }
422  end
423
424  def with_pipe
425    begin
426      r, w = IO.pipe
427      yield r, w
428    ensure
429      r.close unless r.closed?
430      w.close unless w.closed?
431    end
432  end
433
434  def with_pipes(n)
435    ary = []
436    begin
437      n.times {
438        ary << IO.pipe
439      }
440      yield ary
441    ensure
442      ary.each {|r, w|
443        r.close unless r.closed?
444        w.close unless w.closed?
445      }
446    end
447  end
448
449  ECHO = lambda {|arg| [RUBY, '-e', "puts #{arg.dump}; STDOUT.flush"] }
450  SORT = [RUBY, '-e', "puts ARGF.readlines.sort"]
451  CAT = [RUBY, '-e', "IO.copy_stream STDIN, STDOUT"]
452
453  def test_execopts_redirect
454    with_tmpchdir {|d|
455      Process.wait Process.spawn(*ECHO["a"], STDOUT=>["out", File::WRONLY|File::CREAT|File::TRUNC, 0644])
456      assert_equal("a", File.read("out").chomp)
457      if windows?
458        # currently telling to child the file modes is not supported.
459        open("out", "a") {|f| f.write "0\n"}
460      else
461        Process.wait Process.spawn(*ECHO["0"], STDOUT=>["out", File::WRONLY|File::CREAT|File::APPEND, 0644])
462        assert_equal("a\n0\n", File.read("out"))
463      end
464      Process.wait Process.spawn(*SORT, STDIN=>["out", File::RDONLY, 0644],
465                                         STDOUT=>["out2", File::WRONLY|File::CREAT|File::TRUNC, 0644])
466      assert_equal("0\na\n", File.read("out2"))
467      Process.wait Process.spawn(*ECHO["b"], [STDOUT, STDERR]=>["out", File::WRONLY|File::CREAT|File::TRUNC, 0644])
468      assert_equal("b", File.read("out").chomp)
469      # problem occur with valgrind
470      #Process.wait Process.spawn(*ECHO["a"], STDOUT=>:close, STDERR=>["out", File::WRONLY|File::CREAT|File::TRUNC, 0644])
471      #p File.read("out")
472      #assert(!File.read("out").empty?) # error message such as "-e:1:in `flush': Bad file descriptor (Errno::EBADF)"
473      Process.wait Process.spawn(*ECHO["c"], STDERR=>STDOUT, STDOUT=>["out", File::WRONLY|File::CREAT|File::TRUNC, 0644])
474      assert_equal("c", File.read("out").chomp)
475      File.open("out", "w") {|f|
476        Process.wait Process.spawn(*ECHO["d"], STDOUT=>f)
477        assert_equal("d", File.read("out").chomp)
478      }
479      opts = {STDOUT=>["out", File::WRONLY|File::CREAT|File::TRUNC, 0644]}
480      opts.merge(3=>STDOUT, 4=>STDOUT, 5=>STDOUT, 6=>STDOUT, 7=>STDOUT) unless windows?
481      Process.wait Process.spawn(*ECHO["e"], opts)
482      assert_equal("e", File.read("out").chomp)
483      opts = {STDOUT=>["out", File::WRONLY|File::CREAT|File::TRUNC, 0644]}
484      opts.merge(3=>0, 4=>:in, 5=>STDIN, 6=>1, 7=>:out, 8=>STDOUT, 9=>2, 10=>:err, 11=>STDERR) unless windows?
485      Process.wait Process.spawn(*ECHO["ee"], opts)
486      assert_equal("ee", File.read("out").chomp)
487      unless windows?
488        # passing non-stdio fds is not supported on Windows
489        File.open("out", "w") {|f|
490          h = {STDOUT=>f, f=>STDOUT}
491          3.upto(30) {|i| h[i] = STDOUT if f.fileno != i }
492          Process.wait Process.spawn(*ECHO["f"], h)
493          assert_equal("f", File.read("out").chomp)
494        }
495      end
496      assert_raise(ArgumentError) {
497        Process.wait Process.spawn(*ECHO["f"], 1=>Process)
498      }
499      assert_raise(ArgumentError) {
500        Process.wait Process.spawn(*ECHO["f"], [Process]=>1)
501      }
502      assert_raise(ArgumentError) {
503        Process.wait Process.spawn(*ECHO["f"], [1, STDOUT]=>2)
504      }
505      assert_raise(ArgumentError) {
506        Process.wait Process.spawn(*ECHO["f"], -1=>2)
507      }
508      Process.wait Process.spawn(*ECHO["hhh\nggg\n"], STDOUT=>"out")
509      assert_equal("hhh\nggg\n", File.read("out"))
510      Process.wait Process.spawn(*SORT, STDIN=>"out", STDOUT=>"out2")
511      assert_equal("ggg\nhhh\n", File.read("out2"))
512
513      unless windows?
514        # passing non-stdio fds is not supported on Windows
515        assert_raise(Errno::ENOENT) {
516          Process.wait Process.spawn("non-existing-command", (3..60).to_a=>["err", File::WRONLY|File::CREAT])
517        }
518        assert_equal("", File.read("err"))
519      end
520
521      system(*ECHO["bb\naa\n"], STDOUT=>["out", "w"])
522      assert_equal("bb\naa\n", File.read("out"))
523      system(*SORT, STDIN=>["out"], STDOUT=>"out2")
524      assert_equal("aa\nbb\n", File.read("out2"))
525
526      with_pipe {|r1, w1|
527        with_pipe {|r2, w2|
528          opts = {STDIN=>r1, STDOUT=>w2}
529          opts.merge(w1=>:close, r2=>:close) unless windows?
530          pid = spawn(*SORT, opts)
531          r1.close
532          w2.close
533          w1.puts "c"
534          w1.puts "a"
535          w1.puts "b"
536          w1.close
537          assert_equal("a\nb\nc\n", r2.read)
538          r2.close
539          Process.wait(pid)
540        }
541      }
542
543      unless windows?
544        # passing non-stdio fds is not supported on Windows
545        with_pipes(5) {|pipes|
546          ios = pipes.flatten
547          h = {}
548          ios.length.times {|i| h[ios[i]] = ios[(i-1)%ios.length] }
549          h2 = h.invert
550          _rios = pipes.map {|r, w| r }
551          wios  = pipes.map {|r, w| w }
552          child_wfds = wios.map {|w| h2[w].fileno }
553          pid = spawn(RUBY, "-e",
554                  "[#{child_wfds.join(',')}].each {|fd| IO.new(fd, 'w').puts fd }", h)
555          pipes.each {|r, w|
556            assert_equal("#{h2[w].fileno}\n", r.gets)
557          }
558          Process.wait pid;
559        }
560
561        with_pipes(5) {|pipes|
562          ios = pipes.flatten
563          h = {}
564          ios.length.times {|i| h[ios[i]] = ios[(i+1)%ios.length] }
565          h2 = h.invert
566          _rios = pipes.map {|r, w| r }
567          wios  = pipes.map {|r, w| w }
568          child_wfds = wios.map {|w| h2[w].fileno }
569          pid = spawn(RUBY, "-e",
570                  "[#{child_wfds.join(',')}].each {|fd| IO.new(fd, 'w').puts fd }", h)
571          pipes.each {|r, w|
572            assert_equal("#{h2[w].fileno}\n", r.gets)
573          }
574          Process.wait pid
575        }
576
577        closed_fd = nil
578        with_pipes(5) {|pipes|
579          io = pipes.last.last
580          closed_fd = io.fileno
581        }
582        assert_raise(Errno::EBADF) { Process.wait spawn(*TRUECOMMAND, closed_fd=>closed_fd) }
583
584        with_pipe {|r, w|
585          if w.respond_to?(:"close_on_exec=")
586            w.close_on_exec = true
587            pid = spawn(RUBY, "-e", "IO.new(#{w.fileno}, 'w').print 'a'", w=>w)
588            w.close
589            assert_equal("a", r.read)
590            Process.wait pid
591          end
592        }
593      end
594
595      system(*ECHO["funya"], :out=>"out")
596      assert_equal("funya\n", File.read("out"))
597      system(RUBY, '-e', 'STDOUT.reopen(STDERR); puts "henya"', :err=>"out")
598      assert_equal("henya\n", File.read("out"))
599      IO.popen([*CAT, :in=>"out"]) {|io|
600        assert_equal("henya\n", io.read)
601      }
602    }
603  end
604
605  def test_execopts_redirect_dup2_child
606    with_tmpchdir {|d|
607      Process.wait spawn(RUBY, "-e", "STDERR.print 'err'; STDOUT.print 'out'",
608                         STDOUT=>"out", STDERR=>[:child, STDOUT])
609      assert_equal("errout", File.read("out"))
610
611      Process.wait spawn(RUBY, "-e", "STDERR.print 'err'; STDOUT.print 'out'",
612                         STDERR=>"out", STDOUT=>[:child, STDERR])
613      assert_equal("errout", File.read("out"))
614
615      skip "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
616      Process.wait spawn(RUBY, "-e", "STDERR.print 'err'; STDOUT.print 'out'",
617                         STDOUT=>"out",
618                         STDERR=>[:child, 3],
619                         3=>[:child, 4],
620                         4=>[:child, STDOUT]
621                        )
622      assert_equal("errout", File.read("out"))
623
624      IO.popen([RUBY, "-e", "STDERR.print 'err'; STDOUT.print 'out'", STDERR=>[:child, STDOUT]]) {|io|
625        assert_equal("errout", io.read)
626      }
627
628      assert_raise(ArgumentError) { Process.wait spawn(*TRUECOMMAND, STDOUT=>[:child, STDOUT]) }
629      assert_raise(ArgumentError) { Process.wait spawn(*TRUECOMMAND, 3=>[:child, 4], 4=>[:child, 3]) }
630      assert_raise(ArgumentError) { Process.wait spawn(*TRUECOMMAND, 3=>[:child, 4], 4=>[:child, 5], 5=>[:child, 3]) }
631      assert_raise(ArgumentError) { Process.wait spawn(*TRUECOMMAND, STDOUT=>[:child, 3]) }
632    }
633  end
634
635  def test_execopts_exec
636    with_tmpchdir {|d|
637      write_file("s", 'exec "echo aaa", STDOUT=>"foo"')
638      pid = spawn RUBY, 's'
639      Process.wait pid
640      assert_equal("aaa\n", File.read("foo"))
641    }
642  end
643
644  def test_execopts_popen
645    with_tmpchdir {|d|
646      IO.popen("#{RUBY} -e 'puts :foo'") {|io| assert_equal("foo\n", io.read) }
647      assert_raise(Errno::ENOENT) { IO.popen(["echo bar"]) {} } # assuming "echo bar" command not exist.
648      IO.popen(ECHO["baz"]) {|io| assert_equal("baz\n", io.read) }
649      assert_raise(ArgumentError) {
650        IO.popen([*ECHO["qux"], STDOUT=>STDOUT]) {|io| }
651      }
652      IO.popen([*ECHO["hoge"], STDERR=>STDOUT]) {|io|
653        assert_equal("hoge\n", io.read)
654      }
655      assert_raise(ArgumentError) {
656        IO.popen([*ECHO["fuga"], STDOUT=>"out"]) {|io| }
657      }
658      skip "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
659      with_pipe {|r, w|
660        IO.popen([RUBY, '-e', 'IO.new(3, "w").puts("a"); puts "b"', 3=>w]) {|io|
661          assert_equal("b\n", io.read)
662        }
663        w.close
664        assert_equal("a\n", r.read)
665      }
666      IO.popen([RUBY, '-e', "IO.new(9, 'w').puts(:b)",
667               9=>["out2", File::WRONLY|File::CREAT|File::TRUNC]]) {|io|
668        assert_equal("", io.read)
669      }
670      assert_equal("b\n", File.read("out2"))
671    }
672  end
673
674  def test_popen_fork
675    IO.popen("-") {|io|
676      if !io
677        puts "fooo"
678      else
679        assert_equal("fooo\n", io.read)
680      end
681    }
682  rescue NotImplementedError
683  end
684
685  def test_fd_inheritance
686    skip "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
687    with_pipe {|r, w|
688      system(RUBY, '-e', 'IO.new(ARGV[0].to_i, "w").puts(:ba)', w.fileno.to_s, w=>w)
689      w.close
690      assert_equal("ba\n", r.read)
691    }
692    with_pipe {|r, w|
693      Process.wait spawn(RUBY, '-e',
694                         'IO.new(ARGV[0].to_i, "w").puts("bi") rescue nil',
695                         w.fileno.to_s)
696      w.close
697      assert_equal("", r.read)
698    }
699    with_pipe {|r, w|
700      with_tmpchdir {|d|
701	write_file("s", <<-"End")
702	  exec(#{RUBY.dump}, '-e',
703	       'IO.new(ARGV[0].to_i, "w").puts("bu") rescue nil',
704	       #{w.fileno.to_s.dump}, :close_others=>false)
705	End
706        w.close_on_exec = false
707	Process.wait spawn(RUBY, "s", :close_others=>false)
708	w.close
709	assert_equal("bu\n", r.read)
710      }
711    }
712    with_pipe {|r, w|
713      io = IO.popen([RUBY, "-e", "STDERR.reopen(STDOUT); IO.new(#{w.fileno}, 'w').puts('me')"])
714      w.close
715      errmsg = io.read
716      assert_equal("", r.read)
717      assert_not_equal("", errmsg)
718      Process.wait
719    }
720    with_pipe {|r, w|
721      errmsg = `#{RUBY} -e "STDERR.reopen(STDOUT); IO.new(#{w.fileno}, 'w').puts(123)"`
722      w.close
723      assert_equal("", r.read)
724      assert_not_equal("", errmsg)
725    }
726  end
727
728  def test_execopts_close_others
729    skip "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
730    with_tmpchdir {|d|
731      with_pipe {|r, w|
732        system(RUBY, '-e', 'STDERR.reopen("err", "w"); IO.new(ARGV[0].to_i, "w").puts("ma")', w.fileno.to_s, :close_others=>true)
733        w.close
734        assert_equal("", r.read)
735        assert_not_equal("", File.read("err"))
736        File.unlink("err")
737      }
738      with_pipe {|r, w|
739        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)
740        w.close
741        assert_equal("", r.read)
742        assert_not_equal("", File.read("err"))
743        File.unlink("err")
744      }
745      with_pipe {|r, w|
746        w.close_on_exec = false
747        Process.wait spawn(RUBY, '-e', 'IO.new(ARGV[0].to_i, "w").puts("bi")', w.fileno.to_s, :close_others=>false)
748        w.close
749        assert_equal("bi\n", r.read)
750      }
751      with_pipe {|r, w|
752	write_file("s", <<-"End")
753	  exec(#{RUBY.dump}, '-e',
754	       'STDERR.reopen("err", "w"); IO.new(ARGV[0].to_i, "w").puts("mu")',
755	       #{w.fileno.to_s.dump},
756	       :close_others=>true)
757	End
758        Process.wait spawn(RUBY, "s", :close_others=>false)
759        w.close
760        assert_equal("", r.read)
761        assert_not_equal("", File.read("err"))
762        File.unlink("err")
763      }
764      with_pipe {|r, w|
765        io = IO.popen([RUBY, "-e", "STDERR.reopen(STDOUT); IO.new(#{w.fileno}, 'w').puts('me')", :close_others=>true])
766        w.close
767        errmsg = io.read
768        assert_equal("", r.read)
769        assert_not_equal("", errmsg)
770        Process.wait
771      }
772      with_pipe {|r, w|
773        w.close_on_exec = false
774        io = IO.popen([RUBY, "-e", "STDERR.reopen(STDOUT); IO.new(#{w.fileno}, 'w').puts('mo')", :close_others=>false])
775        w.close
776        errmsg = io.read
777        assert_equal("mo\n", r.read)
778        assert_equal("", errmsg)
779        Process.wait
780      }
781      with_pipe {|r, w|
782        w.close_on_exec = false
783        io = IO.popen([RUBY, "-e", "STDERR.reopen(STDOUT); IO.new(#{w.fileno}, 'w').puts('mo')", :close_others=>nil])
784        w.close
785        errmsg = io.read
786        assert_equal("mo\n", r.read)
787        assert_equal("", errmsg)
788        Process.wait
789      }
790
791    }
792  end
793
794  def test_execopts_redirect_self
795    begin
796      with_pipe {|r, w|
797        w << "haha\n"
798        w.close
799        r.close_on_exec = true
800        IO.popen([RUBY, "-e", "print IO.new(#{r.fileno}, 'r').read", r.fileno=>r.fileno, :close_others=>false]) {|io|
801          assert_equal("haha\n", io.read)
802        }
803      }
804    rescue NotImplementedError
805      skip "IO#close_on_exec= is not supported"
806    end
807  end
808
809  def test_execopts_redirect_tempfile
810    bug6269 = '[ruby-core:44181]'
811    Tempfile.open("execopts") do |tmp|
812      pid = assert_nothing_raised(ArgumentError, bug6269) do
813        break spawn(RUBY, "-e", "print $$", out: tmp)
814      end
815      Process.wait(pid)
816      tmp.rewind
817      assert_equal(pid.to_s, tmp.read)
818      tmp.close(true)
819    end
820  end
821
822  def test_execopts_duplex_io
823    IO.popen("#{RUBY} -e ''", "r+") {|duplex|
824      assert_raise(ArgumentError) { system("#{RUBY} -e ''", duplex=>STDOUT) }
825      assert_raise(ArgumentError) { system("#{RUBY} -e ''", STDOUT=>duplex) }
826    }
827  end
828
829  def test_execopts_modification
830    h = {}
831    Process.wait spawn(*TRUECOMMAND, h)
832    assert_equal({}, h)
833
834    h = {}
835    system(*TRUECOMMAND, h)
836    assert_equal({}, h)
837
838    h = {}
839    io = IO.popen([*TRUECOMMAND, h])
840    io.close
841    assert_equal({}, h)
842  end
843
844  def test_system_noshell
845    str = "echo non existing command name which contains spaces"
846    assert_nil(system([str, str]))
847  end
848
849  def test_spawn_noshell
850    str = "echo non existing command name which contains spaces"
851    assert_raise(Errno::ENOENT) { spawn([str, str]) }
852  end
853
854  def test_popen_noshell
855    str = "echo non existing command name which contains spaces"
856    assert_raise(Errno::ENOENT) { IO.popen([str, str]) }
857  end
858
859  def test_exec_noshell
860    with_tmpchdir {|d|
861      write_file("s", <<-"End")
862	  str = "echo non existing command name which contains spaces"
863	  STDERR.reopen(STDOUT)
864	  begin
865	    exec [str, str]
866	  rescue Errno::ENOENT
867	    print "Errno::ENOENT success"
868	  end
869	End
870      r = IO.popen([RUBY, "s", :close_others=>false], "r") {|f| f.read}
871      assert_equal("Errno::ENOENT success", r)
872    }
873  end
874
875  def test_system_wordsplit
876    with_tmpchdir {|d|
877      write_file("script", <<-'End')
878        File.open("result", "w") {|t| t << "haha pid=#{$$} ppid=#{Process.ppid}" }
879        exit 5
880      End
881      str = "#{RUBY} script"
882      ret = system(str)
883      status = $?
884      assert_equal(false, ret)
885      assert(status.exited?)
886      assert_equal(5, status.exitstatus)
887      assert_equal("haha pid=#{status.pid} ppid=#{$$}", File.read("result"))
888    }
889  end
890
891  def test_spawn_wordsplit
892    with_tmpchdir {|d|
893      write_file("script", <<-'End')
894        File.open("result", "w") {|t| t << "hihi pid=#{$$} ppid=#{Process.ppid}" }
895        exit 6
896      End
897      str = "#{RUBY} script"
898      pid = spawn(str)
899      Process.wait pid
900      status = $?
901      assert_equal(pid, status.pid)
902      assert(status.exited?)
903      assert_equal(6, status.exitstatus)
904      assert_equal("hihi pid=#{status.pid} ppid=#{$$}", File.read("result"))
905    }
906  end
907
908  def test_popen_wordsplit
909    with_tmpchdir {|d|
910      write_file("script", <<-'End')
911        print "fufu pid=#{$$} ppid=#{Process.ppid}"
912        exit 7
913      End
914      str = "#{RUBY} script"
915      io = IO.popen(str)
916      pid = io.pid
917      result = io.read
918      io.close
919      status = $?
920      assert_equal(pid, status.pid)
921      assert(status.exited?)
922      assert_equal(7, status.exitstatus)
923      assert_equal("fufu pid=#{status.pid} ppid=#{$$}", result)
924    }
925  end
926
927  def test_popen_wordsplit_beginning_and_trailing_spaces
928    with_tmpchdir {|d|
929      write_file("script", <<-'End')
930        print "fufumm pid=#{$$} ppid=#{Process.ppid}"
931        exit 7
932      End
933      str = " #{RUBY} script "
934      io = IO.popen(str)
935      pid = io.pid
936      result = io.read
937      io.close
938      status = $?
939      assert_equal(pid, status.pid)
940      assert(status.exited?)
941      assert_equal(7, status.exitstatus)
942      assert_equal("fufumm pid=#{status.pid} ppid=#{$$}", result)
943    }
944  end
945
946  def test_exec_wordsplit
947    with_tmpchdir {|d|
948      write_file("script", <<-'End')
949        File.open("result", "w") {|t|
950          if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
951            t << "hehe ppid=#{Process.ppid}"
952          else
953            t << "hehe pid=#{$$} ppid=#{Process.ppid}"
954          end
955        }
956        exit 6
957      End
958      write_file("s", <<-"End")
959	ruby = #{RUBY.dump}
960	exec "\#{ruby} script"
961      End
962      pid = spawn(RUBY, "s")
963      Process.wait pid
964      status = $?
965      assert_equal(pid, status.pid)
966      assert(status.exited?)
967      assert_equal(6, status.exitstatus)
968      if windows?
969        expected = "hehe ppid=#{status.pid}"
970      else
971        expected = "hehe pid=#{status.pid} ppid=#{$$}"
972      end
973      assert_equal(expected, File.read("result"))
974    }
975  end
976
977  def test_system_shell
978    with_tmpchdir {|d|
979      write_file("script1", <<-'End')
980        File.open("result1", "w") {|t| t << "taka pid=#{$$} ppid=#{Process.ppid}" }
981        exit 7
982      End
983      write_file("script2", <<-'End')
984        File.open("result2", "w") {|t| t << "taki pid=#{$$} ppid=#{Process.ppid}" }
985        exit 8
986      End
987      ret = system("#{RUBY} script1 || #{RUBY} script2")
988      status = $?
989      assert_equal(false, ret)
990      assert(status.exited?)
991      result1 = File.read("result1")
992      result2 = File.read("result2")
993      assert_match(/\Ataka pid=\d+ ppid=\d+\z/, result1)
994      assert_match(/\Ataki pid=\d+ ppid=\d+\z/, result2)
995      assert_not_equal(result1[/\d+/].to_i, status.pid)
996
997      if windows?
998        Dir.mkdir(path = "path with space")
999        write_file(bat = path + "/bat test.bat", "@echo %1>out")
1000        system(bat, "foo 'bar'")
1001        assert_equal(%["foo 'bar'"\n], File.read("out"), '[ruby-core:22960]')
1002        system(%[#{bat.dump} "foo 'bar'"])
1003        assert_equal(%["foo 'bar'"\n], File.read("out"), '[ruby-core:22960]')
1004      end
1005    }
1006  end
1007
1008  def test_spawn_shell
1009    with_tmpchdir {|d|
1010      write_file("script1", <<-'End')
1011        File.open("result1", "w") {|t| t << "taku pid=#{$$} ppid=#{Process.ppid}" }
1012        exit 7
1013      End
1014      write_file("script2", <<-'End')
1015        File.open("result2", "w") {|t| t << "take pid=#{$$} ppid=#{Process.ppid}" }
1016        exit 8
1017      End
1018      pid = spawn("#{RUBY} script1 || #{RUBY} script2")
1019      Process.wait pid
1020      status = $?
1021      assert(status.exited?)
1022      assert(!status.success?)
1023      result1 = File.read("result1")
1024      result2 = File.read("result2")
1025      assert_match(/\Ataku pid=\d+ ppid=\d+\z/, result1)
1026      assert_match(/\Atake pid=\d+ ppid=\d+\z/, result2)
1027      assert_not_equal(result1[/\d+/].to_i, status.pid)
1028
1029      if windows?
1030        Dir.mkdir(path = "path with space")
1031        write_file(bat = path + "/bat test.bat", "@echo %1>out")
1032        pid = spawn(bat, "foo 'bar'")
1033        Process.wait pid
1034        status = $?
1035        assert(status.exited?)
1036        assert(status.success?)
1037        assert_equal(%["foo 'bar'"\n], File.read("out"), '[ruby-core:22960]')
1038        pid = spawn(%[#{bat.dump} "foo 'bar'"])
1039        Process.wait pid
1040        status = $?
1041        assert(status.exited?)
1042        assert(status.success?)
1043        assert_equal(%["foo 'bar'"\n], File.read("out"), '[ruby-core:22960]')
1044      end
1045    }
1046  end
1047
1048  def test_popen_shell
1049    with_tmpchdir {|d|
1050      write_file("script1", <<-'End')
1051        puts "tako pid=#{$$} ppid=#{Process.ppid}"
1052        exit 7
1053      End
1054      write_file("script2", <<-'End')
1055        puts "tika pid=#{$$} ppid=#{Process.ppid}"
1056        exit 8
1057      End
1058      io = IO.popen("#{RUBY} script1 || #{RUBY} script2")
1059      result = io.read
1060      io.close
1061      status = $?
1062      assert(status.exited?)
1063      assert(!status.success?)
1064      assert_match(/\Atako pid=\d+ ppid=\d+\ntika pid=\d+ ppid=\d+\n\z/, result)
1065      assert_not_equal(result[/\d+/].to_i, status.pid)
1066
1067      if windows?
1068        Dir.mkdir(path = "path with space")
1069        write_file(bat = path + "/bat test.bat", "@echo %1")
1070        r = IO.popen([bat, "foo 'bar'"]) {|f| f.read}
1071        assert_equal(%["foo 'bar'"\n], r, '[ruby-core:22960]')
1072        r = IO.popen(%[#{bat.dump} "foo 'bar'"]) {|f| f.read}
1073        assert_equal(%["foo 'bar'"\n], r, '[ruby-core:22960]')
1074      end
1075    }
1076  end
1077
1078  def test_exec_shell
1079    with_tmpchdir {|d|
1080      write_file("script1", <<-'End')
1081        File.open("result1", "w") {|t| t << "tiki pid=#{$$} ppid=#{Process.ppid}" }
1082        exit 7
1083      End
1084      write_file("script2", <<-'End')
1085        File.open("result2", "w") {|t| t << "tiku pid=#{$$} ppid=#{Process.ppid}" }
1086        exit 8
1087      End
1088      write_file("s", <<-"End")
1089	ruby = #{RUBY.dump}
1090	exec("\#{ruby} script1 || \#{ruby} script2")
1091      End
1092      pid = spawn RUBY, "s"
1093      Process.wait pid
1094      status = $?
1095      assert(status.exited?)
1096      assert(!status.success?)
1097      result1 = File.read("result1")
1098      result2 = File.read("result2")
1099      assert_match(/\Atiki pid=\d+ ppid=\d+\z/, result1)
1100      assert_match(/\Atiku pid=\d+ ppid=\d+\z/, result2)
1101      assert_not_equal(result1[/\d+/].to_i, status.pid)
1102    }
1103  end
1104
1105  def test_argv0
1106    with_tmpchdir {|d|
1107      assert_equal(false, system([RUBY, "asdfg"], "-e", "exit false"))
1108      assert_equal(true, system([RUBY, "zxcvb"], "-e", "exit true"))
1109
1110      Process.wait spawn([RUBY, "poiu"], "-e", "exit 4")
1111      assert_equal(4, $?.exitstatus)
1112
1113      assert_equal("1", IO.popen([[RUBY, "qwerty"], "-e", "print 1"]).read)
1114      Process.wait
1115
1116      write_file("s", <<-"End")
1117        exec([#{RUBY.dump}, "lkjh"], "-e", "exit 5")
1118      End
1119      pid = spawn RUBY, "s"
1120      Process.wait pid
1121      assert_equal(5, $?.exitstatus)
1122    }
1123  end
1124
1125  def with_stdin(filename)
1126    open(filename) {|f|
1127      begin
1128        old = STDIN.dup
1129        begin
1130          STDIN.reopen(filename)
1131          yield
1132        ensure
1133          STDIN.reopen(old)
1134        end
1135      ensure
1136        old.close
1137      end
1138    }
1139  end
1140
1141  def test_argv0_noarg
1142    with_tmpchdir {|d|
1143      open("t", "w") {|f| f.print "exit true" }
1144      open("f", "w") {|f| f.print "exit false" }
1145
1146      with_stdin("t") { assert_equal(true, system([RUBY, "qaz"])) }
1147      with_stdin("f") { assert_equal(false, system([RUBY, "wsx"])) }
1148
1149      with_stdin("t") { Process.wait spawn([RUBY, "edc"]) }
1150      assert($?.success?)
1151      with_stdin("f") { Process.wait spawn([RUBY, "rfv"]) }
1152      assert(!$?.success?)
1153
1154      with_stdin("t") { IO.popen([[RUBY, "tgb"]]) {|io| assert_equal("", io.read) } }
1155      assert($?.success?)
1156      with_stdin("f") { IO.popen([[RUBY, "yhn"]]) {|io| assert_equal("", io.read) } }
1157      assert(!$?.success?)
1158
1159      status = run_in_child "STDIN.reopen('t'); exec([#{RUBY.dump}, 'ujm'])"
1160      assert(status.success?)
1161      status = run_in_child "STDIN.reopen('f'); exec([#{RUBY.dump}, 'ik,'])"
1162      assert(!status.success?)
1163    }
1164  end
1165
1166  def test_status
1167    with_tmpchdir do
1168      s = run_in_child("exit 1")
1169      assert_equal("#<Process::Status: pid #{ s.pid } exit #{ s.exitstatus }>", s.inspect)
1170
1171      assert_equal(s, s)
1172      assert_equal(s, s.to_i)
1173
1174      assert_equal(s.to_i & 0x55555555, s & 0x55555555)
1175      assert_equal(s.to_i >> 1, s >> 1)
1176      assert_equal(false, s.stopped?)
1177      assert_equal(nil, s.stopsig)
1178    end
1179  end
1180
1181  def test_status_kill
1182    return unless Process.respond_to?(:kill)
1183    return unless Signal.list.include?("QUIT")
1184
1185    with_tmpchdir do
1186      write_file("foo", "sleep 30")
1187      pid = spawn(RUBY, "foo")
1188      Thread.new { sleep 1; Process.kill(:SIGQUIT, pid) }
1189      Process.wait(pid)
1190      s = $?
1191      assert_equal([false, true, false],
1192                   [s.exited?, s.signaled?, s.stopped?],
1193                   "[s.exited?, s.signaled?, s.stopped?]")
1194      assert_send(
1195        [["#<Process::Status: pid #{ s.pid } SIGQUIT (signal #{ s.termsig })>",
1196          "#<Process::Status: pid #{ s.pid } SIGQUIT (signal #{ s.termsig }) (core dumped)>"],
1197         :include?,
1198         s.inspect])
1199      assert_equal(false, s.exited?)
1200      assert_equal(nil, s.success?)
1201    end
1202  end
1203
1204  def test_wait_without_arg
1205    with_tmpchdir do
1206      write_file("foo", "sleep 0.1")
1207      pid = spawn(RUBY, "foo")
1208      assert_equal(pid, Process.wait)
1209    end
1210  end
1211
1212  def test_wait2
1213    with_tmpchdir do
1214      write_file("foo", "sleep 0.1")
1215      pid = spawn(RUBY, "foo")
1216      assert_equal([pid, 0], Process.wait2)
1217    end
1218  end
1219
1220  def test_waitall
1221    with_tmpchdir do
1222      write_file("foo", "sleep 0.1")
1223      ps = (0...3).map { spawn(RUBY, "foo") }.sort
1224      ss = Process.waitall.sort
1225      ps.zip(ss) do |p1, (p2, s)|
1226        assert_equal(p1, p2)
1227        assert_equal(p1, s.pid)
1228      end
1229    end
1230  end
1231
1232  def test_abort
1233    with_tmpchdir do
1234      s = run_in_child("abort")
1235      assert_not_equal(0, s.exitstatus)
1236    end
1237  end
1238
1239  def test_sleep
1240    assert_raise(ArgumentError) { sleep(1, 1) }
1241  end
1242
1243  def test_getpgid
1244    assert_kind_of(Integer, Process.getpgid(Process.ppid))
1245  rescue NotImplementedError
1246  end
1247
1248  def test_getpriority
1249    assert_kind_of(Integer, Process.getpriority(Process::PRIO_PROCESS, $$))
1250  rescue NameError, NotImplementedError
1251  end
1252
1253  def test_setpriority
1254    if defined? Process::PRIO_USER
1255      assert_nothing_raised do
1256        pr = Process.getpriority(Process::PRIO_PROCESS, $$)
1257        Process.setpriority(Process::PRIO_PROCESS, $$, pr)
1258      end
1259    end
1260  end
1261
1262  def test_getuid
1263    assert_kind_of(Integer, Process.uid)
1264  end
1265
1266  def test_groups
1267    gs = Process.groups
1268    assert_instance_of(Array, gs)
1269    gs.each {|g| assert_kind_of(Integer, g) }
1270  rescue NotImplementedError
1271  end
1272
1273  def test_maxgroups
1274    assert_kind_of(Integer, Process.maxgroups)
1275  rescue NotImplementedError
1276  end
1277
1278  def test_geteuid
1279    assert_kind_of(Integer, Process.euid)
1280  end
1281
1282  def test_seteuid
1283    assert_nothing_raised(TypeError) {Process.euid += 0}
1284  rescue NotImplementedError
1285  end
1286
1287  def test_seteuid_name
1288    user = ENV["USER"] or return
1289    assert_nothing_raised(TypeError) {Process.euid = user}
1290  rescue NotImplementedError
1291  end
1292
1293  def test_getegid
1294    assert_kind_of(Integer, Process.egid)
1295  end
1296
1297  def test_setegid
1298    assert_nothing_raised(TypeError) {Process.egid += 0}
1299  rescue NotImplementedError
1300  end
1301
1302  def test_uid_re_exchangeable_p
1303    r = Process::UID.re_exchangeable?
1304    assert(true == r || false == r)
1305  end
1306
1307  def test_gid_re_exchangeable_p
1308    r = Process::GID.re_exchangeable?
1309    assert(true == r || false == r)
1310  end
1311
1312  def test_uid_sid_available?
1313    r = Process::UID.sid_available?
1314    assert(true == r || false == r)
1315  end
1316
1317  def test_gid_sid_available?
1318    r = Process::GID.sid_available?
1319    assert(true == r || false == r)
1320  end
1321
1322  def test_pst_inspect
1323    assert_nothing_raised { Process::Status.allocate.inspect }
1324  end
1325
1326  def test_wait_and_sigchild
1327    if /freebsd|openbsd/ =~ RUBY_PLATFORM
1328      # this relates #4173
1329      # When ruby can use 2 cores, signal and wait4 may miss the signal.
1330      skip "this fails on FreeBSD and OpenBSD on multithreaded environment"
1331    end
1332    signal_received = []
1333    Signal.trap(:CHLD)  { signal_received << true }
1334    pid = fork { sleep 0.1; exit }
1335    Thread.start { raise }
1336    Process.wait pid
1337    sleep 0.1
1338    assert_equal [true], signal_received, " [ruby-core:19744]"
1339  rescue NotImplementedError, ArgumentError
1340  ensure
1341    begin
1342      Signal.trap(:CHLD, 'DEFAULT')
1343    rescue ArgumentError
1344    end
1345  end
1346
1347  def test_no_curdir
1348    with_tmpchdir {|d|
1349      Dir.mkdir("vd")
1350      status = nil
1351      Dir.chdir("vd") {
1352        dir = "#{d}/vd"
1353        # OpenSolaris cannot remove the current directory.
1354        system(RUBY, "--disable-gems", "-e", "Dir.chdir '..'; Dir.rmdir #{dir.dump}")
1355        system({"RUBYLIB"=>nil}, RUBY, "--disable-gems", "-e", "exit true")
1356        status = $?
1357      }
1358      assert(status.success?, "[ruby-dev:38105]")
1359    }
1360  end unless /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
1361
1362  def test_fallback_to_sh
1363    feature = '[ruby-core:32745]'
1364    with_tmpchdir do |d|
1365      open("tmp_script.#{$$}", "w") {|f| f.puts ": ;"; f.chmod(0755)}
1366      assert_not_nil(pid = Process.spawn("./tmp_script.#{$$}"), feature)
1367      wpid, st = Process.waitpid2(pid)
1368      assert_equal([pid, true], [wpid, st.success?], feature)
1369
1370      open("tmp_script.#{$$}", "w") {|f| f.puts "echo $#: $@"; f.chmod(0755)}
1371      result = IO.popen(["./tmp_script.#{$$}", "a b", "c"]) {|f| f.read}
1372      assert_equal("2: a b c\n", result, feature)
1373
1374      open("tmp_script.#{$$}", "w") {|f| f.puts "echo $hghg"; f.chmod(0755)}
1375      result = IO.popen([{"hghg" => "mogomogo"}, "./tmp_script.#{$$}", "a b", "c"]) {|f| f.read}
1376      assert_equal("mogomogo\n", result, feature)
1377
1378    end
1379  end if File.executable?("/bin/sh")
1380
1381  def test_spawn_too_long_path
1382    bug4314 = '[ruby-core:34842]'
1383    assert_fail_too_long_path(%w"echo", bug4314)
1384  end
1385
1386  def test_aspawn_too_long_path
1387    bug4315 = '[ruby-core:34833]'
1388    assert_fail_too_long_path(%w"echo |", bug4315)
1389  end
1390
1391  def assert_fail_too_long_path((cmd, sep), mesg)
1392    sep ||= ""
1393    min = 1_000 / (cmd.size + sep.size)
1394    cmds = Array.new(min, cmd)
1395    exs = [Errno::ENOENT]
1396    exs << Errno::E2BIG if defined?(Errno::E2BIG)
1397    EnvUtil.suppress_warning do
1398      assert_raise(*exs, mesg) do
1399        begin
1400          loop do
1401            Process.spawn(cmds.join(sep), [STDOUT, STDERR]=>:close)
1402            min = [cmds.size, min].max
1403            cmds *= 100
1404          end
1405        rescue NoMemoryError
1406          size = cmds.size
1407          raise if min >= size - 1
1408          min = [min, size /= 2].max
1409          cmds[size..-1] = []
1410          raise if size < 250
1411          retry
1412        end
1413      end
1414    end
1415  end
1416
1417  def test_system_sigpipe
1418    return if windows?
1419
1420    pid = 0
1421
1422    with_tmpchdir do
1423      assert_nothing_raised('[ruby-dev:12261]') do
1424        timeout(3) do
1425          pid = spawn('yes | ls')
1426          Process.waitpid pid
1427        end
1428      end
1429    end
1430  ensure
1431    Process.kill(:KILL, pid) if (pid != 0) rescue false
1432  end
1433
1434  if Process.respond_to?(:daemon)
1435    def test_daemon_default
1436      data = IO.popen("-", "r+") do |f|
1437        break f.read if f
1438        Process.daemon
1439        puts "ng"
1440      end
1441      assert_equal("", data)
1442    end
1443
1444    def test_daemon_noclose
1445      data = IO.popen("-", "r+") do |f|
1446        break f.read if f
1447        Process.daemon(false, true)
1448        puts "ok", Dir.pwd
1449      end
1450      assert_equal("ok\n/\n", data)
1451    end
1452
1453    def test_daemon_nochdir_noclose
1454      data = IO.popen("-", "r+") do |f|
1455        break f.read if f
1456        Process.daemon(true, true)
1457        puts "ok", Dir.pwd
1458      end
1459      assert_equal("ok\n#{Dir.pwd}\n", data)
1460    end
1461
1462    def test_daemon_readwrite
1463      data = IO.popen("-", "r+") do |f|
1464        if f
1465          f.puts "ok?"
1466          break f.read
1467        end
1468        Process.daemon(true, true)
1469        puts STDIN.gets
1470      end
1471      assert_equal("ok?\n", data)
1472    end
1473
1474    def test_daemon_pid
1475      cpid, dpid = IO.popen("-", "r+") do |f|
1476        break f.pid, Integer(f.read) if f
1477        Process.daemon(false, true)
1478        puts $$
1479      end
1480      assert_not_equal(cpid, dpid)
1481    end
1482
1483    if File.directory?("/proc/self/task") && /netbsd[a-z]*[1-6]/ !~ RUBY_PLATFORM
1484      def test_daemon_no_threads
1485        pid, data = IO.popen("-", "r+") do |f|
1486          break f.pid, f.readlines if f
1487          Process.daemon(true, true)
1488          puts Dir.entries("/proc/self/task") - %W[. ..]
1489        end
1490        bug4920 = '[ruby-dev:43873]'
1491        assert_equal(2, data.size, bug4920)
1492        assert_not_include(data.map(&:to_i), pid)
1493      end
1494    else # darwin
1495      def test_daemon_no_threads
1496        data = Timeout.timeout(3) do
1497          IO.popen("-") do |f|
1498            break f.readlines.map(&:chomp) if f
1499            th = Thread.start {sleep 3}
1500            Process.daemon(true, true)
1501            puts Thread.list.size, th.status.inspect
1502          end
1503        end
1504        assert_equal(["1", "false"], data)
1505      end
1506    end
1507  end
1508
1509  def test_popen_cloexec
1510    return unless defined? Fcntl::FD_CLOEXEC
1511    IO.popen([RUBY, "-e", ""]) {|io|
1512      assert(io.close_on_exec?)
1513    }
1514  end
1515
1516  def test_execopts_new_pgroup
1517    return unless windows?
1518
1519    assert_nothing_raised { system(*TRUECOMMAND, :new_pgroup=>true) }
1520    assert_nothing_raised { system(*TRUECOMMAND, :new_pgroup=>false) }
1521    assert_nothing_raised { spawn(*TRUECOMMAND, :new_pgroup=>true) }
1522    assert_nothing_raised { IO.popen([*TRUECOMMAND, :new_pgroup=>true]) {} }
1523  end
1524
1525  def test_execopts_uid
1526    feature6975 = '[ruby-core:47414]'
1527
1528    [30000, [Process.uid, ENV["USER"]]].each do |uid, user|
1529      if user
1530        assert_nothing_raised(feature6975) do
1531          begin
1532            system(*TRUECOMMAND, uid: user)
1533          rescue Errno::EPERM, NotImplementedError
1534          end
1535        end
1536      end
1537
1538      assert_nothing_raised(feature6975) do
1539        begin
1540          system(*TRUECOMMAND, uid: uid)
1541        rescue Errno::EPERM, NotImplementedError
1542        end
1543      end
1544
1545      assert_nothing_raised(feature6975) do
1546        begin
1547          u = IO.popen([RUBY, "-e", "print Process.uid", uid: user||uid], &:read)
1548          assert_equal(uid.to_s, u, feature6975)
1549        rescue Errno::EPERM, NotImplementedError
1550        end
1551      end
1552    end
1553  end
1554
1555  def test_execopts_gid
1556    skip "Process.groups not implemented on Windows platform" if windows?
1557    feature6975 = '[ruby-core:47414]'
1558
1559    [30000, *Process.groups.map {|g| g = Etc.getgrgid(g); [g.name, g.gid]}].each do |group, gid|
1560      assert_nothing_raised(feature6975) do
1561        begin
1562          system(*TRUECOMMAND, gid: group)
1563        rescue Errno::EPERM, NotImplementedError
1564        end
1565      end
1566
1567      gid = "#{gid || group}"
1568      assert_nothing_raised(feature6975) do
1569        begin
1570          g = IO.popen([RUBY, "-e", "print Process.gid", gid: group], &:read)
1571          assert_equal(gid, g, feature6975)
1572        rescue Errno::EPERM, NotImplementedError
1573        end
1574      end
1575    end
1576  end
1577
1578  def test_sigpipe
1579    system(RUBY, "-e", "")
1580    with_pipe {|r, w|
1581      r.close
1582      assert_raise(Errno::EPIPE) { w.print "a" }
1583    }
1584  end
1585
1586  def test_sh_comment
1587    IO.popen("echo a # fofoof") {|f|
1588      assert_equal("a\n", f.read)
1589    }
1590  end if File.executable?("/bin/sh")
1591
1592  def test_sh_env
1593    IO.popen("foofoo=barbar env") {|f|
1594      lines = f.readlines
1595      assert_operator(lines, :include?, "foofoo=barbar\n")
1596    }
1597  end if File.executable?("/bin/sh")
1598
1599  def test_sh_exec
1600    IO.popen("exec echo exexexec") {|f|
1601      assert_equal("exexexec\n", f.read)
1602    }
1603  end if File.executable?("/bin/sh")
1604
1605  def test_setsid
1606    return unless Process.respond_to?(:setsid)
1607    return unless Process.respond_to?(:getsid)
1608    # OpenBSD doesn't allow Process::getsid(pid) when pid is in
1609    # different session.
1610    return if /openbsd/ =~ RUBY_PLATFORM
1611
1612    IO.popen([RUBY, "-e", <<EOS]) do|io|
1613	Marshal.dump(Process.getsid, STDOUT)
1614	newsid = Process.setsid
1615	Marshal.dump(newsid, STDOUT)
1616	STDOUT.flush
1617	# getsid() on MacOS X return ESRCH when target process is zombie
1618	# even if it is valid process id.
1619	sleep
1620EOS
1621      begin
1622        # test Process.getsid() w/o arg
1623        assert_equal(Marshal.load(io), Process.getsid)
1624
1625        # test Process.setsid return value and Process::getsid(pid)
1626        assert_equal(Marshal.load(io), Process.getsid(io.pid))
1627      ensure
1628        Process.kill(:KILL, io.pid) rescue nil
1629        Process.wait(io.pid)
1630      end
1631    end
1632  end
1633
1634
1635
1636end
1637