Project

General

Profile

Bug #5463 » pty_fail.rb

thinkerbot (Simon Chiang), 10/19/2011 12:02 PM

 
require 'pty'

# Wraps PTY.spawn in logic to capture the exit status regardless of exception,
# and across multiple rubies. Extra logging is included to notify when
# various exit routes are triggered.
def spawn_pty(cmd)
PTY.spawn(cmd) do |slave, master, pid|
begin
yield(master, slave, pid)
Process.wait(pid)

rescue PTY::ChildExited
# 1.8.6 and 1.8.7 often will exit this way.
# 1.9.2 does not exit this way.
$stdout.puts "#{pid}\t#{$!.class}"
return $!.status

rescue Exception
# Cleanup the pty on error (specifically the EOF timeout)
$stdout.puts "#{pid}\t#{$!.class}\n#{$!.message.gsub(/^/, ' ')}"
Process.kill(9, pid)
Process.wait(pid)
end
end

$?
end

# Declare some variables -
# * the max number of iterations
# * the cmd to launch the shell
# * the timeout
max = ARGV.shift || 1000
timeout = 3

ENV['PS1'] = '$ ' # Set PS1 for the pty shells that accept it

fields = [:i, :pid, :exit_on, :time_ms, :status, :output]
shells = ARGV.empty? ? ['/bin/bash', '/bin/csh', '/bin/ksh', '/bin/zsh'] : ARGV

shells.each do |cmd|
current_ruby = `ruby --version`.split[0,2].join('-')
log = File.open("#{current_ruby}-#{File.basename(cmd)}.txt", 'w')
log.puts `ruby --version 2>&1`.gsub(/^/, '# ')
log.puts `#{cmd} --version 2>&1`.gsub(/^/, '# ')
log.puts "# #{Time.now}"
log.puts(fields.join("\t"))

info = {:exit_condition => 'error'}
1.upto(max.to_i) do |i|
info[:i] = i

#
# Spawn and run the pty shell session. When the master is ready, send a
# command to exit with status 8. Then use a while loop to read all output
# to str. All errors that occur within spawn_pty are handled such that there
# will be output but the iteration loop will continue on to max.
#

str = ''
start = Time.now
status = spawn_pty(cmd) do |master, slave, pid|
info[:pid] = pid

unless IO.select(nil,[master],nil,timeout)
info[:exit_on] = 'master'
raise "timeout waiting for master (cmd: #{cmd} i:#{i} pid:#{pid})"
end
master.write "exit 8\n"
while true
unless IO.select([slave],nil,nil,timeout)
info[:exit_on] = 'slave'
raise "timeout waiting for slave EOF (cmd: #{cmd} i:#{i} pid:#{pid})\n#{str.inspect}"
end

begin
if slave.eof?
info[:exit_on] = 'eof'
break
end
rescue Errno::EIO
# On Ubuntu slave.eof? can raise an EIO error, which is apparently
# allowed in the specification. This is not the exit route on OS X.
info[:exit_on] = 'eio'
break
end

str << slave.read(1)
end

unless master.closed?
master.close
end

unless slave.closed?
slave.close
end
end

info[:time_ms] = (Time.now - start) * 1000
info[:status] = status.exitstatus
info[:output] = str.inspect
log.puts(fields.map {|field| info[field] }.join("\t"))
end

log.close
end
(1-1/2)