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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -0
- data/README.md +28 -19
- data/docs/API.md +88 -0
- data/docs/MIGRATION.md +15 -0
- data/lib/expect/cleanup.rb +33 -0
- data/lib/expect/configuration.rb +87 -66
- data/lib/expect/interaction.rb +226 -209
- data/lib/expect/logging.rb +157 -156
- data/lib/expect/matcher.rb +30 -29
- data/lib/expect/pattern.rb +8 -2
- data/lib/expect/pattern_list.rb +35 -5
- data/lib/expect/redactor.rb +11 -4
- data/lib/expect/relay.rb +97 -73
- data/lib/expect/relay_writer.rb +2 -1
- data/lib/expect/result.rb +32 -8
- data/lib/expect/session.rb +508 -0
- data/lib/expect/session_resources.rb +10 -2
- data/lib/expect/terminal.rb +78 -78
- data/lib/expect/version.rb +1 -1
- data/lib/expect.rb +333 -520
- data/sig/expect.rbs +355 -0
- metadata +13 -106
- data/.rubocop.yml +0 -66
- data/CONTRIBUTING.md +0 -29
- data/Gemfile +0 -28
- data/Rakefile +0 -38
- data/benchmark/matching.rb +0 -98
- data/benchmark/redactor.rb +0 -64
- data/benchmark/relay.rb +0 -48
- data/benchmark/scaling.rb +0 -70
- data/benchmark/send_slow.rb +0 -41
- data/benchmark/support.rb +0 -114
- data/docs/COMPATIBILITY.md +0 -98
- data/docs/INTERNAL_CONTRACTS.md +0 -154
- data/docs/PERFORMANCE.md +0 -165
- data/docs/RELEASING.md +0 -84
- data/docs/VERIFICATION.md +0 -456
- data/examples/dialogue.rb +0 -30
- data/examples/kibitz/README.md +0 -81
- data/examples/kibitz/kibitz.rb +0 -142
- data/examples/kibitz/test_kibitz.rb +0 -37
- data/examples/ssh_auto.rb +0 -94
- data/examples/ssh_interact.rb +0 -159
- data/examples/ssh_login.rb +0 -64
- data/expect-pty.gemspec +0 -33
- data/script/ci +0 -121
- data/script/release.rb +0 -319
- data/test/buffer_accounting_test.rb +0 -83
- data/test/cleanup_test.rb +0 -251
- data/test/compare_upstream.rb +0 -157
- data/test/configuration_test.rb +0 -133
- data/test/deadline_test.rb +0 -237
- data/test/diagnostics_test.rb +0 -353
- data/test/edge_case_test.rb +0 -262
- data/test/fixtures/ssh_scripts/01_identity.sh +0 -4
- data/test/fixtures/ssh_scripts/02_output.sh +0 -5
- data/test/fixtures/ssh_scripts/03_delayed.sh +0 -6
- data/test/fixtures/ssh_scripts/04_failure.sh +0 -2
- data/test/fixtures/ssh_scripts/05_recovery.sh +0 -3
- data/test/initialization_failure_test.rb +0 -92
- data/test/integration/README.md +0 -109
- data/test/integration/ssh_scripts.rb +0 -94
- data/test/interact_test.rb +0 -253
- data/test/interconnect_test.rb +0 -425
- data/test/io_test.rb +0 -321
- data/test/kibitz_test.rb +0 -45
- data/test/lifecycle_contract_test.rb +0 -71
- data/test/literal_scan_test.rb +0 -97
- data/test/matching_test.rb +0 -211
- data/test/multi_session_test.rb +0 -66
- data/test/ownership_sequence_test.rb +0 -208
- data/test/pattern_offset_test.rb +0 -43
- data/test/process_interruption_test.rb +0 -296
- data/test/process_test.rb +0 -291
- data/test/redactor_test.rb +0 -183
- data/test/relay_recovery_test.rb +0 -451
- data/test/relay_reentrancy_test.rb +0 -159
- data/test/release_test.rb +0 -375
- data/test/ruby_api_test.rb +0 -515
- data/test/scan_reuse_test.rb +0 -109
- data/test/script_logging_test.rb +0 -124
- data/test/support/interact_probe.rb +0 -125
- data/test/support/kibitz_probe.rb +0 -177
- data/test/support/script_probe.rb +0 -158
- data/test/terminal_cleanup_test.rb +0 -349
- data/test/test_helper.rb +0 -58
- data/test/timeout_test.rb +0 -289
- data/test/write_contract_test.rb +0 -105
data/lib/expect/terminal.rb
CHANGED
|
@@ -4,99 +4,99 @@ require "shellwords"
|
|
|
4
4
|
|
|
5
5
|
# 会话终端的模式和窗口尺寸接口;人工接管期间的临时恢复由 interaction.rb 负责。
|
|
6
6
|
class Expect
|
|
7
|
-
#
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
# @api private
|
|
8
|
+
module Terminal
|
|
9
|
+
# 查询可恢复的终端模式字符串,或通过系统 stty 设置模式;参数按数组传递,不经 shell。
|
|
10
|
+
# 辅助进程与管道独立记账,不能覆盖主会话 PID;失败时保留原异常并有界回收。
|
|
11
|
+
def stty(*modes)
|
|
12
|
+
return "" unless tty?
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
modes = modes.flat_map { |mode| Shellwords.split(mode.to_s) }
|
|
15
|
+
modes = ["-g"] if modes.empty?
|
|
16
|
+
reader = sink = resources = nil
|
|
17
|
+
Cleanup.always(-> { cleanup_stty(resources, reader, sink) }) do
|
|
18
|
+
reader, sink = IO.pipe
|
|
19
|
+
resources = SessionResources.new(reader, writer: sink, own: true)
|
|
20
|
+
resources.pid = Process.spawn("stty", *modes, in: to_io, out: sink, err: sink)
|
|
21
|
+
sink.close
|
|
22
|
+
output = reader.read
|
|
23
|
+
status = wait_stty(resources.pid)
|
|
24
|
+
resources.pid = nil
|
|
25
|
+
raise IOError, "stty failed: #{output.strip}" unless status.success?
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
failed = true
|
|
29
|
-
raise
|
|
30
|
-
ensure
|
|
31
|
-
cleanup(failed:) { cleanup_stty(resources, reader, sink) }
|
|
32
|
-
end
|
|
27
|
+
output.strip
|
|
28
|
+
rescue Errno::ENOENT
|
|
29
|
+
raise IOError, "stty executable not found in PATH; install the system terminal utilities"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
# 读取终端的 [行数, 列数];底层并非终端或句柄已关闭时保留原生 IO 异常。
|
|
34
|
+
def winsize = to_io.winsize
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
private
|
|
36
|
+
# 更新终端尺寸,由内核通知前台进程。
|
|
37
|
+
def winsize=(size)
|
|
38
|
+
to_io.winsize = size
|
|
39
|
+
end
|
|
43
40
|
|
|
44
|
-
|
|
45
|
-
def wait_stty(pid)
|
|
46
|
-
Process.waitpid2(pid).last
|
|
47
|
-
rescue Errno::EINTR
|
|
48
|
-
retry
|
|
49
|
-
end
|
|
41
|
+
private
|
|
50
42
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
io.close unless io.closed?
|
|
57
|
-
rescue IOError, SystemCallError => error
|
|
58
|
-
failure ||= error
|
|
43
|
+
# 正常路径阻塞取得真实状态,不额外轮询或固定等待;EINTR 不重新执行 stty。
|
|
44
|
+
def wait_stty(pid)
|
|
45
|
+
Process.waitpid2(pid).last
|
|
46
|
+
rescue Errno::EINTR
|
|
47
|
+
retry
|
|
59
48
|
end
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
49
|
+
|
|
50
|
+
# 即使账本初始化被中断,局部变量中的管道仍须关闭;单端失败不跳过其他清理。
|
|
51
|
+
# 无主异常时由调用方传播首个常规清理错误。
|
|
52
|
+
def cleanup_stty(resources, reader, sink)
|
|
53
|
+
failure = nil
|
|
54
|
+
[reader, sink].compact.each do |io|
|
|
55
|
+
io.close unless io.closed?
|
|
56
|
+
rescue IOError, SystemCallError => error
|
|
57
|
+
failure ||= error
|
|
58
|
+
end
|
|
59
|
+
begin
|
|
60
|
+
reap_stty(resources) if resources
|
|
61
|
+
rescue IOError, SystemCallError => error
|
|
62
|
+
failure ||= error
|
|
63
|
+
end
|
|
64
|
+
raise failure if failure
|
|
64
65
|
end
|
|
65
|
-
raise failure if failure
|
|
66
|
-
end
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
# 每阶段共享固定的 50ms 单调时钟预算:自然退出、TERM、KILL;中断不续期。
|
|
68
|
+
# 仍无法同步回收或系统调用失败时交给 detach 的后台 wait,不伪造退出状态。
|
|
69
|
+
def reap_stty(resources)
|
|
70
|
+
return unless resources.owner == Process.pid
|
|
72
71
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
[nil, "TERM", "KILL"].each do |signal|
|
|
73
|
+
deadline = Expect.monotonic + 0.05
|
|
74
|
+
loop do
|
|
75
|
+
begin
|
|
76
|
+
resources.reap
|
|
77
|
+
return resources.status unless resources.pid && resources.owner == Process.pid
|
|
79
78
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
if signal
|
|
80
|
+
begin
|
|
81
|
+
Process.kill(signal, resources.pid)
|
|
82
|
+
rescue Errno::ESRCH
|
|
83
|
+
# 退出可发生在非阻塞 wait 与信号之间,仍需继续 wait。
|
|
84
|
+
nil
|
|
85
|
+
end
|
|
86
|
+
signal = nil
|
|
86
87
|
end
|
|
87
|
-
|
|
88
|
+
rescue Errno::EINTR
|
|
89
|
+
# 重试沿用本阶段期限,连续 EINTR 也必须交还控制权。
|
|
90
|
+
nil
|
|
88
91
|
end
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
nil
|
|
92
|
-
end
|
|
93
|
-
remaining = deadline - Expect.monotonic
|
|
94
|
-
break unless remaining.positive?
|
|
92
|
+
remaining = deadline - Expect.monotonic
|
|
93
|
+
break unless remaining.positive?
|
|
95
94
|
|
|
96
|
-
|
|
95
|
+
sleep [remaining, 0.005].min
|
|
96
|
+
end
|
|
97
97
|
end
|
|
98
|
+
ensure
|
|
99
|
+
Process.detach(resources.pid) if resources.pid && resources.owner == Process.pid
|
|
98
100
|
end
|
|
99
|
-
ensure
|
|
100
|
-
Process.detach(resources.pid) if resources.pid && resources.owner == Process.pid
|
|
101
101
|
end
|
|
102
102
|
end
|
data/lib/expect/version.rb
CHANGED