expectr 0.9.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/expectr.rb +253 -256
- data/lib/expectr/error.rb +5 -0
- data/lib/expectr/version.rb +1 -1
- data/test/test_core.rb +38 -0
- data/test/test_initialization.rb +17 -0
- data/test/test_interaction.rb +114 -0
- metadata +9 -4
- data/test/test_expectr.rb +0 -81
data/lib/expectr.rb
CHANGED
@@ -1,290 +1,287 @@
|
|
1
|
-
|
2
|
-
#
|
3
|
-
# Copyright (c) Chris Wuest <chris@chriswuest.com>
|
4
|
-
# Expectr is freely distributable under the terms of an MIT-style license.
|
5
|
-
# See COPYING or http://www.opensource.org/licenses/mit-license.php.
|
6
|
-
|
7
|
-
begin
|
8
|
-
require 'pty'
|
9
|
-
rescue LoadError
|
10
|
-
require 'popen4'
|
11
|
-
end
|
12
|
-
|
1
|
+
require 'pty'
|
13
2
|
require 'timeout'
|
14
3
|
require 'thread'
|
15
4
|
|
16
|
-
|
17
|
-
if RUBY_VERSION =~ /^1.8/
|
18
|
-
# Enforcing encoding is not needed in 1.8 (probably.) So, we'll define
|
19
|
-
# String#encode! to do nothing, for interoperability.
|
20
|
-
class String #:nodoc:
|
21
|
-
def encode!(encoding)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
# In Ruby 1.8, we want to ignore SIGCHLD. This is for two reasons:
|
26
|
-
# * SIGCHLD will be sent (and cause exceptions) for every Expectr object
|
27
|
-
# created
|
28
|
-
# * As John Carter documented in his RExpect library, calls to files which
|
29
|
-
# do not exist can cause odd and unexpected behavior.
|
30
|
-
trap 'CHLD', Proc.new { nil }
|
31
|
-
end
|
5
|
+
require 'expectr/error'
|
32
6
|
|
33
|
-
#
|
34
|
-
#
|
35
|
-
# http://expect.nist.gov).
|
7
|
+
# Public: Expectr is an API to the functionality of Expect (see
|
8
|
+
# http://expect.nist.gov) implemented in ruby.
|
36
9
|
#
|
37
10
|
# Expectr contrasts with Ruby's built-in Expect class by avoiding tying in
|
38
|
-
# with IO
|
39
|
-
#
|
11
|
+
# with the IO class, instead creating a new object entirely to allow for more
|
12
|
+
# grainular control over the execution and display of the program being
|
40
13
|
# run.
|
41
14
|
#
|
42
|
-
#
|
43
|
-
# === Simple task automation
|
44
|
-
#
|
45
|
-
# Connect via telnet to remote.example.com, run my_command, and return the
|
46
|
-
# output
|
47
|
-
#
|
48
|
-
# exp = Expectr.new "telnet remote.example.com"
|
49
|
-
# exp.expect "username:"
|
50
|
-
# exp.send "example\r"
|
51
|
-
# exp.expect "password:"
|
52
|
-
# exp.send "my_password\r"
|
53
|
-
# exp.expect "%"
|
54
|
-
# exp.send "my_command\r"
|
55
|
-
# exp.expect "%"
|
56
|
-
# exp.send "logout"
|
57
|
-
#
|
58
|
-
# output = exp.discard
|
59
|
-
#
|
60
|
-
# === Interactive control
|
61
|
-
# Silently connect via ssh to remote.example.com, log in automatically, then
|
62
|
-
# relinquish control to the user. Expect slow networking, so increase
|
63
|
-
# timeout.
|
15
|
+
# Examples
|
64
16
|
#
|
65
|
-
#
|
17
|
+
# # SSH Login to another machine
|
18
|
+
# exp = Expectr.new('ssh user@example.com')
|
19
|
+
# exp.expect("Password:")
|
20
|
+
# exp.send('password')
|
21
|
+
# exp.interact!(blocking: true)
|
66
22
|
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
#
|
73
|
-
# exp.expect /password/
|
74
|
-
# exp.send "my_password\r"
|
75
|
-
# else
|
76
|
-
# puts "Cannot connect to remote.example.com!"
|
77
|
-
# die
|
23
|
+
# # See if a web server is running on the local host, react accordingly
|
24
|
+
# exp = Expectr.new('netstat -ntl|grep ":80 " && echo "WEB"', timeout: 1)
|
25
|
+
# if exp.expeect("WEB")
|
26
|
+
# # Do stuff if we see 'WEB' in the output
|
27
|
+
# else
|
28
|
+
# # Do other stuff
|
78
29
|
# end
|
79
|
-
#
|
80
|
-
# exp.expect "$"
|
81
|
-
# exp.interact
|
82
|
-
#
|
83
30
|
class Expectr
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
31
|
+
# Public: Gets/sets the number of seconds a call to Expectr#expect may last
|
32
|
+
attr_accessor :timeout
|
33
|
+
# Public: Gets/sets the number of bytes to use for the internal buffer
|
34
|
+
attr_accessor :buffer_size
|
35
|
+
# Public: Gets/sets whether to constrain the buffer to the buffer size
|
36
|
+
attr_accessor :constrain
|
37
|
+
# Public: Gets/sets whether to flush program output to STDOUT
|
38
|
+
attr_accessor :flush_buffer
|
39
|
+
# Public: Returns the PID of the running process
|
40
|
+
attr_reader :pid
|
41
|
+
# Public: Returns the active buffer to match against
|
42
|
+
attr_reader :buffer
|
43
|
+
# Public: Returns the buffer discarded by the latest call to Expectr#expect
|
44
|
+
attr_reader :discard
|
96
45
|
|
97
|
-
|
98
|
-
|
99
|
-
#
|
100
|
-
# Expectr.new(cmd, args)
|
101
|
-
#
|
102
|
-
# === Arguments
|
103
|
-
# +cmd+::
|
104
|
-
# Command to be executed (String or File)
|
105
|
-
# +args+::
|
106
|
-
# Hash of modifiers for Expectr. Meaningful values are:
|
107
|
-
# * :buffer_size::
|
108
|
-
# Amount of data to read at a time. Default 8 KiB
|
109
|
-
# * :flush_buffer::
|
110
|
-
# Flush buffer to STDOUT during execution? Default true
|
111
|
-
# * :timeout::
|
112
|
-
# Timeout in seconds for each +expect+ call. Default 30
|
113
|
-
#
|
114
|
-
# === Description
|
115
|
-
#
|
116
|
-
# Spawn +cmd+ and attach to STDIN and STDOUT for new process. Fall back
|
117
|
-
# to using Open4 if PTY is not present (this is the case on Windows
|
118
|
-
# implementations of ruby.
|
46
|
+
# Public: Initialize a new Expectr object.
|
47
|
+
# Spawns a sub-process and attaches to STDIN and STDOUT for the new process.
|
119
48
|
#
|
120
|
-
|
121
|
-
|
122
|
-
|
49
|
+
# cmd - A String or File referencing the application to launch
|
50
|
+
# args - A Hash used to specify options for the new object (default: {}):
|
51
|
+
# :timeout - Number of seconds that a call to Expectr#expect has
|
52
|
+
# to complete (default: 30)
|
53
|
+
# :flush_buffer - Whether to flush output of the process to the
|
54
|
+
# console (default: true)
|
55
|
+
# :buffer_size - Number of bytes to attempt to read from sub-process
|
56
|
+
# at a time. If :constrain is true, this will be the
|
57
|
+
# maximum size of the internal buffer as well.
|
58
|
+
# (default: 8192)
|
59
|
+
# :constrain - Whether to constrain the internal buffer from the
|
60
|
+
# sub-process to :buffer_size (default: false)
|
61
|
+
def initialize(cmd, args={})
|
62
|
+
unless cmd.kind_of? String or cmd.kind_of? File
|
63
|
+
raise ArgumentError, "String or File expected"
|
64
|
+
end
|
65
|
+
|
66
|
+
cmd = cmd.path if cmd.kind_of? File
|
67
|
+
|
68
|
+
@buffer = ''.encode("UTF-8")
|
69
|
+
@discard = ''.encode("UTF-8")
|
70
|
+
|
71
|
+
@timeout = args[:timeout] || 30
|
72
|
+
@flush_buffer = args[:flush_buffer].nil? ? true : args[:flush_buffer]
|
73
|
+
@buffer_size = args[:buffer_size] || 8192
|
74
|
+
@constrain = args[:constrain] || false
|
75
|
+
|
76
|
+
@out_mutex = Mutex.new
|
77
|
+
@out_update = false
|
78
|
+
@interact = false
|
79
|
+
|
80
|
+
@stdout,@stdin,@pid = PTY.spawn(cmd)
|
81
|
+
|
82
|
+
Thread.new do
|
83
|
+
while @pid > 0
|
84
|
+
unless select([@stdout], nil, nil, @timeout).nil?
|
85
|
+
buf = ''.encode("UTF-8")
|
86
|
+
|
87
|
+
begin
|
88
|
+
@stdout.sysread(@buffer_size, buf)
|
89
|
+
rescue Errno::EIO #Application went away.
|
90
|
+
@pid = 0
|
91
|
+
break
|
92
|
+
end
|
93
|
+
|
94
|
+
print_buffer(buf)
|
123
95
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
96
|
+
@out_mutex.synchronize do
|
97
|
+
@buffer << buf
|
98
|
+
if @buffer.length > @buffer_size && @constrain
|
99
|
+
@buffer = @buffer[-@buffer_size..-1]
|
100
|
+
end
|
101
|
+
@out_update = true
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
132
106
|
|
133
|
-
|
107
|
+
Thread.new do
|
108
|
+
Process.wait @pid
|
109
|
+
@pid = 0
|
110
|
+
end
|
111
|
+
end
|
134
112
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
113
|
+
# Public: Relinquish control of the running process to the controlling
|
114
|
+
# terminal, acting as a pass-through for the life of the process. SIGINT
|
115
|
+
# will be caught and sent to the application as "\C-c".
|
116
|
+
#
|
117
|
+
# args - A Hash used to specify options to be used for interaction (default:
|
118
|
+
# {}):
|
119
|
+
# :flush_buffer - explicitly set @flush_buffer to the value specified
|
120
|
+
# :blocking - Whether to block on this call or allow code
|
121
|
+
# execution to continue (default: false)
|
122
|
+
#
|
123
|
+
# Returns the interaction Thread
|
124
|
+
def interact!(args = {})
|
125
|
+
raise ProcessError if @interact
|
141
126
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
buf = ''
|
127
|
+
blocking = args[:blocking] || false
|
128
|
+
@flush_buffer = args[:flush_buffer].nil? ? true : args[:flush_buffer]
|
129
|
+
@interact = true
|
146
130
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
@pid = 0
|
151
|
-
break
|
152
|
-
end
|
131
|
+
# Save our old tty settings and set up our new environment
|
132
|
+
old_tty = `stty -g`
|
133
|
+
`stty -icanon min 1 time 0 -echo`
|
153
134
|
|
154
|
-
|
155
|
-
|
135
|
+
# SIGINT should be set along to the program
|
136
|
+
oldtrap = trap 'INT' do
|
137
|
+
send "\C-c"
|
138
|
+
end
|
139
|
+
|
140
|
+
interact = Thread.new do
|
141
|
+
input = ''.encode("UTF-8")
|
142
|
+
while @pid > 0 && @interact
|
143
|
+
if select([STDIN], nil, nil, 1)
|
144
|
+
c = STDIN.getc.chr
|
145
|
+
send c unless c.nil?
|
146
|
+
end
|
147
|
+
end
|
156
148
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
end
|
162
|
-
end
|
163
|
-
end
|
149
|
+
trap 'INT', oldtrap
|
150
|
+
`stty #{old_tty}`
|
151
|
+
@interact = false
|
152
|
+
end
|
164
153
|
|
165
|
-
|
166
|
-
|
167
|
-
@pid = 0
|
168
|
-
end
|
169
|
-
end
|
154
|
+
blocking ? interact.join : interact
|
155
|
+
end
|
170
156
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
157
|
+
# Public: Report whether or not current Expectr object is in interact mode
|
158
|
+
#
|
159
|
+
# Returns true or false
|
160
|
+
def interact?
|
161
|
+
@interact
|
162
|
+
end
|
163
|
+
|
164
|
+
# Public: Cause the current Expectr object to drop out of interact mode
|
165
|
+
#
|
166
|
+
# Returns nothing.
|
167
|
+
def leave!
|
168
|
+
@interact=false
|
169
|
+
end
|
180
170
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
# Interrupts should be caught and sent to the application.
|
192
|
-
#
|
193
|
-
def interact
|
194
|
-
oldtrap = trap 'INT' do
|
195
|
-
send "\C-c"
|
196
|
-
end
|
171
|
+
# Public: Kill the running process, raise ProcessError if the pid isn't > 1
|
172
|
+
#
|
173
|
+
# signal - Symbol, String, or Fixnum representing the signal to send to the
|
174
|
+
# running process. (default: :TERM)
|
175
|
+
#
|
176
|
+
# Returns true if the process was successfully killed, false otherwise
|
177
|
+
def kill!(signal=:TERM)
|
178
|
+
raise ProcessError unless @pid > 0
|
179
|
+
(Process::kill(signal.to_sym, @pid) == 1)
|
180
|
+
end
|
197
181
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
182
|
+
# Public: Send input to the active process
|
183
|
+
#
|
184
|
+
# str - String to be sent to the active process
|
185
|
+
#
|
186
|
+
# Returns nothing.
|
187
|
+
# Raises Expectr::ProcessError if the process isn't running
|
188
|
+
def send(str)
|
189
|
+
begin
|
190
|
+
@stdin.syswrite str
|
191
|
+
rescue Errno::EIO #Application went away.
|
192
|
+
@pid = 0
|
193
|
+
end
|
194
|
+
raise Expectr::ProcessError unless @pid > 0
|
195
|
+
end
|
210
196
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
197
|
+
# Public: Wraps Expectr#send, appending a newline to the end of the string
|
198
|
+
#
|
199
|
+
# str - String to be sent to the active process (default: '')
|
200
|
+
#
|
201
|
+
# Returns nothing.
|
202
|
+
def puts(str = '')
|
203
|
+
send str + "\n"
|
204
|
+
end
|
217
205
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
206
|
+
# Public: Begin a countdown and search for a given String or Regexp in the
|
207
|
+
# output buffer.
|
208
|
+
#
|
209
|
+
# pattern - String or Regexp representing what we want to find
|
210
|
+
# recoverable - Denotes whether failing to match the pattern should cause the
|
211
|
+
# method to raise an exception (default: false)
|
212
|
+
#
|
213
|
+
# Examples
|
214
|
+
#
|
215
|
+
# exp.expect("this should exist")
|
216
|
+
# # => MatchData
|
217
|
+
#
|
218
|
+
# exp.expect("this should exist") do
|
219
|
+
# # ...
|
220
|
+
# end
|
221
|
+
#
|
222
|
+
# exp.expect(/not there/)
|
223
|
+
# # Raises Timeout::Error
|
224
|
+
#
|
225
|
+
# exp.expect(/not there/, true)
|
226
|
+
# # => nil
|
227
|
+
#
|
228
|
+
# Returns a MatchData object once a match is found if no block is given
|
229
|
+
# Yields the MatchData object representing the match
|
230
|
+
# Raises TypeError if something other than a String or Regexp is given
|
231
|
+
# Raises Timeout::Error if a match isn't found in time, unless recoverable
|
232
|
+
def expect(pattern, recoverable = false)
|
233
|
+
match = nil
|
229
234
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
#
|
238
|
-
# +pattern+::
|
239
|
-
# String or regexp to match against
|
240
|
-
# +recoverable+::
|
241
|
-
# Determines if execution can continue after a timeout
|
242
|
-
#
|
243
|
-
# === Description
|
244
|
-
#
|
245
|
-
# Wait +timeout+ seconds to match +pattern+ in +buffer+. If timeout is
|
246
|
-
# reached, raise an error unless +recoverable+ is true.
|
247
|
-
#
|
248
|
-
def expect(pattern, recoverable = false)
|
249
|
-
match = nil
|
235
|
+
case pattern
|
236
|
+
when String
|
237
|
+
pattern = Regexp.new(Regexp.quote(pattern))
|
238
|
+
when Regexp
|
239
|
+
else
|
240
|
+
raise TypeError, "Pattern class should be String or Regexp"
|
241
|
+
end
|
250
242
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
243
|
+
begin
|
244
|
+
Timeout::timeout(@timeout) do
|
245
|
+
while match.nil?
|
246
|
+
if @out_update
|
247
|
+
@out_mutex.synchronize do
|
248
|
+
match = pattern.match @buffer
|
249
|
+
@out_update = false
|
250
|
+
end
|
251
|
+
end
|
252
|
+
sleep 0.1
|
253
|
+
end
|
254
|
+
end
|
257
255
|
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
end
|
267
|
-
sleep 0.1
|
268
|
-
end
|
269
|
-
end
|
256
|
+
@out_mutex.synchronize do
|
257
|
+
@discard = @buffer[0..match.begin(0)-1]
|
258
|
+
@buffer = @buffer[match.end(0)..-1]
|
259
|
+
@out_update = true
|
260
|
+
end
|
261
|
+
rescue Timeout::Error => details
|
262
|
+
raise details unless recoverable
|
263
|
+
end
|
270
264
|
|
271
|
-
|
272
|
-
|
273
|
-
@buffer = @buffer[match.end(0)..-1]
|
274
|
-
@out_update = true
|
275
|
-
end
|
276
|
-
rescue Timeout::Error => details
|
277
|
-
raise details unless recoverable
|
278
|
-
end
|
265
|
+
block_given? ? yield(match) : match
|
266
|
+
end
|
279
267
|
|
280
|
-
|
281
|
-
|
268
|
+
# Public: Clear output buffer
|
269
|
+
#
|
270
|
+
# Returns nothing.
|
271
|
+
def clear_buffer!
|
272
|
+
@out_mutex.synchronize do
|
273
|
+
@buffer = ''.encode("UTF-8")
|
274
|
+
@out_update = false
|
275
|
+
end
|
276
|
+
end
|
282
277
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
278
|
+
# Internal: Print buffer to STDOUT if @flush_buffer is true
|
279
|
+
#
|
280
|
+
# buf - String to be printed to STDOUT
|
281
|
+
#
|
282
|
+
# Returns nothing.
|
283
|
+
def print_buffer(buf)
|
284
|
+
print buf if @flush_buffer
|
285
|
+
STDOUT.flush unless STDOUT.sync
|
286
|
+
end
|
290
287
|
end
|
data/lib/expectr/version.rb
CHANGED
data/test/test_core.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class CoreTests < Test::Unit::TestCase
|
4
|
+
# For the purpose of testing, we will assume we are working within a POSIX
|
5
|
+
# environment.
|
6
|
+
def setup
|
7
|
+
@exp = Expectr.new("ls /dev", :flush_buffer => false, :timeout => 1,
|
8
|
+
:buffer_size => 4096)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_object_consistency
|
12
|
+
assert_equal false, @exp.flush_buffer
|
13
|
+
assert_equal 1, @exp.timeout
|
14
|
+
assert_equal 4096, @exp.buffer_size
|
15
|
+
end
|
16
|
+
|
17
|
+
# POSIX specifies /dev/console, /dev/null and /dev/tty must exist.
|
18
|
+
def test_match_sets_discard
|
19
|
+
assert_not_equal nil, @exp.expect(/null/)
|
20
|
+
assert_not_equal '', @exp.discard
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_match_failure
|
24
|
+
assert_raises(Timeout::Error) { @exp.expect(/ThisFileShouldNotExist/) }
|
25
|
+
assert_nothing_raised { @exp.expect(/ThisFileShouldNotExist/, true) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_clear_buffer
|
29
|
+
sleep 1
|
30
|
+
assert_not_equal @exp.buffer, ''
|
31
|
+
@exp.clear_buffer!
|
32
|
+
assert_equal '', @exp.buffer
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_pid_set
|
36
|
+
assert @exp.pid > 0
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class InitializationTests < Test::Unit::TestCase
|
4
|
+
def test_spawn_with_file
|
5
|
+
assert_nothing_raised { exp = Expectr.new(File.new("/bin/ls"), :flush_buffer => false) }
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_spawn_with_string
|
9
|
+
assert_nothing_raised { exp = Expectr.new(File.new("/bin/ls"), :flush_buffer => false) }
|
10
|
+
end
|
11
|
+
|
12
|
+
# lib/expectr.rb's permissions should hopefully be set to 0644
|
13
|
+
def test_spawn_failures
|
14
|
+
assert_raises(Errno::ENOENT) { exp = Expectr.new("lib/ThisFileShouldNotExist", :flush_buffer => false) }
|
15
|
+
assert_raises(Errno::EACCES) { exp = Expectr.new("lib/expectr.rb", :flush_buffer => false) }
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class InteractionTest < Test::Unit::TestCase
|
4
|
+
# Assume that bc(1) exists on the system for these tests
|
5
|
+
def setup
|
6
|
+
@exp = Expectr.new("bc", :flush_buffer => false, :timeout => 1)
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_send_and_expect
|
10
|
+
assert_nothing_raised do
|
11
|
+
@exp.send("300+21\n")
|
12
|
+
@exp.expect("321")
|
13
|
+
@exp.puts("quit")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_expect_with_block
|
18
|
+
assert_nothing_raised do
|
19
|
+
@exp.send("300+21\n")
|
20
|
+
@exp.expect("321") { |m| m.nil? ? raise(ArgumentError) : true }
|
21
|
+
end
|
22
|
+
|
23
|
+
assert_raises(TimeoutError) do
|
24
|
+
@exp.send("300+21\n")
|
25
|
+
@exp.expect("xxx") { |m| m.nil? ? raise(ArgumentError) : true }
|
26
|
+
end
|
27
|
+
|
28
|
+
assert_raises(ArgumentError) do
|
29
|
+
@exp.send("300+21\n")
|
30
|
+
@exp.expect("xxx", true) { |m| m.nil? ? raise(ArgumentError) : true }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_send_to_terminated_fails
|
35
|
+
@exp.send("quit\n")
|
36
|
+
sleep 2
|
37
|
+
assert_raises(Expectr::ProcessError) { @exp.send("test\n") }
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_interact_sets_appropriate_flags
|
41
|
+
[
|
42
|
+
Thread.new {
|
43
|
+
assert_equal false, @exp.interact?
|
44
|
+
|
45
|
+
sleep 0.5
|
46
|
+
@exp.interact!.join
|
47
|
+
},
|
48
|
+
Thread.new {
|
49
|
+
sleep 1
|
50
|
+
assert_equal true, @exp.flush_buffer
|
51
|
+
assert_equal true, @exp.interact?
|
52
|
+
|
53
|
+
@exp.flush_buffer = false
|
54
|
+
@exp.send("quit\n")
|
55
|
+
}
|
56
|
+
].each {|x| x.join}
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_interact_mode
|
60
|
+
[
|
61
|
+
Thread.new {
|
62
|
+
sleep 0.5
|
63
|
+
@exp.interact!.join
|
64
|
+
},
|
65
|
+
Thread.new {
|
66
|
+
sleep 1
|
67
|
+
@exp.flush_buffer = false
|
68
|
+
@exp.send("300+21\n")
|
69
|
+
@exp.send("quit\n")
|
70
|
+
}
|
71
|
+
].each {|x| x.join}
|
72
|
+
|
73
|
+
assert_not_nil @exp.expect(/321/)
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_leaving_interact_mode
|
77
|
+
[
|
78
|
+
Thread.new {
|
79
|
+
sleep 0.5
|
80
|
+
@exp.interact!.join
|
81
|
+
},
|
82
|
+
Thread.new {
|
83
|
+
sleep 1
|
84
|
+
@exp.flush_buffer = false
|
85
|
+
assert_nothing_raised { @exp.leave! }
|
86
|
+
assert_equal false, @exp.interact?
|
87
|
+
@exp.send("quit\n")
|
88
|
+
}
|
89
|
+
].each {|x| x.join}
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_blocking_interact_mode
|
93
|
+
[
|
94
|
+
Thread.new {
|
95
|
+
sleep 0.5
|
96
|
+
@exp.interact!(blocking: true)
|
97
|
+
},
|
98
|
+
Thread.new {
|
99
|
+
sleep 1
|
100
|
+
@exp.flush_buffer = false
|
101
|
+
@exp.send("300+21\n")
|
102
|
+
@exp.send("quit\n")
|
103
|
+
}
|
104
|
+
].each {|x| x.join}
|
105
|
+
|
106
|
+
assert_not_nil @exp.expect(/321/)
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_kill_process
|
110
|
+
assert_equal true, @exp.kill!
|
111
|
+
assert_equal 0, @exp.pid
|
112
|
+
assert_raises(Expectr::ProcessError) { @exp.send("test\n") }
|
113
|
+
end
|
114
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: expectr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-10-30 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Expectr is an interface to the functionality of Expect in Ruby
|
15
15
|
email: chris@chriswuest.com
|
@@ -23,9 +23,12 @@ files:
|
|
23
23
|
- Rakefile
|
24
24
|
- expectr.gemspec
|
25
25
|
- lib/expectr.rb
|
26
|
+
- lib/expectr/error.rb
|
26
27
|
- lib/expectr/version.rb
|
27
28
|
- test/helper.rb
|
28
|
-
- test/
|
29
|
+
- test/test_core.rb
|
30
|
+
- test/test_initialization.rb
|
31
|
+
- test/test_interaction.rb
|
29
32
|
homepage: http://github.com/cwuest/expectr
|
30
33
|
licenses:
|
31
34
|
- MIT
|
@@ -52,4 +55,6 @@ signing_key:
|
|
52
55
|
specification_version: 3
|
53
56
|
summary: Expect for Ruby
|
54
57
|
test_files:
|
55
|
-
- test/
|
58
|
+
- test/test_core.rb
|
59
|
+
- test/test_initialization.rb
|
60
|
+
- test/test_interaction.rb
|
data/test/test_expectr.rb
DELETED
@@ -1,81 +0,0 @@
|
|
1
|
-
require 'helper'
|
2
|
-
|
3
|
-
class TestExpectr < Test::Unit::TestCase
|
4
|
-
def setup
|
5
|
-
@exp = Expectr.new "ls /bin", :flush_buffer => false, :timeout => 2, :buffer_size => 4096
|
6
|
-
end
|
7
|
-
|
8
|
-
def test_execution
|
9
|
-
assert_equal @exp.flush_buffer, false
|
10
|
-
assert_equal @exp.timeout, 2
|
11
|
-
assert_equal @exp.buffer_size, 4096
|
12
|
-
end
|
13
|
-
|
14
|
-
def test_match
|
15
|
-
assert_not_equal @exp.expect(/sh/), nil
|
16
|
-
assert_not_equal @exp.discard, ''
|
17
|
-
end
|
18
|
-
|
19
|
-
def test_match_failure
|
20
|
-
assert_raises(Timeout::Error) { @exp.expect /ThisFileShouldNotExist/ }
|
21
|
-
assert_nothing_raised { @exp.expect /ThisFileShouldNotExist/, true }
|
22
|
-
end
|
23
|
-
|
24
|
-
def test_send
|
25
|
-
exp = Expectr.new "bc", :flush_buffer => false
|
26
|
-
exp.send "20+301\n"
|
27
|
-
exp.expect /321/
|
28
|
-
end
|
29
|
-
|
30
|
-
def test_send_to_terminated
|
31
|
-
exp = Expectr.new "ls", :flush_buffer => false
|
32
|
-
sleep 1
|
33
|
-
assert_raises(ArgumentError) { exp.send "test\n" }
|
34
|
-
end
|
35
|
-
|
36
|
-
def test_clear_buffer
|
37
|
-
sleep 1
|
38
|
-
assert_not_equal @exp.buffer, ''
|
39
|
-
@exp.clear_buffer
|
40
|
-
assert_equal @exp.buffer, ''
|
41
|
-
end
|
42
|
-
|
43
|
-
def test_pid_set
|
44
|
-
assert @exp.pid > 0
|
45
|
-
end
|
46
|
-
|
47
|
-
def test_interact
|
48
|
-
unless RUBY_VERSION =~ /1.8/
|
49
|
-
exp = Expectr.new "bc", :flush_buffer => false
|
50
|
-
[
|
51
|
-
Thread.new {
|
52
|
-
sleep 1
|
53
|
-
exp.interact
|
54
|
-
},
|
55
|
-
Thread.new {
|
56
|
-
sleep 2
|
57
|
-
assert_equal exp.flush_buffer, true
|
58
|
-
exp.flush_buffer = false
|
59
|
-
exp.send "300+21\n"
|
60
|
-
exp.send "quit\n"
|
61
|
-
}
|
62
|
-
].each {|x| x.join}
|
63
|
-
|
64
|
-
assert_not_nil exp.expect /321/
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def test_create_with_file
|
69
|
-
assert_nothing_raised { exp = Expectr.new File.new("/bin/ls"), :flush_buffer => false }
|
70
|
-
end
|
71
|
-
|
72
|
-
def test_executable
|
73
|
-
assert_nothing_raised { exp = Expectr.new "/bin/ls", :flush_buffer => false }
|
74
|
-
|
75
|
-
# Ruby 1.8's PTY allows execution of non-executable/nonexistent files without complaint
|
76
|
-
unless RUBY_VERSION =~ /1.8/
|
77
|
-
assert_raises(Errno::ENOENT) { exp = Expectr.new "/bin/ThisFileShouldNotExist", :flush_buffer => false }
|
78
|
-
assert_raises(Errno::EACCES) { exp = Expectr.new "lib/expectr.rb", :flush_buffer => false }
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|