class ShellTest::ShellMethods::Agent

Agent wraps a PTY master-slave pair and provides methods for doing reads and writes. The expect method is inspired by the IO.expect method in the stdlib, but tailored to allow for better timeout control.

Attributes

master[R]

The pty master

slave[R]

The pty slave

timer[R]

The timer managing read timeouts. The timer ensures that the timeouts used by expect are never negative, or nil to indicate no timeout.

If implementing a custom timer, note that timeouts are set on the timer using ‘timer.timeout=` and retrieved via `timer.timeout`.

Public Class Methods

new(master, slave, timer) click to toggle source
   # File lib/shell_test/shell_methods/agent.rb
23 def initialize(master, slave, timer)
24   @master = master
25   @slave  = slave
26   @timer  = timer
27 end

Public Instance Methods

close() click to toggle source

Closes the master and slave.

    # File lib/shell_test/shell_methods/agent.rb
108 def close
109   unless master.closed?
110     master.close
111   end
112   unless slave.closed?
113     slave.close
114   end
115 end
expect(regexp, timeout=nil) click to toggle source

Reads from the slave until the regexp is matched and returns the resulting string. If regexp is a String, then it is converted into a regexp assuming it’s a literal prompt (ie /^regexpz/ - where the regexp string is Regexp escaped). If regexp is nil then expect reads until the slave eof.

A timeout may be given. If the slave doesn’t produce the expected string within the timeout then expect raises a ReadError. A ReadError will be also be raised if the slave eof is reached before the regexp matches.

   # File lib/shell_test/shell_methods/agent.rb
45 def expect(regexp, timeout=nil)
46   if regexp.kind_of?(String)
47     regexp = /#{Regexp.escape(regexp)}\z/
48   end
49 
50   timer.timeout = timeout
51 
52   buffer = ''
53   while true
54     timeout = timer.timeout
55 
56     # Use read+select instead of read_nonblock to avoid polling in a
57     # tight loop.  Don't bother with readpartial and partial lengths. It
58     # would be an optimization, especially because the regexp matches
59     # each loop, but adds complexity because expect could read past the
60     # end of the regexp and it is unlikely to be necessary in test
61     # scenarios (ie this is not meant to be a general solution).
62     unless IO.select([slave],nil,nil,timeout)
63       msg = "timeout waiting for %s after %.2fs" % [regexp ? regexp.inspect : 'EOF', timeout]
64       raise ReadError.new(msg, buffer)
65     end
66 
67     begin
68       c = slave.read(1)
69     rescue Errno::EIO
70       # see notes in Utils#spawn
71       c = nil
72     end
73 
74     if c.nil?
75       if regexp.nil?
76         break
77       else
78         raise ReadError.new("end of file reached", buffer)
79       end
80     end
81 
82     buffer << c
83 
84     if regexp && buffer =~ regexp
85       break
86     end
87   end
88   buffer
89 end
on(regexp, input, timeout=nil) click to toggle source
   # File lib/shell_test/shell_methods/agent.rb
29 def on(regexp, input, timeout=nil)
30   output = expect(regexp, timeout)
31   write(input)
32   output
33 end
read(timeout=nil) click to toggle source

Read to the end of the slave. Raises a ReadError if the slave eof is not reached within the timeout.

   # File lib/shell_test/shell_methods/agent.rb
93 def read(timeout=nil)
94   expect nil, timeout
95 end
write(input, timeout=nil) click to toggle source

Writes to the master. A timeout may be given. If the master doesn’t become available for writing within the timeout then write raises an WriteError.

    # File lib/shell_test/shell_methods/agent.rb
100 def write(input, timeout=nil)
101   unless IO.select(nil,[master],nil,timeout)
102     raise WriteError.new("timeout waiting for master")
103   end
104   master.print input
105 end