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
@@ -1,515 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "test_helper"
4
-
5
- class RubyAPITest < ExpectTest
6
- def test_result_uses_native_struct_conversion_and_pattern_matching
7
- session, = pipe_session
8
- session.buffer = "before value=42 after"
9
- session.expect(/value=(\d+)/, timeout: 0)
10
- result = session.last_result
11
- values = [1, nil, "value=42", "before ", " after", session, ["42"]]
12
-
13
- assert_equal values, result.to_a
14
- assert_equal values, result.deconstruct
15
- assert_equal values, Array(result)
16
- assert_equal ["42"], result.to_h.fetch(:captures)
17
- refute_respond_to result, :to_ary
18
- number, error, match, before, after, connection, captures = result.to_a
19
- assert_equal values, [number, error, match, before, after, connection, captures]
20
- assert_pattern { result => { number: 1, captures: ["42"] } }
21
- end
22
-
23
- def test_send_retains_ruby_reflection_and_write_sends_bytes
24
- client, server = Socket.pair(:UNIX, :STREAM, 0)
25
- @ios.push(client, server)
26
- Expect.open(client) do |session|
27
- session.buffer = "ready"
28
- assert_equal "ready", session.send(:buffer)
29
- assert_nil session.send(:timeout=, nil)
30
- assert_equal 3, session.send(:write, "abc")
31
- assert_equal("abc", bounded { server.read(3) })
32
- end
33
- end
34
-
35
- def test_concise_expect_dsl_drives_a_dialogue_without_a_result_object
36
- session = child('print "name: "; puts "hello " + gets.strip', raw_pty: true)
37
- name = "Ruby"
38
- matched = session.expect(timeout: 2) do
39
- on("name: ") do |connection|
40
- connection.puts(name)
41
- connection.continue
42
- end
43
- on(/hello (\w+)/)
44
- end
45
- assert_equal 2, matched
46
- assert_equal "hello Ruby", session.match
47
- assert_equal ["Ruby"], session.captures
48
- assert_equal 0, session.wait(timeout: 1).exitstatus
49
- end
50
-
51
- def test_concise_dsl_supports_events_multi_session_and_result_objects
52
- first, writer = pipe_session
53
- second, = pipe_session
54
- writer.close
55
- ended = []
56
- timed_out = []
57
- result = Expect.expect_result(timeout: 0.02) do
58
- eof(from: first) do |connection|
59
- ended << connection
60
- connection.continue(reset_timeout: false)
61
- end
62
- on("missing", from: second)
63
- timeout { |sessions| timed_out.concat(sessions) }
64
- end
65
- assert result.timeout?
66
- assert_equal [first], ended
67
- assert_equal [second], timed_out
68
- assert_same second, result.session
69
- end
70
-
71
- def test_explicit_block_parameter_preserves_the_callers_self
72
- session, = pipe_session
73
- session.buffer = "ready"
74
- owner = self
75
- matched = session.expect(timeout: 0) do |patterns|
76
- assert_same owner, self
77
- patterns.on("ready") { assert_same owner, self }
78
- end
79
- assert_equal 1, matched
80
- end
81
-
82
- def test_optional_block_parameter_still_receives_the_pattern_builder
83
- session, = pipe_session
84
- session.buffer = "ready"
85
- matched = session.expect(timeout: 0) { |patterns = nil| patterns.on("ready") }
86
- assert_equal 1, matched
87
- end
88
-
89
- def test_break_from_a_concise_definition_does_not_consume_input
90
- session, writer = pipe_session
91
- writer.write("ready")
92
- result = session.expect(timeout: 0) do
93
- on("ready")
94
- break :cancelled
95
- end
96
- assert_equal :cancelled, result
97
- assert_nil session.last_result
98
- assert_equal "ready", session.to_io.read_nonblock(5)
99
- end
100
-
101
- def test_exception_in_a_concise_definition_does_not_consume_input
102
- session, writer = pipe_session
103
- writer.write("ready")
104
- assert_raises(RuntimeError) do
105
- session.expect(timeout: 0) do
106
- on("ready")
107
- raise "definition failed"
108
- end
109
- end
110
- assert_equal "ready", session.to_io.read_nonblock(5)
111
- end
112
-
113
- def test_block_patterns_drive_a_real_dialogue_with_captured_variables
114
- session = child(<<~'RUBY', raw_pty: true)
115
- print "name: "
116
- name = gets.strip
117
- print "code: "
118
- puts "hello #{name}:#{gets.strip}"
119
- RUBY
120
- name = "Ruby"
121
- result = session.expect_result(timeout: 2) do |patterns|
122
- patterns.on("name: ") do |connection|
123
- connection.puts(name)
124
- connection.continue
125
- end
126
- patterns.on("code: ") do |connection|
127
- connection.send_slow("123\n", delay: 0)
128
- connection.continue(reset_timeout: false)
129
- end
130
- patterns.on(/hello (\w+):(\d+)/)
131
- end
132
-
133
- assert_equal 3, result.number
134
- assert_equal %w[Ruby 123], result.captures
135
- assert_same session, result.session
136
- assert_equal 0, session.wait(timeout: 1).exitstatus
137
- end
138
-
139
- def test_keyword_timeout_and_default_timeout
140
- session, writer = pipe_session
141
- session.timeout = 0
142
- assert session.expect_result("missing").timeout?
143
- assert session.expect_result("missing", timeout: 0.01).timeout?
144
- background do
145
- sleep 0.02
146
- writer.write("ready")
147
- end
148
- assert_equal(1, bounded { session.expect("ready", timeout: nil) })
149
- assert_equal 0, session.timeout
150
- end
151
-
152
- def test_block_uses_keyword_timeout_and_returns_pattern_number
153
- session, = pipe_session
154
- session.buffer = "ready"
155
- number = session.expect(timeout: 0) { |patterns| patterns.on("ready") }
156
- assert_equal 1, number
157
- end
158
-
159
- def test_literal_patterns_are_unambiguous_even_with_callbacks
160
- %w[-i -ex -unknown timeout eof a.c].each do |literal|
161
- session, = pipe_session
162
- session.buffer = "prefix #{literal} suffix"
163
- observed = nil
164
- result = session.expect_result(timeout: 0) do |patterns|
165
- patterns.on(literal) { |connection| observed = connection.match }
166
- end
167
- assert result.matched?, literal
168
- assert_equal literal, observed
169
- assert_equal "prefix ", result.before
170
- assert_equal " suffix", session.buffer
171
- end
172
- end
173
-
174
- def test_positional_strings_are_literal_and_do_not_parse_command_line_flags
175
- %w[-i -ex -re timeout eof].each do |literal|
176
- session, = pipe_session
177
- session.buffer = "before #{literal} after"
178
- result = session.expect_result(literal, timeout: 0)
179
- assert result.matched?
180
- assert_equal literal, result.match
181
- assert_equal " after", session.buffer
182
- end
183
- end
184
-
185
- def test_duplicate_timeout_callbacks_are_rejected_before_reading
186
- session, writer = pipe_session
187
- writer.write("ready")
188
- assert_raises(ArgumentError) do
189
- session.expect(timeout: 0) do
190
- timeout { nil }
191
- timeout { nil }
192
- end
193
- end
194
- assert_equal "ready", session.to_io.read_nonblock(5)
195
- end
196
-
197
- def test_timeout_callback_receives_all_active_sessions
198
- first, = pipe_session
199
- second, = pipe_session
200
- observed = nil
201
- Expect.expect(timeout: 0) do
202
- on("first", from: first)
203
- on("second", from: second)
204
- timeout { |sessions| observed = sessions }
205
- end
206
- assert_equal [first, second], observed
207
- end
208
-
209
- def test_patterns_keep_declaration_priority
210
- session, = pipe_session
211
- session.buffer = "second first"
212
- result = session.expect_result(timeout: 0) do |patterns|
213
- patterns.on("first")
214
- patterns.on("second")
215
- end
216
- assert_equal 1, result.number
217
- assert_equal "second ", result.before
218
- end
219
-
220
- def test_invalid_patterns_and_positional_timeouts_do_not_consume_input
221
- session, writer = pipe_session
222
- writer.write("ready")
223
- [nil, 0].each do |timeout|
224
- assert_raises(ArgumentError) { session.expect(timeout, "ready", timeout: 1) }
225
- assert_raises(ArgumentError) { Expect.expect(timeout, "-i", session, "ready", timeout: 1) }
226
- end
227
- assert_raises(ArgumentError) { session.expect("ready", timeout: 0) { |patterns| patterns.on("ready") } }
228
- assert_raises(ArgumentError) { session.expect(timeout: 0) { |patterns| patterns.on(:eof) } }
229
- assert_raises(ArgumentError) { session.expect(timeout: 0) { |patterns| patterns.on("ready", from: []) } }
230
- assert_raises(ArgumentError) { Expect.expect(timeout: 0) { |patterns| patterns.on("ready") } }
231
- assert_raises(ArgumentError) { Expect.expect(timeout: 0) { |patterns| patterns.timeout { nil } } }
232
- assert_raises(ArgumentError) { session.expect(timeout: -1) { flunk "invalid timeout must fail before the block" } }
233
- assert_empty session.buffer
234
- assert_equal "ready", session.to_io.read_nonblock(5)
235
- end
236
-
237
- def test_definition_exception_does_not_run_callbacks_or_consume_input
238
- session, writer = pipe_session
239
- writer.write("ready")
240
- assert_raises(RuntimeError) do
241
- session.expect(timeout: 0) do |patterns|
242
- patterns.on("ready") { flunk "callbacks must wait until configuration finishes" }
243
- raise "configuration failed"
244
- end
245
- end
246
- assert_nil session.last_result
247
- assert_equal "ready", session.to_io.read_nonblock(5)
248
- end
249
-
250
- def test_callback_exception_preserves_the_match_and_unread_buffer
251
- session, = pipe_session
252
- session.buffer = "ready tail"
253
- error = assert_raises(RuntimeError) do
254
- session.expect(timeout: 0) do |patterns|
255
- patterns.on("ready") { raise "callback failed" }
256
- end
257
- end
258
- assert_equal "callback failed", error.message
259
- assert_equal "ready", session.match
260
- assert_equal " tail", session.buffer
261
- end
262
-
263
- def test_timeout_callback_can_continue_with_active_sessions
264
- session, = pipe_session
265
- observed = nil
266
- result = session.expect_result(timeout: 0) do |patterns|
267
- patterns.on("ready")
268
- patterns.timeout do |sessions|
269
- observed = sessions
270
- session.buffer = "ready"
271
- Expect.continue
272
- end
273
- end
274
- assert_equal [session], observed
275
- assert_equal 1, result.number
276
- end
277
-
278
- def test_continuation_without_reset_observes_the_deadline
279
- session, = pipe_session
280
- session.buffer = "ready"
281
- result = bounded(1) do
282
- session.expect_result(timeout: 0.01) do |patterns|
283
- patterns.on(/(?=ready)/) { Expect.continue(reset_timeout: false) }
284
- end
285
- end
286
- assert result.timeout?
287
- assert_equal "ready", session.buffer
288
- end
289
-
290
- def test_eof_block_receives_the_session_and_remaining_bytes
291
- session, writer = pipe_session
292
- writer.write("tail")
293
- writer.close
294
- observed = nil
295
- result = session.expect_result(timeout: 1) do |patterns|
296
- patterns.eof { |connection| observed = [connection, connection.before] }
297
- end
298
- assert result.eof?
299
- assert_equal [session, "tail"], observed
300
- assert_empty session.buffer
301
- end
302
-
303
- def test_class_block_routes_patterns_to_multiple_sessions
304
- first, = pipe_session
305
- second, writer = pipe_session
306
- writer.write("ready:42")
307
- result = Expect.expect_result(timeout: 1) do |patterns|
308
- patterns.on("missing", from: first)
309
- patterns.on(/ready:(\d+)/, from: [first, second])
310
- end
311
- assert_equal 2, result.number
312
- assert_same second, result.session
313
- assert_equal ["42"], result.captures
314
- end
315
-
316
- def test_class_keyword_from_selects_a_session
317
- session, writer = pipe_session
318
- writer.write("ready")
319
- assert_equal 1, Expect.expect("ready", from: session, timeout: 1)
320
- end
321
-
322
- def test_eof_continuation_keeps_other_sessions_active
323
- first, first_writer = pipe_session
324
- second, second_writer = pipe_session
325
- first_writer.close
326
- observed = []
327
- result = Expect.expect_result(timeout: 1) do |patterns|
328
- patterns.eof(from: first) do |connection|
329
- observed << connection
330
- second_writer.write("ready")
331
- connection.continue(reset_timeout: false)
332
- end
333
- patterns.on("ready", from: second)
334
- end
335
- assert_equal [first], observed
336
- assert_same second, result.session
337
- assert_equal 2, result.number
338
- end
339
-
340
- def test_timeout_before_class_patterns_receives_active_sessions
341
- session, = pipe_session
342
- observed = nil
343
- result = Expect.expect_result(timeout: 0) do |patterns|
344
- patterns.timeout { |sessions| observed = sessions }
345
- patterns.on("missing", from: session)
346
- end
347
- assert result.timeout?
348
- assert_equal [session], observed
349
- end
350
-
351
- def test_open_returns_block_value_and_preserves_borrowed_io
352
- reader, writer = IO.pipe
353
- @ios.push(reader, writer)
354
- connection = nil
355
- result = Expect.open(reader) do |session|
356
- connection = session
357
- :finished
358
- end
359
- assert_equal :finished, result
360
- assert connection.closed?
361
- refute reader.closed?
362
- refute writer.closed?
363
- end
364
-
365
- def test_open_closes_both_owned_handles_on_exception
366
- reader, writer = IO.pipe
367
- @ios.push(reader, writer)
368
- assert_raises(RuntimeError) do
369
- Expect.open(reader, writer: writer, own: true) { raise "stop" }
370
- end
371
- assert reader.closed?
372
- assert writer.closed?
373
- end
374
-
375
- def test_invalid_open_options_close_owned_io_and_preserve_borrowed_io
376
- [false, true].each do |own|
377
- reader, writer = IO.pipe
378
- @ios.push(reader, writer)
379
- assert_raises(ArgumentError) do
380
- Expect.open(reader, writer: writer, own: own, unknown: true) { flunk "invalid options" }
381
- end
382
- assert_equal own, reader.closed?
383
- assert_equal own, writer.closed?
384
- end
385
- end
386
-
387
- def test_boolean_predicates_use_ruby_truthiness_and_have_no_write_arguments
388
- session, = pipe_session
389
- refute session.log_stdout?
390
- session.log_stdout = true
391
- assert session.log_stdout?
392
- session.log_stdout = 0
393
- assert session.log_stdout?
394
- session.log_stdout = nil
395
- refute session.log_stdout?
396
- assert_raises(ArgumentError) { session.log_stdout?(true) }
397
- end
398
-
399
- def test_readiness_accepts_an_empty_group_with_a_keyword_timeout
400
- assert_empty Expect.readable_sessions(timeout: 0)
401
- end
402
-
403
- def test_spawn_closes_on_nonlocal_block_exit
404
- connection = nil
405
- result = Expect.spawn(RbConfig.ruby, "--disable-gems", "-e", "sleep 60", log_stdout: false) do |session|
406
- connection = session
407
- break :finished
408
- end
409
- assert_equal :finished, result
410
- assert connection.closed?
411
- refute connection.alive?
412
- end
413
-
414
- def test_buffer_setter_copies_bytes_and_applies_the_limit
415
- session, = pipe_session(buffer_limit: 4)
416
- value = +"before tail"
417
- session.buffer = value
418
- value.replace("changed")
419
- session.buffer.clear
420
- assert_equal "tail", session.buffer
421
- assert_equal Encoding::BINARY, session.buffer.encoding
422
- assert_equal "tail", session.clear_buffer
423
- assert_empty session.buffer
424
- assert_raises(ArgumentError) { session.buffer = nil }
425
- end
426
-
427
- def test_listeners_setter_takes_a_snapshot_and_validates_before_replacement
428
- session, writer = pipe_session
429
- output = StringIO.new
430
- listeners = [output]
431
- session.listeners = listeners
432
- listeners.clear
433
- session.listeners.clear
434
- assert_raises(ArgumentError) { session.listeners = [Object.new] }
435
- writer.write("ready")
436
- session.expect("ready", timeout: 1)
437
- assert_equal "ready", output.string
438
- session.listeners = []
439
- assert_empty session.listeners
440
- end
441
-
442
- def test_log_block_and_setter_receive_only_read_bytes
443
- session, writer = pipe_session
444
- chunks = []
445
- session.log_to { |bytes| chunks << bytes }
446
- assert_raises(ArgumentError) { session.log_to(StringIO.new) { nil } }
447
- writer.write("first")
448
- session.expect("first", timeout: 1)
449
- output = StringIO.new
450
- session.log_output = output
451
- writer.write("second")
452
- session.expect("second", timeout: 1)
453
- session.log_output = nil
454
- assert_equal "first", chunks.join
455
- assert_equal "second", output.string
456
- refute output.closed?
457
- end
458
-
459
- def test_puts_matches_standard_ruby_io_for_nested_and_recursive_arrays
460
- client, server = Socket.pair(:UNIX, :STREAM, 0)
461
- @ios.push(client, server)
462
- recursive = ["once"]
463
- recursive << recursive
464
- values = [nil, 42, "中文", "already\n", ["nested", ["last"]], recursive, "\xff".b]
465
- expected = StringIO.new("".b)
466
- expected.puts
467
- expected.puts(*values)
468
- Expect.open(client) do |session|
469
- assert_nil session.puts
470
- assert_nil session.puts(*values)
471
- end
472
- assert_equal(expected.string, bounded { server.read(expected.string.bytesize) })
473
- end
474
-
475
- def test_assigning_the_current_log_preserves_file_ownership
476
- session, = pipe_session
477
- Dir.mktmpdir do |directory|
478
- log = session.log_to(File.join(directory, "session.log"))
479
- session.log_output = log
480
- session.close
481
- assert log.closed?
482
- end
483
- end
484
-
485
- def test_write_converts_objects_and_append_returns_the_session
486
- client, server = Socket.pair(:UNIX, :STREAM, 0)
487
- @ios.push(client, server)
488
- values = [nil, 42, %w[a b]]
489
- expected = StringIO.new
490
- count = expected.write(*values)
491
- Expect.open(client) do |session|
492
- assert_equal count, session.write(*values)
493
- assert_same session, session << "tail" << "\n"
494
- end
495
- expected << "tail\n"
496
- assert_equal(expected.string, bounded { server.read(expected.string.bytesize) })
497
- end
498
-
499
- def test_sequence_block_filters_escape_and_keeps_trailing_bytes
500
- session, writer = pipe_session
501
- output = StringIO.new
502
- session.listeners = [output]
503
- observed = []
504
- value = :done
505
- session.on_sequence("STOP") do
506
- observed << value
507
- false
508
- end
509
- writer.write("beforeSTOPafter")
510
- assert_same session, Expect.interconnect(session, timeout: 1)
511
- assert_equal [:done], observed
512
- assert_equal "before", output.string
513
- assert_equal "after", session.buffer
514
- end
515
- end
@@ -1,109 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "test_helper"
4
-
5
- class ScanReuseTest < ExpectTest
6
- def test_each_scan_snapshots_a_session_once_across_non_adjacent_groups
7
- first, = pipe_session
8
- second, = pipe_session
9
- first.buffer = "ready"
10
- list = Expect::PatternList.new
11
- list.on("missing", from: first).on("other", from: second).on("ready", from: first)
12
- matcher = Expect::Matcher.new(list, 0)
13
- snapshots = 0
14
- original = first.method(:buffer)
15
- first.stub(:buffer, lambda {
16
- snapshots += 1
17
- original.call
18
- }) do
19
- 2.times do
20
- result = matcher.__send__(:find_match)
21
- assert_same first, result[0]
22
- assert_equal 3, result[1].number
23
- assert_equal [0, 5, []], result[2]
24
- end
25
- end
26
- assert_equal 2, snapshots
27
- end
28
-
29
- def test_callback_replacement_and_nested_match_are_visible_on_the_next_scan
30
- first, = pipe_session
31
- second, = pipe_session
32
- first.buffer = "start"
33
- events = []
34
- result = Expect.expect_result(timeout: 0) do
35
- on("start", from: first) do |session|
36
- events << session.match
37
- session.buffer = "nested finish"
38
- session.expect("nested ", timeout: 0)
39
- events << session.match
40
- session.continue
41
- end
42
- on("missing", from: second)
43
- on("finish", from: first) { |session| events << session.match }
44
- end
45
- assert_equal 3, result.number
46
- assert_equal ["start", "nested ", "finish"], events
47
- assert_empty first.buffer
48
- end
49
-
50
- def test_shared_io_keeps_first_session_and_identity_despite_equal_hashes
51
- first, writer = pipe_session
52
- second = Expect.open(first.to_io)
53
- @sessions << second
54
- other, other_writer = pipe_session
55
- # IO 的业务相等性不能替代 IO.select 返回对象的身份。
56
- [first.to_io, other.to_io].each do |io|
57
- io.define_singleton_method(:hash) { 0 }
58
- io.define_singleton_method(:eql?) { |_other| true }
59
- end
60
- writer.write("first")
61
- other_writer.write("other")
62
- result = Expect.expect_result(timeout: 0) do
63
- on("missing", from: [first, second, other])
64
- on("other", from: other)
65
- end
66
- assert_same other, result.session
67
- assert_equal "first", first.buffer
68
- assert_empty second.buffer
69
- end
70
-
71
- def test_regexps_share_one_combined_text_and_literal_only_does_not_build_it
72
- session, = pipe_session
73
- session.listeners = [StringIO.new]
74
- [0, 4].each do |count|
75
- session.__send__(:sequences=, {})
76
- count.times { |index| session.on_sequence(/missing#{index}/) }
77
- session.on_sequence("STOP")
78
- history = session.__send__(:relay_history)
79
- concatenations = 0
80
- original = history.method(:+)
81
- history.define_singleton_method(:+) do |other|
82
- concatenations += 1
83
- original.call(other)
84
- end
85
- assert Expect.__send__(:relay_buffer, session, { session => "payload".b })
86
- assert_equal count.zero? ? 0 : 1, concatenations
87
- end
88
- end
89
-
90
- def test_escape_callback_can_change_rules_before_rescanning
91
- session, = pipe_session
92
- output = StringIO.new
93
- session.listeners = [output]
94
- events = []
95
- session.on_sequence(/ONE/) do
96
- events << :one
97
- session.on_sequence(/TWO/) do
98
- events << :two
99
- false
100
- end
101
- true
102
- end
103
- buffers = { session => "aONEbTWOtail".b }
104
- refute Expect.__send__(:relay_buffer, session, buffers)
105
- assert_equal %i[one two], events
106
- assert_equal "ab", output.string
107
- assert_equal "tail", buffers.fetch(session)
108
- end
109
- end