expect-pty 0.3.1 → 0.3.3

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.
@@ -0,0 +1,109 @@
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
data/test/timeout_test.rb CHANGED
@@ -48,6 +48,125 @@ class TimeoutTest < ExpectTest
48
48
  assert_equal :timeout, session.error
49
49
  end
50
50
 
51
+ def test_eof_continuation_without_reset_observes_the_deadline_before_matching_again
52
+ first, = pipe_session
53
+ second, = pipe_session
54
+ first.close
55
+ now = 0.0
56
+ result = Expect.stub(:monotonic, -> { now }) do
57
+ Expect.expect_result(timeout: 1) do
58
+ eof(from: first) do
59
+ now = 2.0
60
+ second.buffer = "ready"
61
+ Expect.continue(reset_timeout: false)
62
+ end
63
+ on("ready", from: second)
64
+ end
65
+ end
66
+
67
+ assert result.timeout?
68
+ assert_same second, result.session
69
+ assert_equal "ready", second.buffer
70
+ assert first.last_result.eof?
71
+ end
72
+
73
+ def test_eof_continuation_can_reset_an_expired_deadline
74
+ first, = pipe_session
75
+ second, = pipe_session
76
+ first.close
77
+ now = 0.0
78
+ result = Expect.stub(:monotonic, -> { now }) do
79
+ Expect.expect_result(timeout: 1) do
80
+ eof(from: first) do
81
+ now = 2.0
82
+ second.buffer = "ready"
83
+ Expect.continue
84
+ end
85
+ on("ready", from: second)
86
+ end
87
+ end
88
+
89
+ assert_equal "ready", result.match
90
+ assert_same second, result.session
91
+ end
92
+
93
+ def test_last_eof_returns_even_when_continuation_deadline_has_expired
94
+ session, = pipe_session
95
+ session.close
96
+ now = 0.0
97
+ result = Expect.stub(:monotonic, -> { now }) do
98
+ session.expect_result(timeout: 1) do
99
+ eof do
100
+ now = 2.0
101
+ Expect.continue(reset_timeout: false)
102
+ end
103
+ end
104
+ end
105
+
106
+ assert result.eof?
107
+ assert_same session, result.session
108
+ end
109
+
110
+ def test_expired_eof_continuation_delivers_all_known_eof_events
111
+ sessions = Array.new(3) { pipe_session.first }
112
+ sessions.each(&:close)
113
+ seen = []
114
+ result = Expect.expect_result(from: sessions, timeout: 0) do
115
+ eof do |session|
116
+ seen << session
117
+ Expect.continue(reset_timeout: false)
118
+ end
119
+ end
120
+
121
+ assert result.eof?
122
+ assert_equal sessions, seen
123
+ assert(sessions.all? { |session| session.last_result.eof? })
124
+ end
125
+
126
+ def test_expired_eof_continuation_handles_known_eof_before_timing_out_live_sources
127
+ first, = pipe_session
128
+ second, = pipe_session
129
+ live, = pipe_session
130
+ [first, second].each(&:close)
131
+ seen = []
132
+ timed_out = nil
133
+ result = Expect.expect_result(timeout: 0) do
134
+ eof(from: [first, second]) do |session|
135
+ seen << session
136
+ live.buffer = "ready"
137
+ Expect.continue(reset_timeout: false)
138
+ end
139
+ on("ready", from: live)
140
+ timeout { |sessions| timed_out = sessions }
141
+ end
142
+
143
+ assert result.timeout?
144
+ assert_equal [first, second], seen
145
+ assert_equal [live], timed_out
146
+ assert_equal "ready", live.buffer
147
+ end
148
+
149
+ def test_reset_after_expired_eof_continuation_resumes_text_matching
150
+ %i[eof timeout].each do |reset_event|
151
+ first, = pipe_session
152
+ second, = pipe_session
153
+ live, = pipe_session
154
+ [first, second].each(&:close)
155
+ result = Expect.expect_result(timeout: 0) do
156
+ eof(from: first) do
157
+ live.buffer = "ready"
158
+ Expect.continue(reset_timeout: false)
159
+ end
160
+ eof(from: second) { Expect.continue(reset_timeout: reset_event == :eof) }
161
+ on("ready", from: live)
162
+ timeout { Expect.continue }
163
+ end
164
+
165
+ assert_equal "ready", result.match
166
+ assert_same live, result.session
167
+ end
168
+ end
169
+
51
170
  def test_restart_timeout_on_receive
52
171
  session, writer = pipe_session
53
172
  session.reset_timeout_on_read = true
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: expect-pty
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - expect-pty contributors
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-09-26 00:00:00.000000000 Z
10
+ date: 2026-09-27 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: forwardable
@@ -105,11 +105,18 @@ extra_rdoc_files: []
105
105
  files:
106
106
  - ".rubocop.yml"
107
107
  - CHANGELOG.md
108
+ - CONTRIBUTING.md
108
109
  - Gemfile
109
110
  - LICENSE
110
111
  - README.md
111
112
  - Rakefile
113
+ - benchmark/matching.rb
114
+ - benchmark/relay.rb
115
+ - benchmark/send_slow.rb
116
+ - benchmark/support.rb
112
117
  - docs/COMPATIBILITY.md
118
+ - docs/INTERNAL_CONTRACTS.md
119
+ - docs/PERFORMANCE.md
113
120
  - docs/RELEASING.md
114
121
  - docs/VERIFICATION.md
115
122
  - examples/dialogue.rb
@@ -123,6 +130,7 @@ files:
123
130
  - lib/expect.rb
124
131
  - lib/expect/configuration.rb
125
132
  - lib/expect/interaction.rb
133
+ - lib/expect/logging.rb
126
134
  - lib/expect/matcher.rb
127
135
  - lib/expect/pattern.rb
128
136
  - lib/expect/pattern_list.rb
@@ -131,9 +139,11 @@ files:
131
139
  - lib/expect/relay_writer.rb
132
140
  - lib/expect/result.rb
133
141
  - lib/expect/session_resources.rb
142
+ - lib/expect/terminal.rb
134
143
  - lib/expect/version.rb
135
144
  - script/ci
136
145
  - script/release.rb
146
+ - test/cleanup_test.rb
137
147
  - test/compare_upstream.rb
138
148
  - test/configuration_test.rb
139
149
  - test/edge_case_test.rb
@@ -150,10 +160,12 @@ files:
150
160
  - test/kibitz_test.rb
151
161
  - test/matching_test.rb
152
162
  - test/multi_session_test.rb
163
+ - test/pattern_offset_test.rb
153
164
  - test/process_test.rb
154
165
  - test/relay_recovery_test.rb
155
166
  - test/release_test.rb
156
167
  - test/ruby_api_test.rb
168
+ - test/scan_reuse_test.rb
157
169
  - test/script_logging_test.rb
158
170
  - test/support/interact_probe.rb
159
171
  - test/support/kibitz_probe.rb
@@ -182,7 +194,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
182
194
  - !ruby/object:Gem::Version
183
195
  version: '0'
184
196
  requirements: []
185
- rubygems_version: 4.0.12
197
+ rubygems_version: 4.0.16
186
198
  specification_version: 4
187
199
  summary: Ruby PTY automation with the Expect.pm interaction model
188
200
  test_files: []