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.
- checksums.yaml +4 -4
- data/.rubocop.yml +41 -4
- data/CHANGELOG.md +14 -0
- data/CONTRIBUTING.md +23 -0
- data/Gemfile +2 -1
- data/README.md +23 -6
- data/benchmark/matching.rb +72 -0
- data/benchmark/relay.rb +48 -0
- data/benchmark/send_slow.rb +41 -0
- data/benchmark/support.rb +94 -0
- data/docs/COMPATIBILITY.md +1 -1
- data/docs/INTERNAL_CONTRACTS.md +65 -0
- data/docs/PERFORMANCE.md +60 -0
- data/docs/RELEASING.md +8 -6
- data/docs/VERIFICATION.md +24 -0
- data/examples/kibitz/kibitz.rb +2 -0
- data/expect-pty.gemspec +2 -1
- data/lib/expect/interaction.rb +13 -6
- data/lib/expect/logging.rb +96 -0
- data/lib/expect/matcher.rb +26 -5
- data/lib/expect/pattern.rb +9 -5
- data/lib/expect/relay.rb +3 -1
- data/lib/expect/relay_writer.rb +2 -0
- data/lib/expect/session_resources.rb +29 -11
- data/lib/expect/terminal.rb +35 -0
- data/lib/expect/version.rb +1 -1
- data/lib/expect.rb +85 -145
- data/script/ci +5 -0
- data/script/release.rb +37 -5
- data/test/cleanup_test.rb +251 -0
- data/test/configuration_test.rb +43 -0
- data/test/io_test.rb +35 -0
- data/test/pattern_offset_test.rb +43 -0
- data/test/process_test.rb +46 -0
- data/test/relay_recovery_test.rb +37 -0
- data/test/release_test.rb +94 -16
- data/test/scan_reuse_test.rb +109 -0
- data/test/timeout_test.rb +119 -0
- metadata +15 -3
|
@@ -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.
|
|
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-
|
|
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.
|
|
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: []
|