- 操作系统
【免费下载链接】zfs
OpenZFS on Linux and FreeBSD
导读
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.run、linux.run、freebsd.run、sunos.run、sanity.run、longevity.run、perf-regression.run、bclone.run等多套运行清单,分别面向通用功能、各操作系统专属用例、快速冒烟、长稳与性能回归等不同场景。
二、构建并安装 zfs-test 包
2.1 从源码构建
在完成 ZFS 源码树的./configure之后,构建测试套件只需一条命令:
$ ./configure $ make pkg-utilsmake 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-runner | test-runner 框架 | tests/test-runner |
/usr/share/zfs/zfs-tests | 测试用例套件 | tests/zfs-tests |
/usr/share/zfs/runfiles | runfile 清单 | tests/runfiles |
三、运行前置条件
根据 tests/README.md 与 scripts/zfs-tests.sh 的校验逻辑,运行测试前必须满足以下条件:
- 三块空白测试盘。通过
$DISKS环境变量以空格分隔指定,例如DISKS='vdb vdc vdd'。若未指定,zfs-tests.sh默认构造三个 loopback 设备用于测试:DISKS='loop0 loop1 loop2'。- 从源码看,脚本默认会在
$FILEDIR(默认/var/tmp)下创建三个稀疏文件file-vdev0、file-vdev1、file-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)。
- 从源码看,脚本默认会在
- 一个非 root 用户,拥有完整基础权限,并能通过
sudo(8)免密切换到 root 来运行测试。- 这是硬性要求:脚本开头就检查
id -u,若以 root 直接运行会立即失败并提示 "This script must not be run as root.";同时校验sudo id -un必须返回 root(见 scripts/zfs-tests.sh)。
- 这是硬性要求:脚本开头就检查
- 指定要保留的池。将不希望被测试触碰的池以空格分隔写入
$KEEP变量。测试开始时会自动把系统上检测到的所有池加入保留列表。- 源码实现为:若未设置
KEEP,脚本自动执行zpool list -Ho name收集全部现存池;若一个都没有则兜底为rpool,随后将其导出为内部变量__ZFS_POOL_EXCLUDE传给测试进程(见 scripts/zfs-tests.sh)。
- 源码实现为:若未设置
- 强烈建议使用专用测试机(VM 亦可)。因为测试套件会向测试机器添加用户和用户组以验证相关功能,残留状态可能影响生产环境。
- 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 | 显示帮助 |
-D | Debug 模式:立即显示所有测试输出(比较嘈杂) |
-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:空格分隔的需要保留的池;未设置时自动收集系统全部现存池。RUNFILES、FILEDIR、FILESIZE、ITERATIONS、TAGS等均有同名选项对应,也都可以通过环境变量覆盖(见 scripts/zfs-tests.sh 的默认值定义)。
五、用 runfile 与 tags 选择测试子集
ZFS Test Suite 允许通过runfile或tags 列表两种方式指定要运行的测试子集。
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.runzfs-tests.sh的find_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/3、2/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/post的setup/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 个步骤:
- 为运行测试的用户配置免密 sudo;
- 修改
configure.ac与相关Makefile.am,把新用例纳入构建系统; - 创建/修改
.runrunfile; - 编写实际测试脚本(
.ksh); - 运行测试用例。
下面逐一步骤展开。
步骤 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.am的SUBDIRS下追加目录名(注意行尾转义,其后还有别的目录):
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_must、log_assert、log_pass、log_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
相关推荐
curl 测试套件(Test Suite)完全指南:运行、调试与编写测试用例
curl 测试套件(Test Suite)完全指南:运行、调试与编写测试用例 本文围绕 curl 仓库中的测试套件展开,系统讲解如何从零构建并运行 curl 的
CLI网络通信radare2 基于 libFuzzer 的模糊测试实践:构建、运行与自定义 Fuzz Target 完全指南
radare2 基于 libFuzzer 的模糊测试实践:构建、运行与自定义 Fuzz Target 完全指南 导读 本文面向想要为 radare2 https
逆向工程网络安全mypy 单元测试实战指南:数据驱动 check-*.test 用例编写、测试运行与调试全解
mypy 单元测试实战指南:数据驱动 check .test 用例编写、测试运行与调试全解 本文是 mypy 仓库单元测试体系的实操指南,以 test data
开发工具静态分析代码质量
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考