FEATURED · 精选文章

Zig 测试体系实践:test/standalone/posix 中依赖进程级状态的 POSIX 独立测试

发布时间 / 2026/9/6 19:00:22
来源 / 创域科博编辑部
栏目 / 资讯中心
Zig 测试体系实践:test/standalone/posix 中依赖进程级状态的 POSIX 独立测试 Zig 测试体系实践test/standalone/posix 中依赖进程级状态的 POSIX 独立测试【免费下载链接】zigMoved to Codeberg项目地址: https://gitcode.com/GitHub_Trending/zig/zigZig 仓库将std.posix的测试拆分为两层绝大多数测试以单元测试形式放在 lib/std/posix/test.zig 中而少数依赖进程级状态当前工作目录、信号处理器、fork、主线程、环境变量等的测试则被隔离到 test/standalone/posix 目录编译为独立可执行文件运行。本文基于该目录的 README.md 展开完整解析这四个独立测试文件各自覆盖的系统调用、build.zig 构建的 libc 测试矩阵libc-less / glibc / musl以及它们如何挂接到主测试体系的test-standalone步骤上帮助读者理解 Zig 官方测试框架对进程级副作用的处理约定。目录定位为什么这些测试不能写成单元测试README.md 给出了该目录的完整定义全文共 8 行但信息密度很高This directory is just for std.posix-related test cases that depend on process-wide state like the current-working directory, signal handlers, fork, the main thread, environment variables, etc. Most tests (e.g, around file descriptors, etc) are with the unit tests inlib/std/posix/test.zig. New tests should be with the unit tests, unless there is a specific reason they cannot.这段说明确立了三条约定范围限定本目录只收std.posix相关、且依赖进程级process-wide状态的测试。所谓进程级状态是指会污染同一进程内其他测试的状态chdir修改全局工作目录、sigaction修改该信号号的处理器、fork派生子进程、读写main线程上下文、设置/查询环境变量等。这些操作无法像单元测试那样做到用完即还原、互不影响因此必须各自独占一个进程。主阵地别处围绕文件描述符fd等无进程级副作用的 POSIX 测试绝大多数放在 lib/std/posix/test.zig 的单元测试中该文件目前有 982 行。准入规则新测试默认应该写进单元测试只有当存在具体原因导致无法作为单元测试运行specific reason they cannot时才放进本目录。这是一条防止测试目录无限膨胀的纪律。目录内容四个测试文件及其进程级依赖当前 test/standalone/posix 下共有四个测试源文件分别对应 README 中列举的几类进程级状态。每个文件都是带pub fn main()的独立可执行程序用std.testing.expectEqual*断言失败即进程退出码非零——由构建系统的 Run 步骤判定成败。cwd.ziggetcwd 与 chdircwd.zig 测试std.posix.getcwd和std.posix.chdir因为 chdir 会改变整个进程的工作目录属于典型进程级状态。文件开头即体现了跨平台门控的写法if (builtin.target.os.tag .wasi) { // WASI doesnt support changing the working directory at all. return; }它包含三个子测试test_chdir_self先用[std.fs.max_path_bytes]u8栈缓冲调用std.posix.getcwd取回当前目录再chdir回该目录断言getcwd结果与之前一致test_chdir_absolute用std.fs.path.dirname从当前绝对路径推出父目录old_cwd should be absolutechdir到父目录后校验test_chdir_relative借助std.testing.tmpDir创建临时目录setAsCwd到其父目录后用相对目录名执行chdir再用std.fs.path.resolve把base_cwd与相对目录名拼成期望路径做比对。这里有一段值得注意的平台适配注释// On Windows, fs.path.resolve returns an uppercase drive letter, but the // drive letter returned by getcwd may be lowercase const resolved_cwd try std.fs.path.resolve(a, .{new_cwd});即 Windows 上驱动号大小写可能不一致所以把getcwd的结果也 resolve 一遍再比较。getenv.zig环境变量读取getenv.zig 测试std.posix.getenv/std.posix.getenvZ。环境变量同样是进程级状态且 Zig 的构建系统会在Run 步骤中注入测试所需的环境变量见下文构建矩阵一节这只能通过独立可执行文件配合setEnvironmentVariable完成单元测试无法做到——这正是该文件必须 standalone 的原因。文件开头的两处门控揭示了std.posix.getenv的平台限制if (builtin.target.os.tag .windows) { return; // Windows env strings are WTF-16, so not supported by Zigs std.posix.getenv() } if (builtin.target.os.tag .wasi and !builtin.link_libc) { return; // std.posix.getenv is not supported on WASI due to the need of allocation }断言分三部分未设置变量getenv()、getenv(BOGUSDOESNOTEXISTENVVAR)、getenvZ(...)三者均应为null与 C 库交叉验证当link_libc时对比std.posix.getenv(USER)与std.c.getenv(USER)保证 Zig 实现与宿主 C 库看到的是同一份环境构建注入变量校验三个由 build.zig 注入的变量包括空值与含的值try std.testing.expectEqualStrings(, std.posix.getenv(ZIG_TEST_POSIX_EMPTY) orelse invalid); try std.testing.expectEqualStrings(testvariable, std.posix.getenv(ZIG_TEST_POSIX_1EQ) orelse invalid); try std.testing.expectEqualStrings(testvariable, std.posix.getenv(ZIG_TEST_POSIX_3EQ) orelse invalid);特别地ZIG_TEST_POSIX_3EQ的值testvariable以开头并包含多个专门用于检验解析实现能否正确处理变量名/变量值边界上的切分。sigaction.zig信号处理器与信号掩码sigaction.zig 是四个测试中逻辑最重的一个覆盖sigaction/raise/kill/sigprocmask这一组进程级状态操作。它对 WASI 与 Windows 直接跳过no sigaction。test_sigaction的完整流程值得逐段理解选用 SIGURG 的原因const test_signo: std.posix.SIG .URG; // URG only because it is ignored by default in debuggers选 URG 是因为调试器默认忽略它避免调试环境下与调试器的信号处理冲突。安装与回读安装SA_SIGINFO | SA_RESETHAND标志的处理器后再次sigaction(signo, null, old_sa)回读断言处理器指针与SA_SIGINFO标志都被内核如实记录触发与 RESETHAND 验证std.posix.raise触发后断言handler_called_count 1随后回读应发现处理器已被RESETHAND重置为SIG.DFL重装并再触发去掉RESETHAND重新安装仅SA_SIGINFO再次raise计数应到 2忽略态验证将处理器设为SIG.IGN并清零 flags再次raise后计数仍为 2回读确认为SIG.IGN。test_sigset_bits则针对sigset_t的位映射正确性源码注释提到think u32/u64 mismatches on big-endian即大端架构上掩码位宽/字节序错配问题。其思路是向自己std.posix.system.getpid()kill一个已被sigprocmask(SIG_BLOCK, ...)屏蔽的信号验证信号确实被延迟、解除屏蔽后才送达处理器。这里还处理了两种合法失败路径qemu 交叉调试时将目标信号 1:1 映射到宿主信号信号数更多的目标会发送失败因此对EINVAL走只清理、不触发的分支测试结束sigaction(test_signo, old_sa, null)恢复原有处理器避免进程级状态泄漏。文件内还有一处与已知缺陷联动的门控macOS x86_64 上跳过对应 issue 15381。relpaths.zig相对路径下的 symlink 与 linkrelpaths.zig 开头的注释直接呼应了 README 的定位// Test relative paths through POSIX APIS. These tests have to change the cwd, so // they shouldnt be Zig unit tests.它先setAsCwd进入tmpDir再执行test_symlink写入目标文件后POSIX 分支用std.posix.symlink(target_name, symlink_name)创建相对符号链接Windows 分支则转换 WTF-16 调用CreateSymbolicLink并对error.AccessDenied宽容放行Windows 上符号链接需要管理员权限this test can legitimately fail最后用std.posix.readlink回读链接目标并断言一致test_link仅linux/illumos执行创建硬链接后对两个 fd 分别std.posix.fstat断言ino相同且nlink 2unlink链接后nlink回落为 1。此外有两处平台门控riscv32/LoongArch 且未链接 libc 时跳过无fstat()MIPS64 上跳过nlink断言在 LLVM 20 上原因不明地失败。构建矩阵build.zig 如何跑遍 libc 组合test/standalone/posix/build.zig 是该目录的运行引擎其核心是一张每个用例 × 每种 libc 组合的矩阵const Case struct { src_path: []const u8, set_env_vars: bool false, }; const cases [_]Case{ .{ .src_path cwd.zig }, .{ .src_path getenv.zig, .set_env_vars true }, .{ .src_path sigaction.zig }, .{ .src_path relpaths.zig }, };build函数对每个用例先生成一个默认 target、link_libc false的可执行文件若默认 target 是 Linux再追加abi .gnuglibc与abi .musl两个变体非 Linux 平台则只补一个link_libc true的变体build.zig#L33-L51。于是平台每个用例的运行次数组合Linux3原生 targetlibc-less-gnuglibc-muslmusl其他 POSIX2libc-less link libc可执行文件名按规则test-posix-{stem}[-libc][-gnu|-musl]生成例如test-posix-getenv-libc-gnu方便在失败日志中直接定位是哪个 libc 变体挂了。环境变量注入只发生在 Run 步骤而非编译步骤且只对标记了set_env_vars true的用例生效build.zig#L73-L77if (case.set_env_vars) { run_cmd.setEnvironmentVariable(ZIG_TEST_POSIX_1EQ, testvariable); run_cmd.setEnvironmentVariable(ZIG_TEST_POSIX_3EQ, testvariable); run_cmd.setEnvironmentVariable(ZIG_TEST_POSIX_EMPTY, ); }这与 getenv.zig 末尾的三条断言一一对应。build.zig中b.standardOptimizeOption(.{})使矩阵支持--optimize参数Debug/Release 模式都能跑。与主测试体系的集成test-standalone 步骤该目录并不是孤立的。主测试入口 test/tests.zig 的addStandaloneTests会创建名为test-standalone的构建步骤并通过包管理依赖挂上名为standalone_test_cases的子包其入口即 test/standalone/build.zigconst step b.step(test-standalone, Run the standalone tests); ... const test_cases_dep b.dependency(standalone_test_cases, .{ ... });test/standalone/build.zig 的build函数通过遍历b.available_deps自动发现每一个 standalone 子目录凡是缺少build.zig的用例会直接std.debug.panic(standalone test case {s} is missing a build.zig file, ...)每个依赖被包装成名为standalone_test_cases.{dep_name}的步骤挂到默认test步骤下。因此test/standalone/posix的test步骤最终会以standalone_test_cases.posix的身份汇入整棵test-standalone依赖树与 simple、child_process、windows_spawn 等其他 standalone 用例一起被 CI 执行。从源码结构看这套目录即依赖、依赖即步骤的机制意味着新增一个 standalone 测试目录时只要该目录提供带test默认步骤的build.zig即可被主测试体系自动发现无需修改 test/tests.zig。编写约定小结新测试该放哪里回到 README.md 给出的规则实际判据可以归纳为一句话你的测试是否会在同一进程内改变其他测试也依赖的全局状态若操作仅涉及文件描述符等进程内局部资源且能在defer中清理干净——写入 lib/std/posix/test.zig 的单元测试若操作涉及 cwd、信号处理器、fork、主线程或环境变量——新建一个带pub fn main()的.zig在 test/standalone/posix/build.zig 的cases数组中登记一行需要注入环境变量时置set_env_vars true并像现有四个文件一样用builtin.target做平台门控WASI 不支持 chdir、Windows 环境变量为 WTF-16、WASI 与 Windows 无 sigaction、符号链接在 Windows 上可能因权限合法失败。这套结构让 Zig 的std.posix同时拥有了高内聚的单元测试主阵地与安全隔离进程级副作用的独立测试区且 libc-less / glibc / musl 三套 libc 组合下全部经过同一矩阵验证。【免费下载链接】zigMoved to Codeberg项目地址: https://gitcode.com/GitHub_Trending/zig/zig创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻