WAMR BA Issues 回归测试框架实战:从 issue 复现到自动化回归
【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit
导读
本文以 WAMR(WebAssembly Micro Runtime)仓库内的 BA Issues 回归测试目录(lib/wasm-micro-runtime-WAMR-2.4.1/tests/regression/ba-issues)为蓝本,系统讲解如何把来自 GitHub issue、模糊测试(fuzzing)报告中的异常 wasm 用例沉淀为可重复执行的回归测试:从helper.sh批量创建用例目录、build_wamr.sh编译多版本wamrc/iwasm运行环境,到在running_config.json中声明运行配置、再用run.py一键回归并解析结果。读完本文,你将掌握一套可直接复用的"bug 复现 → 用例收纳 → 自动回归"工作流,并能针对新 issue 扩展自己的测试条目。
说明:该测试框架位于本仓库内嵌的 WAMR 2.4.1 源码树中,相关脚本与配置均可在
lib/wasm-micro-runtime-WAMR-2.4.1/tests/regression/ba-issues/目录下直接查看。
一、BA Issues 是什么:为每个 bug 建立回归护城河
BA Issues 是 WAMR 维护者针对历史 bug(Bug/Issue)建立的回归测试集合。其核心思想非常朴素:每一个被修复或确认的 issue,都对应一个(或多个)能够触发该 bug 的 wasm 文件,将其按issues/issue-<编号>/目录收纳,并配以"期望的退出码 + 期望的标准输出",从而保证:
- 修复过的 bug 不会在后续版本中"死灰复燃"(回归);
- 通过 ASan(AddressSanitizer)构建的运行时能持续发现内存安全类问题(如
out of bounds memory access、double free、SIGSEGV); - 同一类问题(例如来自同一轮 fuzz 报告的多个用例)可以共享一份运行配置,降低维护成本。
从当前仓库的目录结构看,issues/下已收纳了从 issue-2700 到 issue-3514 的大量用例,issues-deprecated/下则存放因 spec 变更而弃用的用例(如 issue-47 至 issue-84 的PoC.wasm),与 README 描述一一对应。
整个工作流可以概括为四步:
- 用
helper.sh创建issues/issue-xxx目录(必要时解压 zip 用例); - 若新用例需要不同的构建配置,在
build_wamr.sh中追加构建命令; - 在
running_config.json中为新用例添加运行配置; - 用
run.py运行测试并检查结果。
二、helper.sh:批量创建 issue 用例目录
helper.sh的作用是快速为测试用例创建目录,并在需要时解压 zip 格式的用例文件。它在处理"一次性收纳大量 issue 用例"的场景下尤其高效。
# 创建 issues/issue-2944 至 issues/issue-2966 共 23 个目录 ./helper.sh 2944 2966 # 只创建单个目录 issues/issue-2999 ./helper.sh 2999 # 创建目录,并解压目录内的所有 *.zip 文件(-x 选项) ./helper.sh -x 2944 2966从 helper.sh 的源码可以看到其具体行为:
- 脚本会先
cd issues,再调用create_directory()函数创建issue-$1目录; - 传入两个参数时,会校验第二个参数不小于第一个参数,然后从
num1循环到num2逐个建目录(helper.sh); - 启用
-x后,会遍历目录下所有*.zip文件并unzip -o解压到同目录,随后删除原 zip(helper.sh); - 脚本内部注释还保留了
wasm2wat --enable-all PoC.wasm -o PoC.wast的参考用法,提示你可以将 wasm 反汇编为 wat 文本以便审查。
实操提示:目录命名必须遵循
issue-<数字>规范。run.py正是通过正则issue-(\d+)扫描issues/issue-*目录来收集待测用例 ID 的(见 run.py)。
三、build_wamr.sh:构建多版本 wamrc 与 iwasm
回归测试需要覆盖 WAMR 的多种执行模式(classic-interp 经典解释器、fast-interp 快速解释器、fast-jit、llvm-jit、AOT 等),因此需要编译多个配置版本的iwasm运行时。直接运行:
./build_wamr.sh该脚本会依次完成两类构建(build_wamr.sh):
1. 编译wamrc(AOT 编译器):build_wamrc函数先执行wamr-compiler目录下的./build_llvm.sh准备 LLVM 依赖,再在build/build-wamrc下通过 CMake 编译wamr-compiler工程(build_wamr.sh)。
2. 编译多个变体的iwasm:build_iwasm函数接受两个参数——"CMake cache 变量配置"和"运行时名称",在build/build-iwasm-<名称>目录下编译,并统一附加-DCMAKE_BUILD_TYPE=Debug -DWAMR_BUILD_SANITIZER=asan,即所有回归运行时都开启 AddressSanitizer 检测(build_wamr.sh)。脚本内置了以下构建变体:
| 运行时名称 | CMake 配置要点 | 覆盖的测试模式 |
|---|---|---|
default | REF_TYPES + AOT + FAST_INTERP | fast-interp、AOT |
default-gc-enabled | GC + AOT + FAST_INTERP + SPEC_TEST | GC 相关 spec 用例 |
llvm-jit | REF_TYPES + JIT | llvm-jit |
multi-tier-jit | REF_TYPES + FAST_JIT + JIT | classic-interp、fast-jit、llvm-jit、multi-tier-jit |
default-wasi-disabled | 同上 default 但WAMR_BUILD_LIBC_WASI=0 | 禁用 WASI 的 fast-interp、AOT |
llvm-jit-wasi-disabled | 同上 llvm-jit 但WAMR_BUILD_LIBC_WASI=0 | 禁用 WASI 的 llvm-jit |
multi-tier-jit-wasi-disabled | 同上 multi-tier-jit 但WAMR_BUILD_LIBC_WASI=0 | 禁用 WASI 的 multi-tier 系列 |
例如,要新增一个"禁用 WASI + 开启 FASTER_INTERP + BULK_MEMORY + FAST_JIT"的变体,只需在脚本末尾按以下格式追加一行:
# 格式:build_iwasm "CMake cache 变量配置" "运行时名称" build_iwasm "-DWAMR_BUILD_LIBC_WASI=0 -DWAMR_BUILD_LIBC_BUILTIN=1 -DWAMR_BUILD_REF_TYPES=1 -DWAMR_BUILD_BULK_MEMORY=1 -DWAMR_BUILD_JIT=1 -DWAMR_BUILD_FAST_JIT=1" "multi-tier-wasi-disabled"上面这行会编译出build/build-iwasm-multi-tier-wasi-disabled/iwasm,你便可以在 running config 的runtime字段中引用它。脚本末尾的 TODO 注释也提示:后续还可以继续扩展 SGX 等更多版本的运行时。
四、running_config.json:声明用例如何运行与断言
测试如何运行、以什么作为通过标准,全部由 running_config.json 顶层"test cases"数组中的每个条目决定。一个完整条目的字段含义如下:
| 字段 | 说明 |
|---|---|
deprecated | 是否弃用;为true时该条目下的所有用例被跳过,不参与运行 |
ids | 该配置覆盖的 issue 编号列表,可多个 issue 共享一份配置 |
runtime | 使用的运行时名称,对应build/build-<runtime>/iwasm |
file | 用例文件名,支持通配符*.wasm(在issues/issue-<id>/目录内 glob 匹配) |
mode | 执行模式:classic-interp、fast-interp、fast-jit、llvm-jit、aot等 |
options | 传给iwasm的运行时选项,如--heap-size=0 -f to_test |
argument | 传给被测试函数的参数 |
expected return | 断言对象:ret code(期望退出码)、stdout content(期望 stdout 内容,支持模糊匹配)、description(用例描述) |
compile_options(可选) | 需要先用wamrc编译 AOT 时的编译配置,见下文第三种模式 |
4.1 仅运行 iwasm 的简单配置
这是最常见的形式。以 issue-2955 为例,它在fast-interp模式下运行iwasm_fast_interp_unexpected_value.wasm,期望以退出码 0 结束并输出0x44e5d17eb93a0ce:i64:
{ "deprecated": false, "ids": [ 2955 ], "runtime": "iwasm-default-wasi-disabled", "file": "iwasm_fast_interp_unexpected_value.wasm", "mode": "fast-interp", "options": " --heap-size=0 -f to_test", "argument": "", "expected return": { "ret code": 0, "stdout content": "0x44e5d17eb93a0ce:i64", "description": "expected output 0x44e5d17eb93a0ce:i64" } }4.2 多个用例共享一份配置(通配符匹配)
来自同一轮 fuzz 报告的多个用例,往往触发的是同一类问题、期望同一份输出。此时可以把它们的 id 全部放进同一个ids数组,并用通配符匹配文件名。例如 issue-2966、2964、2963、2962 四个用例都以fast-jit模式运行、期望输出0x0:i32:
{ "deprecated": false, "ids": [ 2966, 2964, 2963, 2962 ], "runtime": "iwasm-multi-tier-wasi-disabled", "file": "*.wasm", "mode": "fast-jit", "options": " --heap-size=0 -f to_test", "argument": "", "expected return": { "ret code": 0, "stdout content": "0x0:i32", "description": "expected output 0x0:i32" } }在真实的 running_config.json 中可以看到类似的分组:2966/2964/2963/2962 共享0x0:i32的断言,2961/2960/2959/2958 则共享0x1:i32的断言,充分体现了"同类用例合并配置"的维护思路。
4.3 仅编译(只使用 wamrc)
有些用例只需要验证"能否被wamrc成功编译为 AOT 文件",不需要运行。此时使用compile_options并设置"only compile": true:
{ "deprecated": false, "ids": [ 2956 ], "compile_options": { "compiler": "wamrc", "only compile": true, "in file": "*.wasm", "out file": "out.aot", "options": "--target=x86_64", "expected return": { "ret code": 0, "stdout content": "", "description": "" } } }README 中标注该示例为 dummy config(占位示例),正式使用时应替换为真实用例。此配置对应的实际编译命令由 run.py 中的
COMPILE_AOT_COMMAND = "./build/build-wamrc/{compiler} {options} -o {out_file} {in_file}"拼装。
4.4 先编译再运行(wamrc + iwasm 组合)
当用例需要先编译为 AOT 文件再交给iwasm运行时,将"only compile"设为false,并在外层再补充运行相关的字段:
{ "deprecated": false, "ids": [ 2956 ], "compile_options": { "compiler": "wamrc", "only compile": false, "in file": "*.wasm", "out file": "out.aot", "options": "--target=x86_64", "expected return": { "ret code": 0, "stdout content": "", "description": "" } }, "runtime": "iwasm-multi-tier-wasi-disabled", "file": "out.aot", "mode": "aot", "options": " --heap-size=0 -f to_test", "argument": "", "expected return": { "ret code": 0, "stdout content": "0x0:i32", "description": "expected output 0x0:i32" } }该流程对应run.py中的run_issue_test_wamrc()(先编译并断言编译结果)与run_issue_test_iwasm()(再以aot模式运行out.aot并断言运行结果)两个函数串联执行(run.py)。
4.5 处理弃用用例(deprecated)
WebAssembly spec 持续演进,部分用例可能因 spec 变更而失效。当确认"不是 WAMR 的 bug,而是用例本身应当废弃"(例如通过wasm-validate等工具验证)后,应当:
- 将用例目录移动到
issues-deprecated/下; - 在其 running config 中把
"deprecated"设为true。
示例如下(README 原文案例,对应 issue-47 至 issue-84 的经典解释器回归用例):
{ "deprecated": true, "ids": [ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84 ], "runtime": "iwasm-default", "mode": "classic-interp", "file": "PoC.wasm", "argument": "", "expected return": { "ret code": 0, "stdout content": "", "description": "no segfault" } }run.py遇到deprecated为true的条目时会直接跳过(run.py),目录issues-deprecated/中 issue-47 至 issue-84 的PoC.wasm即为实际落盘证据。
五、run.py:一键回归与结果解读
5.1 运行方式
在ba-issues目录下直接执行:
# 运行全部用例 ./run.py # 只运行指定 issue(--issues / -i 缩写) ./run.py --issues 2833 # 测试 issue #2833 ./run.py -i 2833,2834,2835 # 测试 3 个 issue:#2833 #2834 #2835-i参数会按逗号切分并逐个转为整数(run.py)。脚本每次运行前会删除上一次遗留的issues_tests.log,保证日志干净可追溯(run.py)。
5.2 执行与断言逻辑
run.py的核心执行流程如下:
- 通过
get_issue_ids_should_test()扫描issues/issue-*目录,用正则issue-(\d+)提取全部待测 issue ID(run.py); - 解析
running_config.json,逐条遍历test cases;跳过 deprecated 条目;对每个ids中的 issue,先与"目录中真实存在的 issue"取交集(run.py); - 若配置含
compile_options则先执行run_issue_test_wamrc();only compile为false时编译成功后继续执行run_issue_test_iwasm(); run_issue_test_iwasm()中,mode与命令行参数的映射关系为(run.py):classic-interp→ 追加--interp;fast-interp→ 不追加任何模式参数(默认即 fast-interp);- 其他模式(如
fast-jit、llvm-jit)→ 追加--<mode>; aot→ 使用不含模式参数的 AOT 运行命令模板TEST_AOT_COMMAND。
- 断言规则(run.py):实际退出码须等于期望
ret code;stdout 的匹配采用"精确相等或子串匹配"的宽松策略——若期望内容长度大于 30 个字符,或期望内容恰为"Compile success",则只要实际输出包含该期望串即判为通过,这一设计让超长异常消息(如带地址的报错)也能稳定断言。
5.3 结果输出与常见场景解读
全部跑完且通过时,输出形如:
==== Test results ==== Total: 22 Passed: 22 Failed: 0 Left issues in folder: no more Cases in JSON but not found in folder: no more场景一:目录里有用例,但忘记在 JSON 中添加配置。此时该用例不会被消费,结果中会出现Left issues in folder: #3022:
==== Test results ==== Total: 21 Passed: 21 Failed: 0 missed: 0 Left issues in folder: #3022 Cases in JSON but not found in folder: no more场景二:JSON 中配置了错误 id,或目录中缺少对应用例目录。结果中会出现Cases in JSON but not found in folder: #12345:
==== Test results ==== Total: 21 Passed: 21 Failed: 0 missed: 0 Left issues in folder: #2855 Cases in JSON but not found in folder: #12345场景三:存在失败用例。Failed计数非零,细节写入issues_tests.log:
==== Test results ==== Total: 22 Passed: 21 Failed: 1 Left issues in folder: no more Cases in JSON but not found in folder: no more5.4 失败日志的解读
失败详情会以固定格式追加到issues_tests.log(格式定义见 run.py,写入逻辑见dump_error_log())。例如:
======================================================= Failing issue id: 2945. run with command_lists: ['./build/iwasm-default-wasi-disabled', '--heap-size=0', '-f', 'to_test', '.../issues/issue-2945/iwasm_fast_interp_moob_unhandled.wasm'] exit code (actual, expected) : (1, 0) stdout (actual, expected) : ('Exception: out of bounds memory access', 'Exception: out of bounds memory access') =======================================================这条日志告诉我们:issue-2945 的实际退出码为 1、期望为 0,说明运行虽然抛出了正确的Exception: out of bounds memory access,但进程退出码与断言不符——这正是需要人工判定的"回归失败"信号。该用例对应的 JSON 配置可在 running_config.json 中找到(期望ret code为 1、输出Exception: out of bounds memory access),README 中的日志示例恰好展示的是配置修改前的失败状态。
六、真实用例速览:从配置反推 bug 形态
通过结合 running_config.json 与issues/目录,可以快速理解这套框架实际守护的 bug 类别(下表全部基于当前仓库已存在的配置与用例):
| 类别 | 典型 issue | 期望行为 | 说明 |
|---|---|---|---|
| 内存越界 | 2857 / 2858 | 退出码 1,输出Exception: out of bounds memory access | 验证越界访问以异常形式抛出,而非崩溃 |
| 除零异常 | 2946 / 2948 | 退出码 1,输出Exception: integer divide by zero | fast-interp 下除零必须被正确捕获 |
| 整数溢出 | 2947 | 退出码 1,输出Exception: integer overflow | 防止 double free 类内存破坏 |
| 非法模块 | 2863 / 3130 / 3137 | 退出码 255,输出WASM module load failed: ... | 畸形 wasm 应优雅报错,而非触发 sanitizer 堆溢出 |
| 错误返回值 | 2955 / 2956 | 退出码 0,输出特定 i64 值 | 修复"指令计算结果错误"类 bug |
| 错误异常 | 2949 / 2950 / 2951 / 2952 | 退出码 0,无异常输出 | 修复"本不该抛异常却抛出"的 bug |
| 禁止 SIGSEGV | 3090 / 3122 | 退出码 0 或 1,绝无unhandled SIGSEGV/ ASan SEGV | 内存安全底线守护 |
| 浮点语义 | 3062 | 退出码 0,输出nan:f32 | 修正 fast-interp 的浮点结果错误 |
| 表访问越界 | 3021 / 3023 | 退出码 1,输出Exception: out of bounds table access | bulk memory 相关指令(如table.init)的边界检查 |
| GC 用例 | 315101 / 315102 | 退出码 0,输出in specttest.print_i32(4) | GC 特性下的确定性输出验证 |
这些用例名称也极具自描述性:如iwasm_fast_interp_moob_unhandled.wasm(fast-interp 未处理的越界内存访问)、iwasm_fast_interp_div_zero_double_free2.wasm(除零引发的二次释放)、iwasm_jit_without_exception.wasm(JIT 未抛异常)等,命名即标注了"引擎 + 模式 + 问题类型",便于快速定位。
七、从零接入新 issue 的完整流程
综合以上内容,为 WAMR 添加一个新的 BA issue 回归用例的完整步骤为:
- 准备用例文件:拿到能够稳定触发该 bug 的 wasm(通常来自 issue 附件或 fuzz 报告),确认其可复现;
- 创建目录:运行
./helper.sh <issue编号>(批量则传两个参数,zip 用例加-x)生成issues/issue-<编号>/并把 wasm 放进去; - 确认构建配置:若现有 7 个运行时变体已覆盖所需模式(fast-interp / fast-jit / llvm-jit / classic-interp / aot 及是否启用 WASI),则无需改动;否则在
build_wamr.sh末尾用build_iwasm "<CMake 变量>" "<运行时名称>"追加新变体,然后./build_wamr.sh重新编译; - 编写运行配置:在
running_config.json的test cases数组中新增条目,按用例形态选择"仅 iwasm""仅 wamrc 编译""wamrc + iwasm 组合"三种模板,填写runtime、file、mode、options、argument以及expected return中的退出码、stdout 断言与描述;同一批同类用例可合并ids并使用通配符file; - 回归验证:
./run.py(或./run.py -i <编号>定向验证),确认Passed计数符合预期、Left issues in folder与Cases in JSON but not found in folder均为no more;若有失败,结合issues_tests.log中的实际退出码与 stdout 核对断言是否正确; - (可选)弃用处理:若因 spec 变更确认用例应废弃,将目录移入
issues-deprecated/并将配置中deprecated置为true。
结语
BA Issues 回归测试框架的价值在于"低门槛、强断言、可追溯":一个 wasm 文件 + 一行 JSON 配置,即可把一次线上事故或 fuzz 发现固化成为持续守护 WAMR 各执行模式(解释器、多级 JIT、AOT)与内存安全的自动化防线。对于希望为 WAMR 贡献代码或保障自身 wasm 运行时质量的开发者而言,掌握这套工作流,就等于拥有了"为每个历史 bug 上保险"的标准化方法。相关脚本与全部历史用例均可直接在 lib/wasm-micro-runtime-WAMR-2.4.1/tests/regression/ba-issues 目录中查阅与复用。
【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考