PageRenderTime 43ms CodeModel.GetById 0ms RepoModel.GetById 0ms app.codeStats 1ms

/test/ruby/test_process.rb

http://github.com/ruby/ruby
Ruby | 2495 lines | 2232 code | 225 blank | 38 comment | 89 complexity | 2d4940fc0cbc4378a21f336e974d6d77 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, AGPL-3.0

Large files files are truncated, but you can click here to view the full file

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

Large files files are truncated, but you can click here to view the full file