测试
快速指南
TLDR
运行 just test-update 来运行所有 rust 和 node.js 测试,并自动更新快照
我们有两组测试套件:一组用于 Rust,另一组用于 Node.js。
just test用于运行所有测试。just test-update用于运行所有测试并自动更新快照just test-rust用于运行所有 Rust 测试。just test-node用于运行所有 Node.js 测试。just test-node-rolldown用于仅运行 Rolldown 的 Node.js 测试。just test-node-rollup用于仅运行 Rollup 的测试。
概念
测试是 Rolldown 开发过程中的关键部分。随着我们添加新功能和进行更改,它帮助我们确保打包器的正确性、稳定性和性能。
由于 Rolldown 的本质是一个 bundler,我们更倾向于使用覆盖端到端场景的集成测试,而不是测试单个组件的单元测试。这使我们能够验证整个打包过程是否按预期工作,从输入文件到输出包。
通常,我们使用两种类型的测试:
- 数据驱动测试:测试运行器会查找符合特定约定(例如文件夹结构、文件命名)的测试用例并自动运行它们。这是我们添加新测试的主要方式。
- 手动测试:对于无法轻松用数据驱动方式表达的更复杂场景,我们会编写手动测试代码来设置测试环境、使用特定选项运行打包器,并以程序方式验证输出。
Rust
我们使用 Rust 内置的测试框架来编写和运行测试。测试用例存放在 crates/rolldown/tests 文件夹中。
数据驱动测试
数据驱动测试用例是一个包含 _config.json 文件的文件夹。测试运行器会从 _config.json 读取配置,打包输入文件,并执行输出文件以验证行为。
_config.json 包含测试套件的配置。如果一切正常,你在编辑 _config.json 时应该能够获得自动补全,这得益于 config。
对于所有可用选项,你可以参考
数据驱动测试做什么?
它会生成构建产物的快照,包括:
- 打包后的输出文件
- 打包过程中发出的警告和错误
如果
_test.mjs不存在,则在 Node.js 环境中运行输出文件以验证运行时行为。你可以把它理解为运行node --import ./dist/entry1.mjs --import ./dist/entry2.mjs --import ./dist/entry3.mjs --eval ""。如果存在
_test.mjs,则运行它以验证更复杂的行为。
提示
- 当你运行 Rust 测试时,快照会自动更新。不需要额外命令。
功能完整的数据驱动测试
_config.json 有其局限性,因此我们也支持直接使用 Rust 编写测试。你可以参考
crates/rolldown/tests/rolldown/errors/plugin_error
它本质上只是用 Rust 代码替换 _config.json,由 Rust 代码直接配置打包器。其余部分的工作方式与数据驱动测试相同。
esbuild
Rolldown 也会运行源自 esbuild 打包器测试套件的测试,以验证兼容性。这些测试位于 crates/rolldown/tests/esbuild。
scripts 目录包含用于管理 esbuild 测试的工具:
gen-esbuild-tests- 从 esbuild 的 Go 测试文件生成测试用例。esbuild-snap-diff- 将 Rolldown 的输出快照与 esbuild 的预期输出进行比较。它会生成差异报告和兼容性统计信息,帮助跟踪 Rolldown 的行为与 esbuild 的接近程度。该脚本会在
scripts/src/esbuild-tests/snap-diff/summary/中生成汇总 markdown 文件,并在scripts/src/esbuild-tests/snap-diff/stats/stats.md中生成整体统计信息。
可以通过在文件夹名前加上 . 来跳过测试用例(例如 .test_case_name)。被跳过的测试必须在 scripts/src/esbuild-tests/reasons.ts 中记录原因。
HMR 测试
如果测试用例文件夹包含任何名为 *.hmr-*.js 的文件,则该测试将以启用 HMR 的模式运行。
HMR 编辑文件
- 匹配
*.hmr-*.js模式的文件称为 HMR 编辑文件。 - 这些文件表示对现有源文件的更改。
hmr-后面的部分表示更改的步骤编号。例如,main.hmr-1.js表示在步骤 1 中应用的更改。
测试如何工作
- 所有非 HMR 文件都会被复制到一个临时目录。
- 基于这些文件生成初始构建。
- 然后开始 HMR 步骤 1:使用
.hmr-1.js文件覆盖临时目录中对应的文件,并生成一个 HMR 补丁。 - 这个过程会在步骤 2、3 等中重复。像
*.hmr-2.js、*.hmr-3.js等文件会按步骤依次应用。
示例
如果测试文件夹包含这些文件:
main.jssub.jsmain.hmr-1.jssub.hmr-1.jssub2.hmr-2.js
测试将按以下步骤进行:
- 初始构建:
main.js、sub.js - 步骤 1:
main.js被main.hmr-1.js替换sub.js被sub.hmr-1.js替换
- 步骤 2:
main.js和sub.js保持与步骤 1 相同- 使用
sub2.hmr-2.js的内容添加sub2.js
手动测试
对于无法轻松用数据驱动方式表达的更复杂场景,我们会编写手动测试代码来设置测试环境、使用特定选项运行打包器,并以程序方式验证输出。
这里没什么特别的,基本上就是编写正常的 Rust 测试代码,使用 Rolldown 来执行打包和验证。
test262 集成测试
Rolldown 集成了 test262 测试套件,以验证 ECMAScript 规范符合性。只运行 test/language/module-code 下的测试用例,因为其他测试用例应由 Oxc 侧覆盖。
在设置项目时,运行 just setup 后应该已经初始化了 git 子模块,但你还应该在运行集成测试之前执行 just update-submodule 来更新该子模块。
你可以使用以下命令运行 test262 集成测试:
TEST262_FILTER="attribute" cargo test --test integration test262_module_code -- --no-captureTEST262_FILTER允许你按名称过滤测试(例如"attribute")。如果省略此环境变量,将运行所有测试用例。注意,如果设置了该环境变量,将不会更新结果快照。--no-capture选项会显示所有测试输出。
预期会失败的测试用例列在 crates/rolldown/tests/test262_failures.json 中。
Node.js
Rolldown 使用 Vitest 来测试 Node.js 侧代码。
位于 packages/rolldown/tests 的测试用于测试 Rolldown 的 Node.js API(即 NPM 上发布的 rolldown 包的 API)。
just test-node-rolldown将运行 rolldown 测试。just test-node-rolldown --update将运行测试并更新快照。
数据驱动测试
数据驱动测试位于 packages/rolldown/tests/fixtures。
数据驱动测试用例是一个包含 _config.ts 文件的文件夹。测试运行器会从 _config.ts 读取配置,打包输入文件,并将输出与预期结果进行验证。
手动测试
这里也没什么特别的,基本上就是编写正常的 JavaScript/TypeScript 测试代码,使用 Rolldown 来执行打包和验证。
运行特定文件的测试
要运行特定文件的测试,你可以使用
just test-node-rolldown test-file-name例如,要运行 fixture.test.ts 中的测试,你可以使用 just test-node-rolldown fixture。
提示
运行特定测试
要运行特定测试,你可以使用
just test-node-rolldown -t test-namefixture.test.ts 中的测试名称按其文件夹名称定义。tests/fixtures/resolve/alias 的测试名称将是 resolve/alias。
要运行 tests/fixtures/resolve/alias 测试,你可以使用 just test-node-rolldown -t resolve/alias。
INFO
just test-node-rolldown -t aaa bbb与just test-node-rolldown -t "aaa bbb"不同。前者将运行名称包含aaa或bbb的测试,而后者将运行名称包含aaa bbb的测试。如需更高级的用法,请参考 https://vitest.dev/guide/filtering。
Dev server tests
@rolldown/test-dev-server 是一个小型的 Vite 风格开发服务器,用于验证 rolldown 的 开发引擎——HMR、延迟编译和错误恢复。它的测试位于 packages/test-dev-server/tests,分为两个套件:
| 套件 | 平台 | 驱动的内容 |
|---|---|---|
| browser | browser | 一个真实的 Chromium 页面,连接到一个进程内开发服务器。大多数开发引擎测试都在这里。 |
| fixtures | node | 开发服务器构建到磁盘,并将构建产物作为 node 子进程运行。 |
架构以及测试支架背后的原因记录在 开发服务器测试支架设计文档 中——在修改测试支架本身之前请先阅读。
浏览器 playground
大多数开发引擎回归测试都作为 浏览器 playground 进行测试,而不是单元测试。playground 是一个很小的应用,由进程内开发服务器提供给一个真实的 Chromium 页面,位于:
playground/<name>/每个 playground 通常包含:
dev.config.mjs # rolldown 开发配置(浏览器平台,不设置 dev.port)
index.html # 在 / 提供
main.js # 入口;相对输入路径从此目录解析
package.json # 工作区成员(复制现有的一个)
__tests__/<name>.spec.ts # 规范(保留在源代码中,绝不会被复制)测试支架会根据规范文件的路径发现 playground,将其复制到 playground-temp/<name>/,在由操作系统分配的端口上启动一个进程内开发服务器,打开 Chromium 页面并导航到该页面——因此添加测试只需要一个文件夹加一个规范文件,不需要编辑任何中央注册表。
规范文件从 ~utils 别名导入辅助函数;在运行时,测试支架已经启动服务器并导航了 page:
import { describe, expect, test } from 'vitest';
import { editFile, page, waitForBuildStable } from '~utils';
describe('<name>', () => {
test('应用一次 HMR 更新', async () => {
editFile('main.js', (code) => code.replace('hello', 'world'));
await expect.poll(() => page.textContent('h1')).toBe('world');
});
});与服务器的异步工作同步——绝不要 sleep
使用 expect.poll 轮询 DOM,在后续编辑前 await waitForBuildStable(),或者使用 untilBrowserLogAfter 等待浏览器日志。固定的 sleep 既不稳定又慢。
构建和运行
在修改 Rust 或 dev-server 的 src/ 之后,重新构建一次——测试导入的是编译后的 dist/,而不是 TypeScript 源码:
just build-rolldown
pnpm --filter @rolldown/test-dev-server build然后,在 packages/test-dev-server/tests/ 中运行:
# 单个 playground
pnpm exec vitest run --config=vitest.config.e2e.mts playground/<name>
# 整个 browser 套件
pnpm test:browser一个规范文件 vs. 多个规范文件
同一个 playground 中的多个规范文件会并发运行(文件级并行),每个都会基于共享的 playground-temp/<name>/ 副本 fork 出自己的开发服务器。只有在场景彼此独立时这才是安全的——每个规范文件只导航自己的 DOM,并且只编辑自己的文件(这就是 lazy-compilation 的四个规范如何共存的原因)。当多个场景共享同一个 bundle/入口,以至于一个场景的编辑会重建另一个规范文件的页面时,就应该把它们放在单个规范文件中(这就是 hmr-full-bundle-mode 的场景为何是一个规范文件)。
同一个规范文件中的测试共享一个 page 并按顺序运行,因此不要让它们相互干扰。保持编辑只向前推进——或者恢复它们所做的更改,因为后面的测试不能依赖前一个测试的编辑已经被回滚。并且在任何 reload 之后都要重新获取元素句柄(page.locator(...) / 重新 page.$);reload 会使旧句柄失效。
最好为每个场景提供自己独立的 DOM 节点,这样一个测试的编辑就不会干扰另一个测试的断言——hmr-full-bundle-mode 将 .app、.hmr、.hmr-error 和 .rebuild-error 分开,每个场景一个。
冷启动 playground
有些延迟编译 bug 只有在新服务器的第一次请求中才能复现。添加 __tests__/serve.ts,这样测试支架会启动服务器但不会导航,从而让规范文件自己发起第一次请求:
import type { DevServerHandle, ServeContext } from '~utils';
export async function serve(ctx: ServeContext): Promise<DevServerHandle> {
return ctx.createServer();
}Node fixtures
对于 node 平台,使用 fixtures/<name>/ 加上 fixtures.test.ts(通过 pnpm test:fixtures 运行)——开发服务器构建到磁盘,并将构建产物作为 node 子进程运行。当浏览器 playground 已经可以覆盖行为时,不要在那里添加普通的 HMR / lazy / overlay 回归测试。
Rollup 行为对齐测试
我们也通过将 Rollup 自身的测试运行在 Rolldown 上,来实现与 Rollup 的行为对齐。
为此,packages/rollup-tests/test 中的每个测试用例都会代理到项目根目录中 rollup git 子模块里的对应测试。
在设置项目时,运行 just setup 后应该已经初始化了 git 子模块,但你还应该在运行 Rollup 测试之前执行 just update-submodule 来更新该子模块。
在 /packages/rollup-tests 中:
just test-node-rollup将运行 rollup 测试。just test-node-rollup --update将运行并更新测试状态。
要运行特定测试,请在 just test-node-rollup 中使用 --grep 选项:
just test-node-rollup --grep "function"这将只运行名称匹配 "function" 的测试。有关更多过滤选项,请参阅 Mocha 的 grep 文档。
NOTE
某些 Rollup 测试需要特定版本的 Node.js 才能运行。测试会在其 _config.js 文件中指定 minNodeVersion,当运行的 Node 版本低于所需版本时会自动跳过。除非你的 Node 版本是 24 或更高,否则通过的测试数量会不同。
如何选择测试技术
我们的 Rust 测试基础设施已经强大到足以覆盖 JavaScript 的大多数情况(插件、在配置中传入函数)。 但由于 JavaScript 侧用户仍然是我们的第一类用户,如果可能的话,尽量把测试放在 JavaScript 侧。 以下是一些关于你应该使用哪种测试技术的经验。
TLDR
如果你不想把时间浪费在决定该用哪种方式上,就在 JavaScript 侧添加测试。
优先使用 Rust
- 测试由 rolldown core 发出的 warning 或 error。
- 矩阵测试,假设你想测试一组不同的 format,使用
configVariants你只需一个测试就能做到。 - 与链接算法相关的测试(tree shaking、chunk splitting)。这些测试可能需要大量调试,在 Rust 侧添加测试可以减少编码-调试-编码循环的时间。
优先使用 JavaScript
以上未提到的任何类别,都应放在 JavaScript 侧。
