module ShellTest::ShellMethods::Utils
Public Instance Methods
spawn(cmd, log=[]) { |master, slave| ... }
click to toggle source
Spawns a PTY session and returns the Process::Status for the session upon completion. The PTY process is killed upon an unhandled error (but the error is re-raised for further handling).
Note that $? is set by spawn but is not reliable until 1.9.2 (ish). Prior to that PTY used a cleanup thread that would wait on a spawned process and raise a PTY::ChildExited error in some cases. As a result manual calls to Process.wait (which would set $?) cause a race condition. Rely on the output of spawn instead.
# File lib/shell_test/shell_methods/utils.rb 17 def spawn(cmd, log=[]) 18 # The race condition described above actually applies to both kill and 19 # wait which raise Errno::ESRCH or Errno::ECHILD if they lose the 20 # race. This code is designed to capture those errors if they occur 21 # and then give the cleanup thread a chance to take over; eventually 22 # it will raise a ChildExited error. This is a sketchy use of 23 # exceptions for flow control but there is little option - a 24 # consequence of PTY using threads with side effects. 25 PTY.spawn(cmd) do |slave, master, pid| 26 begin 27 yield(master, slave) 28 29 begin 30 Process.wait(pid) 31 rescue Errno::ECHILD 32 Thread.pass 33 raise 34 end 35 36 rescue PTY::ChildExited 37 # This is the 'normal' exit route on 1.8.6 and 1.8.7. 38 return $!.status 39 40 rescue Exception => error 41 begin 42 43 # Manually cleanup the pid on error. This code no longer cares 44 # what exactly happens to $? - the point is to make sure the 45 # child doesn't become a zombie and then re-raise the error. 46 begin 47 Process.kill(9, pid) 48 rescue Errno::ESRCH 49 Thread.pass 50 raise 51 end 52 53 # Clearing the slave allows quicker exits on OS X. 54 while IO.select([slave],nil,nil,0.1) 55 begin 56 break unless slave.read(1) 57 rescue Errno::EIO 58 # On some linux (ex ubuntu) read can return an eof or fail with 59 # an EIO error when a terminal disconnect occurs and an EIO 60 # condition occurs - the exact behavior is unspecified but the 61 # meaning is the same... no more data is available, so break. 62 break 63 end 64 end 65 66 begin 67 Process.wait(pid) 68 rescue Errno::ECHILD 69 Thread.pass 70 raise 71 end 72 73 rescue PTY::ChildExited 74 # The cleanup thread could finish at any point in the rescue 75 # handling so account for that here. 76 ensure 77 raise error 78 end 79 end 80 end 81 82 $? 83 end