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
The pty master
The pty slave
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
# 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
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
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
# 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 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
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