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.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +28 -0
  3. data/README.md +28 -19
  4. data/docs/API.md +88 -0
  5. data/docs/MIGRATION.md +15 -0
  6. data/lib/expect/cleanup.rb +33 -0
  7. data/lib/expect/configuration.rb +87 -66
  8. data/lib/expect/interaction.rb +226 -209
  9. data/lib/expect/logging.rb +157 -156
  10. data/lib/expect/matcher.rb +30 -29
  11. data/lib/expect/pattern.rb +8 -2
  12. data/lib/expect/pattern_list.rb +35 -5
  13. data/lib/expect/redactor.rb +11 -4
  14. data/lib/expect/relay.rb +97 -73
  15. data/lib/expect/relay_writer.rb +2 -1
  16. data/lib/expect/result.rb +32 -8
  17. data/lib/expect/session.rb +508 -0
  18. data/lib/expect/session_resources.rb +10 -2
  19. data/lib/expect/terminal.rb +78 -78
  20. data/lib/expect/version.rb +1 -1
  21. data/lib/expect.rb +333 -520
  22. data/sig/expect.rbs +355 -0
  23. metadata +13 -106
  24. data/.rubocop.yml +0 -66
  25. data/CONTRIBUTING.md +0 -29
  26. data/Gemfile +0 -28
  27. data/Rakefile +0 -38
  28. data/benchmark/matching.rb +0 -98
  29. data/benchmark/redactor.rb +0 -64
  30. data/benchmark/relay.rb +0 -48
  31. data/benchmark/scaling.rb +0 -70
  32. data/benchmark/send_slow.rb +0 -41
  33. data/benchmark/support.rb +0 -114
  34. data/docs/COMPATIBILITY.md +0 -98
  35. data/docs/INTERNAL_CONTRACTS.md +0 -154
  36. data/docs/PERFORMANCE.md +0 -165
  37. data/docs/RELEASING.md +0 -84
  38. data/docs/VERIFICATION.md +0 -456
  39. data/examples/dialogue.rb +0 -30
  40. data/examples/kibitz/README.md +0 -81
  41. data/examples/kibitz/kibitz.rb +0 -142
  42. data/examples/kibitz/test_kibitz.rb +0 -37
  43. data/examples/ssh_auto.rb +0 -94
  44. data/examples/ssh_interact.rb +0 -159
  45. data/examples/ssh_login.rb +0 -64
  46. data/expect-pty.gemspec +0 -33
  47. data/script/ci +0 -121
  48. data/script/release.rb +0 -319
  49. data/test/buffer_accounting_test.rb +0 -83
  50. data/test/cleanup_test.rb +0 -251
  51. data/test/compare_upstream.rb +0 -157
  52. data/test/configuration_test.rb +0 -133
  53. data/test/deadline_test.rb +0 -237
  54. data/test/diagnostics_test.rb +0 -353
  55. data/test/edge_case_test.rb +0 -262
  56. data/test/fixtures/ssh_scripts/01_identity.sh +0 -4
  57. data/test/fixtures/ssh_scripts/02_output.sh +0 -5
  58. data/test/fixtures/ssh_scripts/03_delayed.sh +0 -6
  59. data/test/fixtures/ssh_scripts/04_failure.sh +0 -2
  60. data/test/fixtures/ssh_scripts/05_recovery.sh +0 -3
  61. data/test/initialization_failure_test.rb +0 -92
  62. data/test/integration/README.md +0 -109
  63. data/test/integration/ssh_scripts.rb +0 -94
  64. data/test/interact_test.rb +0 -253
  65. data/test/interconnect_test.rb +0 -425
  66. data/test/io_test.rb +0 -321
  67. data/test/kibitz_test.rb +0 -45
  68. data/test/lifecycle_contract_test.rb +0 -71
  69. data/test/literal_scan_test.rb +0 -97
  70. data/test/matching_test.rb +0 -211
  71. data/test/multi_session_test.rb +0 -66
  72. data/test/ownership_sequence_test.rb +0 -208
  73. data/test/pattern_offset_test.rb +0 -43
  74. data/test/process_interruption_test.rb +0 -296
  75. data/test/process_test.rb +0 -291
  76. data/test/redactor_test.rb +0 -183
  77. data/test/relay_recovery_test.rb +0 -451
  78. data/test/relay_reentrancy_test.rb +0 -159
  79. data/test/release_test.rb +0 -375
  80. data/test/ruby_api_test.rb +0 -515
  81. data/test/scan_reuse_test.rb +0 -109
  82. data/test/script_logging_test.rb +0 -124
  83. data/test/support/interact_probe.rb +0 -125
  84. data/test/support/kibitz_probe.rb +0 -177
  85. data/test/support/script_probe.rb +0 -158
  86. data/test/terminal_cleanup_test.rb +0 -349
  87. data/test/test_helper.rb +0 -58
  88. data/test/timeout_test.rb +0 -289
  89. 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
@@ -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