|
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
|