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