open4-1.3.4/0000755000004100000410000000000012342130675012577 5ustar www-datawww-dataopen4-1.3.4/README.erb0000644000004100000410000002610312342130675014230 0ustar www-datawww-dataURIS http://rubyforge.org/projects/codeforpeople/ http://www.codeforpeople.com/lib/ruby/ SYNOPSIS open child process with handles on pid, stdin, stdout, and stderr: manage child processes and their io handles easily. INSTALL ~> gem install open4 SAMPLES ---------------------------------------------------------------------------- simple usage ---------------------------------------------------------------------------- harp: > cat sample/simple.rb require "open4" pid, stdin, stdout, stderr = Open4::popen4 "sh" stdin.puts "echo 42.out" stdin.puts "echo 42.err 1>&2" stdin.close ignored, status = Process::waitpid2 pid puts "pid : #{ pid }" puts "stdout : #{ stdout.read.strip }" puts "stderr : #{ stderr.read.strip }" puts "status : #{ status.inspect }" puts "exitstatus : #{ status.exitstatus }" harp: > ruby sample/simple.rb pid : 17273 stdout : 42.out stderr : 42.err status : # exitstatus : 0 ---------------------------------------------------------------------------- in block form - the child process is automatically waited for ---------------------------------------------------------------------------- harp: > cat sample/block.rb require 'open4' status = Open4::popen4("sh") do |pid, stdin, stdout, stderr| stdin.puts "echo 42.out" stdin.puts "echo 42.err 1>&2" stdin.close puts "pid : #{ pid }" puts "stdout : #{ stdout.read.strip }" puts "stderr : #{ stderr.read.strip }" end puts "status : #{ status.inspect }" puts "exitstatus : #{ status.exitstatus }" harp: > ruby sample/block.rb pid : 17295 stdout : 42.out stderr : 42.err status : # exitstatus : 0 ---------------------------------------------------------------------------- exceptions are marshaled from child to parent if fork/exec fails ---------------------------------------------------------------------------- harp: > cat sample/exception.rb require "open4" Open4::popen4 "noexist" harp: > ruby sample/exception.rb /dmsp/reference/ruby-1.8.1//lib/ruby/site_ruby/open4.rb:100:in `popen4': No such file or directory - noexist (Errno::ENOENT) from sample/exception.rb:3 ---------------------------------------------------------------------------- the spawn method provides and even more convenient method of running a process, allowing any object that supports 'each', 'read', or 'to_s' to be given as stdin and any objects that support '<<' to be given as stdout/stderr. an exception is thrown if the exec'd cmd fails (nonzero exitstatus) unless the option 'raise'=>false is given ---------------------------------------------------------------------------- harp: > cat sample/spawn.rb require 'open4' include Open4 cat = ' ruby -e" ARGF.each{|line| STDOUT << line} " ' stdout, stderr = '', '' status = spawn cat, 'stdin' => '42', 'stdout' => stdout, 'stderr' => stderr p status p stdout p stderr stdout, stderr = '', '' status = spawn cat, 0=>'42', 1=>stdout, 2=>stderr p status p stdout p stderr harp: > RUBYLIB=lib ruby sample/spawn.rb 0 "42" "" 0 "42" "" ---------------------------------------------------------------------------- the bg/background method is similar to spawn, but the process is automatically set running in a thread. the returned thread has several methods added dynamically which return the pid and blocking calls to the exitstatus. ---------------------------------------------------------------------------- harp: > cat sample/bg.rb require 'yaml' require 'open4' include Open4 stdin = '42' stdout = '' stderr = '' t = bg 'ruby -e"sleep 4; puts ARGF.read"', 0=>stdin, 1=>stdout, 2=>stderr waiter = Thread.new{ y t.pid => t.exitstatus } # t.exitstatus is a blocking call! while((status = t.status)) y "status" => status sleep 1 end waiter.join y "stdout" => stdout harp: > ruby sample/bg.rb --- status: run --- status: sleep --- status: sleep --- status: sleep --- 21357: 0 --- stdout: "42\n" ---------------------------------------------------------------------------- the timeout methods can be used to ensure execution is preceding at the desired interval. note also how to setup a 'pipeline' ---------------------------------------------------------------------------- harp: > cat sample/stdin_timeout.rb require 'open4' producer = 'ruby -e" STDOUT.sync = true; loop{sleep(rand+rand) and puts 42} "' consumer = 'ruby -e" STDOUT.sync = true; STDIN.each{|line| puts line} "' open4(producer) do |pid, i, o, e| open4.spawn consumer, :stdin=>o, :stdout=>STDOUT, :stdin_timeout => 1.4 end harp: > ruby sample/stdin_timeout.rb 42 42 42 42 42 /dmsp/reference/ruby-1.8.1//lib/ruby/1.8/timeout.rb:42:in `relay': execution expired (Timeout::Error) ---------------------------------------------------------------------------- pfork4 is similar to popen4, but instead of executing a command, it runs ruby code in a child process. if the child process raises an exception, it propagates to the parent. ---------------------------------------------------------------------------- harp: > cat sample/pfork4.rb require 'open4' echo = lambda do $stdout.write $stdin.read raise 'finish implementing me' end org_message = "hello, world!" got_message = nil exception = nil begin Open4.pfork4(echo) do |cid, stdin, stdout, stderr| stdin.write org_message stdin.close got_message = stdout.read end rescue RuntimeError => e exception = e.to_s end puts "org_message: #{org_message}" puts "got_message: #{got_message}" puts "exception : #{exception}" harp: > ruby sample/pfork4.rb org_message: hello, world! got_message: hello, world! exception : finish implementing me HISTORY 1.0.0 - added ability for spawn to take a proc (respond_to?(:call)) cmd = ' ruby -e" 42.times{ puts 0b101010 } " ' include Open4 spawn cmd, :stdout => lambda{|buf| puts buf} 0.9.5: - another patch from Corey Jewett, this time dealing with ruby's handling of chdir and threads. basically the 'cwd' keyword to open4 cannot work with multiple threads (aka background) because ruby cannot cause green threads to have an actuall different working dir. the moral is that the :cwd/'cwd' keyword to spawn will work with 0 or 1 threads in effect. 0.9.4: - patch to #background from Corey Jewett 0.9.3: - removed some debugging output accidentally left in 0.9.2. arggh! 0.9.2: - fixed a descriptor leak. thanks Andre Nathan. 0.9.1: - fixed warning with '-w' : @cid not initialized. thanks blaise tarr. 0.9.0: - added the ability for open4.spawn to take either an array of arguments or multiple arguments in order to specify the argv for the command run. for example open4.spawn ['touch', 'difficult to "quote"'], :stdout=>STDOUT same thing open4.spawn 'touch', 'difficult to "quote"', :stdout=>STDOUT thanks to jordan breeding for this suggestion - added 'cwd'/:cwd keyword. usage is pretty obivous open4.spawn 'pwd', 1=>STDOUT, :cwd=>'/tmp' #=> /tmp this one also from jordan 0.8.0: - fixed a critical bug whereby a process producing tons of stdout, but for which the stdout was not handled, would cause the child process to become blocked/hung writing to the pipe. eg, this command would cause a hang include Open4 spawn 'ruby -e" puts Array.new(65536){ 42 } "' whereas this one would not include Open4 spawn 'ruby -e" puts Array.new(65536){ 42 } "', :stdout=>StringIO.new this version handles the former by spawning a 'null' thread which reads, but does not process stdout/stderr. that way commands which generate tons of output will never become blocked. 0.7.0: - merged functionality of exitstatus/status keywords: include Open4 spawn 'ruby -e "exit 42"' # raises spawn 'ruby -e "exit 42"', :status=>true # ok, returns status spawn 'ruby -e "exit 42"', :status=>42 # raises if status != 42 spawn 'ruby -e "exit 42"', :status=>0,42 # raises if status != 0||42 - the 0.6.0 was broken on rubyforge... this release fixes that (somehow!?) 0.6.0: - added feature for exitstatus to be list of acceptable exit statuses Open4.spawn 'ruby -e "exit 42"' # raises Open4.spawn 'ruby -e "exit 42"', :exitstatus=>[0,42] # ok - added :status switch, which will always simply return the status (no error thrown for failure) Open4.spawn 'ruby -e "exit 42"' # raises status = Open4.spawn 'ruby -e "exit 42"', :status=>true # ok note, however, that any SpawnError does in fact contain the failed status so, even when they are thrown, error status can be retrieved: include Open4 status = begin spawn 'ruby -e "exit 42"' rescue SpawnError => e warn{ e } e.status end 0.5.1: - fixes a __critical__ but in ThreadEnsemble class that had a race condition that could cause thread deadlock. sorry bout that folks. 0.5.0: - on the suggestion of tim pease (thanks tim!), i added timeout features to open4. the command run may have an overall timeout and individual timeouts set for each of the io handles. for example cmd = 'command_that_produce_out_at_one_second_intervals' open4.spawn cmd, :stdout_timeout => 2 or cmd = 'command_that_should_complete_in_about_one_minute' open4.spawn cmd, :timeout => 60 or cmd = 'consumes_input_at_one_line_per_second_rate' input = %w( 42 forty-two 42.0 ) open4.spawn cmd, :stdin=>input, :stdin_timeout=>1 - added 'open4' alias so one can write open4.spawn vs Open4.spawn or even open4(cmd) do |pid,i,o,e| end - added signal info to SpawnError 0.4.0: - improved error handling contributed by jordan breeding. - introduction of background/bg method 0.3.0 : - bug fix from jordan breeding. general clean up. added spawn method. 0.2.0 : - added exception marshaled from child -> parent when exec fails. thanks to jordan breeding for a patch (yay!) and paul brannan for this most excellent idea. 0.1.0 : - fixed docs to correctly show return value of popen4 (pid first not last). thanks Stefanie Tellex for catching this. 0.0.0 : - initial version AUTHOR ara.t.howard@gmail.com LICENSE ruby's open4-1.3.4/README0000644000004100000410000002610312342130675013461 0ustar www-datawww-dataURIS http://rubyforge.org/projects/codeforpeople/ http://www.codeforpeople.com/lib/ruby/ SYNOPSIS open child process with handles on pid, stdin, stdout, and stderr: manage child processes and their io handles easily. INSTALL ~> gem install open4 SAMPLES ---------------------------------------------------------------------------- simple usage ---------------------------------------------------------------------------- harp: > cat sample/simple.rb require "open4" pid, stdin, stdout, stderr = Open4::popen4 "sh" stdin.puts "echo 42.out" stdin.puts "echo 42.err 1>&2" stdin.close ignored, status = Process::waitpid2 pid puts "pid : #{ pid }" puts "stdout : #{ stdout.read.strip }" puts "stderr : #{ stderr.read.strip }" puts "status : #{ status.inspect }" puts "exitstatus : #{ status.exitstatus }" harp: > ruby sample/simple.rb pid : 17273 stdout : 42.out stderr : 42.err status : # exitstatus : 0 ---------------------------------------------------------------------------- in block form - the child process is automatically waited for ---------------------------------------------------------------------------- harp: > cat sample/block.rb require 'open4' status = Open4::popen4("sh") do |pid, stdin, stdout, stderr| stdin.puts "echo 42.out" stdin.puts "echo 42.err 1>&2" stdin.close puts "pid : #{ pid }" puts "stdout : #{ stdout.read.strip }" puts "stderr : #{ stderr.read.strip }" end puts "status : #{ status.inspect }" puts "exitstatus : #{ status.exitstatus }" harp: > ruby sample/block.rb pid : 17295 stdout : 42.out stderr : 42.err status : # exitstatus : 0 ---------------------------------------------------------------------------- exceptions are marshaled from child to parent if fork/exec fails ---------------------------------------------------------------------------- harp: > cat sample/exception.rb require "open4" Open4::popen4 "noexist" harp: > ruby sample/exception.rb /dmsp/reference/ruby-1.8.1//lib/ruby/site_ruby/open4.rb:100:in `popen4': No such file or directory - noexist (Errno::ENOENT) from sample/exception.rb:3 ---------------------------------------------------------------------------- the spawn method provides and even more convenient method of running a process, allowing any object that supports 'each', 'read', or 'to_s' to be given as stdin and any objects that support '<<' to be given as stdout/stderr. an exception is thrown if the exec'd cmd fails (nonzero exitstatus) unless the option 'raise'=>false is given ---------------------------------------------------------------------------- harp: > cat sample/spawn.rb require 'open4' include Open4 cat = ' ruby -e" ARGF.each{|line| STDOUT << line} " ' stdout, stderr = '', '' status = spawn cat, 'stdin' => '42', 'stdout' => stdout, 'stderr' => stderr p status p stdout p stderr stdout, stderr = '', '' status = spawn cat, 0=>'42', 1=>stdout, 2=>stderr p status p stdout p stderr harp: > RUBYLIB=lib ruby sample/spawn.rb 0 "42" "" 0 "42" "" ---------------------------------------------------------------------------- the bg/background method is similar to spawn, but the process is automatically set running in a thread. the returned thread has several methods added dynamically which return the pid and blocking calls to the exitstatus. ---------------------------------------------------------------------------- harp: > cat sample/bg.rb require 'yaml' require 'open4' include Open4 stdin = '42' stdout = '' stderr = '' t = bg 'ruby -e"sleep 4; puts ARGF.read"', 0=>stdin, 1=>stdout, 2=>stderr waiter = Thread.new{ y t.pid => t.exitstatus } # t.exitstatus is a blocking call! while((status = t.status)) y "status" => status sleep 1 end waiter.join y "stdout" => stdout harp: > ruby sample/bg.rb --- status: run --- status: sleep --- status: sleep --- status: sleep --- 21357: 0 --- stdout: "42\n" ---------------------------------------------------------------------------- the timeout methods can be used to ensure execution is preceding at the desired interval. note also how to setup a 'pipeline' ---------------------------------------------------------------------------- harp: > cat sample/stdin_timeout.rb require 'open4' producer = 'ruby -e" STDOUT.sync = true; loop{sleep(rand+rand) and puts 42} "' consumer = 'ruby -e" STDOUT.sync = true; STDIN.each{|line| puts line} "' open4(producer) do |pid, i, o, e| open4.spawn consumer, :stdin=>o, :stdout=>STDOUT, :stdin_timeout => 1.4 end harp: > ruby sample/stdin_timeout.rb 42 42 42 42 42 /dmsp/reference/ruby-1.8.1//lib/ruby/1.8/timeout.rb:42:in `relay': execution expired (Timeout::Error) ---------------------------------------------------------------------------- pfork4 is similar to popen4, but instead of executing a command, it runs ruby code in a child process. if the child process raises an exception, it propagates to the parent. ---------------------------------------------------------------------------- harp: > cat sample/pfork4.rb require 'open4' echo = lambda do $stdout.write $stdin.read raise 'finish implementing me' end org_message = "hello, world!" got_message = nil exception = nil begin Open4.pfork4(echo) do |cid, stdin, stdout, stderr| stdin.write org_message stdin.close got_message = stdout.read end rescue RuntimeError => e exception = e.to_s end puts "org_message: #{org_message}" puts "got_message: #{got_message}" puts "exception : #{exception}" harp: > ruby sample/pfork4.rb org_message: hello, world! got_message: hello, world! exception : finish implementing me HISTORY 1.0.0 - added ability for spawn to take a proc (respond_to?(:call)) cmd = ' ruby -e" 42.times{ puts 0b101010 } " ' include Open4 spawn cmd, :stdout => lambda{|buf| puts buf} 0.9.5: - another patch from Corey Jewett, this time dealing with ruby's handling of chdir and threads. basically the 'cwd' keyword to open4 cannot work with multiple threads (aka background) because ruby cannot cause green threads to have an actuall different working dir. the moral is that the :cwd/'cwd' keyword to spawn will work with 0 or 1 threads in effect. 0.9.4: - patch to #background from Corey Jewett 0.9.3: - removed some debugging output accidentally left in 0.9.2. arggh! 0.9.2: - fixed a descriptor leak. thanks Andre Nathan. 0.9.1: - fixed warning with '-w' : @cid not initialized. thanks blaise tarr. 0.9.0: - added the ability for open4.spawn to take either an array of arguments or multiple arguments in order to specify the argv for the command run. for example open4.spawn ['touch', 'difficult to "quote"'], :stdout=>STDOUT same thing open4.spawn 'touch', 'difficult to "quote"', :stdout=>STDOUT thanks to jordan breeding for this suggestion - added 'cwd'/:cwd keyword. usage is pretty obivous open4.spawn 'pwd', 1=>STDOUT, :cwd=>'/tmp' #=> /tmp this one also from jordan 0.8.0: - fixed a critical bug whereby a process producing tons of stdout, but for which the stdout was not handled, would cause the child process to become blocked/hung writing to the pipe. eg, this command would cause a hang include Open4 spawn 'ruby -e" puts Array.new(65536){ 42 } "' whereas this one would not include Open4 spawn 'ruby -e" puts Array.new(65536){ 42 } "', :stdout=>StringIO.new this version handles the former by spawning a 'null' thread which reads, but does not process stdout/stderr. that way commands which generate tons of output will never become blocked. 0.7.0: - merged functionality of exitstatus/status keywords: include Open4 spawn 'ruby -e "exit 42"' # raises spawn 'ruby -e "exit 42"', :status=>true # ok, returns status spawn 'ruby -e "exit 42"', :status=>42 # raises if status != 42 spawn 'ruby -e "exit 42"', :status=>0,42 # raises if status != 0||42 - the 0.6.0 was broken on rubyforge... this release fixes that (somehow!?) 0.6.0: - added feature for exitstatus to be list of acceptable exit statuses Open4.spawn 'ruby -e "exit 42"' # raises Open4.spawn 'ruby -e "exit 42"', :exitstatus=>[0,42] # ok - added :status switch, which will always simply return the status (no error thrown for failure) Open4.spawn 'ruby -e "exit 42"' # raises status = Open4.spawn 'ruby -e "exit 42"', :status=>true # ok note, however, that any SpawnError does in fact contain the failed status so, even when they are thrown, error status can be retrieved: include Open4 status = begin spawn 'ruby -e "exit 42"' rescue SpawnError => e warn{ e } e.status end 0.5.1: - fixes a __critical__ but in ThreadEnsemble class that had a race condition that could cause thread deadlock. sorry bout that folks. 0.5.0: - on the suggestion of tim pease (thanks tim!), i added timeout features to open4. the command run may have an overall timeout and individual timeouts set for each of the io handles. for example cmd = 'command_that_produce_out_at_one_second_intervals' open4.spawn cmd, :stdout_timeout => 2 or cmd = 'command_that_should_complete_in_about_one_minute' open4.spawn cmd, :timeout => 60 or cmd = 'consumes_input_at_one_line_per_second_rate' input = %w( 42 forty-two 42.0 ) open4.spawn cmd, :stdin=>input, :stdin_timeout=>1 - added 'open4' alias so one can write open4.spawn vs Open4.spawn or even open4(cmd) do |pid,i,o,e| end - added signal info to SpawnError 0.4.0: - improved error handling contributed by jordan breeding. - introduction of background/bg method 0.3.0 : - bug fix from jordan breeding. general clean up. added spawn method. 0.2.0 : - added exception marshaled from child -> parent when exec fails. thanks to jordan breeding for a patch (yay!) and paul brannan for this most excellent idea. 0.1.0 : - fixed docs to correctly show return value of popen4 (pid first not last). thanks Stefanie Tellex for catching this. 0.0.0 : - initial version AUTHOR ara.t.howard@gmail.com LICENSE ruby's open4-1.3.4/white_box/0000755000004100000410000000000012342130675014567 5ustar www-datawww-dataopen4-1.3.4/white_box/leak.rb0000644000004100000410000000047212342130675016033 0ustar www-datawww-datarequire 'open4' pid = Process.pid fds = lambda{|pid| Dir["/proc/#{ pid }/fd/*"]} loop do before = fds[pid] Open4.popen4 'ruby -e"buf = STDIN.read; STDOUT.puts buf; STDERR.puts buf "' do |p,i,o,e| i.puts 42 i.close_write o.read e.read end after = fds[pid] p(after - before) puts end open4-1.3.4/lib/0000755000004100000410000000000012342130675013345 5ustar www-datawww-dataopen4-1.3.4/lib/open4.rb0000644000004100000410000002526412342130675014730 0ustar www-datawww-data# vim: ts=2:sw=2:sts=2:et:fdm=marker require 'fcntl' require 'timeout' require 'thread' module Open4 VERSION = '1.3.4' def Open4.version() VERSION end def Open4.description 'open child process with handles on pid, stdin, stdout, and stderr: manage child processes and their io handles easily.' end class Error < ::StandardError; end def pfork4(fun, &b) Open4.do_popen(b, :block) do |ps_read, _| ps_read.close begin fun.call rescue SystemExit => e # Make it seem to the caller that calling Kernel#exit in +fun+ kills # the child process normally. Kernel#exit! bypasses this rescue # block. exit! e.status else exit! 0 end end end module_function :pfork4 def popen4(*cmd, &b) Open4.do_popen(b, :init) do |ps_read, ps_write| ps_read.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) ps_write.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) exec(*cmd) raise 'forty-two' # Is this really needed? end end alias open4 popen4 module_function :popen4 module_function :open4 def popen4ext(closefds=false, *cmd, &b) Open4.do_popen(b, :init, closefds) do |ps_read, ps_write| ps_read.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) ps_write.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) exec(*cmd) raise 'forty-two' # Is this really needed? end end module_function :popen4ext def self.do_popen(b = nil, exception_propagation_at = nil, closefds=false, &cmd) pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe verbose = $VERBOSE begin $VERBOSE = nil cid = fork { if closefds exlist = [0, 1, 2] | [pw,pr,pe,ps].map{|p| [p.first.fileno, p.last.fileno] }.flatten ObjectSpace.each_object(IO){|io| io.close if (not io.closed?) and (not exlist.include? io.fileno) rescue nil } end pw.last.close STDIN.reopen pw.first pw.first.close pr.first.close STDOUT.reopen pr.last pr.last.close pe.first.close STDERR.reopen pe.last pe.last.close STDOUT.sync = STDERR.sync = true begin cmd.call(ps) rescue Exception => e Marshal.dump(e, ps.last) ps.last.flush ensure ps.last.close unless ps.last.closed? end exit! } ensure $VERBOSE = verbose end [ pw.first, pr.last, pe.last, ps.last ].each { |fd| fd.close } Open4.propagate_exception cid, ps.first if exception_propagation_at == :init pw.last.sync = true pi = [ pw.last, pr.first, pe.first ] begin return [cid, *pi] unless b begin b.call(cid, *pi) ensure pi.each { |fd| fd.close unless fd.closed? } end Open4.propagate_exception cid, ps.first if exception_propagation_at == :block Process.waitpid2(cid).last ensure ps.first.close unless ps.first.closed? end end def self.propagate_exception(cid, ps_read) e = Marshal.load ps_read raise Exception === e ? e : "unknown failure!" rescue EOFError # Child process did not raise exception. rescue # Child process raised exception; wait it in order to avoid a zombie. Process.waitpid2 cid raise ensure ps_read.close end class SpawnError < Error attr 'cmd' attr 'status' attr 'signals' def exitstatus @status.exitstatus end def initialize cmd, status @cmd, @status = cmd, status @signals = {} if status.signaled? @signals['termsig'] = status.termsig @signals['stopsig'] = status.stopsig end sigs = @signals.map{|k,v| "#{ k }:#{ v.inspect }"}.join(' ') super "cmd <#{ cmd }> failed with status <#{ exitstatus.inspect }> signals <#{ sigs }>" end end class ThreadEnsemble attr 'threads' def initialize cid @cid, @threads, @argv, @done, @running = cid, [], [], Queue.new, false @killed = false end def add_thread *a, &b @running ? raise : (@argv << [a, b]) end # # take down process more nicely # def killall c = Thread.critical return nil if @killed Thread.critical = true (@threads - [Thread.current]).each{|t| t.kill rescue nil} @killed = true ensure Thread.critical = c end def run @running = true begin @argv.each do |a, b| @threads << Thread.new(*a) do |*_a| begin b[*_a] ensure killall rescue nil if $! @done.push Thread.current end end end rescue killall raise ensure all_done end @threads.map{|t| t.value} end def all_done @threads.size.times{ @done.pop } end end def to timeout = nil Timeout.timeout(timeout){ yield } end module_function :to def new_thread *a, &b cur = Thread.current Thread.new(*a) do |*_a| begin b[*_a] rescue Exception => e cur.raise e end end end module_function :new_thread def getopts opts = {} lambda do |*args| keys, default, _ = args catch(:opt) do [keys].flatten.each do |key| [key, key.to_s, key.to_s.intern].each do |_key| throw :opt, opts[_key] if opts.has_key?(_key) end end default end end end module_function :getopts def relay src, dst = nil, t = nil send_dst = if dst.respond_to?(:call) lambda{|buf| dst.call(buf)} elsif dst.respond_to?(:<<) lambda{|buf| dst << buf } else lambda{|buf| buf } end unless src.nil? if src.respond_to? :gets while buf = to(t){ src.gets } send_dst[buf] end elsif src.respond_to? :each q = Queue.new th = nil timer_set = lambda do |_t| th = new_thread{ to(_t){ q.pop } } end timer_cancel = lambda do |_t| th.kill if th rescue nil end timer_set[t] begin src.each do |_buf| timer_cancel[t] send_dst[_buf] timer_set[t] end ensure timer_cancel[t] end elsif src.respond_to? :read buf = to(t){ src.read } send_dst[buf] else buf = to(t){ src.to_s } send_dst[buf] end end end module_function :relay def spawn arg, *argv argv.unshift(arg) opts = ((argv.size > 1 and Hash === argv.last) ? argv.pop : {}) argv.flatten! cmd = argv.join(' ') getopt = getopts opts ignore_exit_failure = getopt[ 'ignore_exit_failure', getopt['quiet', false] ] ignore_exec_failure = getopt[ 'ignore_exec_failure', !getopt['raise', true] ] exitstatus = getopt[ %w( exitstatus exit_status status ) ] stdin = getopt[ %w( stdin in i 0 ) << 0 ] stdout = getopt[ %w( stdout out o 1 ) << 1 ] stderr = getopt[ %w( stderr err e 2 ) << 2 ] pid = getopt[ 'pid' ] timeout = getopt[ %w( timeout spawn_timeout ) ] stdin_timeout = getopt[ %w( stdin_timeout ) ] stdout_timeout = getopt[ %w( stdout_timeout io_timeout ) ] stderr_timeout = getopt[ %w( stderr_timeout ) ] status = getopt[ %w( status ) ] cwd = getopt[ %w( cwd dir ) ] closefds = getopt[ %w( close_fds ) ] exitstatus = case exitstatus when TrueClass, FalseClass ignore_exit_failure = true if exitstatus [0] else [*(exitstatus || 0)].map{|i| Integer i} end stdin ||= '' if stdin_timeout stdout ||= '' if stdout_timeout stderr ||= '' if stderr_timeout started = false status = begin chdir(cwd) do Timeout::timeout(timeout) do popen4ext(closefds, *argv) do |c, i, o, e| started = true %w( replace pid= << push update ).each do |msg| break(pid.send(msg, c)) if pid.respond_to? msg end te = ThreadEnsemble.new c te.add_thread(i, stdin) do |_i, _stdin| relay _stdin, _i, stdin_timeout _i.close rescue nil end te.add_thread(o, stdout) do |_o, _stdout| relay _o, _stdout, stdout_timeout end te.add_thread(e, stderr) do |_o, _stderr| # HACK: I think this is a bug relay e, _stderr, stderr_timeout end te.run end end end rescue raise unless(not started and ignore_exec_failure) end raise SpawnError.new(cmd, status) unless (ignore_exit_failure or (status.nil? and ignore_exec_failure) or exitstatus.include?(status.exitstatus)) status end module_function :spawn def chdir cwd, &block return(block.call Dir.pwd) unless cwd Dir.chdir cwd, &block end module_function :chdir def background arg, *argv require 'thread' q = Queue.new opts = { 'pid' => q, :pid => q } case argv.last when Hash argv.last.update opts else argv.push opts end thread = Thread.new(arg, argv){|_arg, _argv| spawn _arg, *_argv} sc = class << thread; self; end sc.module_eval { define_method(:pid){ @pid ||= q.pop } define_method(:spawn_status){ @spawn_status ||= value } define_method(:exitstatus){ @exitstatus ||= spawn_status.exitstatus } } thread end alias bg background module_function :background module_function :bg def maim pid, opts = {} getopt = getopts opts sigs = getopt[ 'signals', %w(SIGTERM SIGQUIT SIGKILL) ] suspend = getopt[ 'suspend', 4 ] pid = Integer pid existed = false sigs.each do |sig| begin Process.kill sig, pid existed = true rescue Errno::ESRCH return(existed ? nil : true) end return true unless alive? pid sleep suspend return true unless alive? pid end return(not alive?(pid)) end module_function :maim def alive pid pid = Integer pid begin Process.kill 0, pid true rescue Errno::ESRCH false end end alias alive? alive module_function :alive module_function :'alive?' end def open4(*cmd, &b) cmd.size == 0 ? Open4 : Open4::popen4(*cmd, &b) end open4-1.3.4/rakefile0000644000004100000410000002365212342130675014314 0ustar www-datawww-dataThis.rubyforge_project = 'codeforpeople' This.author = "Ara T. Howard" This.email = "ara.t.howard@gmail.com" This.homepage = "https://github.com/ahoward/#{ This.lib }" task :license do open('LICENSE', 'w'){|fd| fd.puts "Ruby"} end task :default do puts((Rake::Task.tasks.map{|task| task.name.gsub(/::/,':')} - ['default']).sort) end task :test do run_tests! end namespace :test do task(:unit){ run_tests!(:unit) } task(:functional){ run_tests!(:functional) } task(:integration){ run_tests!(:integration) } end def run_tests!(which = nil) which ||= '**' test_dir = File.join(This.dir, "test") test_glob ||= File.join(test_dir, "#{ which }/**_test.rb") test_rbs = Dir.glob(test_glob).sort div = ('=' * 119) line = ('-' * 119) test_rbs.each_with_index do |test_rb, index| testno = index + 1 command = "#{ This.ruby } -rubygems -w -I ./lib -I ./test/lib #{ test_rb }" puts say(div, :color => :cyan, :bold => true) say("@#{ testno } => ", :bold => true, :method => :print) say(command, :color => :cyan, :bold => true) say(line, :color => :cyan, :bold => true) system(command) say(line, :color => :cyan, :bold => true) status = $?.exitstatus if status.zero? say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print) say("SUCCESS", :color => :green, :bold => true) else say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print) say("FAILURE", :color => :red, :bold => true) end say(line, :color => :cyan, :bold => true) exit(status) unless status.zero? end end task :gemspec do ignore_extensions = ['git', 'svn', 'tmp', /sw./, 'bak', 'gem'] ignore_directories = ['pkg'] ignore_files = ['test/log'] shiteless = lambda do |list| list.delete_if do |entry| next unless test(?e, entry) extension = File.basename(entry).split(%r/[.]/).last ignore_extensions.any?{|ext| ext === extension} end list.delete_if do |entry| next unless test(?d, entry) dirname = File.expand_path(entry) ignore_directories.any?{|dir| File.expand_path(dir) == dirname} end list.delete_if do |entry| next unless test(?f, entry) filename = File.expand_path(entry) ignore_files.any?{|file| File.expand_path(file) == filename} end end lib = This.lib object = This.object version = This.version files = shiteless[Dir::glob("**/**")] executables = shiteless[Dir::glob("bin/*")].map{|exe| File.basename(exe)} #has_rdoc = true #File.exist?('doc') test_files = "test/#{ lib }.rb" if File.file?("test/#{ lib }.rb") summary = object.respond_to?(:summary) ? object.summary : "summary: #{ lib } kicks the ass" description = object.respond_to?(:description) ? object.description : "description: #{ lib } kicks the ass" license = object.respond_to?(:license) ? object.license : "Ruby" if This.extensions.nil? This.extensions = [] extensions = This.extensions %w( Makefile configure extconf.rb ).each do |ext| extensions << ext if File.exists?(ext) end end extensions = [extensions].flatten.compact if This.dependencies.nil? dependencies = [] else case This.dependencies when Hash dependencies = This.dependencies.values when Array dependencies = This.dependencies end end template = if test(?e, 'gemspec.erb') Template{ IO.read('gemspec.erb') } else Template { <<-__ ## <%= lib %>.gemspec # Gem::Specification::new do |spec| spec.name = <%= lib.inspect %> spec.version = <%= version.inspect %> spec.platform = Gem::Platform::RUBY spec.summary = <%= lib.inspect %> spec.description = <%= description.inspect %> spec.license = <%= license.inspect %> spec.files =\n<%= files.sort.pretty_inspect %> spec.executables = <%= executables.inspect %> spec.require_path = "lib" spec.test_files = <%= test_files.inspect %> <% dependencies.each do |lib_version| %> spec.add_dependency(*<%= Array(lib_version).flatten.inspect %>) <% end %> spec.extensions.push(*<%= extensions.inspect %>) spec.rubyforge_project = <%= This.rubyforge_project.inspect %> spec.author = <%= This.author.inspect %> spec.email = <%= This.email.inspect %> spec.homepage = <%= This.homepage.inspect %> end __ } end Fu.mkdir_p(This.pkgdir) gemspec = "#{ lib }.gemspec" open(gemspec, "w"){|fd| fd.puts(template)} This.gemspec = gemspec end task :gem => [:clean, :gemspec] do Fu.mkdir_p(This.pkgdir) before = Dir['*.gem'] cmd = "gem build #{ This.gemspec }" `#{ cmd }` after = Dir['*.gem'] gem = ((after - before).first || after.first) or abort('no gem!') Fu.mv(gem, This.pkgdir) This.gem = File.join(This.pkgdir, File.basename(gem)) end task :readme do samples = '' prompt = '~ > ' lib = This.lib version = This.version Dir['sample*/*'].sort.each do |sample| samples << "\n" << " <========< #{ sample } >========>" << "\n\n" cmd = "cat #{ sample }" samples << Util.indent(prompt + cmd, 2) << "\n\n" samples << Util.indent(`#{ cmd }`, 4) << "\n" cmd = "ruby #{ sample }" samples << Util.indent(prompt + cmd, 2) << "\n\n" cmd = "ruby -e'STDOUT.sync=true; exec %(ruby -I ./lib #{ sample })'" samples << Util.indent(`#{ cmd } 2>&1`, 4) << "\n" end template = if test(?e, 'README.erb') Template{ IO.read('README.erb') } else Template { <<-__ NAME #{ lib } DESCRIPTION INSTALL gem install #{ lib } SAMPLES #{ samples } __ } end open("README", "w"){|fd| fd.puts template} end task :clean do Dir[File.join(This.pkgdir, '**/**')].each{|entry| Fu.rm_rf(entry)} end task :release => [:clean, :gemspec, :gem] do gems = Dir[File.join(This.pkgdir, '*.gem')].flatten raise "which one? : #{ gems.inspect }" if gems.size > 1 raise "no gems?" if gems.size < 1 cmd = "gem push #{ This.gem }" puts cmd puts system(cmd) abort("cmd(#{ cmd }) failed with (#{ $?.inspect })") unless $?.exitstatus.zero? cmd = "rubyforge login && rubyforge add_release #{ This.rubyforge_project } #{ This.lib } #{ This.version } #{ This.gem }" puts cmd puts system(cmd) abort("cmd(#{ cmd }) failed with (#{ $?.inspect })") unless $?.exitstatus.zero? end BEGIN { # support for this rakefile # $VERBOSE = nil require 'ostruct' require 'erb' require 'fileutils' require 'rbconfig' require 'pp' # fu shortcut # Fu = FileUtils # cache a bunch of stuff about this rakefile/environment # This = OpenStruct.new This.file = File.expand_path(__FILE__) This.dir = File.dirname(This.file) This.pkgdir = File.join(This.dir, 'pkg') # grok lib # lib = ENV['LIB'] unless lib lib = File.basename(Dir.pwd).sub(/[-].*$/, '') end This.lib = lib # grok version # version = ENV['VERSION'] unless version require "./lib/#{ This.lib }" This.name = lib.capitalize This.object = eval(This.name) version = This.object.send(:version) end This.version = version # see if dependencies are export by the module # if This.object.respond_to?(:dependencies) This.dependencies = This.object.dependencies end # we need to know the name of the lib an it's version # abort('no lib') unless This.lib abort('no version') unless This.version # discover full path to this ruby executable # c = Config::CONFIG bindir = c["bindir"] || c['BINDIR'] ruby_install_name = c['ruby_install_name'] || c['RUBY_INSTALL_NAME'] || 'ruby' ruby_ext = c['EXEEXT'] || '' ruby = File.join(bindir, (ruby_install_name + ruby_ext)) This.ruby = ruby # some utils # module Util def indent(s, n = 2) s = unindent(s) ws = ' ' * n s.gsub(%r/^/, ws) end def unindent(s) indent = nil s.each_line do |line| next if line =~ %r/^\s*$/ indent = line[%r/^\s*/] and break end indent ? s.gsub(%r/^#{ indent }/, "") : s end extend self end # template support # class Template def initialize(&block) @block = block @template = block.call.to_s end def expand(b=nil) ERB.new(Util.unindent(@template)).result((b||@block).binding) end alias_method 'to_s', 'expand' end def Template(*args, &block) Template.new(*args, &block) end # colored console output support # This.ansi = { :clear => "\e[0m", :reset => "\e[0m", :erase_line => "\e[K", :erase_char => "\e[P", :bold => "\e[1m", :dark => "\e[2m", :underline => "\e[4m", :underscore => "\e[4m", :blink => "\e[5m", :reverse => "\e[7m", :concealed => "\e[8m", :black => "\e[30m", :red => "\e[31m", :green => "\e[32m", :yellow => "\e[33m", :blue => "\e[34m", :magenta => "\e[35m", :cyan => "\e[36m", :white => "\e[37m", :on_black => "\e[40m", :on_red => "\e[41m", :on_green => "\e[42m", :on_yellow => "\e[43m", :on_blue => "\e[44m", :on_magenta => "\e[45m", :on_cyan => "\e[46m", :on_white => "\e[47m" } def say(phrase, *args) options = args.last.is_a?(Hash) ? args.pop : {} options[:color] = args.shift.to_s.to_sym unless args.empty? keys = options.keys keys.each{|key| options[key.to_s.to_sym] = options.delete(key)} color = options[:color] bold = options.has_key?(:bold) parts = [phrase] parts.unshift(This.ansi[color]) if color parts.unshift(This.ansi[:bold]) if bold parts.push(This.ansi[:clear]) if parts.size > 1 method = options[:method] || :puts Kernel.send(method, parts.join) end # always run out of the project dir # Dir.chdir(This.dir) } open4-1.3.4/samples/0000755000004100000410000000000012342130675014243 5ustar www-datawww-dataopen4-1.3.4/samples/jesse-caldwell.rb0000644000004100000410000001045212342130675017470 0ustar www-datawww-data#!/usr/bin/env ruby require 'open4' require 'pp' # define a function we can call later. the function will take two # arguments: # command which we will run via open4 and be able to # send stdin as well as collect the pid, stderr and stdout. # input optional data string to send to command on stdin # # this returns a hash of the command's pid, stderr, stdout, and status. def run_cmd(command, input = nil) # we will use open4 in block form, which means that the variables # used inside the block will not be available once open4 has # finished. as long as variables are declared outside of the # block, they can be set inside the block and are available after # the block has finished. err = out = procid = status = verbose = nil # using a begin so we can use rescue later on. begin # run our command with open4 in block form. stat = Open4::popen4(command) do |pid, stdin, stdout, stderr| # the default behavior of ruby is to internally buffer I/O # ports when they are opened. open4 may not detect that stderr # and/or stdout has closed because ruby is helpfully buffering # the pipe for us. if open4 hangs, try uncommenting the next # two lines. # stderr.sync = true # stdout.sync = true # set procid to pid so we can see it outside of the block. procid = pid # if you want to use stdin, talk to stdin here. i tried it # with bc. generally i only need to capture output, not # interact with commands i'm running. stdin.puts input if input # stdin is opened write only. you'll raise an exception if # you try to read anything from it. here you can try to read # the first character from stdin. # stdin.gets(1) # now close stdin. stdin.close # make stderr and stdout available outside the block as well. # removing the read will return pointers to objects rather # than the data that the objects contain. out = stdout.read err = stderr.read # as stdin is write-only, stderr and stdout are read only. # you'll raise an exception if you try to write to either. # stderr.puts 'building appears to be on fire' # end of open4 block. pid, stdin, stdout and stderr are no # longer accessible. end # now outside of the open4 block, we can get the exit status # of our command by calling stat.exitstatus. status = stat.exitstatus # our function returns status from a command. however, if you # tell the function to run a command that does not exist, ruby # will raise an exception. we will trap that exception here, make # up a non-zero exit status, convert the ruby error to a string, # and populate err with it. rescue Errno::ENOENT => stderr status = 1 err = stderr.to_s # handle null commands gracefully rescue TypeError => stderr status = 2 err = 'Can\'t execute null command.' # done calling and/or rescuing open4. end # uncomment to make function print output. verbose = true # print the values if verbose is not nil. print "\n============================================================" if verbose print "\ncommand: #{ command }" if verbose print "\ninput : \n\n#{ input }\n" if (verbose and input) print "\npid : #{ procid }" if verbose print "\nstatus : #{ status }" if verbose print "\nstdout : #{ out }\n" if verbose print "\nstderr : #{ err }\n" if verbose print "============================================================\n" if verbose # now that (we think) we have handled everything, return a hash # with the process id, standard error, standard output, and the # exit status. return { :pid => procid, # integer :stderr => err, # string :stdout => out, # string :status => status, # integer } # return terminates function. code here will not run! print 'this will never show up.' # end of run_cmd function. end # this will raise an exception which our function will trap, # complaining that the command does not exist. cmd = run_cmd('/bin/does/not/exist') # something that will produce a fair amount of output. you do have # an nmap source tree lying around, right? cmd = run_cmd('cd nmap-5.51 ; ./configure') # bc, to illustrate using stdin. cmd = run_cmd('bc', "2^16\nquit") # uncomment to see hash returned by run_cmd function. # pp cmd # test function with null command cmd = run_cmd(nil) open4-1.3.4/samples/spawn.rb0000644000004100000410000000047312342130675015724 0ustar www-datawww-datarequire 'open4' include Open4 cat = 'ruby -e" ARGF.each{|line| STDOUT << line} "' stdout, stderr = '', '' status = spawn cat, 'stdin' => '42', 'stdout' => stdout, 'stderr' => stderr p status p stdout p stderr stdout, stderr = '', '' status = spawn cat, 0=>'42', 1=>stdout, 2=>stderr p status p stdout p stderr open4-1.3.4/samples/stdin_timeout.rb0000644000004100000410000000043412342130675017460 0ustar www-datawww-datarequire 'open4' producer = 'ruby -e" STDOUT.sync = true; loop{sleep(rand+rand) and puts 42} " 2>/dev/null' consumer = 'ruby -e" STDOUT.sync = true; STDIN.each{|line| puts line} "' open4(producer) do |pid, i, o, e| open4.spawn consumer, 0=>o, 1=>STDOUT, :stdin_timeout => 1.4 end open4-1.3.4/samples/exception.rb0000644000004100000410000000005112342130675016562 0ustar www-datawww-datarequire "open4" Open4::popen4 "noexist" open4-1.3.4/samples/bg.rb0000644000004100000410000000052612342130675015163 0ustar www-datawww-datarequire 'yaml' require 'open4' include Open4 stdin = '42' stdout = '' stderr = '' t = bg 'ruby -e"sleep 4; puts ARGF.read"', 0=>stdin, 1=>stdout, 2=>stderr waiter = Thread.new{ y t.pid => t.exitstatus } # t.exitstatus is a blocking call! while((status = t.status)) y "status" => status sleep 1 end waiter.join y "stdout" => stdout open4-1.3.4/samples/simple.rb0000644000004100000410000000056512342130675016067 0ustar www-datawww-datarequire "open4" pid, stdin, stdout, stderr = Open4::popen4 "sh" stdin.puts "echo 42.out" stdin.puts "echo 42.err 1>&2" stdin.close ignored, status = Process::waitpid2 pid puts "pid : #{ pid }" puts "stdout : #{ stdout.read.strip }" puts "stderr : #{ stderr.read.strip }" puts "status : #{ status.inspect }" puts "exitstatus : #{ status.exitstatus }" open4-1.3.4/samples/block.rb0000644000004100000410000000073012342130675015662 0ustar www-datawww-datarequire 'open4' # # when using block form the child process is automatically waited using # waitpid2 # status = Open4::popen4("sh") do |pid, stdin, stdout, stderr| stdin.puts "echo 42.out" stdin.puts "echo 42.err 1>&2" stdin.close puts "pid : #{ pid }" puts "stdout : #{ stdout.read.strip }" puts "stderr : #{ stderr.read.strip }" end puts "status : #{ status.inspect }" puts "exitstatus : #{ status.exitstatus }" open4-1.3.4/samples/timeout.rb0000644000004100000410000000116512342130675016261 0ustar www-datawww-data require 'open4' def show_failure fork{ yield } Process.wait puts end # # command timeout # show_failure{ open4.spawn 'sleep 42', 'timeout' => 1 } # # stdin timeout # show_failure{ producer = 'ruby -e" STDOUT.sync = true; loop{sleep(rand+rand) and puts 42} " 2>/dev/null' consumer = 'ruby -e" STDOUT.sync = true; STDIN.each{|line| puts line} "' open4(producer) do |pid, i, o, e| open4.spawn consumer, 0=>o, 1=>STDOUT, :stdin_timeout => 1.4 end } # # stdout timeout (stderr is similar) # show_failure{ open4.spawn 'ruby -e" sleep 2 and puts 42 "', 'stdout_timeout' => 1 } open4-1.3.4/samples/pfork4.rb0000644000004100000410000000071412342130675015777 0ustar www-datawww-datarequire 'open4' echo = lambda do $stdout.write $stdin.read raise 'finish implementing me' end org_message = "hello, world!" got_message = nil exception = nil begin Open4.pfork4(echo) do |cid, stdin, stdout, stderr| stdin.write org_message stdin.close got_message = stdout.read end rescue RuntimeError => e exception = e.to_s end puts "org_message: #{org_message}" puts "got_message: #{got_message}" puts "exception : #{exception}" open4-1.3.4/metadata.yml0000644000004100000410000000255612342130675015112 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: open4 version: !ruby/object:Gem::Version version: 1.3.4 platform: ruby authors: - Ara T. Howard autorequire: bindir: bin cert_chain: [] date: 2014-05-15 00:00:00.000000000 Z dependencies: [] description: ! 'open child process with handles on pid, stdin, stdout, and stderr: manage child processes and their io handles easily.' email: ara.t.howard@gmail.com executables: [] extensions: [] extra_rdoc_files: [] files: - LICENSE - README - README.erb - lib/open4.rb - open4.gemspec - rakefile - samples/bg.rb - samples/block.rb - samples/exception.rb - samples/jesse-caldwell.rb - samples/pfork4.rb - samples/simple.rb - samples/spawn.rb - samples/stdin_timeout.rb - samples/timeout.rb - test/lib/test_case.rb - test/pfork4_test.rb - test/popen4_test.rb - test/popen4ext_test.rb - white_box/leak.rb homepage: https://github.com/ahoward/open4 licenses: - Ruby metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: codeforpeople rubygems_version: 2.0.3 signing_key: specification_version: 4 summary: open4 test_files: [] open4-1.3.4/test/0000755000004100000410000000000012342130675013556 5ustar www-datawww-dataopen4-1.3.4/test/pfork4_test.rb0000644000004100000410000000724312342130675016355 0ustar www-datawww-datarequire 'test_case' module Open4 class PFork4Test < TestCase def test_fun_successful_return fun = lambda { 'lucky me' } cid, _ = pfork4 fun assert_equal 0, wait_status(cid) end def test_fun_force_exit exit_code = 43 fun = lambda { exit! exit_code } cid, _ = pfork4 fun assert_equal exit_code, wait_status(cid) end def test_fun_normal_exit exit_code = 43 fun = lambda { exit exit_code } cid, _ = pfork4 fun assert_equal exit_code, wait_status(cid) end def test_fun_does_not_propagate_exception_without_block fun = lambda { raise MyError } cid, _ = pfork4 fun refute_equal 0, wait_status(cid) end def test_fun_propagate_exception_with_block fun = lambda { raise MyError } assert_raises(MyError) { pfork4(fun) {} } end def test_fun_propagate_exception_with_block_avoids_zombie_child_process fun = lambda { raise MyError } assert_raises(MyError) { pfork4(fun) {} } assert_empty Process.waitall end def test_call_block_upon_exception fun = lambda { raise MyError } block_called = false assert_raises(MyError) { pfork4(fun) { block_called = true } } assert_equal true, block_called end def test_passes_child_pid_to_block fun = lambda { $stdout.write Process.pid } cid_in_block = nil cid_in_fun = nil pfork4(fun) do |cid, _, stdout, _| cid_in_block = cid cid_in_fun = stdout.read.to_i end assert_equal cid_in_fun, cid_in_block end def test_io_pipes_without_block via_msg = 'foo' err_msg = 'bar' fun = lambda do $stdout.write $stdin.read $stderr.write err_msg end out_actual, err_actual = nil, nil cid, stdin, stdout, stderr = pfork4 fun stdin.write via_msg stdin.close out_actual = stdout.read err_actual = stderr.read assert_equal via_msg, out_actual assert_equal err_msg, err_actual assert_equal 0, wait_status(cid) end def test_io_pipes_with_block via_msg = 'foo' err_msg = 'bar' fun = lambda do $stdout.write $stdin.read $stderr.write err_msg end out_actual, err_actual = nil, nil status = pfork4(fun) do |_, stdin, stdout, stderr| stdin.write via_msg stdin.close out_actual = stdout.read err_actual = stderr.read end assert_equal via_msg, out_actual assert_equal err_msg, err_actual assert_equal 0, status.exitstatus end def test_exec_in_fun via_msg = 'foo' fun = lambda { exec %{ruby -e "print '#{via_msg}'"} } out_actual = nil status = pfork4(fun) do |_, stdin, stdout, _| stdin.close out_actual = stdout.read end assert_equal via_msg, out_actual assert_equal 0, status.exitstatus end def test_io_pipes_and_then_exception_propagation_with_block via_msg = 'foo' err_msg = 'bar' fun = lambda do $stdout.write $stdin.read $stderr.write err_msg raise MyError end out_actual, err_actual = nil, nil assert_raises(MyError) do pfork4(fun) do |_, stdin, stdout, stderr| stdin.write via_msg stdin.close out_actual = stdout.read err_actual = stderr.read end end assert_equal via_msg, out_actual assert_equal err_msg, err_actual end def test_blocked_on_io_read_and_exception_propagation_with_block fun = lambda do $stdin.read raise MyError end out_actual, err_actual = nil, nil assert_raises(MyError) do pfork4(fun) do |_, stdin, stdout, stderr| stdin.write 'foo' stdin.close out_actual = stdout.read err_actual = stderr.read end end assert_equal '', out_actual assert_equal '', err_actual end end end open4-1.3.4/test/popen4ext_test.rb0000644000004100000410000000421312342130675017070 0ustar www-datawww-datarequire 'test_case' require 'socket' module Open4 class POpen4Test < TestCase UNKNOWN_CMD = 'asdfadsfjlkkk' UNKNOWN_CMD_ERRORS = [Errno::ENOENT, Errno::EINVAL] def test_unknown_command_propagates_exception err = assert_raises(*UNKNOWN_CMD_ERRORS) { popen4ext true, UNKNOWN_CMD } assert_match(/#{UNKNOWN_CMD}/, err.to_s) if on_mri? end def test_exception_propagation_avoids_zombie_child_process assert_raises(*UNKNOWN_CMD_ERRORS) { popen4ext true, UNKNOWN_CMD } assert_empty Process.waitall end def test_exit_failure code = 43 cid, _ = popen4ext true, %{ruby -e "exit #{43}"} assert_equal code, wait_status(cid) end def test_exit_success cid, _ = popen4ext true, %{ruby -e "exit"} assert_equal 0, wait_status(cid) end def test_passes_child_pid_to_block cmd = %{ruby -e "STDOUT.print Process.pid"} cid_in_block = nil cid_in_fun = nil popen4ext(true, cmd) do |cid, _, stdout, _| cid_in_block = cid cid_in_fun = stdout.read.to_i end assert_equal cid_in_fun, cid_in_block end def test_io_pipes_without_block via_msg = 'foo' err_msg = 'bar' cmd = <<-END ruby -e " STDOUT.write STDIN.read STDERR.write '#{err_msg}' " END cid, stdin, stdout, stderr = popen4ext true, cmd stdin.write via_msg stdin.close out_actual = stdout.read err_actual = stderr.read assert_equal via_msg, out_actual assert_equal err_msg, err_actual assert_equal 0, wait_status(cid) end def test_io_pipes_with_block via_msg = 'foo' err_msg = 'bar' out_actual, err_actual = nil cmd = <<-END ruby -e " STDOUT.write STDIN.read STDERR.write '#{err_msg}' " END status = popen4ext(true, cmd) do |_, stdin, stdout, stderr| stdin.write via_msg stdin.close out_actual = stdout.read err_actual = stderr.read end assert_equal via_msg, out_actual assert_equal err_msg, err_actual assert_equal 0, status.exitstatus end def test_close_ignores_errors TCPSocket.new('localhost', 59367).close rescue nil cid, _ = popen4ext true, %{ruby -e "exit"} assert_equal 0, wait_status(cid) end end end open4-1.3.4/test/lib/0000755000004100000410000000000012342130675014324 5ustar www-datawww-dataopen4-1.3.4/test/lib/test_case.rb0000644000004100000410000000071012342130675016621 0ustar www-datawww-data# coding: utf-8 require 'minitest/autorun' require 'open4' require 'rbconfig' module Open4 class TestCase < MiniTest::Unit::TestCase include Open4 # Custom exception class for tests so we don't shadow possible # programming errors. class MyError < RuntimeError; end def on_mri? ::RbConfig::CONFIG['ruby_install_name'] == 'ruby' end def wait_status(cid) Process.waitpid2(cid).last.exitstatus end end end open4-1.3.4/test/popen4_test.rb0000644000004100000410000000361112342130675016350 0ustar www-datawww-datarequire 'test_case' module Open4 class POpen4Test < TestCase UNKNOWN_CMD = 'asdfadsfjlkkk' UNKNOWN_CMD_ERRORS = [Errno::ENOENT, Errno::EINVAL] def test_unknown_command_propagates_exception err = assert_raises(*UNKNOWN_CMD_ERRORS) { popen4 UNKNOWN_CMD } assert_match(/#{UNKNOWN_CMD}/, err.to_s) if on_mri? end def test_exception_propagation_avoids_zombie_child_process assert_raises(*UNKNOWN_CMD_ERRORS) { popen4 UNKNOWN_CMD } assert_empty Process.waitall end def test_exit_failure code = 43 cid, _ = popen4 %{ruby -e "exit #{43}"} assert_equal code, wait_status(cid) end def test_exit_success cid, _ = popen4 %{ruby -e "exit"} assert_equal 0, wait_status(cid) end def test_passes_child_pid_to_block cmd = %{ruby -e "STDOUT.print Process.pid"} cid_in_block = nil cid_in_fun = nil popen4(cmd) do |cid, _, stdout, _| cid_in_block = cid cid_in_fun = stdout.read.to_i end assert_equal cid_in_fun, cid_in_block end def test_io_pipes_without_block via_msg = 'foo' err_msg = 'bar' cmd = <<-END ruby -e " STDOUT.write STDIN.read STDERR.write '#{err_msg}' " END cid, stdin, stdout, stderr = popen4 cmd stdin.write via_msg stdin.close out_actual = stdout.read err_actual = stderr.read assert_equal via_msg, out_actual assert_equal err_msg, err_actual assert_equal 0, wait_status(cid) end def test_io_pipes_with_block via_msg = 'foo' err_msg = 'bar' out_actual, err_actual = nil cmd = <<-END ruby -e " STDOUT.write STDIN.read STDERR.write '#{err_msg}' " END status = popen4(cmd) do |_, stdin, stdout, stderr| stdin.write via_msg stdin.close out_actual = stdout.read err_actual = stderr.read end assert_equal via_msg, out_actual assert_equal err_msg, err_actual assert_equal 0, status.exitstatus end end end open4-1.3.4/open4.gemspec0000644000004100000410000000212512342130675015171 0ustar www-datawww-data## open4.gemspec # Gem::Specification::new do |spec| spec.name = "open4" spec.version = "1.3.4" spec.platform = Gem::Platform::RUBY spec.summary = "open4" spec.description = "open child process with handles on pid, stdin, stdout, and stderr: manage child processes and their io handles easily." spec.license = "Ruby" spec.files = ["LICENSE", "README", "README.erb", "lib", "lib/open4.rb", "open4.gemspec", "rakefile", "samples", "samples/bg.rb", "samples/block.rb", "samples/exception.rb", "samples/jesse-caldwell.rb", "samples/pfork4.rb", "samples/simple.rb", "samples/spawn.rb", "samples/stdin_timeout.rb", "samples/timeout.rb", "test", "test/lib", "test/lib/test_case.rb", "test/pfork4_test.rb", "test/popen4_test.rb", "test/popen4ext_test.rb", "white_box", "white_box/leak.rb"] spec.executables = [] spec.require_path = "lib" spec.test_files = nil spec.extensions.push(*[]) spec.rubyforge_project = "codeforpeople" spec.author = "Ara T. Howard" spec.email = "ara.t.howard@gmail.com" spec.homepage = "https://github.com/ahoward/open4" end open4-1.3.4/LICENSE0000644000004100000410000000007012342130675013601 0ustar www-datawww-datasame as Ruby's http://www.ruby-lang.org/en/LICENSE.txt open4-1.3.4/checksums.yaml.gz0000444000004100000410000000064112342130675016066 0ustar www-datawww-data‹5+9{*úOýú½W}<ßÞç_ýǽ´ñt:Ë^^QäÞû[ï¿á5ú{å÷QN¢ˆM»aÈC 'T²©cè(fãV(@¡•®wmBöâxÌ2iþDÞûÿaQ· »fÁ¢\ÙŽiU°'ûö¬êis™Y— M¾QW0CÊ¢÷_Ïӯ諅@þÄSZ ô[BRð?[´deeê lÐ7ƒKPó¼kµÍ :_Ѳe}qÐAºÛ_<ZQ7‹ØOÑ ëWòbÐÒ`¯uìØðª>³/7Ag‡? 0ti„