Class: ShellTest::ShellMethods::Session
- Inherits:
-
Object
- Object
- ShellTest::ShellMethods::Session
- Includes:
- Utils
- Defined in:
- lib/shell_test/shell_methods/session.rb
Overview
Session is an engine for running shell sessions.
Constant Summary collapse
- DEFAULT_SHELL =
'/bin/sh'- DEFAULT_STTY =
'-echo -onlcr'- DEFAULT_MAX_RUN_TIME =
1
Instance Attribute Summary collapse
-
#log ⇒ Object
readonly
A log of the output at each step (set during run).
-
#max_run_time ⇒ Object
readonly
The maximum run time for the session.
-
#shell ⇒ Object
readonly
The session shell.
-
#status ⇒ Object
readonly
A Process::Status for the session (set by run).
-
#steps ⇒ Object
readonly
An array of entries like [prompt, input, max_run_time, callback] that indicate each step of a session.
-
#stty ⇒ Object
readonly
Aguments string passed stty on run.
-
#timer ⇒ Object
readonly
The session timer, used by agents to determine timeouts.
Instance Method Summary collapse
-
#initialize(options = {}) ⇒ Session
constructor
A new instance of Session.
-
#on(prompt, input = nil, max_run_time = nil, &callback) ⇒ Object
Define a step.
-
#parse(script, options = {}, &block) ⇒ Object
Parses a terminal snippet into steps that a Session can run, and adds those steps to self.
-
#ps1 ⇒ Object
The shell PS1, as configured in ENV.
-
#ps2 ⇒ Object
The shell PS2, as configured in ENV.
-
#result ⇒ Object
Returns what would appear to the user at the current point in the session (with granularity of an input/output step).
-
#run ⇒ Object
Runs each of steps within a shell session and collects the inputs/outputs into log.
-
#spawn ⇒ Object
Spawns a PTY shell session and yields an Agent to the block.
- #split(str) ⇒ Object
-
#summary(format = nil) ⇒ Object
Formats the status of self into a string.
Methods included from Utils
Constructor Details
#initialize(options = {}) ⇒ Session
Returns a new instance of Session.
38 39 40 41 42 43 44 45 46 |
# File 'lib/shell_test/shell_methods/session.rb', line 38 def initialize(={}) @shell = [:shell] || DEFAULT_SHELL @stty = [:stty] || DEFAULT_STTY @timer = [:timer] || Timer.new @max_run_time = [:max_run_time] || DEFAULT_MAX_RUN_TIME @steps = [[nil, nil, nil, nil]] @log = [] @status = nil end |
Instance Attribute Details
#log ⇒ Object (readonly)
A log of the output at each step (set during run)
33 34 35 |
# File 'lib/shell_test/shell_methods/session.rb', line 33 def log @log end |
#max_run_time ⇒ Object (readonly)
The maximum run time for the session
26 27 28 |
# File 'lib/shell_test/shell_methods/session.rb', line 26 def max_run_time @max_run_time end |
#shell ⇒ Object (readonly)
The session shell
17 18 19 |
# File 'lib/shell_test/shell_methods/session.rb', line 17 def shell @shell end |
#status ⇒ Object (readonly)
A Process::Status for the session (set by run)
36 37 38 |
# File 'lib/shell_test/shell_methods/session.rb', line 36 def status @status end |
#steps ⇒ Object (readonly)
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.
30 31 32 |
# File 'lib/shell_test/shell_methods/session.rb', line 30 def steps @steps end |
#stty ⇒ Object (readonly)
Aguments string passed stty on run
20 21 22 |
# File 'lib/shell_test/shell_methods/session.rb', line 20 def stty @stty end |
#timer ⇒ Object (readonly)
The session timer, used by agents to determine timeouts
23 24 25 |
# File 'lib/shell_test/shell_methods/session.rb', line 23 def timer @timer end |
Instance Method Details
#on(prompt, input = nil, max_run_time = nil, &callback) ⇒ Object
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 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.
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/shell_test/shell_methods/session.rb', line 77 def on(prompt, input=nil, max_run_time=nil, &callback) # :yields: output if prompt.kind_of?(Symbol) prompt = ENV[prompt.to_s] end if prompt.nil? raise ArgumentError, "no prompt specified" end # Stagger assignment of step arguments so that the callback will be # recieve the output of the input. Not only is this more intuitive, it # ensures the last step will read to EOF (which expedites waiting on # the session to terminate). last = steps.last last[0] = prompt last[1] = input last[2] = max_run_time steps << [nil, nil, nil, callback] self end |
#parse(script, options = {}, &block) ⇒ Object
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.
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/shell_test/shell_methods/session.rb', line 153 def parse(script, ={}, &block) args = split(script) args.shift # ignore script before first prompt if [:noexit] args.pop else args.last << ps1 if args.last args.concat [ps1, "exit\n", nil, nil] end while !args.empty? prompt = args.shift input = args.shift max_run_time = args.shift output = args.shift callback = make_callback(output, &block) on(prompt, input, max_run_time, &callback) end self end |
#ps1 ⇒ Object
The shell PS1, as configured in ENV.
49 50 51 |
# File 'lib/shell_test/shell_methods/session.rb', line 49 def ps1 ENV['PS1'] end |
#ps2 ⇒ Object
The shell PS2, as configured in ENV.
54 55 56 |
# File 'lib/shell_test/shell_methods/session.rb', line 54 def ps2 ENV['PS2'] end |
#result ⇒ Object
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.
251 252 253 |
# File 'lib/shell_test/shell_methods/session.rb', line 251 def result log.join end |
#run ⇒ Object
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.
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/shell_test/shell_methods/session.rb', line 223 def run spawn do |agent| timeout = nil steps.each do |prompt, input, max_run_time, callback| buffer = agent.expect(prompt, timeout) log << buffer if callback callback.call buffer end if input log << input agent.write(input) end timeout = max_run_time end end end |
#spawn ⇒ Object
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).
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/shell_test/shell_methods/session.rb', line 180 def spawn @log = [] @status = super(shell) do |master, slave| agent = Agent.new(master, slave, timer) timer.start(max_run_time) if stty # It would be lovely to work this into steps somehow, or to set # the stty externally like: # # system("stty #{stty} < '#{master.path}'") # # Unfortunately the former complicates result and the latter # doesn't work. In tests the stty settings DO get set but they # don't refresh in the pty. log << agent.on(ps1, "stty #{stty}\n") log << agent.on(ps1, "echo $?\n") log << agent.on(ps1, "\n") unless log.last == "0\n#{ps1}" raise "stty failure\n#{summary}" end log.clear end begin yield agent rescue Agent::ReadError log << $!.buffer $!. << "\n#{summary}" raise end timer.stop agent.close end self end |
#split(str) ⇒ Object
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/shell_test/shell_methods/session.rb', line 99 def split(str) scanner = StringScanner.new(str) scanner.scan_until(/(#{Regexp.escape(ps1)})/) scanner.pos -= scanner[1].to_s.length args = [] while output = scanner.scan_until(/(#{Regexp.escape(ps1)}|#{Regexp.escape(ps2)}|\{\{(.*?)\}\})/) match = scanner[1] input = scanner[2] ? "#{scanner[2]}\n" : scanner.scan_until(/\n/) max_run_time = -1 input.sub!(/\#\s*\[(\d+(?:\.\d+)?)\].*$/) do max_run_time = $1.to_f nil end case match when ps1 prompt = match if max_run_time == -1 max_run_time = nil end when ps2 prompt = match else output = output.chomp(match) prompt = output.split("\n").last end args << output args << prompt args << input args << max_run_time end args << scanner.rest args end |
#summary(format = nil) ⇒ Object
Formats the status of self into a string. A format string can be provided - it is evaluated using ‘%’ using arguments: [shell, elapsed_time, result]
258 259 260 261 262 263 264 265 |
# File 'lib/shell_test/shell_methods/session.rb', line 258 def summary(format=nil) (format || %Q{ %s (elapsed: %.2fs max: %.2fs) ========================================================= %s ========================================================= }) % [shell, timer.elapsed_time, max_run_time, result] end |