应返回 syscall.ENOSPC(Linux/macOS)或 syscall.ERROR_DISK_FULL(Windows),而非 errors.New;io.Copy 需处理短写(n < len(p) 且 err == nil)并检查 io.ErrShortWrite。

用 io.ErrShortWrite 和自定义 Writer 模拟磁盘满
Go 标准库不提供“让磁盘突然变满”的系统级开关,所以得从 I/O 接口层伪造错误。核心思路是:实现一个 io.Writer,在写到指定字节数后返回 io.ErrNoSpace(或更通用的 syscall.ENOSPC),而不是靠真实挂载点配额。这样测试能稳定复现,且不依赖宿主机状态。
常见错误是直接 panic 或返回 errors.New("no space") —— 这会导致调用方无法识别为磁盘空间问题,比如 os.WriteFile 内部只检查 syscall.ENOSPC 或 io.ErrNoSpace 才触发重试/清理逻辑。
- 必须返回
syscall.ENOSPC(Linux/macOS)或syscall.ERROR_DISK_FULL(Windows),不能只用字符串匹配 - 若被测代码用
io.Copy,需注意它对短写(err == nil但n )的处理逻辑,此时应优先触发错误而非短写 - 测试中避免用
os.CreateTemp创建真实文件再填满——速度慢、不可控、清理麻烦
替换 os.Stdout / os.Stderr 时小心缓冲和关闭行为
很多 CLI 工具把日志或输出直写 os.Stdout,想测磁盘满就临时替换成 mock writer。但 os.Stdout 是带缓冲的 *os.File,而 mock Writer 若没实现 io.Closer 或忽略 Close() 调用,会导致被测代码 panic 或死锁。
典型场景:工具调用 log.SetOutput(os.Stdout) 后执行写操作,然后调用 os.Stdout.Close() —— 如果 mock writer 的 Close() 方法没返回 nil 或错误,就会中断流程。
立即学习“go语言免费学习笔记(深入)”;
- mock
Writer应同时实现io.Writer和io.Closer,Close()返回nil(除非你真要模拟 close 失败) - 若被测代码用了
bufio.Writer包裹os.Stdout,记得在测试前调用Flush(),否则错误可能卡在缓冲区里不抛出 - 不要用
os.Stdout = myMock直接赋值——这会破坏并发安全;改用依赖注入,比如函数接收w io.Writer参数
测试 os.WriteFile 类函数时绕过 syscall 层更可靠
os.WriteFile 底层调用 syscall.Write,而 syscall 错误码由内核返回,没法在用户态伪造。强行 mount tmpfs 并塞满再测,既慢又受权限限制。更轻量的做法是:用 io/fs 接口抽象文件系统,把 os.WriteFile 替换为接受 fs.FS 和 fs.ReadFile 的版本,然后传入一个返回 syscall.ENOSPC 的 memfs 实现。
容易踩的坑是误以为只要 patch os.WriteFile 函数就能生效——它被内联或链接进二进制后很难 monkey patch,尤其在非测试构建中。
- 优先重构被测代码,把 “写文件” 抽成可注入的接口,例如
func writeFile(fs fs.FS, name string, data []byte, perm fs.FileMode) error - 用
github.com/spf13/afero这类内存文件系统库,调用afero.WriteFile前设置ErrWrite字段为syscall.ENOSPC - 如果必须测原生
os.WriteFile,可用gomonkey在测试中打桩,但仅限于未内联的函数调用,且需禁用-gcflags="-l"防止优化掉符号
真实 ENOSPC 和 mock 错误的兼容性差异
有些 Go 程序会区分“磁盘满”和“权限拒绝”,比如用 errors.Is(err, syscall.ENOSPC) 判断后执行清理逻辑,而另一些则用 strings.Contains(err.Error(), "no space")。mock 时若只返回 io.ErrNoSpace,前者能过,后者会失败。
这不是 bug,而是设计选择:标准库中 io.ErrNoSpace 是一个预定义变量,类型是 *os.PathError,其 Err 字段才是 syscall.ENOSPC。直接返回 io.ErrNoSpace 不等于返回底层 syscall 错误。
- 最兼容的做法是构造一个
*os.PathError:&os.PathError{Op: "write", Path: "/tmp/test", Err: syscall.ENOSPC} - Windows 下必须用
syscall.ERROR_DISK_FULL,不是ENOSPC;跨平台测试建议用errors.Is(err, syscall.ENOSPC) || errors.Is(err, syscall.ERROR_DISK_FULL) - 别在 mock 中硬编码路径名(如
"/dev/full"),这会让错误栈看起来像真实设备错误,干扰调试
真正难的是让错误发生在正确的位置——比如写第 4096 字节时失败,还是 flush 时才报错。这取决于你 mock 的是 Write() 还是 Close(),得按被测代码的实际调用链来定。










