news 2026/9/23 7:09:45

OpenZFS ZFS Test Suite 完全指南:构建、运行与自定义测试用例实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OpenZFS ZFS Test Suite 完全指南:构建、运行与自定义测试用例实战
  • 操作系统

【免费下载链接】zfs

OpenZFS on Linux and FreeBSD

项目地址:https://gitcode.com/gh_mirrors/zf/zfs
点击查看免费下载

导读

ZFS Test Suite(ZTS)是 OpenZFS 项目自带的端到端测试框架,覆盖 zfs/zpool 命令行、ARC 缓存、快照、加密、zvol、故障处理等上百个功能模块,用于在 Linux 与 FreeBSD 上系统化验证 ZFS 行为。本文以仓库内的 tests/README.md 为骨架,结合 scripts/zfs-tests.sh 与 tests/test-runner/bin/test-runner.py.in 的源码实现,完整讲解测试套件的构建安装、运行前置条件、zfs-tests.sh全部选项、runfile 与 tags 筛选机制、结果日志解读,并手把手演示如何新增一个属于自己的测试用例(zpool_example)。读完本文,你将能够在自己的 ZFS 环境中跑通整套测试、精准定位并运行单个用例、读懂测试结果,并具备为 OpenZFS 贡献新测试用例的完整能力。

一、ZFS Test Suite 总体架构

ZFS Test Suite 运行在名为test-runner的框架之上。从源码结构看,整个测试体系由三层组成:

  • test-runner 执行引擎:Python 实现的 test-runner.py,负责读取 runfile、调度测试、执行超时控制、汇总结果;
  • 启动包装脚本:Bash 实现的 scripts/zfs-tests.sh,负责环境探测、磁盘准备、约束 PATH、调用 test-runner 并汇总报告;
  • 测试用例与 runfile:位于 tests/zfs-tests/tests/functional 下按功能模块组织的数百个.ksh测试脚本,以及 tests/runfiles 下描述"跑哪些测试"的.run配置文件。

test-runner 与标准 ZFS 工具一同构建,并被打包进zfs-test包中。仓库内 tests/runfiles 目录实际提供了common.runlinux.runfreebsd.runsunos.runsanity.runlongevity.runperf-regression.runbclone.run等多套运行清单,分别面向通用功能、各操作系统专属用例、快速冒烟、长稳与性能回归等不同场景。

二、构建并安装 zfs-test 包

2.1 从源码构建

在完成 ZFS 源码树的./configure之后,构建测试套件只需一条命令:

$ ./configure $ make pkg-utils

make pkg-utils会生成zfs-test相关的安装包。产物(.rpm.deb)可按发行版选择对应的包管理命令安装:

  • 从源码安装时:
    $ rpm -ivh ./zfs-test*.rpm # 或 $ dpkg -i ./zfs-test*.deb
  • 如果你的发行版仓库已提供zfs-test包(即 ZFS 并非自源码安装),也可以直接用包管理器安装:
    $ yum install zfs-test # 或 $ apt-get install zfs-test

2.2 包内包含什么

安装后,测试套件的关键资源落在以下标准路径(对应仓库内的tests/目录):

安装路径作用对应仓库路径
/usr/share/zfs/zfs-tests.sh启动脚本scripts/zfs-tests.sh
/usr/share/zfs/test-runnertest-runner 框架tests/test-runner
/usr/share/zfs/zfs-tests测试用例套件tests/zfs-tests
/usr/share/zfs/runfilesrunfile 清单tests/runfiles

三、运行前置条件

根据 tests/README.md 与 scripts/zfs-tests.sh 的校验逻辑,运行测试前必须满足以下条件:

  1. 三块空白测试盘。通过$DISKS环境变量以空格分隔指定,例如DISKS='vdb vdc vdd'。若未指定,zfs-tests.sh默认构造三个 loopback 设备用于测试:DISKS='loop0 loop1 loop2'
    • 从源码看,脚本默认会在$FILEDIR(默认/var/tmp)下创建三个稀疏文件file-vdev0file-vdev1file-vdev2(默认大小FILESIZE=4G),再通过losetup(Linux,见 scripts/zfs-tests.sh)或mdconfig(FreeBSD)挂接为 loop 设备;若指定-f则直接使用稀疏文件本身。
    • 脚本会校验磁盘数量:非性能模式下NUM_DISKS少于 3 将直接报错Not enough disks (N/3 minimum)(见 scripts/zfs-tests.sh)。
  2. 一个非 root 用户,拥有完整基础权限,并能通过sudo(8)免密切换到 root 来运行测试。
    • 这是硬性要求:脚本开头就检查id -u,若以 root 直接运行会立即失败并提示 "This script must not be run as root.";同时校验sudo id -un必须返回 root(见 scripts/zfs-tests.sh)。
  3. 指定要保留的池。将不希望被测试触碰的池以空格分隔写入$KEEP变量。测试开始时会自动把系统上检测到的所有池加入保留列表。
    • 源码实现为:若未设置KEEP,脚本自动执行zpool list -Ho name收集全部现存池;若一个都没有则兜底为rpool,随后将其导出为内部变量__ZFS_POOL_EXCLUDE传给测试进程(见 scripts/zfs-tests.sh)。
  4. 强烈建议使用专用测试机(VM 亦可)。因为测试套件会向测试机器添加用户和用户组以验证相关功能,残留状态可能影响生产环境。
  5. FreeBSD 特有mountd(8)必须使用/etc/zfs/exports作为导出文件之一。默认情况下在/etc/rc.conf中设置zfs_enable=yes即可。

四、运行测试套件

4.1 两种运行模式

前置条件满足后,直接运行安装好的启动脚本:

$ /usr/share/zfs/zfs-tests.sh

另一种方式是从源码树直接运行,便于开发者快速验证自己的修改。此模式下,测试会使用源码树中的 ZFS 工具与内核模块(而非系统已安装版本):

$ ./scripts/zfs-tests.sh

需要注意的是:为避免某些类型的失败,源码树模式要求系统已安装 ZFS 的udev 规则(可手动安装,或确保系统上已装有某个版本的 ZFS)。从源码结构看,脚本会通过constrain_path(见 scripts/zfs-tests.sh)构建一个受限 PATH:INTREE模式下将$(top_builddir)/tests/zfs-tests/bin设为约束路径,并把$CMD_DIR下的标准 zfs 工具与tests/zfs-tests/cmd下的测试专用工具软链接进去,确保测试调用的是源码树版本而非系统版本。

4.2 zfs-tests.sh 选项全解

README 列出以下核心选项,结合 scripts/zfs-tests.sh 的getopts解析(L400-L483)与 usage 输出(L344-L398),整理成下表:

选项说明
-v详细输出。调用 test-runner 前会额外记录测试环境信息,包括所用 runfile、目标 DISKS、要保留的池等
-q静默模式。传递给 test-runner,仅在控制台输出未通过的测试与结果汇总
-x清理所有 testpool、dm、loop 与文件(不安全)。会尝试销毁任何名为testpool的池、未使用的 DM 设备以及由 file-vdev 支撑的 loopback 设备。该操作可能误删与测试无关的资源,仅限专用测试环境使用
-k测试失败后禁止清理。test-runner 退出时不做额外清理,便于保留现场分析特定测试
-f直接使用稀疏文件而非 loopback 设备。此模式下依赖真实块设备的测试会被跳过
-c仅创建并填充受限 PATH 后退出
-I NUM迭代次数(默认 1;源码校验必须大于 0)
-d DIR在 DIR 目录中为 vdev 创建稀疏文件(默认/var/tmp/),该目录必须可被所有用户写(world-writable)
-s SIZE使用 SIZE 大小的 vdev(默认4G
-r RUNFILES运行 RUNFILES 中的测试(默认common.run,linux.run,脚本按uname自动拼接)
-t PATH运行 test suite 下相对 PATH 的单个测试
-T TAGS以逗号分隔的 tags 列表(默认functional
-u USER以 USER 身份运行单个测试(默认 root)

此外,从 scripts/zfs-tests.sh 的 usage 可见 README 之外还支持以下选项,供高级调试与 CI 场景使用:

选项说明
-h显示帮助
-DDebug 模式:立即显示所有测试输出(比较嘈杂)
-K将测试名记录到/dev/kmsg
-O测试超时时,将调试信息 dump 到/dev/kmsg
-S启用 stack tracer(对性能有负面影响)
-R自动重跑失败的测试
-m启用 kmemleak 报告(仅 Linux)
-n NFSFILE使用 NFSFILE 文件确定 NFS 配置

4.3 环境变量速查

  • DISKS:空格分隔的测试盘列表;未设置时脚本自动创建 loop 设备(loop0 loop1 loop2)。
  • KEEP:空格分隔的需要保留的池;未设置时自动收集系统全部现存池。
  • RUNFILESFILEDIRFILESIZEITERATIONSTAGS等均有同名选项对应,也都可以通过环境变量覆盖(见 scripts/zfs-tests.sh 的默认值定义)。

五、用 runfile 与 tags 选择测试子集

ZFS Test Suite 允许通过runfiletags 列表两种方式指定要运行的测试子集。

5.1 runfile 格式

runfile 的格式在test-runner(1)手册中有详细说明,zfs-tests.sh使用的 runfile 可在/usr/share/zfs/runfiles下参考。仓库内的 tests/runfiles/common.run 是真实示例,其结构为 INI 风格:

[DEFAULT] pre = setup quiet = False pre_user = root user = root timeout = 600 post_user = root post = cleanup failsafe_user = root failsafe = callbacks/zfs_failsafe tags = ['functional'] [tests/functional/alloc_class] tests = ['alloc_class_001_pos', 'alloc_class_002_neg', ...] tags = ['functional', 'alloc_class']

[DEFAULT]段定义全局默认行为(setup/cleanup前后置脚本、运行用户、600 秒超时、失败兜底回调等),每个[tests/functional/...]段则为一个测试目录组,tests = [...]列出该组要执行的用例名,tags为该组打上的标签(首个标签通常是functional)。

要使用自定义 runfile,用-r指定:

$ /usr/share/zfs/zfs-tests.sh -r my_tests.run

zfs-tests.shfind_runfile会依次尝试$RUNFILE_DIR/<name>$RUNFILE_DIR/<name>.run<name><name>.run四种变体来定位文件(见 scripts/zfs-tests.sh),所以-r linux-r linux.run均可命中仓库内的linux.run

5.2 tags 筛选

如果不指定 runfile,可以设置 tags 只运行特定测试:

$ /usr/share/zfs/zfs-tests.sh -T zpool_add

此外,-T还支持分数形式(如1/32/3),用于把全部测试按 tag 均分给多台执行机并行跑。源码中split_tags会先汇总各 runfile 中的 tags、去重、剔除functional,再按NR % den == num - 1取模选出对应份(见 scripts/zfs-tests.sh),保证每份测试交错分布。

5.3 运行单个测试

-t选项可以按路径或名称精确定位单个测试。按路径时需相对测试套件根目录,例如:

$ ./scripts/zfs-tests.sh -t tests/functional/cli_root/zfs_bookmark/zfs_bookmark_cliargs.ksh

也可以只给名字,脚本会在整个套件中按名字查找:

$ ./scripts/zfs-tests.sh -t zfs_bookmark_cliargs

源码实现中,-t-T互斥;若传入的是名字而非路径,会通过find "$STF_SUITE" -name "$SINGLETEST*"定位用例;单个测试运行时,zfs-tests.sh会在$FILEDIR下动态生成一个临时 runfile(含pre/postsetup/cleanup脚本探测),再交给 test-runner 执行(见 scripts/zfs-tests.sh)。

六、测试结果解读

6.1 输出格式

测试运行时,每个测试结束会打印一行信息,全部结束后输出结果汇总,其中包含完整日志的位置(形如/var/tmp/test_results/[ISO 8601 日期])。一次带-v的正常运行大致如下(取自 tests/README.md 的示例):

$ /usr/share/zfs/zfs-tests.sh -v -d /tmp/test --- Configuration --- Runfile: /usr/share/zfs/runfiles/linux.run STF_TOOLS: /usr/share/zfs/test-runner STF_SUITE: /usr/share/zfs/zfs-tests STF_PATH: /var/tmp/constrained_path.G0Sf FILEDIR: /tmp/test FILES: /tmp/test/file-vdev0 /tmp/test/file-vdev1 /tmp/test/file-vdev2 LOOPBACKS: /dev/loop0 /dev/loop1 /dev/loop2 DISKS: loop0 loop1 loop2 NUM_DISKS: 3 FILESIZE: 4G ITERATIONS: 1 TAGS: functional Keep pool(s): rpool /usr/share/zfs/test-runner/bin/test-runner.py -c /usr/share/zfs/runfiles/linux.run \ -T functional -i /usr/share/zfs/zfs-tests -I 1 Test: /usr/share/zfs/zfs-tests/tests/functional/arc/setup (run as root) [00:00] [PASS] ...more than 1100 additional tests... Test: /usr/share/zfs/zfs-tests/tests/functional/zvol/zvol_swap/cleanup (run as root) [00:00] [PASS] Results Summary SKIP 52 PASS 1129 Running Time: 02:35:33 Percent passed: 95.6% Log directory: /var/tmp/test_results/20180515T054509

--- Configuration ---段(由zfs-tests.sh-v打印)把运行环境完整暴露出来:所用 runfile、test-runner 与套件路径、约束 PATH、稀疏文件、loopback、磁盘数、文件大小、迭代次数、tags 以及保留的池,方便排查环境差异。

6.2 结果状态机

从 tests/test-runner/bin/test-runner.py.in 的Result类(L75-L112)可以看出每个测试的判定逻辑:

  • PASS:进程退出码为 0;
  • SKIP:退出码为 4(例如-f文件模式下依赖真实块设备的测试即属此类);
  • FAIL:其他非零退出码,或检测到 kmemleak 泄漏输出;
  • KILLED:测试超时被强制终止;
  • RERAN:该测试被重跑过(配合-R自动重跑机制)。

timeout 默认在 runfile 的[DEFAULT]段设置为 600 秒;test-runner 的Cmd类兜底超时为 60 秒(L179-L180),按墙钟时间计时。完整日志统一写入/var/tmp/test_results/(源码中BASEDIR = '/var/tmp/test_results',见 L43)下以 ISO 8601 时间戳命名的目录。

七、实战:新增并运行一个测试用例(zpool_example)

以 tests/README.md 的zpool_example为例,新增一个测试用例可以归纳为5 个步骤

  1. 为运行测试的用户配置免密 sudo;
  2. 修改configure.ac与相关Makefile.am,把新用例纳入构建系统;
  3. 创建/修改.runrunfile;
  4. 编写实际测试脚本(.ksh);
  5. 运行测试用例。

下面逐一步骤展开。

步骤 1:配置免密 sudo

测试脚本不能以 root 直接运行(见第三节的硬性校验),因此需要为执行测试的普通用户配置无密码 sudo 权限。

步骤 2:修改构建系统文件

在仓库根目录的configure.ac中,于AC_CONFIG_FILES段加入新目录的 Makefile:

tests/zfs-tests/tests/functional/cli_root/zpool_example/Makefile

同时修改 runfiles 的构建清单(仓库中对应的构建组织文件为 tests/Makefile.am,测试目录的构建入口为 tests/zfs-tests/Makefile.am 与 tests/zfs-tests/tests/Makefile.am,它们共同构成"套件根 → runfiles → 功能目录"的层次化 Makefile 结构),将新 runfile 加入分发列表,例如:

pkgdatadir = $(datadir)/@PACKAGE@/runfiles dist_pkgdata_DATA = \ zpool_example.run \ common.run \ freebsd.run \ linux.run \ longevity.run \ perf-regression.run \ sanity.run \ sunos.run

步骤 3:创建 runfile

创建tests/runfiles/zpool_example.run,定义最常见的运行属性(超时、输出目录、tags、用例列表):

[DEFAULT] timeout = 600 outputdir = /var/tmp/test_results tags = ['functional'] tests = ['zpool_example_001_pos']

如果是在已有套件中新增用例,runfile 已存在,只需更新tests =一节。例如新增zpool_example_002_pos时:

[DEFAULT] timeout = 600 outputdir = /var/tmp/test_results tags = ['functional'] tests = ['zpool_example_001_pos', 'zpool_example_002_pos']

步骤 4:编写测试脚本

tests/zfs-tests/tests/functional/cli_root/Makefile.amSUBDIRS下追加目录名(注意行尾转义,其后还有别的目录):

zpool_example \

然后创建tests/zfs-tests/tests/functional/cli_root/zpool_example/Makefile.am,声明该目录下有一个测试脚本zpool_example_001_pos.ksh

pkgdatadir = $(datadir)/@PACKAGE@/zfs-tests/tests/functional/cli_root/zpool_example dist_pkgdata_SCRIPTS = \ zpool_example_001_pos.ksh

最后在tests/zfs-tests/tests/functional/cli_root/zpool_example/下创建测试脚本本体:

# DESCRIPTION: # zpool_example Test # # STRATEGY: # 1. Demo a very basic test case # DISKS_DEV1="/dev/loop0" DISKS_DEV2="/dev/loop1" TESTPOOL=EXAMPLE_POOL function cleanup { # Cleanup destroy_pool $TESTPOOL log_must rm -f $DISKS_DEV1 log_must rm -f $DISKS_DEV2 } log_assert "zpool_example" # Run function "cleanup" on exit log_onexit cleanup # Prep backend device log_must dd if=/dev/zero of=$DISKS_DEV1 bs=512 count=140000 log_must dd if=/dev/zero of=$DISKS_DEV2 bs=512 count=140000 # Create pool log_must zpool create $TESTPOOL $type $DISKS_DEV1 $DISKS_DEV2 log_pass "zpool_example"

这个脚本展示了测试用例的标准骨架与辅助函数用法:log_assert声明断言、log_onexit cleanup注册退出清理、log_must包装每条必须成功的命令、log_pass标记通过。这些辅助函数来自测试套件的 include 库(仓库内 tests/zfs-tests/include 与 test-runner 的 tests/test-runner/include/logapi.shlib)。

步骤 5:运行测试用例

运行方式与第四节一致,两种路径皆可:

  • 通过test-runner.py:以 runfile 为输入,即执行上面创建的zpool_example.run
  • 通过zfs-tests.sh:可以执行 runfile,也可以用-t直接运行单个用例:
$ ./scripts/zfs-tests.sh -t tests/functional/cli_root/zpool_example/zpool_example_001_pos.ksh

八、编写测试用例的实用建议

  • 复用现有库函数:绝大多数用例都应基于log_mustlog_assertlog_passlog_onexit等断言/日志原语编写,保证结果能被 test-runner 正确归类和汇总;
  • 善用 tags 与 runfile 分层:新用例通常先打上functional标签;若运行只需几秒钟,可参照 tests/runfiles/common.run 头部的注释建议同时加入sanity.run,纳入快速冒烟集;
  • 环境隔离:测试会创建/销毁池与用户,务必在专用测试机或 VM 中运行;KEEP变量与-x清理选项是保护既有环境的两道防线;
  • 调试利器:失败后使用-k保留现场、-v查看完整环境配置、-t单跑复现、-O/-K(详见选项表)在超时或挂起时向/dev/kmsg输出调试信息。

通过本文介绍的构建、运行、筛选、解读与扩展全流程,你可以把 OpenZFS 的上千个功能测试变成自己开发与排障的常备工具,也能以zpool_example为模板,向社区贡献高质量的测试用例。

  • 操作系统

【免费下载链接】zfs

OpenZFS on Linux and FreeBSD

项目地址:https://gitcode.com/gh_mirrors/zf/zfs
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 7:07:24

解决leetcode第4059题字典序最大的答案数组

4059.字典序最大的答案数组难度&#xff1a;困难问题描述&#xff1a;给你一个长度为n的整数数组nums。你可以重新排列其中的元素以形成任意排列perm。定义一个长度为15的数组power。对于每个0<i<15&#xff0c;考查perm的前j个元素的第(14-i)位&#xff0c;power[i]是满…

作者头像 李华
网站建设 2026/9/23 7:06:42

基于 Java Spring Boot 的货运通服务平台设计与实现

温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 1. 项目背景与意义 随着物流行业的快速发展&#xff0c;传统货运管理方式存在信息不透明、调度效率低、货物跟踪困难等问题。本文设计并实现一个基于 Java Spring Boot…

作者头像 李华
网站建设 2026/9/23 7:05:29

Electron+Python开发晨间效率工具实战

1. 项目背景与核心需求每天早上开机后的前30分钟&#xff0c;往往是工作效率最低的时段。大多数人会陷入"开机发呆"的状态&#xff1a;机械地打开邮箱、社交软件、新闻网站&#xff0c;然后漫无目的地浏览&#xff0c;等到真正开始工作时&#xff0c;宝贵的晨间精力已…

作者头像 李华
网站建设 2026/9/23 7:04:35

学术论文审稿意见处理全攻略:从解析到实战

1. 论文投稿的生死关卡&#xff1a;审稿意见解析第一次收到期刊审稿意见时&#xff0c;我的手心全是汗。那密密麻麻的修改建议里&#xff0c;藏着论文能否被录用的关键密码。作为在学术圈摸爬滚打十年的"老油条"&#xff0c;我逐渐摸清了审稿人的思维模式——他们就像…

作者头像 李华
网站建设 2026/9/23 6:57:07

2026年AI四大前沿技术解析与应用场景

1. 前沿模型技术全景解析2026年的AI模型能力边界正在被四大技术方向重新定义&#xff1a;混合专家系统&#xff08;MoE&#xff09;、多模态融合、超长上下文窗口和自进化架构。这些技术突破正在从实验室走向商业应用&#xff0c;彻底改变人机交互的底层逻辑。MoE架构通过动态路…

作者头像 李华
网站建设 2026/9/23 6:56:18

Android图形渲染进阶:从GLSurfaceView到手动EGL环境管理

用了两年GLSurfaceView&#xff0c;我一直觉得它是 Android 平台上最省心的封装。直到我开始接视频滤镜、多路画面混合和自定义渲染管线&#xff0c;才发现它的封装也成了天花板。那段时间我花了不少力气把GLSurfaceView从核心链路里摘掉&#xff0c;自己接管 EGL 环境、渲染线…

作者头像 李华