docs/Terminal/05-Testing-And-Troubleshooting.md
状态日期:2026-05-27
dotnet test tests/Everywhere.Core.Tests/Everywhere.Core.Tests.csproj --no-restore --filter "FullyQualifiedName~Everywhere.Core.Tests.Terminal"
当前基线:
通过: 145
跳过: 4
失败: 0
总计: 149
dotnet build tests/Everywhere.Terminal.TestApp/Everywhere.Terminal.TestApp.csproj --no-restore
当前基线:
0 warnings
0 errors
dotnet run --no-restore --project tests/Everywhere.Terminal.TestApp/Everywhere.Terminal.TestApp.csproj -- --shell zsh
dotnet run --no-restore --project tests/Everywhere.Terminal.TestApp/Everywhere.Terminal.TestApp.csproj -- --shell bash
dotnet run --no-restore --project tests/Everywhere.Terminal.TestApp/Everywhere.Terminal.TestApp.csproj -- --shell pwsh
输入多行命令时,TestApp 支持 JSON string 形式:
"echo \"A\"\necho \"B\""
dotnet run --no-restore --project tests/Everywhere.Terminal.TestApp/Everywhere.Terminal.TestApp.csproj -- --observe --shell zsh --duration 2500
观察输出包括:
文件:
tests/Everywhere.Core.Tests/Terminal/TerminalParserTests.cs
覆盖:
文件:
tests/Everywhere.Core.Tests/Terminal/TerminalLineBufferTests.cs
覆盖:
CopyLines。文件:
tests/Everywhere.Core.Tests/Terminal/PtyExecutionTests.cs
包含两类:
| 类型 | 用途 |
|---|---|
| simulated PTY | 精确构造 marker 顺序和延迟 |
| real PTY | 验证真实 shell、真实 line editor、真实脚本 |
重要 real PTY 回归:
| 测试 | 覆盖 |
|---|---|
RichStrategy_Zsh_DisablesBangHistoryForDoubleQuotedExclamation | zsh ! 不进入 dquote> |
RichStrategy_Bash_DisablesHistoryAndHistoryExpansion | bash history/histexpand 关闭 |
RichStrategy_CurrentPlatform_WithShellIntegration_ExecutesScenario | 当前平台 Rich 单行/多行/续行 |
重要 simulated 回归:
| 测试 | 覆盖 |
|---|---|
DetectStrategy_WaitsForCommandReadyBeforeReturningRich | Detect 等待 B |
RichStrategy_WaitsForCommandFinishedAfterSilentPeriod | Rich C 后静默必须等 D |
RichStrategy_MultiLine_MultiC_Simulated_OutputPreservesOrder | 多 C 输出顺序 |
RichStrategy_CommandLine_UsesOsc633E | CommandLine 采用 E marker |
\x0a症状:
CommandLine:
pwd\x0als -F
原因:
shell integration 为了安全发送 OSC payload,会把 LF 编码为 \x0a。parser 旧逻辑未反解。
修复:
TerminalParser.DecodeOsc633Value 支持:
| 编码 | 结果 |
|---|---|
\xNN | 对应字符 |
\\ | 反斜杠 |
| unknown escape | 原样保留 |
验证:
Feed_DecodesEscapedShellIntegrationCommandLine
dquote>症状:
echo "Process completed!"
dquote>
ExitCode: null
原因:
交互 zsh 的 history expansion 会处理双引号中的 !,导致 line editor 进入续行状态,命令没有进入 preexec,因此没有 E/C/D。
修复:
zsh 集成脚本:
unsetopt BANG_HIST。HISTFILE=/dev/null。SAVEHIST=0。zshaddhistory return 1。precmd 重复收束。验证命令:
echo "Process completed!"
预期:
Process completed!
ExitCode: 0
症状风险:
! 被 history expansion。修复:
bash 集成脚本:
set +o history
set +H
HISTFILE=/dev/null
HISTSIZE=0
HISTFILESIZE=0
history -c
验证:
printf 'HISTFILE=%s\n' "${HISTFILE-<unset>}"
printf 'HISTSIZE=%s\n' "${HISTSIZE-<unset>}"
printf 'HISTFILESIZE=%s\n' "${HISTFILESIZE-<unset>}"
set -o | grep -E '^(history|histexpand)'
history
echo "Process completed!"
预期:
HISTFILE=/dev/null
HISTSIZE=0
HISTFILESIZE=0
histexpand off
history off
Process completed!
症状:
echo "Start: $(date +%H:%M:%S)"
python3 -c "import time; time.sleep(10)"
echo "End: $(date +%H:%M:%S)"
旧输出只有:
Start: 20:13:27
原因:
Rich 旧逻辑在收到 C 后,如果 2.1 秒没有输出且没有 prompt match,就假设完成。这对静默命令是错误的。
修复:
只要 _activeRun is not null:
idle -> continue waiting for D
验证:
RichStrategy_WaitsForCommandFinishedAfterSilentPeriod
真实 TestApp 验证预期:
Start: HH:MM:SS
End: HH:MM:SS + 10s
ExitCode: 0
症状:
dearva
原因:
LF 后 buffer 内部会创建一个 live empty line。GetText 已裁剪,但 UI 旧 snapshot 可能保留这行。
修复:
TerminalLineBuffer.CopyLines 与 GetText 一样裁掉尾部空行,但不修改内部 _lines。
验证:
CopyLines_OmitsTrailingEmptyLiveLine
症状:
dearva
原因:
终端 repaint、清行、固定列输出可能留下右侧空格。
修复:
AddLine 和 ReplaceLine 统一 TrimEnd(' ')。
验证:
Write_TrailingSpaces_AreTrimmedFromStoredLines
症状:
CommandReady marker callback bracketedPaste=false
原因:
zsh 旧实现把 OSC 633;B 放在 prompt 字符串中,可能早于 zle 真正启用 bracketed paste。
修复:
zsh 在 zle-line-init 中:
emit zle_bracketed_paste[1]
emit OSC 633;B
预期顺序:
?2004h -> OSC 633;B
验证:
DetectStrategy_WaitsForCommandReadyBeforeReturningRich
看日志:
[Detect] Shell Integration command-ready detected ... using Rich strategy
[Detect] Falling back to None strategy
正常 Rich 必须看到:
E command=...
C
...
D exitCode=...
A
B
如果没有 C:
如果有 C 没有 D:
比较:
run.OutputText
和 UI CodeBlock.Inlines。
如果 OutputText 正确而 UI 错,问题在 TerminalCodeBlockBridge 或 CopyLines。
如果 OutputText 也错,问题在 parser capture 或 strategy。
看原始 output 是否包含:
\r
CSI K
CSI J
cursor movement
OSC 633
如果原始输出靠 CR 覆盖,必须验证 TerminalLineBuffer 的最终行,而不是简单 string append。
需要,但不要替代 PTY 策略测试。
适合 headless UI 的内容:
TerminalCodeBlockBridge 初始刷新。不适合 headless UI 的内容:
这些必须用 strategy tests 或 TestApp 真实验证。