Aptos MonoMove 差分测试套件(mono-move-testsuite)完全指南:V1 与 V2 VM 的双轨端到端验证

发布时间:2026/9/18 10:11:18
Aptos MonoMove 差分测试套件(mono-move-testsuite)完全指南:V1 与 V2 VM 的双轨端到端验证 Aptos MonoMove 差分测试套件mono-move-testsuite完全指南V1 与 V2 VM 的双轨端到端验证【免费下载链接】aptos-coreAptos is a layer 1 blockchain built to support the widespread use of blockchain through better technology and user experience.项目地址: https://gitcode.com/GitHub_Trending/ap/aptos-core本文是 aptos-core 仓库中third_party/move/mono-move/testsuite包名mono-move-testsuite的实战技术指南。该测试套件是 MonoMovev2VM 项目质量保障的核心每个 Move 源码或汇编输入都会同时在传统 MoveVMV1与 MonoMovev2上运行并对比行为同时支持将 specializer 的 golden 输出bytecode、stackless IR、micro-ops固化为基线。读完本文你将掌握测试目录组织方式、// RUN:/// CHECK:指令系统的完整语义、.exp基线的更新机制UPBL1以及如何基于 tests/differential.rs 与 src/runner.rs 理解测试的执行管线从而为 MonoMove 新增功能或修复缺陷时写出合格的差分用例。1. 什么是 mono-move-testsuiteMonoMove 是 aptos-core 中对 Move 字节码解释器的重新实现v2其核心目标是与既有 V1 VM 保持行为一致性。mono-move-testsuite正是为验证这一目标而设计的端到端差分differential测试套件一个输入文件两份执行结果强制比对。核心机制如下引自 AGENTS.mdEnd-to-end differential tests for the MonoMove VM: each Move source (or assembly) input runs on both the V1 VM and MonoMove (v2), and their behavior is compared. Inputs may also pin specializer golden output (bytecode, stackless IR, micro-ops).即每个输入同时满足两类约束行为一致性V1 与 V2 对同一输入的执行结果必须一致返回值、事件、错误路径specializer golden 固定输入还可以将--print请求的中间表示快照字节码反汇编、stackless IR、micro-ops固化为基线文件防止编译器/降级器行为漂移。2. 框架与测试发现机制测试使用datatest-stable数据驱动测试框架用例全部位于tests/test_cases/differential/目录下。测试入口在 tests/differential.rslet files: Vec(String, SourceKind) WalkDir::new(tests/test_cases/differential) .follow_links(false) .min_depth(1) .into_iter() .flatten() .filter_map(|e| { let path e.path(); let kind path .extension() .and_then(|ext| ext.to_str()) .and_then(SourceKind::from_extension)?; Some((path.display().to_string(), kind)) }) .collect();从源码可见发现规则是按扩展名选择输入类型见 src/compile.rs扩展名SourceKind编译方式.moveMove使用move-compiler-v2v2 编译器编译见compile_move_source.masmMasm使用move-asm汇编器汇编为单一模块见assemble_masm_source对应的编译入口src/compile.rspub fn compile(source: str, kind: SourceKind) - ResultVecCompiledModule { match kind { SourceKind::Move compile_move_source(source), SourceKind::Masm assemble_masm_source(source).map(|m| vec![m]), } }在Cargo.toml中该测试被声明为harness false的自定义测试目标由differential.rs的main自行调度Cargo.toml[[test]] name differential harness false实际用例覆盖了十分广泛的语言特性目录均位于 tests/test_cases/differential/arithmetic算术与窄整型宽度、bool、calls跨模块调用与多返回值、casts、closures、constants、control_flow、enums、generics泛型实例化、global_storage、natives、optimizer、programs、refs、structs、vectors。3. 指令系统Directives完全解析每个输入文件通过嵌入的行内注释// RUN:指令驱动测试管线。指令的解析实现在 src/parser.rs其文件头注释即为最权威的语义文档。一个测试文件是有序的 publish 与 execute 步骤序列每个 execute 步骤可附带若干 check 指令。3.1// RUN: publish [--print(sections)]— 发布模块publish之后的所有非指令行直到下一个// RUN:或文件末尾会被原样收集为 Move 源码文本编译成一个或多个模块并同时发布到两个 VM 的测试存储中。多个 publish 块的模块在整个测试期间持续累积。--print(sections)可选修饰符请求生成.exp快照支持的分区为src/parser.rs分区含义注意事项bytecodeMove 字节码反汇编对.masm输入非法——字节码本身就是输入无汇编意义stacklessstackless 执行 IR—micro-ops降级后的 micro-ops—frame-layout每函数的 GC 帧布局较新的扩展分区分区无论书写顺序如何都按上表顺序输出未知 token 或空分区列表会在解析期直接导致测试失败bail!。重复分区也会报错。对于 publish 期无法完成降级的函数如未被实例化的泛型函数micro-ops 分区中会渲染为skipped (reason)。例如 generic_identity.exp 中fun identity(): skipped (generic function not instantiated)3.2// RUN: execute addr::mod::fn --args ... [--heap-size n]— 双 VM 执行execute在两个 VM 上调用指定函数并对比行为。语法为// RUN: execute addr::module::function [--args v1, v2, ...] [--heap-size n]--args逗号分隔的十进制字面量列表按函数参数类型解析支持全部整数类型u8..u256/i8..i256以及bool仅接受true/false见parse_bool_arg、address、signer。--heap-size n将 MonoMove 堆设置为n字节。小堆会在分配压力下强制触发垃圾回收GC用于确定性验证 GC 行为。V1 VM 没有该旋钮会直接忽略它见 src/parser.rs 与 src/runner.rs。修饰符可以任意顺序出现--边界按 --切分避免参数值内的--被误切。执行产生的规范化输出形如results: v1, v2成功或error: message失败如 abort。多个返回值和事件会组合渲染返回值以results:开头、事件以events:开头、中间以|连接无返回值且无事件的调用渲染为results:见 src/runner.rs。3.3// CHECK:家族 — 输出匹配// CHECK:指令必须紧跟 execute 指令其字面量与两个 VM 的规范化输出进行比较。解析逻辑见 src/parser.rs 与 src/matcher.rs指令匹配对象匹配方式适用场景// CHECK: literalV1和V2精确匹配trim 后两 VM 应当一致的场景// CHECK-V1: literal仅 V1精确匹配两 VM 有意分歧时如不同的 abort 错误消息// CHECK-V2: literal仅 V2精确匹配同上// CHECK-SUBSTR: patternV1和V2子串匹配abort 消息含易变片段函数定义索引、代码偏移时// CHECK-V1-SUBSTR: pattern仅 V1子串匹配同上单侧// CHECK-V2-SUBSTR: pattern仅 V2子串匹配同上单侧单个 execute 步骤之后可以跟随多个 check 指令每个都被独立验证src/parser.rs。值得注意的源码细节只有步骤实际检查到的 VM 才会被执行needs_v1/needs_v2判定见 src/runner.rs。这让测试可以只断言一侧——某些 native 函数只有一个 VM 能运行例如aggregator_v2V1 测试环境未安装其扩展直接运行会 panic。3.4// CHECK-GC-COUNT: n— GC 次数断言该指令断言 MonoMovev2在上一个 execute 步骤中恰好执行了n次垃圾回收。它是 v2 专属指令V1 VM 没有 GC。与--heap-size配合可以确定性地驱动 GC 次数并直接读取 runner 记录的gc_count进行比对src/matcher.rs。实际示例见 generic_copy_gc.move通过0x0::test_utils::force_gc()主动触发 GC 后用// CHECK-GC-COUNT: 1断言确实发生了 1 次回收同时用// CHECK: results: 203验证跨 GC 的对象拷贝语义正确拷贝后修改副本不影响原对象。3.5// CHECK-ERROR-PARITY— VM 错误一致性该指令不带参数断言两个 VM 都以 VM 错误失败且 v2 的失败在映射回 V1 术语后与 V1 报告的状态码status code、子状态sub-status、消息、错误位置完全一致。预期值不是测试中的字面量而是运行时取自 V1 的实际输出见 src/parser.rs。实现要点src/runner.rs 与 src/matcher.rs这是唯一读取映射后描述而非 MonoMove 原生错误文本的指令因此它直接验证了mono-move-output中v1_error::describe的 V1 等价映射逻辑若错误没有 V1 等价形式、映射无法复现 V1 的消息或子状态、或 MonoMove 未能定位失败位置指令都会失败Move abort 不在覆盖范围内abort 不携带可映射的 VM 错误且两个 VM 本就以相同方式渲染 abortaborted: code n (msg) in location见render_abort所以用CHECK:直接比较即可。若对 abort 误用本指令matcher 会明确报错提示改用CHECK:。4. 基线Golden文件与UPBL1更新机制每个带有--print分区的输入都有一个同名name.exp基线文件与输入文件同目录存放。例如 basic_arithmetic.move 对应 basic_arithmetic.exp其中依次固化了bytecode、stackless、micro-ops三个分区 bytecode // Bytecode version v10 module 0x1::test // Function definition at index 0 fun add(l0: u64, l1: u64): u64 move_loc l0 move_loc l1 add ret stackless // module 0x1::test fun add(r0, r1): u64 { slots: params(2), locals(0), temps(1) r0: u64 r1: u64 r2: u64 code: L0: 0: r2 : add r0, r1 2 1: ret [r2] 3 } micro-ops // module 0x1::test fun add() { frame_data_size: 24 entry_gas: 59 code: 0: AddU64 [16] - [0] [8] 2 1: Move8 [0] - [16] 3 2: Return 3 }基线验证与更新的写入逻辑位于 src/runner.rsif !snapshot.is_empty() { let baseline test_path.with_extension(exp); move_prover_test_utils::baseline_test::verify_or_update_baseline(baseline, snapshot)?; }规范要求基线更新必须是可解释的针对本次变更而不是盲目覆盖。每次修改编译器、specializer 或降级器后都应检查.exp的 diff 是否符合预期再决定是否用UPBL1刷新。5. 运行测试两条核心命令引自 AGENTS.md# 对照基线进行验证推荐日常使用 cargo test -p mono-move-testsuite --test differential # 更新基线仅当你确认变更导致 .exp 内容变化是正确且可解释的时候 UPBL1 cargo test -p mono-move-testsuite --test differentialUPBL1环境变量切换verify_or_update_baseline的行为验证模式检查快照与.exp一致并失败于不一致更新模式则将新快照写回.exp文件。6. 执行管线源码剖析理解 src/runner.rs 的run_test即可把握整个管线核心步骤如下初始化创建GlobalContext1 个执行 worker与执行守卫为 V1 构建RuntimeEnvironment与InMemoryStorage为 V2 构建InMemoryModuleProvider。发布 prelude将完整 Move stdlib与test_utils库模块同时注入两个 VMprelude_modules见 src/runner.rs因此测试源码可以直接调用真实 stdlib native。stdlib 只编译一次并通过OnceLock全局共享。逐步处理Publish步骤compile(sources, kind)编译/汇编模块 → V1 侧序列化后直接插入内存存储跳过完整发布工作流的兼容性检查对差分测试足够→ V2 侧由 loader惰性构建可执行体 → 若请求了--print分区则渲染快照累积到snapshot。Execute步骤按needs_v1/needs_v2决定运行哪些 VM → V1 用MoveVM::execute_loaded_functionV2 用共享管线引擎with_mono_function→ 归一化输出返回值 事件→check_output逐条验证 check 指令。基线落盘snapshot非空时与.exp基线验证或更新。两个关键归一化机制保证了对比的公平性参数/返回值类型统一V1 与 V2 共享PrimitiveKind描述src/runner.rs它镜像了 MonoMove 的帧槽布局size/align因此同一字节缓冲既可用于 V1 的 BCS 序列化也可用于 V2 的原始帧存储。V1 的结果用于推导参数种类param_kinds/return_kinds当步骤跳过 V1 时则通过load_signature_v1仅加载签名来推导。native 扩展对齐V1 侧 seed 了与 MonoMove 侧相同的固定输入交易哈希、脚本哈希、链 ID、状态大小等见TEST_*常量与seed_extensions保证扩展支持的 native如transaction_context、event在两侧产生一致输出。V1 的 native 表还会用测试专用 native 覆盖同名生产 native确保比较的是相同实现src/runner.rs。7. 完整示例解读7.1 最简 Move 用例basic_arithmetic.move 是理解整套机制的最小闭环// RUN: publish --print(bytecode,stackless,micro-ops) module 0x1::test { fun add(a: u64, b: u64): u64 { a b } } // RUN: execute 0x1::test::add --args 3, 5 // CHECK: results: 8 // RUN: execute 0x1::test::add --args 0, 0 // CHECK: results: 0它同时演示了发布 三分区 golden 固定、两个 execute 步骤、以及CHECK对双 VM 的联合断言。7.2 汇编输入.masmadd_values.masm 演示了直接用字节码汇编编写用例且只请求stackless, micro-ops两个分区bytecode对 masm 非法// RUN: publish --print(stackless,micro-ops) module 0x66::test fun add_values(a: u64, b: u64): u64 copy_loc a copy_loc b add ret // RUN: execute 0x66::test::add_values --args 1, 2 // CHECK: results: 3这类用例的价值在于可以构造编译器前端无法直接产出的特殊字节码序列如copy_loc使用、非标准参数排布直接考验降级器的正确性。7.3 泛型与 skipped 标记generic_identity.move 展示泛型实例化发布阶段无法为未实例化的identityT生成 micro-ops因此 golden 中标记skipped (generic function not instantiated)但通过call_identity_u64完成实例化后执行路径完全可用// RUN: publish --print(bytecode,stackless,micro-ops) module 0x1::test { fun identityT(x: T): T { x } public fun call_identity_u64(x: u64): u64 { identityu64(x) } } // RUN: execute 0x1::test::call_identity_u64 --args 42 // CHECK: results: 428. 为 MonoMove 贡献差分用例的实践建议基于上述机制为 MonoMove 新增或修复功能时建议优先添加.move用例绝大多数语言特性都可以用 v2 编译器前端直接表达先到tests/test_cases/differential/下合适子目录添加源码与// RUN: publish --print(...)。特殊降级路径用.masm当需要覆盖编译器前端无法产出的字节码形态寄存器复用、非常规参数排布、非单调返回等参考arithmetic/add_values.masm、calls/下的 masm 用例手工汇编。GC 相关改动必配--heap-sizeCHECK-GC-COUNT参考 generic_copy_gc.move用小堆配合test_utils::force_gc()确定性触发回收再断言 GC 次数与跨 GC 语义。VM 错误路径用CHECK-ERROR-PARITY凡是会产生 VM 错误的场景而非 Move abort优先用该指令锁定 V1 的 status / sub-status / message / location同时验证mono-move-output的错误映射。合并前审查.expdiff跑UPBL1更新基线后人工确认每个分区尤其 micro-ops的变化都对应本次有意为之的降级变化。9. 进一步阅读指令语义权威定义src/parser.rs 文件头注释执行与基线逻辑src/runner.rsCHECK 匹配实现src/matcher.rs编译/汇编入口与SourceKindsrc/compile.rs测试入口与用例发现tests/differential.rs全部差分用例tests/test_cases/differential/测试包配置Cargo.toml【免费下载链接】aptos-coreAptos is a layer 1 blockchain built to support the widespread use of blockchain through better technology and user experience.项目地址: https://gitcode.com/GitHub_Trending/ap/aptos-core创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询