class ShellTest::ShellMethods::Session
Session
is an engine for running shell sessions.
Constants
- DEFAULT_MAX_RUN_TIME
- DEFAULT_SHELL
- DEFAULT_STTY
Attributes
A log of the output at each step (set during run)
The maximum run time for the session
The session shell
A Process::Status for the session (set by run)
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.
Aguments string passed stty on run
The session timer, used by agents to determine timeouts
Public Class Methods
# 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
Define a step. At each step:
-
The session waits until the prompt is matched
-
The input is written to the shell (if given)
-
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 overallmax_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
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
The shell PS1, as configured in ENV.
# File lib/shell_test/shell_methods/session.rb 49 def ps1 50 ENV['PS1'] 51 end
The shell PS2, as configured in ENV.
# File lib/shell_test/shell_methods/session.rb 54 def ps2 55 ENV['PS2'] 56 end
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
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
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).
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
# 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
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