1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

* lib/open3.rb (Open3.popen3): merge hash options if given.

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@20448 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
akr 2008-12-02 10:23:48 +00:00
parent e44a6c4708
commit 852b4629c1
3 changed files with 298 additions and 19 deletions

View file

@ -1,3 +1,7 @@
Tue Dec 2 19:22:13 2008 Tanaka Akira <akr@fsij.org>
* lib/open3.rb (Open3.popen3): merge hash options if given.
Tue Dec 2 15:31:42 2008 Yukihiro Matsumoto <matz@ruby-lang.org>
* lib/net/protocol.rb (Net::BufferedIO#rbuf_fill): use

View file

@ -32,15 +32,23 @@
#
module Open3
#
# Open stdin, stdout, and stderr streams and start external executable.
# In addition, a thread for waiting the started process is noticed.
# The thread has a thread variable :pid which is the pid of the started
# process.
# The thread has a pid method and thread variable :pid which is the pid of
# the started process.
#
# Block form:
#
# Open3.popen3(cmd... [, opts]) {|stdin, stdout, stderr, wait_thr|
# pid = wait_thr.pid # pid of the started process.
# ...
# exit_status = wait_thr.value # Process::Status object returned.
# }
#
# Non-block form:
#
# stdin, stdout, stderr, wait_thr = Open3.popen3(cmd)
# stdin, stdout, stderr, wait_thr = Open3.popen3(cmd... [, opts])
# pid = wait_thr[:pid] # pid of the started process.
# ...
# stdin.close # stdin, stdout and stderr should be closed in this form.
@ -48,11 +56,43 @@ module Open3
# stderr.close
# exit_status = wait_thr.value # Process::Status object returned.
#
# Block form:
# The parameters +cmd...+ is passed to Kernel#spawn.
# So a commandline string and list of argument strings can be accepted as follows.
#
# Open3.popen3(cmd) { |stdin, stdout, stderr, wait_thr| ... }
# Open3.popen3("echo a") {|i, o, e, t| ... }
# Open3.popen3("echo", "a") {|i, o, e, t| ... }
#
# The parameter +cmd+ is passed directly to Kernel#spawn.
# If the last parameter, opts, is a Hash, it is recognized as an option for Kernel#spawn.
#
# Open3.popen3("pwd", :chdir=>"/") {|i,o,e,t|
# p o.read.chomp #=> "/"
# }
#
# opts[STDIN], opts[STDOUT] and opts[STDERR] in the option are set for redirection.
#
# If some of the three elements in opts are specified,
# pipes for them are not created.
# In that case, block arugments for the block form and
# return values for the non-block form are decreased.
#
# # No pipe "e" for stderr
# Open3.popen3("echo a", STDERR=>nil) {|i,o,t| ... }
# i,o,t = Open3.popen3("echo a", STDERR=>nil)
#
# If the value is nil as above, the elements of opts are removed.
# So standard input/output/error of current process are inherited.
#
# If the value is not nil, it is passed as is to Kernel#spawn.
# So pipeline of commands can be constracted as follows.
#
# Open3.popen3("yes", STDIN=>nil, STDERR=>nil) {|o1,t1|
# Open3.popen3("head -10", STDIN=>o1, STDERR=>nil) {|o2,t2|
# o1.close
# p o2.read #=> "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"
# p t1.value #=> #<Process::Status: pid 13508 SIGPIPE (signal 13)>
# p t2.value #=> #<Process::Status: pid 13510 exit 0>
# }
# }
#
# wait_thr.value waits the termination of the process.
# The block form also waits the process when it returns.
@ -60,26 +100,56 @@ module Open3
# Closing stdin, stdout and stderr does not wait the process.
#
def popen3(*cmd)
pw = IO::pipe # pipe[0] for read, pipe[1] for write
pr = IO::pipe
pe = IO::pipe
if Hash === cmd.last
opts = cmd.pop.dup
else
opts = {}
end
pid = spawn(*cmd, STDIN=>pw[0], STDOUT=>pr[1], STDERR=>pe[1])
child_io = []
parent_io = []
if !opts.include?(STDIN)
pw = IO.pipe # pipe[0] for read, pipe[1] for write
opts[STDIN] = pw[0]
pw[1].sync = true
child_io << pw[0]
parent_io << pw[1]
elsif opts[STDIN] == nil
opts.delete(STDIN)
end
if !opts.include?(STDOUT)
pr = IO.pipe
opts[STDOUT] = pr[1]
child_io << pr[1]
parent_io << pr[0]
elsif opts[STDOUT] == nil
opts.delete(STDOUT)
end
if !opts.include?(STDERR)
pe = IO.pipe
opts[STDERR] = pe[1]
child_io << pe[1]
parent_io << pe[0]
elsif opts[STDERR] == nil
opts.delete(STDERR)
end
pid = spawn(*cmd, opts)
wait_thr = Process.detach(pid)
pw[0].close
pr[1].close
pe[1].close
pi = [pw[1], pr[0], pe[0], wait_thr]
pw[1].sync = true
child_io.each {|io| io.close }
result = [*parent_io, wait_thr]
if defined? yield
begin
return yield(*pi)
return yield(*result)
ensure
[pw[1], pr[0], pe[0]].each{|p| p.close unless p.closed?}
parent_io.each{|io| io.close unless io.closed?}
wait_thr.join
end
end
pi
result
end
module_function :popen3
end

205
test/test_open3.rb Normal file
View file

@ -0,0 +1,205 @@
require 'test/unit'
require 'open3'
require 'shellwords'
require_relative 'ruby/envutil'
class TestOpen3 < Test::Unit::TestCase
RUBY = EnvUtil.rubybin
def test_exit_status
Open3.popen3(RUBY, '-e', 'exit true') {|i,o,e,t|
assert_equal(true, t.value.success?)
}
Open3.popen3(RUBY, '-e', 'exit false') {|i,o,e,t|
assert_equal(false, t.value.success?)
}
end
def test_stdin
Open3.popen3(RUBY, '-e', 'exit STDIN.gets.chomp == "t"') {|i,o,e,t|
i.puts 't'
assert_equal(true, t.value.success?)
}
Open3.popen3(RUBY, '-e', 'exit STDIN.gets.chomp == "t"') {|i,o,e,t|
i.puts 'f'
assert_equal(false, t.value.success?)
}
end
def test_stdout
Open3.popen3(RUBY, '-e', 'STDOUT.print "foo"') {|i,o,e,t|
assert_equal("foo", o.read)
}
end
def test_stderr
Open3.popen3(RUBY, '-e', 'STDERR.print "bar"') {|i,o,e,t|
assert_equal("bar", e.read)
}
end
def test_block
r = Open3.popen3(RUBY, '-e', 'STDOUT.print STDIN.read') {|i,o,e,t|
i.print "baz"
i.close
assert_equal("baz", o.read)
"qux"
}
assert_equal("qux", r)
end
def test_noblock
i,o,e,t = Open3.popen3(RUBY, '-e', 'STDOUT.print STDIN.read')
i.print "baz"
i.close
assert_equal("baz", o.read)
ensure
i.close if !i.closed?
o.close if !o.closed?
e.close if !e.closed?
end
def test_commandline
commandline = Shellwords.join([RUBY, '-e', 'print "quux"'])
Open3.popen3(commandline) {|i,o,e,t|
assert_equal("quux", o.read)
}
end
def test_pid
Open3.popen3(RUBY, '-e', 'print $$') {|i,o,e,t|
pid = o.read.to_i
assert_equal(pid, t[:pid])
assert_equal(pid, t.pid)
}
end
def test_disable
Open3.popen3(RUBY, '-e', '', STDIN=>nil) {|o,e,t|
assert_kind_of(Thread, t)
}
Open3.popen3(RUBY, '-e', '', STDOUT=>nil) {|i,e,t|
assert_kind_of(Thread, t)
}
Open3.popen3(RUBY, '-e', '', STDERR=>nil) {|i,o,t|
assert_kind_of(Thread, t)
}
Open3.popen3(RUBY, '-e', '', STDIN=>nil, STDOUT=>nil, STDERR=>nil) {|t|
assert_kind_of(Thread, t)
}
end
def with_pipe
r, w = IO.pipe
yield r, w
ensure
r.close if !r.closed?
w.close if !w.closed?
end
def with_reopen(io, arg)
old = io.dup
io.reopen(arg)
yield old
ensure
io.reopen(old)
old.close if old && !old.closed?
end
def test_disable_stdin
with_pipe {|r, w|
with_reopen(STDIN, r) {|old|
Open3.popen3(RUBY, '-e', 's=STDIN.read; STDOUT.print s+"o"; STDERR.print s+"e"', STDIN=>nil) {|o,e,t|
assert_kind_of(Thread, t)
w.print "x"
w.close
assert_equal("xo", o.read)
assert_equal("xe", e.read)
}
}
}
end
def test_disable_stdout
with_pipe {|r, w|
with_reopen(STDOUT, w) {|old|
w.close
Open3.popen3(RUBY, '-e', 's=STDIN.read; STDOUT.print s+"o"; STDERR.print s+"e"', STDOUT=>nil) {|i,e,t|
assert_kind_of(Thread, t)
i.print "y"
i.close
STDOUT.reopen(old)
assert_equal("yo", r.read)
assert_equal("ye", e.read)
}
}
}
end
def test_disable_stderr
with_pipe {|r, w|
with_reopen(STDERR, w) {|old|
w.close
Open3.popen3(RUBY, '-e', 's=STDIN.read; STDOUT.print s+"o"; STDERR.print s+"e"', STDERR=>nil) {|i,o,t|
assert_kind_of(Thread, t)
i.print "y"
i.close
STDERR.reopen(old)
assert_equal("yo", o.read)
assert_equal("ye", r.read)
}
}
}
end
def test_plug_pipe
Open3.popen3(RUBY, '-e', 'STDOUT.print "1"') {|i1,o1,e1,t1|
Open3.popen3(RUBY, '-e', 'STDOUT.print STDIN.read+"2"', STDIN=>o1) {|o2,e2,t2|
assert_equal("12", o2.read)
}
}
end
def test_tree_pipe
ia,oa,ea,ta = Open3.popen3(RUBY, '-e', 'i=STDIN.read; STDOUT.print i+"a"; STDERR.print i+"A"')
ob,eb,tb = Open3.popen3(RUBY, '-e', 'i=STDIN.read; STDOUT.print i+"b"; STDERR.print i+"B"', STDIN=>oa)
oc,ec,tc = Open3.popen3(RUBY, '-e', 'i=STDIN.read; STDOUT.print i+"c"; STDERR.print i+"C"', STDIN=>ob)
od,ed,td = Open3.popen3(RUBY, '-e', 'i=STDIN.read; STDOUT.print i+"d"; STDERR.print i+"D"', STDIN=>eb)
oe,ee,te = Open3.popen3(RUBY, '-e', 'i=STDIN.read; STDOUT.print i+"e"; STDERR.print i+"E"', STDIN=>ea)
of,ef,tf = Open3.popen3(RUBY, '-e', 'i=STDIN.read; STDOUT.print i+"f"; STDERR.print i+"F"', STDIN=>oe)
og,eg,tg = Open3.popen3(RUBY, '-e', 'i=STDIN.read; STDOUT.print i+"g"; STDERR.print i+"G"', STDIN=>ee)
oa.close
ea.close
ob.close
eb.close
oe.close
ee.close
ia.print "0"
ia.close
assert_equal("0abc", oc.read)
assert_equal("0abC", ec.read)
assert_equal("0aBd", od.read)
assert_equal("0aBD", ed.read)
assert_equal("0Aef", of.read)
assert_equal("0AeF", ef.read)
assert_equal("0AEg", og.read)
assert_equal("0AEG", eg.read)
ensure
ia.close if !ia.closed?
oa.close if !oa.closed?
ea.close if !ea.closed?
ob.close if !ob.closed?
eb.close if !eb.closed?
oc.close if !oc.closed?
ec.close if !ec.closed?
od.close if !od.closed?
ed.close if !ed.closed?
oe.close if !oe.closed?
ee.close if !ee.closed?
of.close if !of.closed?
ef.close if !ef.closed?
og.close if !og.closed?
eg.close if !eg.closed?
end
end