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,154 +0,0 @@
1
- # 内部状态与数据归属
2
-
3
- 这些状态供 Matcher、Relay 和会话生命周期协作使用,不是公共接口。
4
-
5
- ## 模块职责
6
-
7
- | 模块 | 职责 |
8
- |-------------------------------------------------|----------------------------------------------------|
9
- | `expect.rb` | 会话入口、PTY 启动、缓冲、直接读写和关闭流程 |
10
- | `configuration.rb` | 配置校验与可复制的默认快照 |
11
- | `pattern.rb`、`pattern_list.rb`、`result.rb` | 字节定位、声明顺序及原生结果值 |
12
- | `matcher.rb` | 一次等待的匹配、事件派发和期限 |
13
- | `session_resources.rb` | IO、日志与直属子进程的所有权账本和 GC 兜底 |
14
- | `logging.rb` | 日志目标、监听器及同步输出;不持有第二份资源所有权 |
15
- | `terminal.rb` | `stty` 和窗口尺寸的会话终端接口 |
16
- | `interaction.rb`、`relay.rb`、`relay_writer.rb` | 人工接管、转义处理、共同调度和各目标发送游标 |
17
-
18
- `redactor.rb` 提供公共 `Expect::Redactor` 字节过滤接口,供会话日志、各方向诊断及上层库共用,不持有 IO,也不接管协议缓冲。
19
-
20
- 会话能力文件延续 `interaction.rb` 的类重开方式,入口为 `expect/pty`;独立过滤器可通过 `expect/redactor` 加载。按职责组织方法,不引入新的继承层级,也不复制会话状态。
21
-
22
- 跨对象调用使用 `__send__` 访问受保护或私有方法。变更以下入口时,应同时检查调用方与失败恢复路径:
23
-
24
- | 会话内部入口 | 使用方 | 不变量 |
25
- |--------------------------------------------------------------|--------------------------|--------------------------------------------------------|
26
- | `reset_result`、`record_match`、`record_eof`、`record_error` | Matcher | 先保存结果再执行回调;错误保留原始输入 |
27
- | `read_available` | Matcher、Relay、写入背压 | 转接缓冲交接时禁止直接传播和裁剪;普通匹配缓冲需要裁剪 |
28
- | `interaction_buffer`、`restore_relay_buffer` | Matcher、Relay | 嵌套匹配及转接退出时把未消费字节交还原所有者 |
29
- | `sequences`、`relay_history`、`relay_callback` | Relay、转义扫描 | 已识别转义只回调一次,已发送前缀不重放 |
30
- | `queue_output`、`relay_outputs`、`propagate` | Relay、转义扫描 | 每个目标独立保存短写进度;同步传播只用于普通匹配 |
31
-
32
- | 状态 | 所有者与修改入口 | 交接、超时与关闭 |
33
- |-----------------------|---------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
34
- | `@buffer` | 会话的未消费匹配输入;`read_available` 追加,`record_match` 消费,`clear_buffer` 移交 | Matcher 用公开 `buffer` 的副本扫描;一次扫描的重复来源复用副本,下轮重新获取。超时保留字节。关闭不清空它,便于检查尾部 |
35
- | `@interaction_buffer` | Relay 当前持有的待处理输入;转接背压读取也追加到这里 | 转义回调内的 Matcher 临时移回 `@buffer`;Matcher 的 ensure 将余下字节交回 Relay。Relay 的 ensure 恢复上层交互状态,并把未处理字节还给普通缓冲 |
36
- | `@relay_outputs` | 源会话持有的各目标发送游标;`queue_output` 创建,RelayWriter 推进 | 超时或写失败后保留原目标和已交付位置;重入续发,不重放成功前缀。关闭放弃剩余交付并清空队列,不关闭借用目标 |
37
- | `@relay_history` | 源会话的正则转义历史窗口;`relay_buffer` 更新 | 同一规则跨次转接保留,规则改变或转义消费后清空。窗口遵守 buffer_limit 或内部默认上限,固定 UTF-8 正则不保留开头的孤立续字节。关闭清空 |
38
- | `@relay_callback` | 已识别转义但尚未交付完前缀时,源会话暂存的回调 | 所有前缀目标交付完成后执行一次;超时保留,关闭释放。不能在等待写入后重新扫描这段已识别转义 |
39
-
40
- 匹配优先级始终是“声明组 → 会话 → 模式”,不是文本位置;多个会话包装同一 IO
41
- 时,就绪处理使用对象身份选择首个声明会话。回调前后用于判断缓冲是否变化的快照仍独立保留,不能换成可变内部字符串。零长度或
42
- preserve_buffer 的继续匹配必须遵守 stalled 保护。
43
-
44
- 文本与 EOF 回调遵守相同的继续期限:`continue(reset_timeout: false)` 返回后先检查原期限,再开始下一轮文本匹配。EOF
45
- 继续已到期时仍依次派发已知 EOF,不读取新输入;超时仅通知仍活跃的来源,保留未消费缓冲。所有来源都已 EOF 时直接返回
46
- EOF,不再制造一次超时事件。EOF 或超时回调重置期限后恢复普通匹配顺序。
47
-
48
- 转义扫描只在一轮内复用 `history + buffer`,只含字面规则时不构造它。回调继续后重新读取规则、历史和缓冲;不能跨回调保存文本快照。字面转义的潜在前缀暂存,完整前缀交付后才执行转义回调。
49
-
50
- Relay 在移动缓冲、恢复写入期限之前检查全部来源的 `relay_owner`,有重叠时抛出 `ReentrancyError`。
51
- 重复来源先去重,标记检查和登记只使用短临界区;不跨 IO 或用户回调持锁。退出及准备失败时只归还已转移的缓冲,
52
- 只释放当前调用的 token,不能清除外层标记。不同来源的嵌套转接、转义回调中的 Matcher 和转接返回后的顺序恢复保持可用。
53
- 自定义目标先产生副作用再抛错而不返回计数时,库不能猜测进度;该协议边界不承诺恰好一次,也不承诺通用线程安全。
54
-
55
- ## 关闭与所有权
56
-
57
- SessionResources 保存创建者 PID、所属句柄、直属子进程和所属日志。借用 IO 不关闭,fork 后的非创建者不发送信号或回收父进程的孩子。soft_close
58
- 最多发送 TERM 并保留未退出 PID;hard_close 可在有限等待后发送 KILL。
59
-
60
- 工厂和构造器在校验之前登记资源归属,失败时沿用同一清理流程;启动成功必须显式记录,不能用 PID 非空推断整个工厂调用已经成功。即使
61
- exec 成功后诊断输出失败,也要立即回收未交付给调用方的子进程。
62
-
63
- 资源账本构造本身也可能被中断。账本尚未发布时,`cleanup_session` 仅按局部 `own` 关闭真实 IO,按对象身份去重,
64
- 一端失败仍尝试其他端;借用 IO 保持打开。账本存在后仍调用统一的 `close`,不维护第二份生命周期状态。
65
- `cleanup(failed:)` 的关键字表示本次作用域是否已有异常;只有已有主异常时才抑制常规清理错误,保留同一异常对象。
66
- 内部动作使用 `close_resources`、`close_child`、`mark_eof` 等直接名称,不保留旧私有方法别名。
67
-
68
- 显式关闭遇到 IOError/SystemCallError 时,继续尝试其他句柄、交互包装器、日志和子进程清理,最后传播首个清理错误;已经在传播的其他异常保留。失败资源继续持有,后续关闭可重试。GC
69
- 终结器独立尝试句柄、日志、非阻塞回收,常规清理错误不向外传播。终结器不能强引用会话本身。
70
-
71
- `SessionResources#reap` 只做一次非阻塞系统调用,EINTR 交给所属流程决定:主会话 `process_status` 返回当前未知/缓存状态,
72
- `wait_for_child` 沿用阶段开始时的绝对期限,并在重试间休眠。自然等待、TERM 等待分别使用调用方预算,硬关闭的 KILL 阶段保留 1 秒预算;
73
- 零预算也先尝试一次,信号 EINTR 不重新计时。soft_close 不发 KILL,ECHILD 清空 PID,ESRCH 后仍尝试回收,预算耗尽也返回已取得的状态。
74
- GC 只做一次非阻塞回收、最多两次 KILL 尝试及一次 detach;回收/信号 EINTR 不跳过后续阶段,不执行用户日志回调。
75
- detach 成功才移交 PID,失败则保留未知 PID/status。每个操作仍受创建者归属约束;持续系统调用失败只保证有限尝试,不保证返回前退出。
76
-
77
- 是否保留原始异常由当前构造、块或关闭作用域显式记录,不能直接读取调用者 rescue 中的 `$!`。`Interrupt` 和 `SystemExit`
78
- 同样先清理再传播;`break` / `throw` 不是异常,此时清理失败仍应抛出。
79
-
80
- `stty` 为辅助管道和 PID 创建独立的 SessionResources,不写入主会话的 PID/status,不关闭主会话。
81
- 账本构造被中断时仍关闭局部变量中已经创建的管道;fork 后的副本只关闭本地管道,不向父进程的辅助 PID 发信号或 detach,等待途中也重新核对归属。
82
- 正常路径读取输出并阻塞回收,EINTR 只重试 wait,不再次启动命令,也不增加固定等待。
83
- 异常路径先尝试关闭两个管道,再依次等待自然退出、发送 TERM、发送 KILL;每阶段使用固定的 50ms 单调时钟预算,
84
- 非阻塞 wait 或 signal 的 EINTR 不延长期限。ECHILD 立即释放该 PID 的归属,不向可能复用的 PID 发信号;
85
- ESRCH 后仍尝试回收。三个阶段结束或系统调用失败后仍有 PID 时交给 `Process.detach` 异步回收,不保证返回前退出,
86
- 也不伪造退出状态。预算约束进程轮询,不是任意同步 IO 或系统调度的硬实时保证。
87
- 本次主异常(包括 Interrupt/SystemExit)保持对象身份;没有主异常时传播首个 IOError/SystemCallError 清理失败。
88
- 永久失败的 close 只保证被尝试,不能报告成句柄已关闭;缺失 stty 仍为 IOError 并保留 ENOENT cause。
89
-
90
- ## 缓冲裁剪与字面扫描
91
-
92
- `buffer_discarded_bytes` 随会话累计,只在匹配窗口执行 `trim_buffer` 时增加。读取、`buffer=`、降低上限和下一次匹配应用上限都可能触发裁剪;匹配消费、清空、EOF 和 Relay 交接不计入,关闭后仍保留计数。它是可观察的丢弃量,不是缓冲满事件,也不提供全文归档。
93
-
94
- `@buffer_generation` 仅在替换、消费、清空、裁剪和 Relay 恢复时递增,同代次只追加。Matcher 按会话与模式对象身份记录字面未命中的代次、字节数及模式值;新增输入只回看模式长度减一的重叠区。无新字节可跳过重复扫描,代次或模式值改变则从头扫描。缓存只活在一次 Matcher 中,不保留文本副本。正则仍扫描完整窗口,不推断其长度、锚点或前瞻范围。
95
-
96
- ## 相对期限与总期限
97
-
98
- 公共 `deadline:` 使用 `Expect.monotonic` 的有限绝对秒数,`nil` 表示无总期限;它与 `timeout` 取较早者。`reset_timeout_on_read` 及所有继续回调只能重置相对期限,总期限固定。总期限过后不读取或消费新的文本匹配;正则计算结束后也要重查期限。已知 EOF 保留派发顺序,全部来源已 EOF 时直接返回 EOF。
99
-
100
- 未设置总期限时,`timeout: 0` 保持现有缓冲匹配和首次非阻塞轮询语义。期限检查是协作式的,不强行打断单次正则、日志、同步监听器或用户回调;正则执行限时由调用方的 `Regexp` 实例控制。
101
-
102
- 直接写入及 Relay 目标的 `write_timeout` 用于背压等待和中断重试,不是持续成功短写的总耗时限制。
103
- 匹配 `deadline`、写入期限和 Relay 总 `timeout` 分别管理,不自动把匹配期限传给回调中的写入。
104
- 直接写入必须先验证计数为正整数且不超过本次 chunk,再推进 offset;`:wait_writable` 仍进入背压路径,空输入返回 0。
105
- `WriteTimeout#bytes_written` 只计已确认字节;读取输出引发嵌套写入超时时,对外保留当前写入进度,原异常留在 cause。
106
-
107
- ## 诊断与脱敏归属
108
-
109
- `logging.rb` 分开处理接收日志、诊断和协议转发。`diagnostic_output` 借用 Logger、可写对象或回调,不进入资源账本;默认沿用 stderr。回调接收冻结的事件 Hash 和 message 字符串,不含会话对象。诊断失败与其他同步 IO 错误同样保留已读取的原始输入。
110
-
111
- `redactor.rb` 是无 IO 的公共字节过滤器。会话复制并追加注册秘密,每个接收日志流以及发送、接收诊断方向各自持有过滤器。暂存最长秘密长度减一的尾部,重叠秘密合并为隐藏区间,过滤发生在 `inspect` 转义之前。缓冲快照可能只剩秘密中间字节,启用脱敏时整体隐藏,不重复展示其内容。
112
-
113
- 公共构造器及 `patterns=` 会校验并复制秘密;空模式列表直接交付字节,更新规则不清除已经建立的掩码。
114
- 同一模式的命中按字节偏移递增枚举,相交或相邻区间合并后才写入掩码;不能通过跳过整个秘密长度省略重叠命中。
115
- 合并仍与现有掩码取并集,保留其他模式和旧规则已标记的隐藏字节,不改变跨块连续区间只输出一个替换标记的规则。
116
- 默认替换标记为 `[FILTERED]`,上层库可显式指定自己的标记。类方法 `redact` 使用独立流并以 `finish(partial: false)` 结束完整文本;
117
- `append`/`finish` 的默认流策略仍隐藏末尾疑似秘密前缀。作用域所有权、终端渲染和异常字段选择由调用方负责。
118
-
119
- EOF 结束接收流,日志或诊断目标替换先冲刷旧流,显式关闭结束所有方向;尾部疑似秘密前缀保守隐藏。GC 只清理所属资源,不执行用户回调或过滤尾部输出。调用方需显式关闭以交付尾部;借用日志和诊断目标不关闭,库打开的日志文件仍由 SessionResources 管理。
120
-
121
- 日志路径以覆盖模式重新打开前先结束旧流,冲刷失败不能提前截断目标文件。尾部回调重入并轮换日志时,外层重新读取当前目标及所有权;诊断冲刷对方向取快照并排空回调产生的尾部,交接前的发送和接收仍归旧目标。匹配诊断中的嵌套等待可消费缓冲,但不能替换外层已记录的 Result 或正式模式回调所见结果。
122
-
123
- 过滤不修改匹配缓冲、Result、stdout 或 listeners,不推断编码、转义等变换后的秘密,也不能追溯删除已交付日志。同步目标须及时返回;自定义回调抛出的非 IO 异常原样传播。
124
-
125
- ## 生命周期与错误边界
126
-
127
- 会话 IO、输入 EOF 与直属子进程状态是正交维度。软关闭后 `closed?` 与 `alive?` 同时为真是合法结果;借用 IO、外部关闭和外部 `waitpid` 也不能被单个线性枚举准确替代。外部已回收的 PID 必须清除,无法取得的退出状态保持未知。
128
-
129
- 参数问题使用 `ArgumentError`;匹配等待的 EOF 和超时保留为 Result 事件,底层 IO 异常保留原对象;`SpawnError` 表示启动失败,`WriteTimeout` 继承 `IOError` 并携带写入进度。不得仅为统一命名而包装所有错误、丢失原异常或混淆等待事件与失败。
130
-
131
- ## 新增回归入口
132
-
133
- | 不变量 | 测试 |
134
- | --- | --- |
135
- | stty 主错误优先、辅助进程回收、EINTR 预算、ECHILD/ESRCH、detach 和主会话隔离 | terminal_cleanup_test |
136
- | 裁剪计数、正常消费、转接交接与日志错误 | buffer_accounting_test |
137
- | 外部回收、借用 IO、关闭失败重试、原生错误分类 | lifecycle_contract_test |
138
- | 绝对期限、接收和回调重置、过期文本、EOF、EINTR、零轮询 | deadline_test |
139
- | Logger/IO/回调、分片与重叠秘密、二进制、流尾部、关闭失败、覆盖与重入 | diagnostics_test |
140
- | 公共过滤器独立加载、完整文本与分块、空规则、规则更新、输入复制、安全摘要及固定种子字节区间差分 | redactor_test |
141
- | 字面跨读取命中、声明优先级、缓存失效、操作序列与全量扫描对照 | literal_scan_test |
142
-
143
- ## 既有回归入口
144
-
145
- | 不变量 | 测试 |
146
- |----------------------------------------------------------|-------------------------------------------------------------------|
147
- | 部分关闭失败、错误保留、借用 IO、非 owner、软硬关闭和 GC | cleanup_test、process_test、edge_case_test |
148
- | 字节偏移、二进制和 UTF-8 分片、零长度、可选捕获 | pattern_offset_test、matching_test、ruby_api_test、edge_case_test |
149
- | 一轮快照、同 IO 首来源、回调换规则、嵌套匹配 | scan_reuse_test、multi_session_test、interconnect_test |
150
- | 短写、转义拆包、超时恢复、不重放、回调前缀顺序 | relay_recovery_test、interconnect_test |
151
- | 无限/零/有限超时、继续重置/保留、EINTR、接收重置 | timeout_test(控制输入到达时间)、edge_case_test |
152
- | 总期限和目标写期限、连续中断不延长写超时 | relay_recovery_test |
153
-
154
- 保留独立的 Matcher、Relay、write、wait 期限合同;仅为去重而统一事件循环会扩大上述状态交接的影响范围。
data/docs/PERFORMANCE.md DELETED
@@ -1,165 +0,0 @@
1
- # 性能基准
2
-
3
- 从源码目录安装开发依赖后运行,基准本身仅使用标准库:
4
-
5
- ```sh
6
- bundle exec ruby benchmark/matching.rb
7
- bundle exec ruby benchmark/relay.rb
8
- bundle exec ruby benchmark/send_slow.rb
9
- bundle exec ruby benchmark/scaling.rb
10
- bundle exec ruby benchmark/redactor.rb
11
- ```
12
-
13
- 默认预热一次、采样五次。结果分别写入 `tmp/benchmark/matching.json`、`relay.json`、`send_slow.json`、`scaling.json`、`redactor.json`
14
- ,该目录不提交。每个场景先核对非空输入的预期结果,再计时;每轮计时后再次核对最后一次执行的结果。输出包括
15
- Ruby、平台、源码提交、工作区是否修改、库源码 SHA-256、输入规模、迭代次数、处理字节数、墙钟耗时、分配对象数和 GC 次数。
16
-
17
- ## 工作负载与边界
18
-
19
- | 脚本 | 范围 |
20
- |-----------|----------------------------------------------------------------------------------------------------------------------------------------------|
21
- | matching | 4 KiB、64 KiB、1 MiB;1、8、32 个正则;首个命中、末个命中、全未命中;UTF-8 前缀和可选捕获;1、8、32 个会话、多组重复来源和同时就绪的真实管道 |
22
- | relay | 无转义、字面转义、16 个正则转义;正常目标与每次只接受 17 字节的目标共同接收相同数据 |
23
- | send_slow | 真实本地 socket;无回显、持续回显;零延迟和每字符 1ms 延迟 |
24
- | redactor | 空规则、无命中/稀疏/连续/高重叠;包含关系、多秘密、二进制、原文替换标记、1/7/4096 字节分块、partial finish、重复 finish 及 pending 中更新规则 |
25
-
26
- `matching` 的扫描用内部 `find_match` 单独度量缓冲扫描,排除 PTY 启动和回调开销;就绪场景包含管道写入、选择和读取。`relay`
27
- 的前三项单独度量转义扫描,混合目标项运行完整转接循环。短写模拟目标吞吐受限,不等同于真实慢网络。`send_slow` 包含
28
- socket、接收线程和完整回显校验的成本;无回显时计时止于接收方收齐数据。
29
-
30
- 处理字节数表示每轮提供给场景的输入字节数(混合目标为两份交付量),不是正则引擎实际访问内存的次数。分配量是整个 Ruby
31
- 进程的计数,socket 场景包含接收线程的分配。基准不提供 CPU 或网络隔离;耗时波动不能直接归因于代码变化。
32
-
33
- `matching` 还比较同一次等待中持续追加 32 个 1 KiB 块后的字面与正则扫描;该场景每块都检查未命中,最后验证完整偏移,相关检查包含在计时内。初始窗口分别为 64 KiB 和 1 MiB,均使用 32 个模式。
34
-
35
- `scaling` 默认使用 1、16、64 个真实管道来源,等待并消费所有同时就绪的标记;另一场景把目标管道填满且不消费,验证其他来源仍可触发退出转义。`--sessions N` 可单独选择容量,运行前检查进程描述符上限。`--smoke` 仅使用 1、8 个来源。
36
-
37
- 每个样本记录计时区间前后的 RSS 和打开描述符数,以及进程的描述符上限;采样自身不进入计时或分配计数。RSS 是端点快照,受 Ruby 堆和分配器保留影响,不代表峰值或存活对象大小。平台不支持某项采样时记录 `null`。描述符快照也不是长期泄漏证明,应同时检查重复样本和关闭流程。
38
-
39
- `redactor` 每次操作创建独立过滤器,分块数据在计时前准备,计时包含过滤器构造、append 和两次 finish。
40
- 主要输入规模为 64 KiB,高重叠秘密长度为 1/32/1024/4096 字节;smoke 缩小为 512 字节及 1/32/128 字节秘密。
41
- 公共 Runner 会加载完整 Expect(含 PTY);基准本身不启动 PTY 会话,独立 `require "expect/redactor"` 的无 PTY 契约由测试验证。
42
-
43
- ## 同环境对照
44
-
45
- 对照目录必须包含已知提交的完整源码。两个版本使用同一套基准脚本、同一 Ruby 和依赖,在机器空闲时交替运行,比较原始样本的中位数与分配量:
46
-
47
- ```sh
48
- bundle exec ruby benchmark/matching.rb --library /path/to/baseline/lib --samples 5 --output tmp/benchmark/before.json
49
- bundle exec ruby benchmark/matching.rb --samples 5 --output tmp/benchmark/after.json
50
- ```
51
-
52
- 其他脚本支持相同公共参数。`--iterations N` 可增加单个样本工作量;比较双方必须使用相同参数。源码 SHA-256
53
- 用于区分同一提交上的未提交修改。修改工作负载后,应对两个版本重新取样。
54
-
55
- 没有 Git 的源码归档或安装目录仍可运行;提交和工作区状态记为 `null`,保留源码 SHA-256。
56
-
57
- ## 正则输入复用验证(2026-09-27)
58
-
59
- `Pattern#locate` 只在正则要求的编码与输入编码不同时复制输入;其他正则复用只读缓冲。Ruby 字符串可能共享底层存储,因此不能把减少一次
60
- `dup` 直接解释为少复制整个缓冲的字节数;这里报告实际对象分配和墙钟样本。
61
-
62
- macOS arm64、Ruby 4.0.6、Bundler 4.0.17,使用同一套 `benchmark/matching.rb` 和依赖,对修改前后的 `lib` 分别运行
63
- `--samples 5 --iterations 100`。下表为五次样本的中位数,每项均校验匹配结果。此前用 30 次迭代进行的首轮比较方向一致。
64
-
65
- | 场景 | 修改前耗时 | 修改后耗时 | 修改前分配 | 修改后分配 |
66
- |--------------------------------|------------|------------|------------|------------|
67
- | 4 KiB、32 个正则、全未命中 | 1.985 ms | 1.415 ms | 6,501 | 3,301 |
68
- | 64 KiB、32 个正则、全未命中 | 17.068 ms | 11.070 ms | 6,501 | 3,301 |
69
- | 1 MiB、32 个正则、全未命中 | 257.474 ms | 165.734 ms | 6,501 | 3,301 |
70
- | 1 MiB、32 个正则、最后命中 | 265.848 ms | 172.001 ms | 7,301 | 4,101 |
71
- | 1 MiB UTF-8 前缀、固定编码正则 | 78.348 ms | 74.721 ms | 1,201 | 1,201 |
72
-
73
- 固定 UTF-8 正则仍需要编码副本,分配量不变;约 4.6% 的耗时差不能视为稳定收益。单正则大缓冲场景也基本不变。上述结果适用于本次机器与工作负载,不设为
74
- CI 性能门槛。
75
-
76
- ```sh
77
- bundle exec ruby benchmark/matching.rb --smoke
78
- bundle exec ruby benchmark/relay.rb --smoke
79
- bundle exec ruby benchmark/send_slow.rb --smoke
80
- bundle exec ruby benchmark/scaling.rb --smoke
81
- bundle exec ruby benchmark/redactor.rb --smoke
82
- ```
83
-
84
- `script/ci` 运行这些小规模正确性检查,不设置墙钟性能阈值。热点优化必须有实际收益证据;减少对象分配不代表所有输入都会变快,也不能替代完整测试和安装验证。
85
-
86
- ## 持续字面扫描验证(2026-09-27)
87
-
88
- 基线为 `b5e9159` 的源码归档,使用本轮同一套基准脚本对照工作树。macOS arm64、Ruby 4.0.6、Bundler 4.0.17,第二轮为 `--samples 5 --iterations 5`;下表为每个样本五次操作的中位数。首轮三次采样方向一致,正则仍保持完整窗口。
89
-
90
- | 场景 | 基线耗时 | 工作树耗时 | 基线分配 | 工作树分配 |
91
- | --- | --- | --- | --- | --- |
92
- | 64 KiB 起始窗口、32 字面模式、32 次追加 | 80.422 ms | 6.579 ms | 2,051 | 2,221 |
93
- | 1 MiB 起始窗口、32 字面模式、32 次追加 | 977.942 ms | 44.526 ms | 2,051 | 2,221 |
94
- | 1 MiB 起始窗口、32 正则、32 次追加 | 301.139 ms | 301.379 ms | 7,346 | 7,346 |
95
- | 1 MiB、32 正则、静态全未命中 | 8.314 ms | 8.289 ms | 166 | 166 |
96
- | 32 来源、4 KiB、8 个重复来源组 | 0.724 ms | 0.884 ms | 1,446 | 1,446 |
97
-
98
- 大窗口字面持续扫描耗时减少约 95.4%,代价是每次 Matcher 的小量未命中缓存分配;小窗口多分组场景仍有期限检查开销,不能声称所有场景变快。样本内描述符数保持不变。RSS 端点受全进程之前运行的场景影响,不据此声称内存峰值下降。
99
-
100
- 原始记录位于本地忽略目录 `tmp/core-improvements/matching-before-final.json` 与 `matching-after-final.json`,源码摘要分别为 `ab9e8e8b8fd1f237986963b35ec3e823a16ab107fa9d6eaf8f01bc9487ce357b` 和 `985b6bf1d319f90db1fb5692a2736aa69cd81ddec78d88d552e7152e11ceaf5f`。归档无独立 Git 元数据时只记录源码摘要,不借用上层仓库提交。
101
-
102
- 容量场景可单独复现:
103
-
104
- ```sh
105
- bundle exec ruby benchmark/scaling.rb --samples 3 --iterations 10
106
- (ulimit -n 4096; bundle exec ruby benchmark/scaling.rb --sessions 1000 --samples 3 --iterations 3)
107
- ```
108
-
109
- 1,000 来源需要 2,000 个管道端点,超过本机默认 256 的软上限;只提高本次基准子进程上限。此场景不包含 PTY 子进程、网络、持续负载或尾延迟,不能作为 1,000 个真实设备并发承诺,也不足以据此引入新的 Reactor 后端。
110
-
111
- 同一工作树的三次容量样本中,1、16、64 来源各执行十轮的耗时中位数分别为 0.179、1.372、8.285 ms;1,000 来源执行三轮为 401.878 ms。1,000 来源样本的描述符数均为 2,007 → 2,007,RSS 端点从约 35.8 MB 增至 37.9 MB,不能解释为稳定内存上限。阻塞目标场景十轮共 0.186 ms,其他来源仍可推进,描述符数均为 13 → 13。
112
-
113
- 原始记录为 `tmp/core-improvements/scaling-final.json` 和 `scaling-1000-final.json`,库源码摘要与上述工作树匹配记录一致。当前证据支持保留既有 select 调度,先量化真实负载,再决定是否需要替换后端。
114
-
115
- ## Redactor 重叠区间合并验证(2026-09-27)
116
-
117
- 基线为 `v0.5.0` / `b7cd25e736acbfcb43e412ec4d233f9b2602cb03` 的源码归档。使用同一份
118
- `benchmark/redactor.rb`、macOS arm64、Ruby 4.0.6、Bundler 4.0.17 和相同依赖,按“基线 → 候选”交替运行两轮,
119
- 每轮 `--samples 5 --iterations 20`。下表是最后一轮五个样本的中位数,耗时和分配均为 **20 次操作总量**。
120
- 每项输出在预热及各样本结束后校验,性能采样期间没有并行运行项目测试。
121
-
122
- | 工作负载 | 基线 ms | 候选 ms | 基线分配对象 | 候选分配对象 |
123
- | --- | ---: | ---: | ---: | ---: |
124
- | 空规则 | 0.438 | 0.416 | 541 | 541 |
125
- | 单秘密无命中 | 0.774 | 0.973 | 561 | 561 |
126
- | 多秘密无命中 | 2.305 | 1.447 | 641 | 641 |
127
- | 稀疏命中 | 1.145 | 1.175 | 581 | 581 |
128
- | 连续短秘密 | 26.159 | 9.345 | 262,661 | 541 |
129
- | 重叠 / 1 字节秘密 | 113.787 | 42.799 | 1,311,241 | 541 |
130
- | 重叠 / 32 字节秘密 | 189.706 | 97.756 | 1,310,661 | 581 |
131
- | 重叠 / 1024 字节秘密 | 642.376 | 429.316 | 1,290,821 | 581 |
132
- | 重叠 / 4096 字节秘密 | 1698.130 | 1361.663 | 1,229,381 | 581 |
133
- | 包含关系 | 2.881 | 2.915 | 26,201 | 26,201 |
134
- | 二进制跨块重叠 | 7.967 | 8.299 | 101,221 | 101,221 |
135
- | 原文包含替换标记 | 2.758 | 2.837 | 21,081 | 21,081 |
136
- | 1 字节分块 | 34.612 | 34.151 | 589,161 | 589,161 |
137
- | 7 字节分块 | 6.746 | 6.716 | 93,361 | 93,361 |
138
- | 4096 字节分块 | 1.434 | 1.456 | 10,781 | 10,781 |
139
- | partial finish | 0.128 | 0.129 | 2,141 | 2,141 |
140
- | exact finish | 0.117 | 0.113 | 2,021 | 2,021 |
141
- | pending 中更新规则 | 0.075 | 0.076 | 961 | 961 |
142
-
143
- 两轮中连续短秘密耗时减少 63.6%–64.3%;重叠秘密按长度分别减少 62.4%–62.8%、48.5%–48.9%、
144
- 33.1%–33.2%、19.7%–19.8%。重叠查找仍逐字节进行,只减少重复掩码分配/覆盖,没有跳过重叠命中或改变脱敏范围。
145
-
146
- 短样本中的无命中结果波动明显,因此又通过本地 `tmp/guide-review/redactor-common.rb` 筛选同一驱动的常见负载,
147
- 每轮改为 `--samples 5 --iterations 500`,再交替运行两轮。该包装器只跳过上述五项密集命中,不改变任何输入、过滤或校验逻辑。
148
- 第二轮中单秘密无命中为 23.883 → 24.009 ms,多秘密为 56.148 → 56.081 ms,1 字节分块为 873.397 → 870.462 ms。
149
- 存在小幅代价:稀疏命中两轮慢 2.3%/4.5%,4096 字节分块慢 2.2%/2.5%,规则更新慢 1.0%/6.0%;
150
- 后者第二轮 500 次操作为 1.562 → 1.655 ms。其余常见负载约在 -2.8% 至 +3.6% 之间,分配量不变。
151
- 保留此优化的取舍是以少量区间记录计算换取密集/重叠场景的显著分配下降,不声称所有负载都更快,也不设置 CI 墙钟阈值。
152
-
153
- 复现全负载:
154
-
155
- ```sh
156
- bundle exec ruby benchmark/redactor.rb --library /path/to/v0.5.0/lib --samples 5 --iterations 20 --output tmp/benchmark/redactor-before.json
157
- bundle exec ruby benchmark/redactor.rb --samples 5 --iterations 20 --output tmp/benchmark/redactor-after.json
158
- ```
159
-
160
- 原始样本:`tmp/guide-review/redactor-before-{4,5}.json`、`redactor-after-{4,5}.json`;
161
- 延长采样:`redactor-common-before-{1,2}.json`、`redactor-common-after-{1,2}.json`。均在忽略目录,不随 Gem 分发。
162
- 基线库 SHA-256 为 `d2a55eb1aa1d00658195f21f32adf37cf3ab8a24bb6a5bcf9564f6e9a6047b28`,
163
- 性能采样时的候选库(版本号仍为 `0.5.0`)为 `753d403cf4801900cb53be5c34b04da0ffa313f25b4d2fb23a6d25ffce36e75a`,
164
- 驱动文件为 `f99f6c8f1ec21d4d97c984dbfd51fd3e17b64fa7968095323e16d68d4a0d4cc0`。
165
- 这些是合成字节过滤负载,不代表完整设备会话吞吐;RSS 端点不用于声称峰值内存降低。
data/docs/RELEASING.md DELETED
@@ -1,84 +0,0 @@
1
- # 发布版本
2
-
3
- 项目名为 `expect-ruby`,RubyGems 名为 `expect-pty`。使用 `script/release.rb --rubygems-only` 可仅发布 RubyGems;不加该选项时还会创建
4
- GitHub Release,附带同一个 Gem 和 `SHA256SUMS`。生成文件统一放在已被 Git 忽略的 `tmp/` 下。
5
-
6
- ## 准备版本
7
-
8
- 1. 更新 `lib/expect/version.rb` 的 `Expect::VERSION`,例如 `0.3.3`。
9
- 2. 把 `CHANGELOG.md` 的 `Unreleased` 内容移到对应版本标题下,例如 `## 0.3.3 - 2026-09-27`;可以保留空的 `Unreleased` 标题。
10
- 3. 提交源码,发布时工作区必须干净。若同时发布 GitHub Release,还需推送到 `main`,远端 `main` 必须包含该提交,已有同名标签必须指向该提交。
11
-
12
- 发布脚本只接受正式版 `X.Y.Z`;未归档的变更会阻止发布。
13
-
14
- ## 仅发布 RubyGems
15
-
16
- 本地已登录 `gem` 时,脚本直接使用已有凭据。如果 RubyGems 要求一次性验证码,`gem push` 会提示输入。此模式不调用 `gh`,不要求推送到
17
- GitHub,也不创建标签或 GitHub Release。
18
-
19
- ```sh
20
- bundle install
21
- ruby script/release.rb --rubygems-only --dry-run
22
- ruby script/release.rb --rubygems-only
23
- ```
24
-
25
- 默认执行 `script/ci` 的检查、完整测试、构建和隔离安装验证,构建包保存在 `tmp/ci/expect-pty-版本号.gem`。随后复制到
26
- `tmp/release/版本号/candidate-*/` 的独占目录,核对包内文件并生成发布说明和校验文件。该副本贯穿后续发布,目录会保留供失败重试。正式发布前再次确认源码未变,上传
27
- RubyGems 后下载远端包核对 SHA256。
28
-
29
- 正式发布逐个核对包内普通文件在目标 Git 提交中的存在性、原始字节和执行权限,同时核对 gemspec 的安装元数据及主页。
30
- `git status` 为空不能替代这一检查:忽略规则可能隐藏被 glob 收入包的本地文件,`assume-unchanged` 和 `core.filemode`
31
- 也可能隐藏差异。候选包必须与已提交源码一致。
32
-
33
- `--dry-run` 只做本地验证,可在提交前使用。它仍要求版本号和发布说明完整。
34
-
35
- ## 同时发布 GitHub Release
36
-
37
- 此模式还会复用 `gh auth login` 的登录状态:
38
-
39
- ```sh
40
- ruby script/release.rb --dry-run
41
- ruby script/release.rb
42
- ```
43
-
44
- 正式发布先创建 GitHub Release,再上传 RubyGems,最后下载 RubyGems 上的包核对 SHA256。GitHub 上还没有标签时,会为当前提交创建
45
- `v版本号` 标签。
46
-
47
- ## GitHub Actions 发布
48
-
49
- GitHub Runner 不会继承本机的 Gem 登录状态。要在 Actions 发布 RubyGems,需在仓库的 Settings → Secrets and variables →
50
- Actions 中配置 `RUBYGEMS_API_KEY`,使用具有 `Push rubygem` 权限的发布 Key。
51
-
52
- ```sh
53
- git tag -a v0.3.3 -m 'Release v0.3.3'
54
- git push origin v0.3.3
55
- gh workflow run release.yml --ref v0.3.3 --repo gatework/expect-ruby
56
- ```
57
-
58
- 也可以在 Actions → Release → Run workflow 选择对应版本标签。工作流仅支持手动触发,避免本地发布时出现第二次并发上传。
59
-
60
- 发布作业先验证标签与版本号一致,再复用 CI 的 Linux/macOS、Ruby 3.2/3.3/3.4/4.0 共 8 个环境。全部通过后,下载 Ubuntu / Ruby
61
- 4.0 作业验证过的 Gem,交给同一个发布脚本;发布阶段不重新构建。
62
-
63
- 未配置 `RUBYGEMS_API_KEY` 时,GitHub Release 仍会创建,RubyGems 步骤会明确失败;此时可以下载 Release 中的原包,在本地使用已有登录状态完成上传。
64
-
65
- ## 失败后继续
66
-
67
- 保留脚本输出的 `Artifact` 路径,用该候选 Gem 重试发布;更换 RubyGems 工具版本或重新构建可能得到不同字节,同一个版本不得覆盖已有内容。也可以直接指定从
68
- CI 或 Release 下载的原包:
69
-
70
- ```sh
71
- ruby script/release.rb --rubygems-only --artifact tmp/ci/expect-pty-0.3.3.gem
72
- ```
73
-
74
- 将示例路径替换为实际输出的 `Artifact` 路径。`--artifact` 会跳过构建和测试,但仍核对包与当前源码是否一致;需要同时恢复
75
- GitHub Release 时去掉 `--rubygems-only`。
76
-
77
- 工作流失败时优先使用 Re-run failed jobs,继续使用本次 CI 保存的产物。需要在本地恢复时,检出发布标签对应的干净源码,下载该
78
- Release 的 Gem,再通过 `--artifact` 指定它。
79
-
80
- 脚本会校验现有 RubyGems 版本和 GitHub Release 附件的 SHA256;一致时复用,不一致时中止。已有 GitHub Release
81
- 缺少附件时会补传,已有版本和附件不会被覆盖。
82
-
83
- 上传中断若留下 `starter` 状态的空附件,脚本会明确指出附件名称。先确认没有其他发布或上传在运行,再在 GitHub Release
84
- 中删除该失败附件,使用原包重试;脚本不会自动删除可能仍在上传的附件。