Gemini CLI 集成测试体系全解:构建、运行、沙箱矩阵与内存/性能回归基线
Gemini CLI 的集成测试框架负责在受控环境中执行构建产物(bundle),验证 CLI 与文件系统、PTY 交互、遥测等端到端行为的正确性。本篇基于仓库文档 docs/integration-tests.md 及其背后的源码实现展开,读完后你将掌握:如何构建待测 bundle、如何按沙箱矩阵运行 e2e 测试、如何用 golden 文件机制回放模型响应、如何用 deflake 脚本与 CI 工作流保障测试稳定性,以及如何运行内存与性能回归测试并更新基线。
框架总览
集成测试的目标是验证 Gemini CLI 的端到端功能:测试进程拉起已构建的 CLI 产物,在隔离的文件系统目录中运行它,并断言其行为符合预期。测试位于 integration-tests/ 目录,使用自定义测试运行器驱动——从源码结构看,该运行器由 Vitest 与 TestRig 测试装备类组合而成:
- Vitest 配置见 integration-tests/vitest.config.ts:单测试超时 5 分钟、自动重试 2 次、启用文件级并行(8~16 线程),并通过全局环境变量
GEMINI_TEST_TYPE: 'integration'标记测试类型; TestRig位于 packages/test-utils/src/test-rig.ts,负责创建隔离的测试工作区与假 HOME 目录、生成settings.json、以子进程或 PTY 方式启动 CLI、解析遥测日志并做断言辅助。
值得注意的实现细节:TestRig 通过环境变量选择被测对象——默认执行 bundle/gemini.js(见 BUNDLE_PATH 定义),设置 INTEGRATION_TEST_GEMINI_BINARY_PATH 可换为自定义二进制,设置 INTEGRATION_TEST_USE_INSTALLED_GEMINI=true 则改为验证已安装的 gemini 命令。启动前的 _getCleanEnv 会清除大部分 GEMINI_* 环境变量(保留 GEMINI_API_KEY、GEMINI_MODEL、GEMINI_DEBUG 等白名单项),并把 GEMINI_CLI_HOME 指向测试专用 HOME,确保测试环境不受本机配置污染。
准备待测构建包
运行任何集成测试前,必须先构建一个真正可测的 release bundle:
npm run bundle
对应 package.json 中的 bundle 脚本,它会先生成 git commit 信息、构建 devtools 包与浏览器 MCP,再用 esbuild 产出 bundle/gemini.js。TestRig 拉起的正是这个文件,因此修改 CLI 源码后必须重新执行该命令,而修改测试代码则无需重新构建。
运行测试:命令与沙箱矩阵
集成测试不包含在默认的 npm run test 中,必须显式运行。最常用的快捷方式是:
npm run test:e2e
从 package.json 可以看到,它实际等价于 cross-env VERBOSE=true KEEP_OUTPUT=true npm run test:integration:sandbox:none,即默认以“无沙箱 + 保留输出 + 详细日志”的方式运行,方便本地调试。
沙箱矩阵
test:integration:all 会依次在三种沙箱环境下跑完整套测试:
npm run test:integration:all
也可以单独运行某一类:
npm run test:integration:sandbox:none
npm run test:integration:sandbox:docker
npm run test:integration:sandbox:podman
从脚本定义看,三者的差异完全由环境变量 GEMINI_SANDBOX 驱动:
sandbox:none:GEMINI_SANDBOX=false vitest run --root ./integration-tests;sandbox:docker:GEMINI_SANDBOX=docker,并且会先执行npm run build:sandbox构建沙箱容器镜像,再运行测试;sandbox:podman:GEMINI_SANDBOX=podman直接运行。
TestRig 生成的 settings.json 中会写入 sandbox: env['GEMINI_SANDBOX'](见 _createSettingsFile 实现),使被测 CLI 按指定沙箱启动。此外 TestRig.getDefaultTimeout() 会按环境调整轮询超时:CI 上 60 秒、容器内 30 秒、本地 15 秒。
运行子集测试与按名称定位
要运行部分测试文件,直接使用文件名(不含扩展名)作为参数,命令是 test:e2e 或任一 test:integration* 脚本:
npm run test:e2e list_directory write_file
要按测试名运行单个测试,使用 Vitest 的 --test-name-pattern 标志(注意双横线透传):
npm run test:e2e -- --test-name-pattern "reads a file"
模型响应 golden 文件回放机制
集成测试大多不依赖真实模型,而是使用伪造的模型响应(fake responses)做确定性回放。TestRig.setup() 会把指定的 .responses 文件拷贝到测试目录,并向 CLI 传递对应参数(见 _getCommandAndArgs):
- 常规回放:
--fake-responses <path>; - 非严格模式:
--fake-responses-non-strict <path>; - 录制模式:当
REGENERATE_MODEL_GOLDENS=true时改为--record-responses <path>,让 CLI 把真实模型输出写入文件。
重新生成 golden 文件
随着实现变化,golden 文件需要不定期重新录制:
REGENERATE_MODEL_GOLDENS="true" npm run test:e2e
警告:本地录制时,请检查更新后的响应中是否包含模型可能带出的你本人或系统的敏感信息,提交前应审查这些内容。
警告:测试结尾必须执行 await rig.cleanup(),否则录制的新响应不会从测试目录拷回原 golden 文件——TestRig.cleanup() 中正是判断 REGENERATE_MODEL_GOLDENS === 'true' 后才执行 copyFileSync 回写源文件(见 test-rig.ts)。
Deflake:新测试入库前的稳定性验证
在新增集成测试前,应先用 deflake 机制至少跑 5 遍,确认它不是不稳定测试(flaky)。
Deflake 脚本
npm run deflake -- --runs=5 --command="npm run test:e2e -- -- --test-name-pattern '<your-new-test-name>'"
scripts/deflake.js 的参数为 --command(必选,要重复执行的命令)与 --runs(次数,默认 5)。脚本的额外细节是:运行前会临时把 .dockerignore 替换为仅包含 .integration-tests 的内容(避免测试目录被打包进沙箱镜像),结束后恢复原文件;任一运行失败,最终退出码为 1。仓库还提供了针对整条集成命令的快捷脚本 deflake:test:integration:sandbox:none / :docker 等。
Deflake 工作流
在 CI 侧也可以用 GitHub Actions 工作流远程执行:
gh workflow run deflake.yml --ref <your-branch> -f test_name_pattern="<your-test-name-pattern>"
工作流定义位于 .github/workflows/deflake.yml。
内存回归测试
内存回归测试用于检测关键 CLI 场景下的堆增长与泄漏,位于 memory-tests/ 目录。它与普通集成测试的区别在于:测量内存占用并与提交到仓库的基线比较。
运行
内存测试同样不在 npm run test 或 npm run test:e2e 中,CI 中夜间运行,本地手动执行:
npm run test:memory
更新基线
如果有意变更了影响内存的行为,可更新基线:
UPDATE_MEMORY_BASELINES=true npm run test:memory
这会运行测试、取中位数快照并覆盖 memory-tests/baselines.json。更新后应审查变更再提交。
工作原理
Harness 为 packages/test-utils 中的 MemoryTestHarness(见 memory-test-harness.ts):
- 多次强制垃圾回收以降低噪声;
- 取中位数快照过滤毛刺;
- 与基线比较,容忍度为 10%;
- 提供
analyzeSnapshots()分析跨 3 个快照的持续性泄漏。
数据源是被测 CLI 输出的 gemini_cli.memory.usage 遥测指标——TestRig 的 readMemoryMetrics() 会从 telemetry.log 中解析该指标并按时间戳聚合为内存快照序列。
性能回归测试
性能回归测试检测墙钟时间、CPU 占用与事件循环延迟的退化,位于 perf-tests/ 目录,同样与标准集成测试分离,因为它的比较对象是提交的性能基线。
运行
npm run test:perf
更新基线
UPDATE_PERF_BASELINES=true npm run test:perf
该过程会多次运行测试(含预热),做 IQR 离群点过滤后覆盖 perf-tests/baselines.json,审查后提交。
工作原理
Harness 为 PerfTestHarness(见 perf-test-harness.ts):
- 使用
performance.now()测量墙钟时间; - 使用
process.cpuUsage()测量 CPU 占用; - 使用
perf_hooks.monitorEventLoopDelay()监控事件循环延迟; - 应用 IQR(四分位距)过滤剔除离群样本;
- 与基线比较,容忍度为 15%。
诊断:KEEP_OUTPUT 与 VERBOSE
运行器提供两个环境变量用于排障:
保留测试输出
把测试运行期间创建的临时文件保留下来,便于检查文件系统相关问题:
KEEP_OUTPUT=true npm run test:integration:sandbox:none
保留输出时,运行器会打印本次运行唯一目录的路径。
详细日志
VERBOSE=true npm run test:integration:sandbox:none
两者同时设置时,输出既流式打印到控制台,也写入测试临时目录中的日志文件。详细日志的格式会明确标出日志来源:
--- TEST: <log dir>:<test-name> ---
... output from the gemini command ...
--- END TEST: <log dir>:<test-name> ---
从实现看,KEEP_OUTPUT/VERBOSE 的作用贯穿整个 TestRig:子进程 stdout/stderr 与 PTY 输出在两个变量任一为 true 时都会回显到控制台;而 integration-tests/globalSetup.ts 在 teardown 阶段会删除本次运行的 .integration-tests/<run-id>/ 目录,除非 KEEP_OUTPUT=true——这正是“保留输出”语义的落点。
测试产物目录结构
每次集成测试运行会在 .integration-tests 目录内创建唯一的运行目录,其中每个测试文件一个子目录、每个测试用例一个子目录:
.integration-tests/
└── <run-id>/
└── <test-file-name>.test.js/
└── <test-case-name>/
├── output.log
└── ...other test artifacts...
globalSetup 会为运行目录写入 INTEGRATION_TEST_FILE_DIR,并自动清理历史运行目录(保留最近 5 次以便调试)。TestRig.setup() 在此目录下按 <testDir>(工作区)与 <testDir>-home(假 HOME)组织每个用例的产物,并预写两份 settings.json(项目级与用户级)及 state.json,其中将遥测目标强制指向本地收集器文件 telemetry.log、关闭自动更新、关闭 IDE 连接提示——这些都是保证测试可复现的关键配置。
Lint 与格式化
集成测试文件与主构建流程一样接受 lint 检查,也可手动运行:
npm run lint
加上自动修复:
npm run lint:fix
持续集成
为确保集成测试始终执行,仓库定义了 chained_e2e.yml 工作流(名为 “Testing: E2E (Chained)”)。从工作流定义看,它的触发条件包括:
push到main分支;merge_group(PR 进入合并队列时);- 由 “Trigger E2E” 工作流完成事件链式触发,以及手动
workflow_dispatch(可指定 head_sha 与仓库名);
工作流带并发组控制(同分支取消进行中任务)与 pending 状态上报逻辑,并在 sandbox:none、sandbox:docker、sandbox:podman 三类沙箱环境中分别运行集成测试,保证 Gemini CLI 在每种沙箱形态下都被覆盖验证。
关键命令与环境变量速查
| 命令 / 变量 | 作用 |
|---|---|
npm run bundle |
构建待测 release bundle(CLI 源码变更后必须重跑) |
npm run test:e2e |
快捷运行:VERBOSE=true KEEP_OUTPUT=true + 无沙箱 |
npm run test:integration:all |
依次跑 none / docker / podman 三套沙箱 |
npm run test:integration:sandbox:<none|docker|podman> |
单跑某一沙箱形态 |
--test-name-pattern "<name>" |
按测试名运行单个用例 |
REGENERATE_MODEL_GOLDENS=true |
录制并回写模型响应 golden 文件 |
npm run deflake -- --runs=5 --command="..." |
本地重复运行以验证稳定性 |
gh workflow run deflake.yml -f test_name_pattern="..." |
CI 侧 deflake |
npm run test:memory / UPDATE_MEMORY_BASELINES=true |
内存回归测试 / 更新基线 |
npm run test:perf / UPDATE_PERF_BASELINES=true |
性能回归测试 / 更新基线 |
KEEP_OUTPUT=true |
保留 .integration-tests/<run-id>/ 产物供排查 |
VERBOSE=true |
流式回显子进程/PTY 输出并写入日志 |
RUN_FLAKY_INTEGRATION=1 |
启用被标记为 flaky 的测试(见 test-helper.ts 中 skipFlaky 逻辑) |
相关入口文件:integration-tests/(测试用例与 vitest 配置)、packages/test-utils/src/test-rig.ts(测试装备核心)、packages/test-utils/src/memory-test-harness.ts 与 packages/test-utils/src/perf-test-harness.ts(回归 harness)、scripts/deflake.js(deflake 脚本)、.github/workflows/chained_e2e.yml(CI 定义)。注意运行集成测试要求 Node.js ≥ 20(见 package.json 的 engines 声明),且 docker 沙箱形态依赖预先构建的沙箱镜像(npm run build:sandbox 会作为其前置步骤自动执行)。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00