You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GitHub Action中运行Go伪终端测试失败:read /dev/ptmx输入输出错误

问题:GitHub Action中Go测试使用pty库出现read /dev/ptmx: input/output error错误

我在GitHub Action流水线中运行如下Go测试用例,该测试使用github.com/creack/pty库开启伪终端:

func TestMysqlConnect(t *testing.T) {
    ctx, cancel := context.WithTimeout(context.Background(), connectTimeout)
    defer cancel()

    cmd := exec.CommandContext(ctx, "xxx", "connect", ...)

    t.Logf("About to run connect command %q", cmd.String())

    ptmx, err := pty.Start(cmd)
    require.NoErrorf(t, err, "Failed to start pseudoterminal")
    defer func() { _ = ptmx.Close() }()

    go func() {
        ptmx.Write([]byte("SELECT 1;\n"))
        ptmx.Write([]byte{4})
    }()

    var buff bytes.Buffer
    _, err = io.Copy(&buff, ptmx)
    require.NoError(t, err)

    require.Contains(t, buff.String(), "1 row in set")
}

其中xxx是一个CLI命令,本质是通过SSH连接到目标机器并打开MySQL CLI。该测试在本地Apple M2环境可正常运行,但在使用Ubuntu 22.04镜像的GitHub Action中执行时,io.Copy操作抛出错误:read /dev/ptmx: input/output error。

我尝试提取错误信息,发现其类型为fs.PathError,仅包含OP=read、Path=/dev/ptmx、Err=input/output字段,无法获取更多调试信息。此外我已尝试使用shell: 'script -q -e -c "bash {0}"'执行测试,但仍出现相同错误:

- name: run integration test
  shell: 'script -q -e -c "bash {0}"'
  run: go test -run Test ./services/some-service -tags=integration

补充说明:

  • 无法控制xxx命令的内部逻辑,无法修改其中的SSH命令添加-t参数;
  • /dev/ptmx文件权限为Dcrw-rw-rw-,看起来是正确的。

请问该问题的原因是什么?或者如何获取更多错误细节来调试?


可能的原因及调试方案

可能原因

  1. GitHub Action环境PTY限制:GitHub Action的Ubuntu runner是容器化环境,对伪终端的支持可能存在限制,或者默认终端配置(如TERM环境变量)与本地M2环境不一致。当xxx命令通过SSH连接时,即使外部开启了PTY,SSH会话内部可能因为缺少终端交互所需的环境变量,导致MySQL CLI或SSH进程异常退出,进而引发/dev/ptmx的IO错误。
  2. 进程退出时机冲突:测试中goroutine写入SQL命令和EOF(\x04)后,xxx命令或MySQL CLI可能快速退出,此时io.Copy尝试从已关闭的PTMX设备读取,就会触发IO错误。本地环境因进程调度、终端处理速度差异,未触发该问题。
  3. PTY设备生命周期异常:容器化环境中PTY设备的创建、销毁机制与本地不同,当子进程xxx意外终止时,PTMX设备状态未被正确清理,导致后续读取操作报错。

调试方案

1. 监控子进程状态

在测试中添加对cmd进程的状态监控,明确进程退出原因和退出码:

go func() {
    err := cmd.Wait()
    if err != nil {
        t.Logf("Command exited with error: %v, exit code: %d", err, cmd.ProcessState.ExitCode())
    } else {
        t.Logf("Command exited successfully, exit code: %d", cmd.ProcessState.ExitCode())
    }
}()

2. 捕获完整输出(含stderr)

默认exec.Command的stderr未重定向到PTY,可能错过xxx命令或SSH的错误日志,可将stderr合并到stdout:

cmd.Stderr = cmd.Stdout

这样buff中会包含所有输出,便于定位xxx内部的问题。

3. 模拟本地环境变量

本地与GitHub Action的环境变量(如TERM)可能存在差异,测试时手动设置终端类型:

cmd.Env = append(os.Environ(), "TERM=xterm")

4. 调整IO错误处理逻辑

如果是进程正常退出导致的读取错误,可针对性忽略,同时保留输出检查:

_, err = io.Copy(&buff, ptmx)
// 忽略进程退出引发的IO错误,仅检查关键输出
if err != nil {
    t.Logf("IO copy error: %v", err)
}
require.Contains(t, buff.String(), "1 row in set")

5. 检查runner的PTY基础支持

在流水线中添加步骤,验证runner环境的PTY功能:

- name: Check PTY support
  run: |
    echo "TERM: $TERM"
    ls -l /dev/ptmx
    script -q -c "echo 'PTY test passed'" /dev/null

内容的提问来源于stack exchange,提问作者Avishay28

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 06:22:41