Optimism 验收测试(Acceptance Tests)运行指南:在单进程 devnet 中跑通 OP Stack 全栈端到端测试
【免费下载链接】optimismOptimism is Ethereum, scaled.项目地址: https://gitcode.com/GitHub_Trending/op/optimism
本文基于 Optimism 单体仓库(monorepo)中op-acceptance-tests模块的官方运行文档,系统讲解验收测试的定位、运行方式、客户端选择、Kona prestate 构建、依赖编译、并行度调优与日志排查。读者读完可以掌握:如何用just test运行单个或整包验收测试、如何通过环境变量切换 op-node/kona-node 与 op-reth/op-geth 组合、如何构建可复现的 Kona prestate、以及如何利用MarkFlaky与日志产物定位不稳定测试。文中所有命令与配置均以当前仓库的实际实现为准,并给出对应源码路径供深入查阅。
什么是验收测试(Acceptance Tests)
验收测试存放在 op-acceptance-tests/tests/ 目录下,它们不是普通的单元测试,而是在一个进程内(in-process)devnet上运行的完整端到端场景。其核心特征是:
- 在单个
go test进程内启动整个 devnet,覆盖完整技术栈:智能合约、Go 服务(op-node、op-batcher、op-proposer、op-challenger 等)以及 Rust 二进制(kona-node、kona-host、op-reth)。 - 由于依赖全部需要本地构建,因此运行前必须先把所有依赖编译好。
- 测试由
op-devstack/sysgo提供的 devstack 运行时驱动,以普通 Go 测试的形式执行。
官方推荐的执行路径有两种(见 op-acceptance-tests/README.md):
just # 或 just acceptance-test gotestsum -- go test ./tests/...新增验收测试时,只需要在tests目录下添加普通的 Go 测试即可,没有外部注册表或 manifest 需要维护。判断一个测试是否"够格"成为验收测试,标准是:它能描述一个 OP Stack 的用户可见行为,并且在该行为被破坏时大声失败(fail loudly)。
运行验收测试
前置条件
- mise 工具:所有工具版本都在仓库根目录的 mise.toml 中锁定。运行前执行
mise install安装 just、gotestsum、forge 等固定版本工具;若 shell 未激活 mise,需用mise exec --前缀包裹命令(详见 开发工作流文档)。 - C 工具链:需要可用的
clang或gcc,用于 Rust 构建。 - RUST_JIT_BUILD=1:本地运行时务必设置此环境变量。它让测试框架在需要时自动构建所需 Rust 二进制(如 op-reth),利用 cargo 的重建检测(rebuild detection)避免手工预编译。
运行单个测试或包(推荐)
在op-acceptance-tests/目录下执行:
# 运行单个测试 cd op-acceptance-tests && RUST_JIT_BUILD=1 mise exec -- just test -run TestMyTest ./op-acceptance-tests/tests/base/ # 运行整个包 cd op-acceptance-tests && RUST_JIT_BUILD=1 mise exec -- just test ./op-acceptance-tests/tests/base/...just test目标会先构建依赖,然后把你的参数透传给go test -count=1 -timeout 30m。从 op-acceptance-tests/justfile 的实现可以看到,-count=1与-timeout 30m是默认值——只有当参数中没有显式出现-count=或-timeout=时才会追加,因此调用者可以覆盖,例如:
cd op-acceptance-tests && RUST_JIT_BUILD=1 mise exec -- just test -count=10 -run TestMyTest ./op-acceptance-tests/tests/base/运行全部测试
cd op-acceptance-tests && RUST_JIT_BUILD=1 mise exec -- just acceptance-test该目标通过gotestsum运行所有测试包,输出结构化日志,并采用有界的并行度(bounded parallelism)。需要注意的是,acceptance-test覆盖的是整个./op-acceptance-tests/tests/...树,不存在按包划分的子目标。
直接使用 gotestsum(跳过 just 封装)
如果需要完全控制参数,可以直接调用 gotestsum(见 op-acceptance-tests/README.md):
cd op-acceptance-tests mkdir -p results gotestsum --format testname --junitfile ./results/results.xml -- \ -count=1 -p 4 -parallel 4 -timeout 30m ./tests/...just封装会按可用 CPU 数自动计算默认值:包级并发 job 数 = CPU 数,包内t.Parallel数 = CPU 数的一半,超时默认 30 分钟。
选择 L2 客户端组合
每次运行都覆盖整个测试树,但启动哪种 L2 客户端由两个环境变量决定,读取位置在 op-devstack/sysgo/mixed_runtime.go:
| 环境变量 | 可选值 | 默认值 | 作用 |
|---|---|---|---|
DEVSTACK_L2CL_KIND | op-node、kona-node | op-node | 选择 L2 共识层(CL)客户端 |
DEVSTACK_L2EL_KIND | op-reth、op-geth、op-reth-proof-v2 | op-reth | 选择 L2 执行层(EL)客户端 |
从源码可以看到对应的类型定义(MixedL2CLKind/MixedL2ELKind),以及环境变量为空时回退默认值的逻辑:devstackL2ELKind()/devstackL2CLKind()直接读取环境变量,ResolveMixedL2CLKind()在未设置时返回op-node。
使用示例:
cd op-acceptance-tests && DEVSTACK_L2CL_KIND=kona-node RUST_JIT_BUILD=1 mise exec -- just acceptance-testCI 会运行op-acceptance-tests任务的两个变体:memory-all-opn-op-reth-<l1_fork>(op-node + op-reth)与memory-all-kona-op-reth-<l1_fork>(kona-node + op-reth)。
按客户端跳过测试
某个测试在特定客户端下无法运行时,在代码内自行跳过,而不是从测试列表中剔除。相关的跳过辅助函数同样定义在 op-devstack/sysgo/mixed_runtime.go:
sysgo.SkipOnKonaNode(t, reason)— 当 CL 为 kona-node 时跳过;sysgo.SkipOnOpReth(t, reason)— 当 EL 为 op-reth 时跳过;sysgo.SkipOnOpGeth(t, reason)— 当 EL 为 op-geth 时跳过。
此外还有FlakyOnKonaNode(t, reason),它把t.MarkFlaky(reason)限定在 kona-node 组合下生效。
构建 Kona Prestate
部分测试(如 superfaultproofs、interop 故障证明)需要kona prestate。这一点需要特别注意:它不由build-deps或RUST_JIT_BUILD处理。有两种构建方式:
方式一:可复现构建(有 Docker 时首选)
mise exec -- just reproducible-prestate-kona该命令在仓库根目录执行,产出哈希与 CI/发布构建一致的 prestate,可在任何安装了 Docker 的主机上运行。其底层对应根目录 justfile 中的reproducible-prestate-kona目标(内部调用cd rust && just build-kona-reproducible-prestate)。
方式二:原生构建(无 Docker 时的备选)
cd rust && mise exec -- just build-kona-prestates仅适用于 Linux 且安装了 MIPS 交叉编译工具链的环境。这种构建产出的哈希不会与发布构建匹配,因此只适合本地测试运行(不要求哈希与已部署版本一致)。如果既没有 Docker 也没有 MIPS 工具链,请让用户代为构建 prestate。
build-deps 到底做了什么
just build-deps目标(由just test自动调用)在非 CI环境下依次执行以下步骤(见 op-acceptance-tests/justfile 的实现,CI 中因CIRCLECI环境变量存在而直接跳过):
- mise— 执行
mise install,确保 gotestsum、forge 等工具可用; - 合约—
cd packages/contracts-bedrock && just install && just build-no-tests,完成 forge 编译; - Cannon prestates— 执行
just cannon-prestates,构建 kona prestate 产物(根 justfile 中对应cd rust && just build-kona-prestates-auto,在装有 MIPS64 交叉链接器时原生构建,否则回退到 Docker); - Rust 二进制— 执行
just build-rust-release,构建 kona-node、kona-host、op-reth 以及仅用于测试的op-reth-sdm-fixture(该 fixture 不进入生产包与镜像)。
构建是增量的——没有改动时重复运行会非常快。也可以直接运行just build-deps预先构建而不执行测试。
并行度调优(仅acceptance-test)
使用just acceptance-test时,运行器通过三个环境变量控制并行度:
| 环境变量 | 含义 | 默认值 |
|---|---|---|
ACCEPTANCE_TEST_JOBS | 并行测试的包数量(对应go test -p) | 12 |
ACCEPTANCE_TEST_PARALLEL | 每个包内的go test -parallel值 | 1 |
ACCEPTANCE_TEST_TIMEOUT | 每个包的测试超时 | 30m |
覆盖示例:
cd op-acceptance-tests && ACCEPTANCE_TEST_PARALLEL=2 ACCEPTANCE_TEST_TIMEOUT=1h mise exec -- just acceptance-test从 op-acceptance-tests/justfile 的注释可以了解这些默认值的由来:每个ParallelT验收测试都会自建一整套 devstack(L1 geth + op-node + op-reth/op-geth + batcher + proposer + challenger),真正的成本驱动因素是并发的 devstack 数量。此前无界的默认值(-p≈ CPU 数 ~20、-parallel≈ 10)会打满 CPU 和内存,触发全 run 范围的 "context deadline exceeded" 批量失败。因此当前实现收拢为单一并发轴:-parallel=1关闭包内并行(每个包二进制同一时刻只跑一个测试),由-p直接限制并发测试 devstack 数量。默认值 12 远低于观测到的约 30 个并发 devstack 的崩溃阈值,同时墙钟时间接近未设上限时的基线。
日志输出(仅acceptance-test)
使用just acceptance-test时,日志写入op-acceptance-tests/logs/testrun-<时间戳>/目录:
all.log— 完整测试输出;raw_go_events.log— JSON 格式的 Go 测试事件流;flaky-tests.txt— 通过MarkFlaky()标记的测试清单(由 justfile 中的rg -n "MarkFlaky\\("扫描生成);- 失败详情目录
failed/。
JUnit 格式结果 XML 写入op-acceptance-tests/results/results.xml。在 CircleCI 多节点并行场景下,运行器还会通过CIRCLE_NODE_TOTAL/CIRCLE_NODE_INDEX结合历史耗时对测试集分片,每个节点产出独立的results-<index>.xml与raw_go_events-<index>.log。
使用just test时,输出只写到 stdout,不产生上述文件。
MarkFlaky:把不稳定测试的真相留在代码里
如果某个测试在常规验收运行中不稳定(flaky),正确做法是在代码中用devtest.T.MarkFlaky(reason)标记它,让"真相"与测试本身待在一起。其语义(见 op-devstack/devtest/testing.go):
- 将测试后续的失败降级为跳过(skip),除非设置
DEVNET_FAIL_FLAKY_TESTS=true强制其正常失败; - 测试通过时也会被标记为
FLAKY_PASS跳过,从而在统计上不占用绿色结果; - 设置
DEVNET_EXPECT_PRECONDITIONS_MET=true会把所有跳过转为失败(用于严格校验前置条件的 CI 场景)。
MarkFlaky的实现细节:置位flaky标志后,Error/Errorf/Fail/FailNow都会在真正失败前检查shouldSkipFlakyFailure(),命中则输出FLAKY_FAIL: <reason>的注解式跳过;测试结束时若未失败且未强制失败,则输出FLAKY_PASS跳过。该机制有完整的单元测试覆盖,见 op-devstack/devtest/testing_test.go。
注意:MarkFlaky是临时缓解手段,绝不能作为缺失等待条件的替代品——不稳定测试的根治办法是修复缺失的等待条件(详见下文"测试编写要点")。
测试编写要点与最佳实践
虽然本文聚焦于"运行",但了解编写规范有助于理解运行期行为。官方对编写新测试的完整指导见 docs/ai/writing-acceptance-tests.md,核心原则包括:
- 一个测试只验证一个行为:名称用平实的英文描述用户可见行为,如
TestTransferMovesFunds,不要写成TestTransferMovesFundsAndChargesGasAndUpdatesNonce; - 测试表达需求(requirements):通过 DSL 暴露可复用的领域操作,隐藏传输细节、异步等待等机制;测试保留场景特定的策略与断言;
- 禁止
time.Sleep与手写重试循环:可靠性来自等待正确的后置条件(余额、区块号、事件出现、头推进),而不是等待更久;每次等待必须有界超时,失败时给出可操作的信息; - 禁止在生产代码中添加测试专用分支:生产路径与测试路径必须是同一条路径,唯一的变体点是显式、可观察的接缝(preset 选择、DSL 注入的替身、启动时读取一次的启动标志);
- 失败必须自足(self-sufficient):可操作的断言信息、确定性 fixture、可持久化的日志产物,让 CI 失败仅凭日志即可诊断,无需重跑。
典型测试骨架(来自官方文档):
func TestSomething(gt *testing.T) { t := devtest.ParallelT(gt) sys := presets.NewMinimal(t) // 1. Arrange: 通过 DSL 入口播种状态(用户、合约、节点) alice := sys.FunderL2.NewFundedEOA(eth.OneEther) bob := sys.Wallet.NewEOA(sys.L2EL) // 2. Act: 调用单个 DSL 动作方法 alice.Transfer(bob.Address(), eth.OneHundredthEther) // 3. Assert: 通过 DSL 验证方法确认用户可见结果 bob.VerifyBalance(eth.OneHundredthEther) }Kona-SP1 超级根覆盖测试
对于 Kona-SP1 相关覆盖,规范分层与证明边界定义在 rust/kona/sp1/README.md 中,相关验收包运行方式见 op-acceptance-tests/README.md:
# 必需的原生核心覆盖 RUST_JIT_BUILD=1 go test -count=1 -timeout=60m \ ./tests/interop/proofs/serial \ ./tests/interop/proofs-singlechain # 定时全 ELF 覆盖(需先构建 ELF 与执行器) KONA_SP1_ELF_DIR="$PWD/../rust/kona/sp1/elf" \ KONA_SP1_SUPER_RANGE_ELF_EXECUTOR_PATH="$PWD/../rust/target/release/kona-sp1-super-range-executor" \ RUST_JIT_BUILD=1 go test -count=1 -parallel=1 -timeout=120m \ ./tests/interop/proofs/sp1日志级别与格式配置
通过go test运行时,devstack 验收测试支持用 CLI 标志或环境变量配置日志(见 op-acceptance-tests/README.md):
| CLI 标志 | 环境变量 | 作用 |
|---|---|---|
--log.level LEVEL | LOG_LEVEL | 日志级别(debug/info/warn/error) |
--log.format FORMAT | LOG_FORMAT | 日志格式 |
--log.color | LOG_COLOR | 是否着色 |
--log.pid | LOG_PID | 是否输出 PID |
示例:
LOG_LEVEL=info go test -v ./tests/interop/message/... -run TestInteropHappyTx常见问题与排查
| 现象 | 解决办法 |
|---|---|
| 缺少 prestate | 运行cd op-acceptance-tests && mise exec -- just build-deps,或在仓库根目录执行mise exec -- just cannon-prestates |
| 合约过期 | 在packages/contracts-bedrock下重新构建:cd packages/contracts-bedrock && mise exec -- just build-no-tests |
| 缺少 Rust 二进制 | 在仓库根目录执行mise exec -- just build-rust-release |
| gotestsum 未找到 | 运行mise install安装全部锁定的工具 |
此外,op-acceptance-tests/README.md 还提醒:部分测试需要 CI 专用环境变量,本地会直接跳过(测试代码中有环境变量守卫);若某个测试行为与预期不符,先检查它是否有环境变量门控。关于更底层的工具链版本约定与常规开发流程,请参阅 docs/ai/dev-workflow.md。
结语
Optimism 的验收测试体系把"单进程内运行完整 devnet"作为设计基点:just test负责增量构建依赖并精准运行指定测试,just acceptance-test提供有界并行、结构化日志与 JUnit 产出的全量运行,环境变量让同一个测试树可以覆盖 op-node/kona-node × op-reth/op-geth 的不同客户端矩阵,MarkFlaky则把不稳定测试的处置权交还给代码本身。理解这些机制后,无论是本地调试、新增场景还是 CI 排障,都能在 op-acceptance-tests/tests/ 这个单一入口上高效推进。
【免费下载链接】optimismOptimism is Ethereum, scaled.项目地址: https://gitcode.com/GitHub_Trending/op/optimism
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考