expect-pty 0.2.0
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 +7 -0
- data/.rubocop.yml +29 -0
- data/CHANGELOG.md +25 -0
- data/Gemfile +10 -0
- data/LICENSE +21 -0
- data/README.md +264 -0
- data/Rakefile +38 -0
- data/docs/COMPATIBILITY.md +65 -0
- data/docs/RELEASING.md +55 -0
- data/docs/VERIFICATION.md +146 -0
- data/examples/dialogue.rb +30 -0
- data/examples/kibitz/README.md +73 -0
- data/examples/kibitz/kibitz.rb +139 -0
- data/examples/kibitz/test_kibitz.rb +37 -0
- data/examples/ssh_auto.rb +94 -0
- data/examples/ssh_interact.rb +159 -0
- data/examples/ssh_login.rb +64 -0
- data/expect-pty.gemspec +25 -0
- data/lib/expect/configuration.rb +113 -0
- data/lib/expect/engine.rb +141 -0
- data/lib/expect/interconnect.rb +170 -0
- data/lib/expect/pattern.rb +62 -0
- data/lib/expect/pattern_list.rb +90 -0
- data/lib/expect/pty.rb +4 -0
- data/lib/expect/resources.rb +63 -0
- data/lib/expect/result.rb +14 -0
- data/lib/expect/version.rb +6 -0
- data/lib/expect.rb +591 -0
- data/script/ci +44 -0
- data/script/release.rb +267 -0
- data/test/compare_upstream.rb +157 -0
- data/test/configuration_test.rb +90 -0
- data/test/edge_case_test.rb +183 -0
- data/test/fixtures/ssh_scripts/01_identity.sh +4 -0
- data/test/fixtures/ssh_scripts/02_output.sh +5 -0
- data/test/fixtures/ssh_scripts/03_delayed.sh +6 -0
- data/test/fixtures/ssh_scripts/04_failure.sh +2 -0
- data/test/fixtures/ssh_scripts/05_recovery.sh +3 -0
- data/test/integration/README.md +90 -0
- data/test/integration/ssh_scripts.rb +94 -0
- data/test/interact_test.rb +151 -0
- data/test/interconnect_test.rb +226 -0
- data/test/io_test.rb +184 -0
- data/test/kibitz_test.rb +45 -0
- data/test/matching_test.rb +178 -0
- data/test/multi_session_test.rb +66 -0
- data/test/process_test.rb +244 -0
- data/test/release_test.rb +153 -0
- data/test/ruby_api_test.rb +515 -0
- data/test/script_logging_test.rb +124 -0
- data/test/support/interact_probe.rb +125 -0
- data/test/support/kibitz_probe.rb +177 -0
- data/test/support/script_probe.rb +157 -0
- data/test/test_helper.rb +58 -0
- data/test/timeout_test.rb +170 -0
- metadata +97 -0
data/test/io_test.rb
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
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_group_and_stdout_logging_can_be_controlled_separately
|
|
53
|
+
session, writer = pipe_session
|
|
54
|
+
listener = StringIO.new
|
|
55
|
+
session.listeners = [listener]
|
|
56
|
+
writer.write("one")
|
|
57
|
+
session.expect("one", timeout: 1)
|
|
58
|
+
assert_equal "one", listener.string
|
|
59
|
+
session.log_listeners = false
|
|
60
|
+
session.log_stdout = true
|
|
61
|
+
output, = capture_io do
|
|
62
|
+
writer.write("two")
|
|
63
|
+
session.expect("two", timeout: 1)
|
|
64
|
+
end
|
|
65
|
+
assert_equal "two", output
|
|
66
|
+
assert_equal "one", listener.string
|
|
67
|
+
session.listeners = []
|
|
68
|
+
assert_empty session.listeners
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def test_debug_and_internal_output
|
|
72
|
+
session, writer = pipe_session
|
|
73
|
+
session.debug_level = 1
|
|
74
|
+
_, diagnostics = capture_io do
|
|
75
|
+
writer.write("ready")
|
|
76
|
+
session.expect("ready", timeout: 1)
|
|
77
|
+
end
|
|
78
|
+
assert_match(/matched pattern 1/, diagnostics)
|
|
79
|
+
refute_match(/received/, diagnostics)
|
|
80
|
+
session.debug_level = 2
|
|
81
|
+
_, diagnostics = capture_io do
|
|
82
|
+
writer.write("more")
|
|
83
|
+
session.expect("more", timeout: 1)
|
|
84
|
+
end
|
|
85
|
+
assert_match(/received "more"/, diagnostics)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def test_send_slow_collects_replies_even_when_logging_disabled
|
|
89
|
+
session = child("loop { char = STDIN.read(1); break unless char; print char.upcase }", raw_pty: true)
|
|
90
|
+
session.log_listeners = false
|
|
91
|
+
start = Expect.monotonic
|
|
92
|
+
assert_equal 3, session.send_slow("abc", delay: 0.02)
|
|
93
|
+
assert_operator Expect.monotonic - start, :>=, 0.06
|
|
94
|
+
assert_equal 1, session.expect("ABC", timeout: 1)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def test_large_bidirectional_write_does_not_deadlock
|
|
98
|
+
session = child("STDIN.binmode; STDOUT.binmode; loop { print STDIN.readpartial(4096) }", raw_pty: true,
|
|
99
|
+
write_timeout: 3)
|
|
100
|
+
payload = (0..255).to_a.pack("C*") * 1024
|
|
101
|
+
bounded do
|
|
102
|
+
assert_equal payload.bytesize, session.write(payload)
|
|
103
|
+
assert_equal 1, session.expect(payload, timeout: 3)
|
|
104
|
+
end
|
|
105
|
+
assert_equal payload, session.match
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def test_write_timeout_on_backpressure
|
|
109
|
+
session = child('puts "ready"; sleep 30', raw_pty: true, write_timeout: 0.05)
|
|
110
|
+
session.expect("ready", timeout: 2)
|
|
111
|
+
assert_raises(Expect::WriteTimeout) { bounded { session.write("x" * 1_000_000) } }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def test_readiness_returns_readable_sessions
|
|
115
|
+
first, = pipe_session
|
|
116
|
+
second, writer = pipe_session
|
|
117
|
+
assert_empty Expect.readable_sessions(first, second)
|
|
118
|
+
writer.write("data")
|
|
119
|
+
assert_equal [second], Expect.readable_sessions(first, second)
|
|
120
|
+
second.expect("data", timeout: 1)
|
|
121
|
+
assert_empty Expect.readable_sessions(first, second)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def test_readiness_waits_for_data_and_does_not_consume_data
|
|
125
|
+
first, = pipe_session
|
|
126
|
+
second, writer = pipe_session
|
|
127
|
+
background do
|
|
128
|
+
sleep 0.03
|
|
129
|
+
writer.write("ready")
|
|
130
|
+
end
|
|
131
|
+
assert_equal([second], bounded { Expect.readable_sessions(first, second, timeout: 1) })
|
|
132
|
+
assert_equal 1, second.expect("ready", timeout: 0)
|
|
133
|
+
assert_empty Expect.readable_sessions(first, second, timeout: 0)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def test_readiness_accepts_keyword_and_unlimited_timeout
|
|
137
|
+
session, writer = pipe_session
|
|
138
|
+
assert_empty Expect.readable_sessions(session, timeout: 0.01)
|
|
139
|
+
background do
|
|
140
|
+
sleep 0.03
|
|
141
|
+
writer.write("ready")
|
|
142
|
+
end
|
|
143
|
+
assert_equal([session], bounded { Expect.readable_sessions(session, timeout: nil) })
|
|
144
|
+
assert_equal [session], Expect.readable_sessions(session, timeout: 0)
|
|
145
|
+
assert_raises(ArgumentError) { Expect.readable_sessions(session, timeout: -1) }
|
|
146
|
+
assert_raises(ArgumentError) { Expect.readable_sessions(0, session, timeout: 1) }
|
|
147
|
+
assert_raises(ArgumentError) { Expect.readable_sessions("invalid") }
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def test_readiness_excludes_closed_sessions
|
|
151
|
+
first, = pipe_session
|
|
152
|
+
second, writer = pipe_session
|
|
153
|
+
first.close
|
|
154
|
+
writer.write("ready")
|
|
155
|
+
assert_equal [second], Expect.readable_sessions(first, second, timeout: 0)
|
|
156
|
+
assert_empty Expect.readable_sessions
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def test_read_io_error_is_reported
|
|
160
|
+
Tempfile.create("expect-write-only") do |file|
|
|
161
|
+
File.open(file.path, "w") do |writer|
|
|
162
|
+
session = Expect.open(writer)
|
|
163
|
+
@sessions << session
|
|
164
|
+
result = session.expect_result("x", timeout: 1)
|
|
165
|
+
assert_instance_of IOError, result.error
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def test_log_io_failure_does_not_masquerade_as_child_eof
|
|
171
|
+
session = child('puts "ready"; STDIN.gets; puts "response"; sleep 30', raw_pty: true)
|
|
172
|
+
assert_equal 1, session.expect("ready\n", timeout: 2)
|
|
173
|
+
session.log_to(->(_) { raise Errno::EIO, "log failed" })
|
|
174
|
+
session.write("continue\n")
|
|
175
|
+
result = session.expect_result("response", timeout: 2)
|
|
176
|
+
assert_instance_of Errno::EIO, result.error
|
|
177
|
+
refute result.eof?
|
|
178
|
+
refute session.eof?
|
|
179
|
+
assert session.alive?
|
|
180
|
+
assert_includes result.before, "response\n"
|
|
181
|
+
session.log_output = nil
|
|
182
|
+
assert_equal 1, session.expect("response", timeout: 0)
|
|
183
|
+
end
|
|
184
|
+
end
|
data/test/kibitz_test.rb
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
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
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "test_helper"
|
|
4
|
+
|
|
5
|
+
class MatchingTest < ExpectTest
|
|
6
|
+
def test_literal_regexp_number_and_result_tuple
|
|
7
|
+
session, = pipe_session
|
|
8
|
+
session.buffer = "before a.c between abc:42 after"
|
|
9
|
+
assert_equal 2, session.expect("absent", "a.c", timeout: 0)
|
|
10
|
+
assert_equal "before ", session.before
|
|
11
|
+
assert_equal "a.c", session.match
|
|
12
|
+
result = session.expect_result(/abc:(\d+)/, timeout: 0)
|
|
13
|
+
assert_equal [1, nil, "abc:42", " between ", " after", session, ["42"]], result.to_a
|
|
14
|
+
assert_equal ["42"], session.captures
|
|
15
|
+
assert result.matched?
|
|
16
|
+
refute result.timeout?
|
|
17
|
+
refute result.eof?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_regexp_callback_captures_lexical_arguments
|
|
21
|
+
session, = pipe_session
|
|
22
|
+
session.buffer = "prefix xyz42 suffix"
|
|
23
|
+
observed = []
|
|
24
|
+
argument = :value
|
|
25
|
+
number = session.expect(timeout: 0) do
|
|
26
|
+
on(/xyz(\d+)/) { |object| observed << [object.captures, argument] }
|
|
27
|
+
end
|
|
28
|
+
assert_equal 1, number
|
|
29
|
+
assert_equal [[["42"], :value]], observed
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_native_regexp_and_literal_event_names
|
|
33
|
+
session, = pipe_session
|
|
34
|
+
session.buffer = "timeout eof -word a.c end99"
|
|
35
|
+
assert_equal 1, session.expect("timeout", timeout: 0)
|
|
36
|
+
assert_equal 1, session.expect("eof", timeout: 0)
|
|
37
|
+
assert_equal 1, session.expect("-word", timeout: 0)
|
|
38
|
+
assert_equal 1, session.expect("a.c", timeout: 0)
|
|
39
|
+
assert_equal 1, session.expect(/end\d+/, timeout: 0)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_patterns_have_priority_over_their_position_in_buffer
|
|
43
|
+
session, = pipe_session
|
|
44
|
+
session.buffer = "second first"
|
|
45
|
+
assert_equal 1, session.expect("first", "second", timeout: 0)
|
|
46
|
+
assert_equal "second ", session.before
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def test_regexp_uses_actual_offset_with_lookbehind
|
|
50
|
+
session, = pipe_session
|
|
51
|
+
session.buffer = "same prefix same end"
|
|
52
|
+
session.expect(/(?<=prefix )same/, timeout: 0)
|
|
53
|
+
assert_equal "same prefix ", session.before
|
|
54
|
+
assert_equal " end", session.after
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def test_unmatched_capture_and_zero_width_pattern
|
|
58
|
+
session, = pipe_session
|
|
59
|
+
session.buffer = "b"
|
|
60
|
+
assert_equal 1, session.expect(/(a)?(?=b)/, timeout: 0)
|
|
61
|
+
assert_equal [nil], session.captures
|
|
62
|
+
assert_equal "", session.match
|
|
63
|
+
assert_equal "b", session.buffer
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_capture_list_clears_for_literal_and_timeout
|
|
67
|
+
session, = pipe_session
|
|
68
|
+
session.buffer = "a1 b"
|
|
69
|
+
session.expect(/a(\d)/, timeout: 0)
|
|
70
|
+
session.expect("b", timeout: 0)
|
|
71
|
+
assert_empty session.captures
|
|
72
|
+
assert_nil session.expect(/absent/, timeout: 0)
|
|
73
|
+
assert_nil session.match
|
|
74
|
+
assert_nil session.after
|
|
75
|
+
assert_empty session.captures
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_buffer_retained_on_timeout_and_manually_replaceable
|
|
79
|
+
session, writer = pipe_session
|
|
80
|
+
writer.write("some string")
|
|
81
|
+
assert_nil session.expect("other", timeout: 0.03)
|
|
82
|
+
assert_equal :timeout, session.error
|
|
83
|
+
assert_equal "some string", session.before
|
|
84
|
+
assert_equal 1, session.expect("some", timeout: 0)
|
|
85
|
+
assert_equal " string", session.buffer
|
|
86
|
+
session.buffer = "new"
|
|
87
|
+
assert_equal "new", session.clear_buffer
|
|
88
|
+
assert_empty session.buffer
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def test_preserve_buffer_and_buffer_limit
|
|
92
|
+
session, = pipe_session
|
|
93
|
+
session.preserve_buffer = true
|
|
94
|
+
session.buffer = "prefix token tail"
|
|
95
|
+
2.times { assert_equal 1, session.expect("token", timeout: 0) }
|
|
96
|
+
assert_equal "prefix token tail", session.buffer
|
|
97
|
+
session.buffer_limit = 4
|
|
98
|
+
assert_equal "tail", session.buffer
|
|
99
|
+
session.buffer_limit = nil
|
|
100
|
+
session.buffer = "123456"
|
|
101
|
+
assert_equal "123456", session.buffer
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def test_split_patterns_across_reads
|
|
105
|
+
session, writer = pipe_session
|
|
106
|
+
background do
|
|
107
|
+
writer.write("pass")
|
|
108
|
+
sleep 0.03
|
|
109
|
+
writer.write("word: ")
|
|
110
|
+
end
|
|
111
|
+
assert_equal 1, session.expect(/password: /, timeout: 1)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def test_utf8_regexp_split_character_and_byte_offsets
|
|
115
|
+
session, writer = pipe_session
|
|
116
|
+
bytes = "前缀密码:结束".b
|
|
117
|
+
background do
|
|
118
|
+
bytes.each_byte do |byte|
|
|
119
|
+
writer.write(byte.chr)
|
|
120
|
+
sleep 0.001
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
assert_equal 1, session.expect(/(密码):/, timeout: 1)
|
|
124
|
+
assert_equal "前缀".b, session.before
|
|
125
|
+
assert_equal ["密码".b], session.captures
|
|
126
|
+
assert_equal 1, session.expect("结束", timeout: 1)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def test_binary_nul_and_invalid_utf8
|
|
130
|
+
session, writer = pipe_session
|
|
131
|
+
writer.write("\xff\x00abc\xfe".b)
|
|
132
|
+
assert_equal 1, session.expect(/\x00(abc)/n, timeout: 1)
|
|
133
|
+
assert_equal "\xff".b, session.before
|
|
134
|
+
assert_equal ["abc"], session.captures
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def test_native_regexp_anchors_and_flags
|
|
138
|
+
session, = pipe_session
|
|
139
|
+
session.buffer = "a\nb\nc"
|
|
140
|
+
session.preserve_buffer = true
|
|
141
|
+
assert_nil session.expect("^b$", timeout: 0)
|
|
142
|
+
assert_nil session.expect(/\Ab\z/, timeout: 0)
|
|
143
|
+
assert_equal 1, session.expect(/^b$/, timeout: 0)
|
|
144
|
+
assert_equal 1, session.expect(/a.b/m, timeout: 0)
|
|
145
|
+
assert_nil session.expect(/a.b/, timeout: 0)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def test_eof_callback_consumes_remaining_buffer
|
|
149
|
+
session, writer = pipe_session
|
|
150
|
+
writer.write("last bytes")
|
|
151
|
+
writer.close
|
|
152
|
+
seen = nil
|
|
153
|
+
result = session.expect_result(timeout: 1) { eof { |object| seen = object.before } }
|
|
154
|
+
assert_nil result.number
|
|
155
|
+
assert result.eof?
|
|
156
|
+
assert_equal "last bytes", seen
|
|
157
|
+
assert_empty session.buffer
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def test_read_without_patterns_and_match_before_eof
|
|
161
|
+
session, writer = pipe_session
|
|
162
|
+
writer.write("END")
|
|
163
|
+
writer.close
|
|
164
|
+
assert_equal 1, session.expect("END", timeout: nil)
|
|
165
|
+
assert session.expect_result(timeout: nil).eof?
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def test_argument_validation_does_not_read_io
|
|
169
|
+
session, = pipe_session
|
|
170
|
+
assert_raises(ArgumentError) { session.expect("x", timeout: -1) }
|
|
171
|
+
assert_raises(ArgumentError) { session.expect("x", timeout: Float::INFINITY) }
|
|
172
|
+
assert_raises(ArgumentError) { session.expect(["a", 42], timeout: 0) }
|
|
173
|
+
assert_raises(ArgumentError) { session.expect("-i", [], timeout: 0) }
|
|
174
|
+
assert_raises(ArgumentError) { session.expect(Object.new, timeout: 0) }
|
|
175
|
+
assert_raises(ArgumentError) { Expect.expect("x", timeout: 0) }
|
|
176
|
+
assert_raises(ArgumentError) { session.buffer_limit = -1 }
|
|
177
|
+
end
|
|
178
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "test_helper"
|
|
4
|
+
|
|
5
|
+
class MultiSessionTest < ExpectTest
|
|
6
|
+
def test_multi_session_pattern_groups_and_global_numbering
|
|
7
|
+
first, first_writer = pipe_session
|
|
8
|
+
second, second_writer = pipe_session
|
|
9
|
+
first_writer.write("irrelevant")
|
|
10
|
+
second_writer.write("second:42")
|
|
11
|
+
result = Expect.expect_result(timeout: 1) do
|
|
12
|
+
on("first", from: first)
|
|
13
|
+
on(/second:(\d+)/, from: second)
|
|
14
|
+
end
|
|
15
|
+
assert_equal 2, result.number
|
|
16
|
+
assert_same second, result.session
|
|
17
|
+
assert_equal ["42"], result.captures
|
|
18
|
+
assert_equal "irrelevant", first.buffer
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_shared_patterns_across_sessions
|
|
22
|
+
first, = pipe_session
|
|
23
|
+
second, writer = pipe_session
|
|
24
|
+
writer.write("ready")
|
|
25
|
+
assert_equal 1, Expect.expect("ready", from: [first, second], timeout: 1)
|
|
26
|
+
assert_equal "ready", second.match
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_eof_continue_removes_closed_session_and_waits_for_remaining
|
|
30
|
+
first, first_writer = pipe_session
|
|
31
|
+
second, second_writer = pipe_session
|
|
32
|
+
first_writer.close
|
|
33
|
+
ended = []
|
|
34
|
+
result = Expect.expect_result(timeout: 1) do
|
|
35
|
+
eof(from: first) do |connection|
|
|
36
|
+
ended << connection
|
|
37
|
+
second_writer.write("done")
|
|
38
|
+
Expect.continue(reset_timeout: false)
|
|
39
|
+
end
|
|
40
|
+
on("done", from: second)
|
|
41
|
+
end
|
|
42
|
+
assert_equal [first], ended
|
|
43
|
+
assert_equal 2, result.number
|
|
44
|
+
assert_same second, result.session
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_all_eof_continuation_returns_without_waiting_forever
|
|
48
|
+
first, writer = pipe_session
|
|
49
|
+
writer.close
|
|
50
|
+
result = bounded { first.expect_result(timeout: nil) { eof { Expect.continue } } }
|
|
51
|
+
assert result.eof?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_same_session_in_multiple_groups_is_read_once
|
|
55
|
+
session, writer = pipe_session
|
|
56
|
+
writer.write("right")
|
|
57
|
+
other, = pipe_session
|
|
58
|
+
result = Expect.expect_result(timeout: 1) do
|
|
59
|
+
on("wrong", from: session)
|
|
60
|
+
on("missing", from: other)
|
|
61
|
+
on("right", from: session)
|
|
62
|
+
end
|
|
63
|
+
assert_equal 3, result.number
|
|
64
|
+
assert_equal "right", result.match
|
|
65
|
+
end
|
|
66
|
+
end
|