expect-pty 0.5.3 → 0.6.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -0
- data/README.md +28 -19
- data/docs/API.md +88 -0
- data/docs/MIGRATION.md +15 -0
- data/lib/expect/cleanup.rb +33 -0
- data/lib/expect/configuration.rb +87 -66
- data/lib/expect/interaction.rb +226 -209
- data/lib/expect/logging.rb +157 -156
- data/lib/expect/matcher.rb +30 -29
- data/lib/expect/pattern.rb +8 -2
- data/lib/expect/pattern_list.rb +35 -5
- data/lib/expect/redactor.rb +11 -4
- data/lib/expect/relay.rb +97 -73
- data/lib/expect/relay_writer.rb +2 -1
- data/lib/expect/result.rb +32 -8
- data/lib/expect/session.rb +508 -0
- data/lib/expect/session_resources.rb +10 -2
- data/lib/expect/terminal.rb +78 -78
- data/lib/expect/version.rb +1 -1
- data/lib/expect.rb +333 -520
- data/sig/expect.rbs +355 -0
- metadata +13 -106
- data/.rubocop.yml +0 -66
- data/CONTRIBUTING.md +0 -29
- data/Gemfile +0 -28
- data/Rakefile +0 -38
- data/benchmark/matching.rb +0 -98
- data/benchmark/redactor.rb +0 -64
- data/benchmark/relay.rb +0 -48
- data/benchmark/scaling.rb +0 -70
- data/benchmark/send_slow.rb +0 -41
- data/benchmark/support.rb +0 -114
- data/docs/COMPATIBILITY.md +0 -98
- data/docs/INTERNAL_CONTRACTS.md +0 -154
- data/docs/PERFORMANCE.md +0 -165
- data/docs/RELEASING.md +0 -84
- data/docs/VERIFICATION.md +0 -456
- data/examples/dialogue.rb +0 -30
- data/examples/kibitz/README.md +0 -81
- data/examples/kibitz/kibitz.rb +0 -142
- data/examples/kibitz/test_kibitz.rb +0 -37
- data/examples/ssh_auto.rb +0 -94
- data/examples/ssh_interact.rb +0 -159
- data/examples/ssh_login.rb +0 -64
- data/expect-pty.gemspec +0 -33
- data/script/ci +0 -121
- data/script/release.rb +0 -319
- data/test/buffer_accounting_test.rb +0 -83
- data/test/cleanup_test.rb +0 -251
- data/test/compare_upstream.rb +0 -157
- data/test/configuration_test.rb +0 -133
- data/test/deadline_test.rb +0 -237
- data/test/diagnostics_test.rb +0 -353
- data/test/edge_case_test.rb +0 -262
- data/test/fixtures/ssh_scripts/01_identity.sh +0 -4
- data/test/fixtures/ssh_scripts/02_output.sh +0 -5
- data/test/fixtures/ssh_scripts/03_delayed.sh +0 -6
- data/test/fixtures/ssh_scripts/04_failure.sh +0 -2
- data/test/fixtures/ssh_scripts/05_recovery.sh +0 -3
- data/test/initialization_failure_test.rb +0 -92
- data/test/integration/README.md +0 -109
- data/test/integration/ssh_scripts.rb +0 -94
- data/test/interact_test.rb +0 -253
- data/test/interconnect_test.rb +0 -425
- data/test/io_test.rb +0 -321
- data/test/kibitz_test.rb +0 -45
- data/test/lifecycle_contract_test.rb +0 -71
- data/test/literal_scan_test.rb +0 -97
- data/test/matching_test.rb +0 -211
- data/test/multi_session_test.rb +0 -66
- data/test/ownership_sequence_test.rb +0 -208
- data/test/pattern_offset_test.rb +0 -43
- data/test/process_interruption_test.rb +0 -296
- data/test/process_test.rb +0 -291
- data/test/redactor_test.rb +0 -183
- data/test/relay_recovery_test.rb +0 -451
- data/test/relay_reentrancy_test.rb +0 -159
- data/test/release_test.rb +0 -375
- data/test/ruby_api_test.rb +0 -515
- data/test/scan_reuse_test.rb +0 -109
- data/test/script_logging_test.rb +0 -124
- data/test/support/interact_probe.rb +0 -125
- data/test/support/kibitz_probe.rb +0 -177
- data/test/support/script_probe.rb +0 -158
- data/test/terminal_cleanup_test.rb +0 -349
- data/test/test_helper.rb +0 -58
- data/test/timeout_test.rb +0 -289
- data/test/write_contract_test.rb +0 -105
data/test/io_test.rb
DELETED
|
@@ -1,321 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "test_helper"
|
|
4
|
-
|
|
5
|
-
class IOTest < ExpectTest
|
|
6
|
-
def test_socket_pair_send_and_expect
|
|
7
|
-
client, server = Socket.pair(:UNIX, :STREAM, 0)
|
|
8
|
-
@ios.push(client, server)
|
|
9
|
-
session = Expect.open(client)
|
|
10
|
-
@sessions << session
|
|
11
|
-
background do
|
|
12
|
-
server.write("prompt>")
|
|
13
|
-
server.write(server.gets.upcase)
|
|
14
|
-
end
|
|
15
|
-
assert_equal 1, session.expect("prompt>", timeout: 1)
|
|
16
|
-
assert_equal 6, session.write("hello\n")
|
|
17
|
-
assert_equal 1, session.expect("HELLO\n", timeout: 1)
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def test_log_file_append_truncate_disable_and_manual_output
|
|
21
|
-
session, writer = pipe_session
|
|
22
|
-
Dir.mktmpdir do |dir|
|
|
23
|
-
path = File.join(dir, "session.log")
|
|
24
|
-
File.write(path, "old")
|
|
25
|
-
log = session.log_to(path)
|
|
26
|
-
writer.write("reply")
|
|
27
|
-
session.expect("reply", timeout: 1)
|
|
28
|
-
session.write_log(" annotation")
|
|
29
|
-
session.log_output = nil
|
|
30
|
-
assert log.closed?
|
|
31
|
-
assert_equal "oldreply annotation", File.binread(path)
|
|
32
|
-
session.log_to(path, mode: "w")
|
|
33
|
-
writer.write("new")
|
|
34
|
-
session.expect("new", timeout: 1)
|
|
35
|
-
session.log_output = nil
|
|
36
|
-
assert_equal "new", File.binread(path)
|
|
37
|
-
writer.write("unlogged")
|
|
38
|
-
session.expect("unlogged", timeout: 1)
|
|
39
|
-
assert_equal "new", File.binread(path)
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def test_logging_matches_upstream_received_data_only
|
|
44
|
-
session = child("puts STDIN.gets.reverse", raw_pty: true)
|
|
45
|
-
chunks = []
|
|
46
|
-
session.log_to(->(data) { chunks << data })
|
|
47
|
-
session.write("abc\n")
|
|
48
|
-
session.expect("cba", timeout: 2)
|
|
49
|
-
assert_equal "\ncba\n", chunks.join
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def test_new_log_files_are_private_without_changing_existing_permissions
|
|
53
|
-
session, = pipe_session
|
|
54
|
-
Dir.mktmpdir do |directory|
|
|
55
|
-
previous_umask = File.umask(0o022)
|
|
56
|
-
begin
|
|
57
|
-
%w[a w].each do |mode|
|
|
58
|
-
path = File.join(directory, "session-#{mode}.log")
|
|
59
|
-
session.log_to(path, mode: mode)
|
|
60
|
-
assert_equal 0o600, File.stat(path).mode & 0o777
|
|
61
|
-
session.log_output = nil
|
|
62
|
-
File.chmod(0o640, path)
|
|
63
|
-
session.log_to(path, mode: mode)
|
|
64
|
-
assert_equal 0o640, File.stat(path).mode & 0o777
|
|
65
|
-
end
|
|
66
|
-
ensure
|
|
67
|
-
File.umask(previous_umask)
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def test_group_and_stdout_logging_can_be_controlled_separately
|
|
73
|
-
session, writer = pipe_session
|
|
74
|
-
listener = StringIO.new
|
|
75
|
-
session.listeners = [listener]
|
|
76
|
-
writer.write("one")
|
|
77
|
-
session.expect("one", timeout: 1)
|
|
78
|
-
assert_equal "one", listener.string
|
|
79
|
-
session.log_listeners = false
|
|
80
|
-
session.log_stdout = true
|
|
81
|
-
output, = capture_io do
|
|
82
|
-
writer.write("two")
|
|
83
|
-
session.expect("two", timeout: 1)
|
|
84
|
-
end
|
|
85
|
-
assert_equal "two", output
|
|
86
|
-
assert_equal "one", listener.string
|
|
87
|
-
session.listeners = []
|
|
88
|
-
assert_empty session.listeners
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def test_debug_and_internal_output
|
|
92
|
-
session, writer = pipe_session
|
|
93
|
-
session.debug_level = 1
|
|
94
|
-
_, diagnostics = capture_io do
|
|
95
|
-
writer.write("ready")
|
|
96
|
-
session.expect("ready", timeout: 1)
|
|
97
|
-
end
|
|
98
|
-
assert_match(/matched pattern 1/, diagnostics)
|
|
99
|
-
refute_match(/received/, diagnostics)
|
|
100
|
-
session.debug_level = 2
|
|
101
|
-
_, diagnostics = capture_io do
|
|
102
|
-
writer.write("more")
|
|
103
|
-
session.expect("more", timeout: 1)
|
|
104
|
-
end
|
|
105
|
-
assert_match(/received "more"/, diagnostics)
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
def test_quiet_mode_does_not_format_byte_content_for_diagnostics
|
|
109
|
-
session, peer = Socket.pair(:UNIX, :STREAM, 0)
|
|
110
|
-
@ios.push(session, peer)
|
|
111
|
-
connection = Expect.open(session, debug_level: 0)
|
|
112
|
-
@sessions << connection
|
|
113
|
-
inspected = 0
|
|
114
|
-
probe = TracePoint.new(:c_call) do |event|
|
|
115
|
-
inspected += 1 if event.defined_class == String && event.method_id == :inspect
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
probe.enable do
|
|
119
|
-
connection.write("hello")
|
|
120
|
-
peer.write("reply")
|
|
121
|
-
connection.expect("reply", timeout: 1)
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
assert_equal 0, inspected
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def test_send_slow_collects_replies_even_when_logging_disabled
|
|
128
|
-
session = child("loop { char = STDIN.read(1); break unless char; print char.upcase }", raw_pty: true)
|
|
129
|
-
session.log_listeners = false
|
|
130
|
-
start = Expect.monotonic
|
|
131
|
-
assert_equal 3, session.send_slow("abc", delay: 0.02)
|
|
132
|
-
assert_operator Expect.monotonic - start, :>=, 0.06
|
|
133
|
-
assert_equal 1, session.expect("ABC", timeout: 1)
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
def test_send_slow_zero_delay_only_polls_and_later_reply_remains_readable
|
|
137
|
-
client, peer = Socket.pair(:UNIX, :STREAM, 0)
|
|
138
|
-
@ios.push(client, peer)
|
|
139
|
-
session = Expect.open(client, log_stdout: false)
|
|
140
|
-
@sessions << session
|
|
141
|
-
waits = []
|
|
142
|
-
original = client.method(:wait_readable)
|
|
143
|
-
client.stub(:wait_readable, lambda { |timeout|
|
|
144
|
-
waits << timeout
|
|
145
|
-
original.call(0)
|
|
146
|
-
}) do
|
|
147
|
-
assert_equal 8, session.send_slow("a中😀", delay: 0)
|
|
148
|
-
end
|
|
149
|
-
assert_equal [0, 0, 0], waits
|
|
150
|
-
assert_equal("a中😀".b, bounded { peer.read(8) })
|
|
151
|
-
peer.write("late reply")
|
|
152
|
-
assert_equal 1, session.expect("late reply", timeout: 1)
|
|
153
|
-
assert_empty session.buffer
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
def test_send_slow_preserves_character_delay_and_write_timeout
|
|
157
|
-
session, = pipe_session
|
|
158
|
-
calls = []
|
|
159
|
-
failure = Expect::WriteTimeout.new(bytes_written: 0)
|
|
160
|
-
session.stub(:sleep, ->(duration) { calls << [:sleep, duration] }) do
|
|
161
|
-
session.stub(:write, lambda { |data|
|
|
162
|
-
calls << [:write, data]
|
|
163
|
-
raise failure
|
|
164
|
-
}) do
|
|
165
|
-
assert_same failure, assert_raises(Expect::WriteTimeout) { session.send_slow("中b", delay: 0.25) }
|
|
166
|
-
end
|
|
167
|
-
end
|
|
168
|
-
assert_equal [[:sleep, 0.25], [:write, "中"]], calls
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
def test_large_bidirectional_write_does_not_deadlock
|
|
172
|
-
session = child("STDIN.binmode; STDOUT.binmode; loop { print STDIN.readpartial(4096) }", raw_pty: true,
|
|
173
|
-
write_timeout: 3)
|
|
174
|
-
payload = (0..255).to_a.pack("C*") * 1024
|
|
175
|
-
bounded do
|
|
176
|
-
assert_equal payload.bytesize, session.write(payload)
|
|
177
|
-
assert_equal 1, session.expect(payload, timeout: 3)
|
|
178
|
-
end
|
|
179
|
-
assert_equal payload, session.match
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
def test_write_timeout_on_backpressure
|
|
183
|
-
session = child('puts "ready"; sleep 30', raw_pty: true, write_timeout: 0.05)
|
|
184
|
-
session.expect("ready", timeout: 2)
|
|
185
|
-
assert_raises(Expect::WriteTimeout) { bounded { session.write("x" * 1_000_000) } }
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
def test_readiness_returns_readable_sessions
|
|
189
|
-
first, = pipe_session
|
|
190
|
-
second, writer = pipe_session
|
|
191
|
-
assert_empty Expect.readable_sessions(first, second)
|
|
192
|
-
writer.write("data")
|
|
193
|
-
assert_equal [second], Expect.readable_sessions(first, second)
|
|
194
|
-
second.expect("data", timeout: 1)
|
|
195
|
-
assert_empty Expect.readable_sessions(first, second)
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
def test_readiness_retries_an_interrupted_select
|
|
199
|
-
session, writer = pipe_session
|
|
200
|
-
writer.write("ready")
|
|
201
|
-
original = IO.method(:select)
|
|
202
|
-
interrupted = true
|
|
203
|
-
select = lambda do |*arguments|
|
|
204
|
-
if interrupted
|
|
205
|
-
interrupted = false
|
|
206
|
-
raise Errno::EINTR
|
|
207
|
-
end
|
|
208
|
-
original.call(*arguments)
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
IO.stub(:select, select) do
|
|
212
|
-
assert_equal [session], Expect.readable_sessions(session, timeout: 1)
|
|
213
|
-
end
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
def test_write_retries_an_interrupted_nonblocking_write
|
|
217
|
-
reader, writer = Socket.pair(:UNIX, :STREAM, 0)
|
|
218
|
-
@ios.push(reader, writer)
|
|
219
|
-
session = Expect.open(reader)
|
|
220
|
-
@sessions << session
|
|
221
|
-
original = reader.method(:write_nonblock)
|
|
222
|
-
interrupted = true
|
|
223
|
-
write = lambda do |*arguments, **options|
|
|
224
|
-
if interrupted
|
|
225
|
-
interrupted = false
|
|
226
|
-
raise Errno::EINTR
|
|
227
|
-
end
|
|
228
|
-
original.call(*arguments, **options)
|
|
229
|
-
end
|
|
230
|
-
|
|
231
|
-
reader.stub(:write_nonblock, write) do
|
|
232
|
-
assert_equal 5, session.write("hello")
|
|
233
|
-
end
|
|
234
|
-
assert_equal "hello", writer.read(5)
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
def test_write_retries_an_interrupted_writable_wait
|
|
238
|
-
reader, writer = Socket.pair(:UNIX, :STREAM, 0)
|
|
239
|
-
@ios.push(reader, writer)
|
|
240
|
-
session = Expect.open(reader, write_timeout: 1)
|
|
241
|
-
@sessions << session
|
|
242
|
-
original = reader.method(:write_nonblock)
|
|
243
|
-
waiting = true
|
|
244
|
-
write = lambda do |*arguments, **options|
|
|
245
|
-
if waiting
|
|
246
|
-
waiting = false
|
|
247
|
-
:wait_writable
|
|
248
|
-
else
|
|
249
|
-
original.call(*arguments, **options)
|
|
250
|
-
end
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
reader.stub(:write_nonblock, write) do
|
|
254
|
-
IO.stub(:select, ->(*) { raise Errno::EINTR }) do
|
|
255
|
-
assert_equal 5, session.write("hello")
|
|
256
|
-
end
|
|
257
|
-
end
|
|
258
|
-
assert_equal "hello", writer.read(5)
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
def test_readiness_waits_for_data_and_does_not_consume_data
|
|
262
|
-
first, = pipe_session
|
|
263
|
-
second, writer = pipe_session
|
|
264
|
-
background do
|
|
265
|
-
sleep 0.03
|
|
266
|
-
writer.write("ready")
|
|
267
|
-
end
|
|
268
|
-
assert_equal([second], bounded { Expect.readable_sessions(first, second, timeout: 1) })
|
|
269
|
-
assert_equal 1, second.expect("ready", timeout: 0)
|
|
270
|
-
assert_empty Expect.readable_sessions(first, second, timeout: 0)
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
def test_readiness_accepts_keyword_and_unlimited_timeout
|
|
274
|
-
session, writer = pipe_session
|
|
275
|
-
assert_empty Expect.readable_sessions(session, timeout: 0.01)
|
|
276
|
-
background do
|
|
277
|
-
sleep 0.03
|
|
278
|
-
writer.write("ready")
|
|
279
|
-
end
|
|
280
|
-
assert_equal([session], bounded { Expect.readable_sessions(session, timeout: nil) })
|
|
281
|
-
assert_equal [session], Expect.readable_sessions(session, timeout: 0)
|
|
282
|
-
assert_raises(ArgumentError) { Expect.readable_sessions(session, timeout: -1) }
|
|
283
|
-
assert_raises(ArgumentError) { Expect.readable_sessions(0, session, timeout: 1) }
|
|
284
|
-
assert_raises(ArgumentError) { Expect.readable_sessions("invalid") }
|
|
285
|
-
end
|
|
286
|
-
|
|
287
|
-
def test_readiness_excludes_closed_sessions
|
|
288
|
-
first, = pipe_session
|
|
289
|
-
second, writer = pipe_session
|
|
290
|
-
first.close
|
|
291
|
-
writer.write("ready")
|
|
292
|
-
assert_equal [second], Expect.readable_sessions(first, second, timeout: 0)
|
|
293
|
-
assert_empty Expect.readable_sessions
|
|
294
|
-
end
|
|
295
|
-
|
|
296
|
-
def test_read_io_error_is_reported
|
|
297
|
-
Tempfile.create("expect-write-only") do |file|
|
|
298
|
-
File.open(file.path, "w") do |writer|
|
|
299
|
-
session = Expect.open(writer)
|
|
300
|
-
@sessions << session
|
|
301
|
-
result = session.expect_result("x", timeout: 1)
|
|
302
|
-
assert_instance_of IOError, result.error
|
|
303
|
-
end
|
|
304
|
-
end
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
def test_log_io_failure_does_not_masquerade_as_child_eof
|
|
308
|
-
session = child('puts "ready"; STDIN.gets; puts "response"; sleep 30', raw_pty: true)
|
|
309
|
-
assert_equal 1, session.expect("ready\n", timeout: 2)
|
|
310
|
-
session.log_to(->(_) { raise Errno::EIO, "log failed" })
|
|
311
|
-
session.write("continue\n")
|
|
312
|
-
result = session.expect_result("response", timeout: 2)
|
|
313
|
-
assert_instance_of Errno::EIO, result.error
|
|
314
|
-
refute result.eof?
|
|
315
|
-
refute session.eof?
|
|
316
|
-
assert session.alive?
|
|
317
|
-
assert_includes result.before, "response\n"
|
|
318
|
-
session.log_output = nil
|
|
319
|
-
assert_equal 1, session.expect("response", timeout: 0)
|
|
320
|
-
end
|
|
321
|
-
end
|
data/test/kibitz_test.rb
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "test_helper"
|
|
4
|
-
require_relative "support/kibitz_probe"
|
|
5
|
-
|
|
6
|
-
class KibitzTest < ExpectTest
|
|
7
|
-
def test_two_terminals_share_shell_echo_signals_and_logged_output
|
|
8
|
-
Dir.mktmpdir { |directory| assert KibitzProbe.shared_shell(directory)[:passed] }
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def test_noproc_split_custom_escape_does_not_leak_to_peer
|
|
12
|
-
Dir.mktmpdir { |directory| assert KibitzProbe.direct(directory, ending: :host)[:passed] }
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def test_guest_escape_ends_both_sides_and_restores_terminals
|
|
16
|
-
Dir.mktmpdir { |directory| assert KibitzProbe.direct(directory, ending: :guest)[:passed] }
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def test_noescape_forwards_control_character_as_data
|
|
20
|
-
Dir.mktmpdir { |directory| assert KibitzProbe.noescape(directory)[:passed] }
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def test_shared_process_failure_is_reported
|
|
24
|
-
Dir.mktmpdir { |directory| assert KibitzProbe.process_failure(directory)[:passed] }
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def test_relay_timeout_restores_terminals_and_closes_peer
|
|
28
|
-
Dir.mktmpdir { |directory| assert KibitzProbe.relay_timeout(directory)[:passed] }
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def test_missing_partner_times_out_and_removes_socket
|
|
32
|
-
session = Expect.new(log_stdout: false)
|
|
33
|
-
@sessions << session
|
|
34
|
-
saved = InteractProbe.configuration(session)
|
|
35
|
-
session.spawn(RbConfig.ruby, KibitzProbe::EXAMPLE, "--timeout", "0.2")
|
|
36
|
-
result = session.expect_result(%r{--join (/tmp/expect-kibitz-[^\r\n ]+/peer\.sock)}, timeout: 3)
|
|
37
|
-
assert result.matched?
|
|
38
|
-
path = result.captures.first
|
|
39
|
-
assert session.expect_result(:eof, timeout: 3).eof?
|
|
40
|
-
assert_includes session.before, "timed out waiting for partner"
|
|
41
|
-
assert_equal 1, session.wait(timeout: 1).exitstatus
|
|
42
|
-
assert_equal saved, InteractProbe.configuration(session)
|
|
43
|
-
refute File.exist?(File.dirname(path))
|
|
44
|
-
end
|
|
45
|
-
end
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "test_helper"
|
|
4
|
-
|
|
5
|
-
class LifecycleContractTest < ExpectTest
|
|
6
|
-
def test_external_reaping_releases_pid_without_fabricating_process_status
|
|
7
|
-
session = child('puts "ready"; exit 7', raw_pty: true)
|
|
8
|
-
assert_equal 1, session.expect("ready", timeout: 2)
|
|
9
|
-
pid = session.pid
|
|
10
|
-
assert_equal(7, bounded { Process.waitpid2(pid).last.exitstatus })
|
|
11
|
-
refute session.alive?
|
|
12
|
-
assert_nil session.pid
|
|
13
|
-
assert_nil session.process_status
|
|
14
|
-
assert_nil session.wait(timeout: 0)
|
|
15
|
-
Process.stub(:kill, ->(*) { flunk "must not signal an externally reaped PID" }) do
|
|
16
|
-
2.times { session.close }
|
|
17
|
-
end
|
|
18
|
-
assert session.closed?
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def test_external_reader_close_does_not_take_ownership_of_the_writer
|
|
22
|
-
reader, producer = IO.pipe
|
|
23
|
-
consumer, writer = IO.pipe
|
|
24
|
-
@ios.push(reader, producer, consumer, writer)
|
|
25
|
-
session = Expect.open(reader, writer: writer)
|
|
26
|
-
@sessions << session
|
|
27
|
-
session.buffer = "tail"
|
|
28
|
-
reader.close
|
|
29
|
-
assert session.closed?
|
|
30
|
-
assert session.eof?
|
|
31
|
-
refute session.alive?
|
|
32
|
-
assert_raises(IOError) { session.write("blocked") }
|
|
33
|
-
2.times { session.close }
|
|
34
|
-
refute writer.closed?
|
|
35
|
-
assert_equal "tail", session.buffer
|
|
36
|
-
writer.write("borrowed")
|
|
37
|
-
assert_equal "borrowed", consumer.read(8)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def test_failed_handle_close_can_be_retried_without_losing_other_owned_handles
|
|
41
|
-
reader, writer = IO.pipe
|
|
42
|
-
@ios.push(reader, writer)
|
|
43
|
-
session = Expect.open(reader, writer: writer, own: true)
|
|
44
|
-
@sessions << session
|
|
45
|
-
failure = IOError.new("close failed")
|
|
46
|
-
reader.stub(:close, -> { raise failure }) do
|
|
47
|
-
assert_same failure, assert_raises(IOError) { session.close }
|
|
48
|
-
assert writer.closed?
|
|
49
|
-
refute reader.closed?
|
|
50
|
-
assert session.closed?
|
|
51
|
-
end
|
|
52
|
-
session.close
|
|
53
|
-
assert reader.closed?
|
|
54
|
-
assert_nil session.close
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def test_timeout_and_eof_remain_events_and_write_errors_remain_io_errors
|
|
58
|
-
session, writer = pipe_session
|
|
59
|
-
timeout = session.expect_result("missing", timeout: 0)
|
|
60
|
-
assert timeout.timeout?
|
|
61
|
-
assert_equal :timeout, timeout.error
|
|
62
|
-
writer.close
|
|
63
|
-
eof = session.expect_result(:eof, timeout: 1)
|
|
64
|
-
assert eof.eof?
|
|
65
|
-
assert_equal :eof, eof.error
|
|
66
|
-
failure = Expect::WriteTimeout.new(bytes_written: 3)
|
|
67
|
-
assert_kind_of IOError, failure
|
|
68
|
-
assert_equal 3, failure.bytes_written
|
|
69
|
-
assert_operator Expect::SpawnError, :<, StandardError
|
|
70
|
-
end
|
|
71
|
-
end
|
data/test/literal_scan_test.rb
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "test_helper"
|
|
4
|
-
|
|
5
|
-
class LiteralScanTest < ExpectTest
|
|
6
|
-
def test_new_bytes_can_complete_a_previously_missing_literal
|
|
7
|
-
session, writer = pipe_session
|
|
8
|
-
list = Expect::PatternList.new([session], ["password:", "word:"])
|
|
9
|
-
matcher = Expect::Matcher.new(list, nil)
|
|
10
|
-
session.buffer = "prefix pass"
|
|
11
|
-
assert_nil matcher.__send__(:find_match)
|
|
12
|
-
writer.write("word:")
|
|
13
|
-
session.__send__(:read_available)
|
|
14
|
-
result = matcher.__send__(:find_match)
|
|
15
|
-
assert_equal 1, result[1].number
|
|
16
|
-
assert_equal [7, 9, []], result[2]
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def test_later_text_for_an_earlier_pattern_keeps_declaration_priority
|
|
20
|
-
session, writer = pipe_session
|
|
21
|
-
list = Expect::PatternList.new([session], %w[preferred ready])
|
|
22
|
-
matcher = Expect::Matcher.new(list, nil)
|
|
23
|
-
session.buffer = "ready"
|
|
24
|
-
assert_equal 2, matcher.__send__(:find_match)[1].number
|
|
25
|
-
writer.write(" preferred")
|
|
26
|
-
session.__send__(:read_available)
|
|
27
|
-
assert_equal 1, matcher.__send__(:find_match)[1].number
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def test_destructive_buffer_changes_invalidate_old_misses
|
|
31
|
-
session, writer = pipe_session
|
|
32
|
-
list = Expect::PatternList.new([session], ["start", "token", /END/])
|
|
33
|
-
matcher = Expect::Matcher.new(list, nil)
|
|
34
|
-
session.buffer = "x" * 100
|
|
35
|
-
assert_nil matcher.__send__(:find_match)
|
|
36
|
-
session.buffer = "start"
|
|
37
|
-
assert_equal 1, matcher.__send__(:find_match)[1].number
|
|
38
|
-
session.clear_buffer
|
|
39
|
-
assert_nil matcher.__send__(:find_match)
|
|
40
|
-
writer.write("token")
|
|
41
|
-
session.__send__(:read_available)
|
|
42
|
-
assert_equal 2, matcher.__send__(:find_match)[1].number
|
|
43
|
-
session.expect("token", timeout: 0)
|
|
44
|
-
session.buffer_limit = 8
|
|
45
|
-
writer.write("xxxxstart")
|
|
46
|
-
session.__send__(:read_available)
|
|
47
|
-
assert_equal [3, 5, []], matcher.__send__(:find_match)[2]
|
|
48
|
-
session.clear_buffer
|
|
49
|
-
session.__send__(:restore_relay_buffer, "token")
|
|
50
|
-
assert_equal 2, matcher.__send__(:find_match)[1].number
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def test_repeated_scans_equal_a_fresh_matcher_across_operation_sequences
|
|
54
|
-
session, writer = pipe_session
|
|
55
|
-
patterns = ["abc", "bc", "\0\xff".b, /END/n]
|
|
56
|
-
list = Expect::PatternList.new([session], patterns)
|
|
57
|
-
matcher = Expect::Matcher.new(list, nil)
|
|
58
|
-
random = Random.new(40_033)
|
|
59
|
-
chunks = ["a", "b", "c", "x", "END", "\0", "\xff".b]
|
|
60
|
-
150.times do
|
|
61
|
-
case random.rand(6)
|
|
62
|
-
when 0
|
|
63
|
-
session.buffer = chunks.sample(random: random) * random.rand(1..4)
|
|
64
|
-
when 1
|
|
65
|
-
session.clear_buffer
|
|
66
|
-
when 2
|
|
67
|
-
session.buffer_limit = [nil, 2, 5, 12].sample(random: random)
|
|
68
|
-
when 3
|
|
69
|
-
session.__send__(:restore_relay_buffer, chunks.sample(random: random).b)
|
|
70
|
-
when 4
|
|
71
|
-
session.expect("x", timeout: 0)
|
|
72
|
-
else
|
|
73
|
-
writer.write(chunks.sample(random: random))
|
|
74
|
-
session.__send__(:read_available)
|
|
75
|
-
end
|
|
76
|
-
expected = Expect::Matcher.new(list, nil).__send__(:find_match)
|
|
77
|
-
actual = matcher.__send__(:find_match)
|
|
78
|
-
assert_equal signature(expected), signature(actual), "buffer=#{session.buffer.inspect}"
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def test_a_dynamically_registered_pattern_is_scanned_from_the_start
|
|
83
|
-
session, = pipe_session
|
|
84
|
-
session.buffer = "prefix ready"
|
|
85
|
-
list = Expect::PatternList.new([session], ["missing"])
|
|
86
|
-
matcher = Expect::Matcher.new(list, nil)
|
|
87
|
-
assert_nil matcher.__send__(:find_match)
|
|
88
|
-
list.on("prefix")
|
|
89
|
-
assert_equal [0, 6, []], matcher.__send__(:find_match)[2]
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
private
|
|
93
|
-
|
|
94
|
-
def signature(result)
|
|
95
|
-
result ? [result[0], result[1].number, result[2]] : []
|
|
96
|
-
end
|
|
97
|
-
end
|