NixpkgsredisTestHook实战指南:在 checkPhase 自动拉起 Redis 测试服务器
【免费下载链接】nixpkgsNix Packages collection & NixOS项目地址: https://gitcode.com/GitHub_Trending/ni/nixpkgs
redisTestHook是 Nixpkgs 提供的标准 setup hook,用于在软件包的checkPhase期间自动启动一个 Redis(实际为 Valkey 提供的redis-server二进制)测试实例,并在测试结束后自动停止。本指南基于 doc/hooks/redis-test-hook.section.md 与 pkgs/by-name/re/redisTestHook 的源码实现,带你掌握其声明式用法、可调变量、自定义checkPhase的写法,以及底层 hook 机制的工作原理,使你的 Nix 包在构建阶段即可完成对 Redis 依赖的集成测试。
redisTestHook 是什么:在 checkPhase 自动拉起 Redis
redisTestHook是 Nixpkgs 通过makeSetupHook生成的一个 setup hook(定义见 package.nix)。把它加入nativeCheckInputs后,Nix 构建框架会自动将其中的preCheckHooks与postCheckHooks注入到checkPhase的执行流程中:
- 进入
checkPhase之前(即preCheck之后、测试命令执行之前),自动执行redisStart启动一个 Redis 服务器; checkPhase中的测试命令执行完毕后(postCheck阶段),自动执行redisStop关闭该服务器。
整个生命周期由 hook 管理,你不需要在checkPhase里手工写启动/清理逻辑,也不必担心测试失败后残留 Redis 进程导致构建挂起(详见下文"底层实现"一节关于输出重定向的设计)。
基础用法:把 redisTestHook 加入 nativeCheckInputs
最直接的用法是在stdenv.mkDerivation(或其派生变体)中声明依赖:
{ stdenv, redis, redisTestHook, }: stdenv.mkDerivation { # ... nativeCheckInputs = [ redisTestHook ]; }需要说明的是:
- 这里的
redis参数仅用于示例中的"待测包依赖 Redis"场景;redisTestHook自身所需的redis-server/redis-cli二进制已通过substitutions内置(见 package.nix 中的cli、server两个替换变量),你无需手动传入redis; - 当前仓库中
redisTestHook实际替换的是Valkey提供的redis-server与redis-cli可执行文件(lib.getExe' valkey "redis-cli"/lib.getExe' valkey "redis-server"),Valkey 是 Redis 的社区分支,向下兼容 Redis 协议与命令行接口,因此测试客户端无论连接redis-cli还是各语言 Redis 客户端均可正常工作; - 若测试产物需要在 build 目录之外连接该服务器,请结合各平台网络限制(如 Darwin 下需要设置
__darwinAllowLocalNetworking,见下文的测试示例)。
使用自定义 checkPhase:必须保留 runHook 调用
如果你在包中覆写了checkPhase,那么 hook 机制依赖的runHook preCheck/runHook postCheck调用必须保留,否则redisStart与redisStop不会被执行:
{ checkPhase = '' runHook preCheck # ... your tests runHook postCheck ''; }这是 Nixpkgs 所有 setup hook 的通用约定:preCheckHooks会在runHook preCheck处依次执行,postCheckHooks会在runHook postCheck处依次执行。省略其中任何一行,都会导致 hook 提前或延后触发,甚至完全失效(例如只写runHook preCheck而不写runHook postCheck,测试结束后 Redis 进程将不会被清理)。
可调变量:端口与 Unix Socket 路径
hook 启动的 Redis 同时监听一个 TCP 端口和一个 Unix Domain Socket,二者都可以通过变量覆盖。文档 redis-test-hook.section.md 明确列出的变量如下:
导出变量(子进程可见):
| 变量 | 含义 | 默认值 |
|---|---|---|
REDIS_SOCKET | Unix domain socket 路径 | $NIX_BUILD_TOP/run/redis.sock |
REDIS_SOCKET会被export,因此checkPhase中的测试脚本以及被测试程序都能通过环境变量感知 socket 位置。
仅 Bash 可见变量(不导出):
| 变量 | 含义 | 默认值 |
|---|---|---|
redisTestPort | Redis 监听的 TCP 端口 | 6379 |
注意redisTestPort是 Bash 专用变量(不导出),它只在 hook 自身的redisStart函数中读取;你的测试脚本若要使用端口号,需要自行引用$redisTestPort。
覆盖端口与 socket 路径的示例
{ stdenv, redis, redisTestHook, }: stdenv.mkDerivation { # ... nativeCheckInputs = [ redisTestHook ]; preCheck = '' redisTestPort=6390; ''; }同理,在preCheck中设置REDIS_SOCKET=/tmp/customredis.sock即可自定义 socket 位置。端口冲突或系统限制(如根目录 /tmp 的挂载选项)下的路径调整,都可以在这一步完成。
底层实现:读懂 redis-test-hook.sh 的完整工作流
hook 的全部逻辑集中在 redis-test-hook.sh,全文仅 45 行,注册与实现一目了然:
preCheckHooks+=('redisStart') postCheckHooks+=('redisStop')redisStart()的执行流程:
- 端口默认值:若
redisTestPort未设置或为空,则置为6379; - 准备运行目录:
mkdir -p "$NIX_BUILD_TOP/run",Redis 的配置与 socket 都放在构建目录内,保证一次构建一个隔离实例; - socket 默认值:若
REDIS_SOCKET为空,则设为$NIX_BUILD_TOP/run/redis.sock,并export REDIS_SOCKET; - 生成 Redis 配置文件
$NIX_BUILD_TOP/run/redis.conf(该路径同时被export为REDIS_CONF),关键配置项包括:
unixsocket ${REDIS_SOCKET} port ${redisTestPort} protected-mode no enable-debug-command yes enable-module-command yesprotected-mode no:允许从本机非回环地址访问,方便测试程序使用 TCP 连接(如经127.0.0.1:端口);enable-debug-command yes与enable-module-command yes:开启 Redis 的 DEBUG 与 MODULE 命令,为需要调试命令或动态加载模块的测试场景提供能力;
- 启动服务器:以
@server@ "$REDIS_CONF" > /dev/null 2>&1 &后台启动(@server@是makeSetupHook在实例化时替换为valkey包内redis-server的绝对路径),并记录 PID 到REDIS_PID; - 等待就绪:循环执行
@cli@ --scan -s "$REDIS_SOCKET"(即redis-cli --scan -s <socket>),直到命令成功返回才继续,保证进入测试前服务器已可接受连接,避免竞态。
redisStop()则简单地执行kill "$REDIS_PID"结束后台服务器。
关于输出重定向的 Darwin 设计
源码注释专门说明了> /dev/null 2>&1的用意:在 Darwin(macOS)上,如果后台进程的标准输出未被重定向,父进程会变成 launchd 而不是 bash。这意味着一旦测试失败导致postCheck的redisStop未执行,Redis 进程会残留并一直运行,使 Nix 构建永久挂起。通过将输出重定向到/dev/null,确保进程的父进程始终是 bash,从而让kill能够可靠生效。这一细节是"测试失败不挂死构建"的关键保障,也是 hook 实现值得借鉴之处。
官方自测:一个完整的真实使用样例
Nixpkgs 为redisTestHook提供了自测用例 test.nix,它同时验证了 TCP 端口与 Unix socket 两种连接方式,是学习该 hook 用法的绝佳参考:
stdenv.mkDerivation { name = "redis-test-hook-test"; nativeCheckInputs = [ valkey redisTestHook ]; dontUnpack = true; doCheck = true; preCheck = '' redisTestPort=6380 REDIS_SOCKET=/tmp/customredis.sock ''; checkPhase = '' runHook preCheck echo "running test" if redis-cli --scan -p $redisTestPort; then echo "connected to redis via localhost" PORT_TEST_RAN=1 fi if redis-cli --scan -s $REDIS_SOCKET; then echo "connected to redis via domain socket" SOCKET_TEST_RAN=1 fi runHook postCheck ''; installPhase = '' [[ $PORT_TEST_RAN == 1 && $SOCKET_TEST_RAN == 1 ]] echo "test passed" touch $out ''; __darwinAllowLocalNetworking = true; }该测试暴露了几个值得照抄的实践点:
- 两个连接通道都测:通过
redis-cli --scan -p $redisTestPort验证 TCP 端口连接,通过redis-cli --scan -s $REDIS_SOCKET验证 Unix socket 连接,并以环境变量记录结果、在installPhase中断言,保证两路连接都真正可用; - 自定义端口与 socket:在
preCheck里同时覆盖redisTestPort=6380与REDIS_SOCKET=/tmp/customredis.sock,证明两个变量均可按需调整; - Darwin 网络权限:设置
__darwinAllowLocalNetworking = true,允许测试在 macOS 沙箱中发起本地网络连接(TCP 通道在 Darwin 下需要该授权)。
此外,package.nix 的passthru.tests还注册了python3-valkey = python3Packages.valkey,即使用 Python 的 Valkey 客户端库作为集成测试的一部分,验证 hook 与语言级客户端配合工作的场景。
应用场景与注意事项小结
推荐场景:凡是构建期测试需要真实 Redis 实例的 Nix 包——例如 Redis 模块/命令扩展、缓存中间件、消息队列客户端、需要SET/GET/PUBLISH等真实行为的单元与集成测试——都可以通过一行nativeCheckInputs = [ redisTestHook ];获得开箱即用的测试数据库。
注意事项:
- 覆写
checkPhase时务必保留runHook preCheck/runHook postCheck; redisTestPort默认6379,若与构建环境中的其他服务冲突,请在preCheck中改用一个高位端口;- socket 与配置文件均位于
$NIX_BUILD_TOP/run下,随构建目录隔离,无需手动清理; - 测试命令需在
runHook postCheck之前完成所有 Redis 交互,因为postCheck阶段会立即kill服务器进程。
结合 redis-test-hook.sh 的实现与 test.nix 的验证,你可以在自己的 Nix 包中安全、可复现地启用构建期 Redis 测试,同时理解其在端口、socket、进程生命周期与平台差异上的完整行为。
【免费下载链接】nixpkgsNix Packages collection & NixOS项目地址: https://gitcode.com/GitHub_Trending/ni/nixpkgs
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考