Mojo 编译器(KGEN)测试体系实战指南:从 lit 测试编写到 Bazel 运行全流程
【免费下载链接】mojoThe Modular Platform (includes MAX & Mojo)项目地址: https://gitcode.com/GitHub_Trending/mo/mojo
本文围绕 Mojo 编译器(KGEN)的测试基础设施展开,系统讲解测试目录布局、bazelw test运行方式、%parse-mojo-isolated等测试工具链、RUN 行模式、FileCheck 断言约定与诊断测试写法。读完后你将能够独立为 Mojo 编译器的前端解析、优化 Pass 或集成行为编写符合规范的 lit 测试,并理解每个测试工具背后的源码实现。
1. 测试政策:每次变更都必须附带测试
Mojo 编译器团队没有专门的 QE(质量保障)部门,因此核心政策是:每个开发者(本质上每一个包含功能变更的 PR)都必须附带测试。这不是可选的"建议",而是提交流程的硬性要求——这也解释了为什么仓库中Mojo/test/目录下有上千个测试文件:测试与实现代码是同等地位的交付物。
2. 测试目录布局
仓库将测试按编译器阶段拆分到不同目录,各目录承担的职责与主工具如下:
| 目录 | 用途 | 主要工具 |
|---|---|---|
Mojo/test/mojo-parser/ | 前端单元测试(词法、解析、类型检查) | %parse-mojo-isolated |
Mojo/test/mojo-integration/ | 全链路编译器测试(elaboration、codegen、执行) | %mojo、kgen |
Mojo/test/kgen/transforms/ | 单个优化 Pass 测试 | kgen-opt |
Mojo/test/test-packages/ | 供隔离测试使用的标准库桩(stubs) | N/A |
从 Mojo/test/kgen/ 目录结构看,kgen 测试实际还细分为lit-dialect、lower-lit、co-to-llvm、kgen-to-llvm等按 IR 阶段划分的子目录,与文档描述的"按阶段隔离"策略一致。
3. 运行测试:bazelw test
测试通过 Bazel 的lit_tests规则(定义于 bazel/api.bzl,底层实现在 bazel/internal/lit.bzl)构建与运行。常用命令:
# 运行全部 parser 测试 ./bazelw test //Mojo/test/mojo-parser:mojo-parser # 运行单个测试文件 ./bazelw test //Mojo/test/mojo-parser:closures/unified_closure.mojo.test # 运行集成测试 ./bazelw test //Mojo/test/mojo-integration:test # 带 ASAN(AddressSanitizer)运行 ./bazelw test --config=asan //Mojo/test/mojo-parser:mojo-parser查看 Mojo/test/mojo-parser/BUILD.bazel 可以看到lit_tests目标mojo-parser的srcs通过glob(["**/*.mojo", "**/*.test"])收集所有测试文件,tools则注入了//Mojo/tools/kgen-opt、//Mojo/tools/kgen-translate和//Mojo/tools/mojo(部分测试会预编译包以覆盖字节码加载路径)。集成测试的对应目标定义在 Mojo/test/mojo-integration/BUILD.bazel,目标名为test。
4. 测试二进制与工具链
| 工具 | 用途 |
|---|---|
%parse-mojo-isolated | 使用测试桩解析 Mojo(kgen-translate -import-mojo的别名) |
kgen-translate | 解析 Mojo 源码,输出 MLIR(LIT dialect) |
kgen-opt | 运行 MLIR 优化 Pass |
kgen | 完整编译器驱动(elaboration、codegen) |
%mojo/mojo | 完整的 Mojo 编译器与运行时 |
FileCheck | LLVM 输出验证工具 |
4.1 关于 %parse-mojo-isolated:它是一个 lit 替换,不是独立二进制
%parse-mojo-isolated并不是一个单独的可执行文件——它是定义在 Mojo/test/mojo-parser/lit.cfg.py 中的 lit 变量替换(substitution)。查看该文件源码可以确认其展开形式:
translate_with_prebuilt_packages = ( "kgen-translate -import-mojo -mojo-enable-prebuilt-packages" " -mojo-search-paths={0}".format(config.parser_stubs_source) ) config.substitutions.append(("%parse-mojo-isolated", translate_with_prebuilt_packages))即最终展开为:
kgen-translate -import-mojo -mojo-enable-prebuilt-packages \ -mojo-search-paths=Mojo/test/test-packages其设计意图是:让解析器只加载最小的标准库桩,而不是完整标准库,从而使测试更快、隔离性更强。lit.cfg.py同时配置了测试套件名mojo-parser、识别的文件后缀[".mojo", ".test"]以及排除列表(如debuginfo/inputs、lsp/inputs等非测试数据目录)。
4.2 标准库桩(test-packages)
所有 parser 测试都可以访问位于 Mojo/test/test-packages 的极小标准库桩。实际目录中包含std(子包builtin、collections、format、prelude、reflection)与docs_package等桩模块。因此测试中无需使用裸 MLIR 类型或字面量——可以直接写Int和42。
文档同时强调:不推荐在 Mojo 语言或编译器测试中使用真实标准库。标准库本身在Mojo/stdlib/test中单独测试,编译器测试应保持自包含,只依赖自身与 test-packages 桩。
5. 常见 RUN 行模式
5.1 Parser 测试(mojo-parser)
# 基础解析输出校验 # RUN: %parse-mojo-isolated %s | FileCheck %s # 错误/诊断校验 # RUN: %parse-mojo-isolated -verify-diagnostics %s # 附带额外 Pass(如控制流降级、生命周期检查) # RUN: %parse-mojo-isolated %s | kgen-opt -lower-semantic-cf -check-lifetimes | FileCheck %s # 预期失败(错误场景) # RUN: not %parse-mojo-isolated %s 2>&1 | FileCheck %s真实示例:Mojo/test/mojo-parser/parameters.mojo 首行的 RUN 为%parse-mojo-isolated %s | kgen-opt --kgen-print-inline-type-values | FileCheck %s,即"解析 → 附加 Pass 美化类型值 → FileCheck 校验 IR 签名",与文档模式完全对应。
5.2 集成测试(mojo-integration)
# 完整编译并执行 # RUN: %mojo %s | FileCheck %s # elaboration 测试 # RUN: kgen -elaborate %s --verify-diagnostics # GPU 编译 # RUN: %mojo-build --target-accelerator="nvidia:sm_90a" %s -o %t例如 Mojo/test/mojo-integration/closures.mojo 使用# RUN: %mojo -debug-level full %s 2 3 | FileCheck %s,把编译产物直接带参数执行并校验标准输出——这正是集成测试"编译 + 运行"双重的典型形态。
5.3 Transform 测试(kgen/transforms)
# 单个 Pass # RUN: kgen-opt -automatic-inline %s | FileCheck %s # 多个 Pass # RUN: kgen-opt -sroa -mem-2-reg %s | FileCheck %s # Pass pipeline # RUN: kgen-opt -pass-pipeline='builtin.module(kgen.func(loop-unrolling, canonicalize))' %s | FileCheck %s真实示例:Mojo/test/kgen/transforms/apply-inliner.mlir 首行为// RUN: kgen-opt -verify-parameters -apply-inliner -split-input-file %s | FileCheck %s,展示了 Pass 名与-split-input-file的组合用法;kgen测试的 Mojo/test/kgen/BUILD.bazel 则注入了kgen、kgen-opt、kgen-translate、hash-mlir、llvm-module-split等工具。
6. 常用命令行标志
| 标志 | 作用 |
|---|---|
-verify-diagnostics | 校验expected-error/expected-warning注解 |
-split-input-file | 按// -----分隔符分段处理输入文件 |
-mlir-print-debuginfo | 在 IR 输出中附带调试信息 |
--kgen-print-inline-type-values | 美化打印类型值 |
-o /dev/null | 丢弃输出(用于纯错误测试) |
--mojo-disable-builtins | 不使用标准库桩 |
-allow-unregistered-dialect | 容忍未注册的 MLIR dialect |
其中--kgen-print-inline-type-values在前端测试中出现频率极高(见第 5.1 节parameters.mojo示例),其作用是让内联类型值以更易读的形式输出,从而让 FileCheck 的断言不必依赖不稳定的内部表示。
7. FileCheck 断言约定
7.1 基础模式
# CHECK-LABEL: lit.fn @"function_name" # 节标记(重置状态) # CHECK: expected_text # 匹配包含该文本的行 # CHECK-NEXT: next_line # 匹配紧邻的下一行 # CHECK-SAME: same_line # 在同一行上匹配 # CHECK-NOT: unwanted # 验证某文本不出现 # CHECK-DAG: unordered # 按任意顺序匹配 # CHECK-EMPTY: # 匹配空行7.2 变量捕获
# CHECK: %[[VAR:.*]] = some_op # 捕获匹配文本为 VAR # CHECK: use %[[VAR]] # 引用已捕获的 VAR # CHECK: {{.*}} # 匹配任意字符(通配符)[Mojo/test/mojo-parser/parameters.mojo](https://link.gitcode.com/i/fbfae7d25db02a40df8a2eb7a40ed80b)是一个变量捕获的典型用例:
# CHECK: %[[TMP1:.*]] = kgen.param.constant: !Int = <size> # CHECK: %[[TMP2:.*]] = kgen.param.constant: !Int = <size> # CHECK: %[[TMP3:.*]] = kgen.param.constant: !Int = <size> # CHECK: %[[RES:.*]] = lit.call {{.*}}@"take_3index{{.*}}(%[[TMP1]], %[[TMP2]], %[[TMP3]])先用通配符捕获 SSA 变量名,再断言后续lit.call使用的正是这三个值——这种"捕获-引用"链是验证 IR 结构正确性的常用手法。
7.3 多个 check 前缀
# RUN: ... | FileCheck %s --check-prefix=CHECK-FOO # CHECK-FOO: 特定于 foo 场景的断言当同一个测试文件需要针对同一段输出做不同维度的断言时,可用--check-prefix指定额外的检查前缀集合。
8. 错误与诊断测试
8.1 诊断注解
# expected-error @+1 {{error message}} # 错误出现在下一行 # expected-error @below {{message}} # 错误出现在下一个非注释行 # expected-error @-1 {{message}} # 错误出现在上一行 # expected-warning @+1 {{warning}} # warning 注解 # expected-note @+1 {{note}} # note 注解配合-verify-diagnostics标志启用这些注解的检查。测试通过的条件是:所有预期的诊断都被发出,且没有出现任何未预期的诊断。这一双向约束保证了诊断行为既"齐全"又"干净"。
8.2 错误测试标准范式
# RUN: %parse-mojo-isolated -verify-diagnostics %s def broken(): # expected-error @+1 {{unknown identifier 'foo'}} var x = fooMojo/test/mojo-parser/目录下存在大量以此范式编写的错误测试,如function_types_errors.mojo、lexical_errors.mojo、main_errors.mojo等,命名以_errors结尾是该目录的惯用约定。
9. 测试需求与条件(REQUIRES / UNSUPPORTED)
# REQUIRES: NVIDIA-GPU # 仅在 GPU 可用时运行 # REQUIRES: system-linux # 仅在 Linux 上运行 # UNSUPPORTED: asan # AddressSanitizer 下跳过 # UNSUPPORTED: system-darwin # macOS 上跳过这类特征由 lit 的 feature 机制提供:如 bazel/internal/llvm-lit/lit.common.cfg.py 中会依据环境向config.available_features注册shell、macos-26+、air-objdump等可用特征,REQUIRES/UNSUPPORTED行即按此集合决定是否运行。仓库中Mojo/test/mojo-integration/compile_offload/compile_shared.mojo等文件使用了REQUIRES条件,是实际可用的参考。
10. Lit 变量速查
| 变量 | 含义 |
|---|---|
%s | 测试文件完整路径 |
%S | 测试文件所在目录 |
%t | 临时文件路径(每个测试唯一) |
%t.mojo | 带.mojo扩展名的临时文件 |
GPU 编译类测试(如# RUN: %mojo-build --target-accelerator="nvidia:sm_90a" %s -o %t)正是依赖%t为每个测试生成独立的临时输出文件,避免并行测试间相互覆盖。
11. 编写高质量测试的规范
11.1 命名与体量
- 按特性命名,而不是按工单号(工单号写在注释里,如
# Related to MOCO-1234); - 将相关测试归入同一文件以减少开销;
- 超过约200 行的文件应当拆分;
- 目录用特性名称组织。
11.2 mojo-parser 与 mojo-integration 的取舍
- mojo-parser:前端测试的首选位置,使用不经过完整编译器的隔离解析器;
- mojo-integration:仅当 mojo-parser 无法覆盖时使用(如 elaborator、codegen、后端测试),且需要代码属主(code owner)批准。
这一分级策略与 4.1 节的"最小桩 + 隔离解析"设计一脉相承:绝大多数前端行为都不需要付出完整编译的代价。
11.3 核心清单(Summary)
- 保持测试小而精:每文件目标不超过 200 行;
- 归组相关测试:一个文件放多个相关测试点,降低开销;
- 使用描述性命名:文件名对应特性而非工单号;
- 工单引用走注释:
# Related to MOCO-1234; - 使用测试桩:parser 测试避免依赖真实标准库;
- 优先 mojo-parser:仅在必要时使用 mojo-integration;
- 善用
-split-input-file:在单文件内组织多个独立测试用例(各段以// -----分隔,如 Mojo/test/kgen/transforms/apply-inliner.mlir 所示)。
12. 小结
Mojo 编译器测试体系的精髓可以概括为三层:Bazel + lit 的驱动层(./bazelw test一条命令覆盖数千个测试)、按编译器阶段隔离的执行层(parser 用最小桩走kgen-translate,Pass 走kgen-opt,端到端才动用%mojo)、FileCheck + 诊断注解的断言层(CHECK系列指令配合-verify-diagnostics双向校验)。对新贡献者而言,最实用的入门路径是:先通读 Mojo/docs/compiler/testing.md 本指南,再对照 Mojo/test/mojo-parser/lit.cfg.py 理解变量替换机制,最后参考 Mojo/test/mojo-parser/parameters.mojo 与 Mojo/test/kgen/transforms/apply-inliner.mlir 等真实样例动手编写第一个测试。
【免费下载链接】mojoThe Modular Platform (includes MAX & Mojo)项目地址: https://gitcode.com/GitHub_Trending/mo/mojo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考