class ShellTest::ShellMethods::Session

Session is an engine for running shell sessions.

Constants

DEFAULT_MAX_RUN_TIME
DEFAULT_SHELL
DEFAULT_STTY

Attributes

log[R]

A log of the output at each step (set during run)

max_run_time[R]

The maximum run time for the session

shell[R]

The session shell

status[R]

A Process::Status for the session (set by run)

steps[R]

An array of entries like [prompt, input, max_run_time, callback] that indicate each step of a session. See the on method for adding steps.

stty[R]

Aguments string passed stty on run

timer[R]

The session timer, used by agents to determine timeouts

Public Class Methods

new(options={}) click to toggle source
   # File lib/shell_test/shell_methods/session.rb
38 def initialize(options={})
39   @shell = options[:shell] || DEFAULT_SHELL
40   @stty  = options[:stty]  || DEFAULT_STTY
41   @timer = options[:timer] || Timer.new
42   @max_run_time = options[:max_run_time] || DEFAULT_MAX_RUN_TIME
43   @steps   = [[nil, nil, nil, nil]]
44   @log     = []
45   @status  = nil
46 end

Public Instance Methods

on(prompt, input=nil, max_run_time=nil) { |output| ... } click to toggle source

Define a step. At each step:

  1. The session waits until the prompt is matched

  2. The input is written to the shell (if given)

  3. The output passed to the callback (if given)

If the next prompt (or an EOF if there is no next prompt) is not reached within max_run_time then a ReadError occurs. Special considerations:

  • The prompt can be a regular expression, a string, or a Symbol (set to the same ENV value - ex :PS1)

  • A nil max_run_time indicates no maximum run time - which more accurately means the input can go until the overall max_run_time for the session runs out.

  • The output passed to the callback will include the string matched by the next prompt, if present.

Returns self.

   # File lib/shell_test/shell_methods/session.rb
77 def on(prompt, input=nil, max_run_time=nil, &callback) # :yields: output
78   if prompt.kind_of?(Symbol)
79     prompt = ENV[prompt.to_s]
80   end
81 
82   if prompt.nil?
83     raise ArgumentError, "no prompt specified"
84   end
85 
86   # Stagger assignment of step arguments so that the callback will be
87   # recieve the output of the input. Not only is this more intuitive, it
88   # ensures the last step will read to EOF (which expedites waiting on
89   # the session to terminate).
90   last = steps.last
91   last[0] = prompt
92   last[1] = input
93   last[2] = max_run_time
94 
95   steps << [nil, nil, nil, callback]
96   self
97 end
parse(script, options={}, &block) click to toggle source

Parses a terminal snippet into steps that a Session can run, and adds those steps to self. The snippet should utilize ps1 and ps2 as set on self. An exit command is added unless the :noexit option is set to true.

session = Session.new
session.parse %{
$ echo abc
abc
}
session.run.result   # => "$ echo abc\nabc\n$ exit\nexit\n"

Steps are registered with a callback block, if given, to recieve the expected and actual outputs during run. Normally the callback is used to validate that the run is going as planned.

    # File lib/shell_test/shell_methods/session.rb
153 def parse(script, options={}, &block)
154   args = split(script)
155   args.shift # ignore script before first prompt
156 
157   if options[:noexit]
158     args.pop
159   else
160     args.last << ps1 if args.last
161     args.concat [ps1, "exit\n", nil, nil]
162   end
163 
164   while !args.empty?
165     prompt = args.shift
166     input  = args.shift
167     max_run_time = args.shift
168     output = args.shift
169     callback = make_callback(output, &block)
170 
171     on(prompt, input, max_run_time, &callback)
172   end
173 
174   self
175 end
ps1() click to toggle source

The shell PS1, as configured in ENV.

   # File lib/shell_test/shell_methods/session.rb
49 def ps1
50   ENV['PS1']
51 end
ps2() click to toggle source

The shell PS2, as configured in ENV.

   # File lib/shell_test/shell_methods/session.rb
54 def ps2
55   ENV['PS2']
56 end
result() click to toggle source

Returns what would appear to the user at the current point in the session (with granularity of an input/output step).

Currently result ONLY works as intended when stty is set to turn off input echo and output carriage returns, either with ‘-echo -onlcr’ (the default) or ‘raw’. Otherwise the inputs can appear twice in the result and there will be inconsistent end-of-lines.

    # File lib/shell_test/shell_methods/session.rb
251 def result
252   log.join
253 end
run() click to toggle source

Runs each of steps within a shell session and collects the inputs/outputs into log. After run the exit status of the session is set into status.

    # File lib/shell_test/shell_methods/session.rb
223 def run
224   spawn do |agent|
225     timeout  = nil
226     steps.each do |prompt, input, max_run_time, callback|
227       buffer = agent.expect(prompt, timeout)
228       log << buffer
229 
230       if callback
231         callback.call buffer
232       end
233 
234       if input
235         log << input
236         agent.write(input)
237       end
238 
239       timeout = max_run_time
240     end
241   end
242 end
spawn() { |agent| ... } click to toggle source

Spawns a PTY shell session and yields an Agent to the block. The session is logged to log and the final exit status set into status (any previous values are overwritten).

Calls superclass method ShellTest::ShellMethods::Utils#spawn
    # File lib/shell_test/shell_methods/session.rb
180 def spawn
181   @log = []
182   @status = super(shell) do |master, slave|
183     agent = Agent.new(master, slave, timer)
184     timer.start(max_run_time)
185 
186     if stty
187       # It would be lovely to work this into steps somehow, or to set
188       # the stty externally like:
189       #
190       #   system("stty #{stty} < '#{master.path}'")
191       #
192       # Unfortunately the former complicates result and the latter
193       # doesn't work.  In tests the stty settings DO get set but they
194       # don't refresh in the pty.
195       log << agent.on(ps1, "stty #{stty}\n")
196       log << agent.on(ps1, "echo $?\n")
197       log << agent.on(ps1, "\n")
198 
199       unless log.last == "0\n#{ps1}"
200         raise "stty failure\n#{summary}"
201       end
202 
203       log.clear
204     end
205 
206     begin
207       yield agent
208     rescue Agent::ReadError
209       log << $!.buffer
210       $!.message << "\n#{summary}"
211       raise
212     end
213 
214     timer.stop
215     agent.close
216   end
217   self
218 end
split(str) click to toggle source
    # File lib/shell_test/shell_methods/session.rb
 99 def split(str)
100   scanner = StringScanner.new(str)
101   scanner.scan_until(/(#{Regexp.escape(ps1)})/)
102   scanner.pos -= scanner[1].to_s.length
103 
104   args = []
105   while output = scanner.scan_until(/(#{Regexp.escape(ps1)}|#{Regexp.escape(ps2)}|\{\{(.*?)\}\})/)
106     match = scanner[1]
107     input = scanner[2] ? "#{scanner[2]}\n" : scanner.scan_until(/\n/)
108 
109     max_run_time = -1
110     input.sub!(/\#\s*\[(\d+(?:\.\d+)?)\].*$/) do
111       max_run_time = $1.to_f
112       nil
113     end
114 
115     case match
116     when ps1
117       prompt = match
118       if max_run_time == -1
119         max_run_time = nil
120       end
121     when ps2
122       prompt = match
123     else
124       output = output.chomp(match)
125       prompt = output.split("\n").last
126     end
127 
128     args << output
129     args << prompt
130     args << input
131     args << max_run_time
132   end
133 
134   args << scanner.rest
135   args
136 end
summary(format=nil) click to toggle source

Formats the status of self into a string. A format string can be provided - it is evaluated using ‘%’ using arguments: [shell, elapsed_time, result]

    # File lib/shell_test/shell_methods/session.rb
258       def summary(format=nil)
259         (format || %Q{
260 %s (elapsed: %.2fs max: %.2fs)
261 =========================================================
262 %s
263 =========================================================
264 }) % [shell, timer.elapsed_time, max_run_time, result]
265       end