☰
MongoDB 仓库中的 Resmoke Bazel 规则:在 Bazel 中运行 jstest 套件、多版本测试与模糊测试
2026/10/10 4:34:17 网站建设 项目流程

MongoDB 仓库中的 Resmoke Bazel 规则:在 Bazel 中运行 jstest 套件、多版本测试与模糊测试

【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo

本指南系统讲解 MongoDB 开源仓库中bazel/resmoke提供的 Bazel 构建规则,涵盖三大核心能力:用resmoke_suite_test在 Bazel 中驱动 resmoke 执行 jstest 套件、用multiversion_setup下载旧版本 MongoDB 二进制进行多版本(multiversion)测试,以及用jstestfuzz_generate生成随机化 JS 模糊测试。读完本文,你将掌握resmoke_suite_test的完整属性语义、srcs 自动推导原理、测试分片与日志产物定位,以及如何通过 pin 标志复现一次精确的测试失败。

一、整体架构:三条规则的分工

bazel/resmoke是一组将 resmoke 测试执行流程"搬到" Bazel 的规则集合,入口文档为 bazel/resmoke/README.md。它包含三个核心规则/宏:

规则作用实现文件
resmoke_suite_test把一个 resmoke 套件(YAML 配置)包装成 Bazel test targetresmoke.bzl
multiversion_setup调用db-contrib-tool setup-repro-env下载旧版 MongoDB 二进制multiversion/multiversion.bzl
jstestfuzz_generate调用 jstestfuzz 生成随机化.js测试文件jstestfuzz.bzl

配套实现还包括 resmoke_shim.py(resmoke 的 Bazel 适配层)、derive_suite_selectors.py(预构建期从套件 YAML 推导测试文件标签)、resmoke_config_generator.py(生成套件配置)以及 jstestfuzz_run.sh(模糊测试生成包装脚本)。

二、resmoke_suite_test:把 resmoke 套件变成 Bazel 测试目标

resmoke_suite_test是核心入口,它把一个 resmoke 套件包装成可被bazel test执行的测试目标。规则的实际形态是一个宏(macro),内部最终创建py_binary(resmoke shim)和一个私有规则_resmoke_test——后者在 resmoke.bzl 中实现为一个"重新实现的 py_test":生成一个 launcher 脚本,负责展开$(location)/make-variable、构造DEPS_PATH环境变量并 exec resmoke shim。

2.1 最小示例

load("//bazel/resmoke:resmoke.bzl", "resmoke_suite_test") resmoke_suite_test( name = "core", config = "//buildscripts/resmokeconfig:suites/core.yml", data = [ "//jstests/libs:all_subpackage_javascript_files", ], deps = [ "//src/mongo/db:mongod", "//src/mongo/shell:mongo", ], )

其中config指向的 buildscripts/resmokeconfig/suites/core.yml 是仓库中真实存在的套件配置,其selector.roots以 glob 形式声明测试文件,例如jstests/core/**/*.js。

2.2 属性详解

属性类型必填默认值说明
nameName是—测试目标唯一名称
configLabel是—套件的 resmoke YAML 配置,必须含selector.roots字段,运行时直接传给 resmoke
srcsLabel 列表否[]测试源文件覆盖项;为空时由预构建生成器从套件 YAML 的selector.roots自动推导
dataLabel 列表否[]测试运行时所需的额外文件,典型如jstests/libs下的 JS 库
depsLabel 列表否[]测试依赖的 MongoDB 二进制(mongod、mongos 等),运行时被放置到 PATH 上
resmoke_args字符串列表否[]追加传给 resmoke 运行器的命令行参数
shard_count整数否None将套件拆分为多少个并行分片执行,详见"测试分片"
multiversion_depsLabel 列表否[]multiversion_setup目标,其下载目录通过--multiversionDir传给 resmoke

从源码看(resmoke.bzl),宏还接受config_roots(显式 selector 根路径/glob 字符串,用于 corpus 是规则产出目录的套件)、test_root_granularity("file" 或 "directory")、timeout(默认"eternal")等高级参数。运行时框架还会自动注入一批默认参数(见 resmoke.bzl):--archiveMode=directory、--archiveLimitMb=500、--testTimeout、--releasesFile、--historicTestRuntimes、--tssTestList、--mongoVersionFile等;在 Evergreen 环境(--//bazel/resmoke:in_evergreen=True)下还会追加--log=evg、--cedarReportFile=cedar_report.json、--continueOnFailure等参数。

2.3 srcs 自动推导的底层机制

当srcs为空时,宏不会直接去读 YAML,而是查找预构建生成器derive_suite_selectors.py产出的 .resmoke_suites_derived.bzl 中的SUITE_SELECTORS字典(键为//package:suites/xxx.yml形式的 config label,值为对应 srcs 标签列表)。生成器在 wrapper_hook 预构建阶段运行:

  • 扫描 buildscripts/resmokeconfig/suites 与matrix_suites/generated_suites等固定套件目录,并通过 resmoke_modules.yml 发现模块套件目录;
  • 只解析每个 YAML 的selector:块(derive_suite_selectors.py),将roots中的 glob 映射为 Bazel 标签:dir/*.js映射为//dir:all_javascript_files、dir/**/*.js映射为//dir:all_subpackage_javascript_files,复杂模式则通过文件系统 glob 展开并去重;
  • 跳过使用from_target的套件;对没有 roots 的 selector 记为空列表。

当srcs被显式提供时,宏会创建一个*_config目标,由resmoke_config_generator.py生成一份以解析后 roots 替换原 roots 的新配置;当srcs与config_roots均为空时(passthrough 模式),原 YAML 配置原样传给 resmoke。若 config 在SUITE_SELECTORS中找不到且未显式给srcs,宏会直接fail,提示提供显式srcs或确保套件 YAML 含selector.roots。

2.4 resmoke shim:Bazel 与 resmoke 之间的适配层

每个测试目标最终执行的是 resmoke_shim.py 包装后的 resmoke.py。shim 承担了大量适配工作,值得了解:

  • 路径与依赖注入:读取DEPS_PATH(deps中二进制的路径,冒号分隔)并将其各目录追加到PATH,保证 mongod/mongos/mongo 可被 resmoke 找到;通过PYTHON_IMPORTS_FILE把 data 依赖声明的 Python import 路径加入PYTHONPATH,使 JS 测试里runNonMongoProgram拉起的 Python 子进程能导入同样包;
  • 短路径符号链接:把TEST_TMPDIR、TEST_UNDECLARED_OUTPUTS_DIR在/tmp下创建随机短名符号链接(避免超长路径问题),并把 runfiles 根下的仓库内容符号链接进工作目录;
  • 参数规范化:默认追加--storageEngineCacheSizeGB=4(inMemory 引擎)或--storageEngineCacheSizeGB=1,注入 Evergreen build 信息(--buildId、--taskId、--gitRevision等,来源于 volatile-status),追加--basePort(通过acquire_local_resource("port_block")获取端口块)、--reportFile、--dbpathPrefix、--archiveDirectory等;
  • 分片透传:当 Bazel 设置TEST_SHARD_INDEX/TEST_TOTAL_SHARDS时,将其翻译为 resmoke 的--shardIndex/--shardCount;
  • multiversion 标签注入:读取MULTIVERSION_CONFIG_FILE与MULTIVERSION_VERSIONS,为last-lts/last-continuous/last-patch追加对应的 FCV tag 到--excludeWithAnyTags(resmoke_shim.py)。

三、测试分片(Test Sharding)

当指定shard_count时,Bazel 会把同一测试目标并行运行多次(最多达到指定的分片数),每个分片拿到唯一的索引(0 到 N-1),resmoke 据此决定每个分片运行的测试子集,每个分片产出独立的输出与日志。注意:分片是 resmoke--jobs标志的替代方案,resmoke_suite_test中不应再使用--jobs。

源码层面有一个值得注意的细节:Bazel 原生shard_count属性上限为 50(resmoke.bzl 中注释明确 "Having more than 50 shards is indicative of poor test organization")。需要超过 50 分片的套件,宏会把原生属性钉在上限,并通过_forced_shards_transition配置转换把真正的分片数写入--test_sharding_strategy=forced=N(resmoke.bzl)。该行为受--//bazel/resmoke:support_extended_shard_count标志控制(默认 True,见 BUILD.bazel)。

四、测试日志与输出目录

Bazel 为每次测试运行在 workspace 根下的bazel-testlogs符号链接中创建独立输出目录。以目标//jstests/suites/query-execution:core为例:

bazel-testlogs/jstests/suites/query-execution/core/ ├── test.log # 主测试输出日志,包含 resmoke.py 的输出 ├── test.outputs/ │ ├── report.json # JSON 格式测试结果 │ ├── resource_usage.txt # 周期性记录的资源使用指标 │ └── data/ # resmoke fixture 的数据目录 │ └── job0/ │ ├── mongorunner/ │ └── resmoke/ │ ├── WiredTiger* │ ├── journal/ │ └── diagnostic.data/

其中resource_usage.txt由 resource_monitor.py 通过ResourceMonitor周期性写入;分片模式下报告文件名为report_shard_<index>_of_<count>.json。本地运行(非--log=evg)时数据目录保留在test.outputs/data,便于调试后直接检查 WiredTiger 数据与诊断信息。

五、实用命令

5.1 从套件中运行单个测试

bazel test //jstests/suites/query-execution:core --test_sharding_strategy=disabled --test_arg=jstests/core/js/jssymbol.js

--test_sharding_strategy=disabled避免分片干扰,--test_arg把单个测试路径透传给 resmoke。

5.2 携带额外 resmoke 标志运行

任何--test_arg都会被传播为 resmoke.py 的标志。下面的命令以timeseries命名匹配jstests/core下所有.timeseries.js测试,重复执行 2 次,并开启全部 feature flag:

# Runs all tests from the core suite with timeseries in their name, twice, with all feature flags enabled. bazel test //jstests/suites/query-execution:core \ --test_sharding_strategy=disabled \ --test_arg=--repeatTests=2 \ --test_arg=--runAllFeatureFlagTests \ --test_arg=--skipExcludedTests \ `fdfind -t f --full-path ".timeseries\.js$" jstests/core | awk '{print "--test_arg=" $0}'`

(示例中fdfind可用find jstests/core -name "*.timeseries.js"之类的等价命令替代。)此外,Evergreen 环境相关的标志位于 BUILD.bazel:--//bazel/resmoke:in_evergreen、--//bazel/resmoke:installed_dist_test、--//bazel/resmoke:skip_deps_for_cquery均为 bool_flag。

六、multiversion_setup:多版本测试的二进制下载与标签排除

multiversion_setup通过db-contrib-tool setup-repro-env下载旧版 MongoDB 二进制,并为last-lts、last-continuous版本生成配套的排除标签。底层实现(multiversion/multiversion.bzl)实际执行:

db-contrib-tool setup-repro-env <version> --edition <edition> --installDir <output_dir>/.install --linkDir <output_dir>

下载动作被标记为no-cache、no-remote-cache、no-sandbox、no-remote,因为二进制来自 Evergreen 且会随新构建变化,且 db-contrib-tool 会读取声明输入之外的凭据文件。

6.1 预定义目标与版本

一组预定义目标位于//bazel/resmoke/multiversion(定义见 multiversion/BUILD.bazel):

目标版本
//bazel/resmoke/multiversion:last-ltslast-lts
//bazel/resmoke/multiversion:last-continuouslast-continuous
//bazel/resmoke/multiversion:7.07.0
//bazel/resmoke/multiversion:8.0.168.0.16

要针对特定版本测试,需新增一个multiversion_setup目标。

6.2 属性

属性类型必填说明
nameName是目标唯一名称,同时作为<name>_exclude_tags目标的名称前缀
versionString是传给db-contrib-tool setup-repro-env的 MongoDB 版本串,如"7.0"、"8.0.16"、"last-lts"、"last-continuous"

edition(enterprise/community)不由用户设置,宏会根据构建配置自动选择:当//bazel/config:build_enterprise_enabled为真时用enterprise,否则用targeted(multiversion.bzl)。由于该目标会在构建期从 Evergreen 下载外部二进制,其可见性被限制在//buildscripts与//jstests子包内,以维护构建可复现性(见 multiversion/BUILD.bazel)。

6.3 在 resmoke_suite_test 中使用多版本

通过multiversion_deps传入一个或多个multiversion_setup目标:

load("//bazel/resmoke:resmoke.bzl", "resmoke_suite_test") resmoke_suite_test( name = "multiversion_sanity_check_last_continuous_new_new_old", config = ":multiversion_sanity_check_last_continuous_new_new_old.yml", multiversion_deps = [ "//bazel/resmoke/multiversion:last-continuous", ], deps = [ "//src/mongo/db:mongod", "//src/mongo/shell:mongo", ], )

宏会把每个 multiversion dep 展开为 resmoke 的--multiversionDir参数,并注入MULTIVERSION_CONFIG_FILE、MULTIVERSION_VERSIONS环境变量(resmoke.bzl)。

6.4 Exclude tags:自动跳过不兼容测试

对last-lts与last-continuous版本,multiversion_setup会创建配套的<name>_exclude_tags目标,运行resmoke.py generate-multiversion-exclude-tags(实现见 multiversion.bzl),产出的 YAML 会在该multiversion_setup出现在multiversion_deps中时自动通过--tagFile传给 resmoke,从而无需额外配置即可跳过与旧版二进制不兼容的测试。对于last-patch版本同样会生成排除标签;其他普通版本则生成一个空的无操作标签文件。

shim 还会基于 multiversion 配置追加--excludeWithAnyTags:固定排除multiversion_incompatible,backport_required_multiversion,并根据版本追加 FCV 相关 tag(resmoke_shim.py)。这里依赖的 etc/backports_required_for_multiversion_tests.yml 与 src/mongo/util/version/releases.yml 都是仓库内真实存在的配套数据。

6.5 用固定二进制复现失败

每个multiversion_setup目标都有一个名为<name>-pin的string_flag。在命令行传入它,会让db-contrib-tool下载指定构建而非当前最新版:

bazel test //buildscripts/resmokeconfig:multiversion_sanity_check_last_continuous_new_new_old \ --//bazel/resmoke/multiversion:last-continuous-pin=<evg-version-id>

标志接受db-contrib-tool setup-repro-env能理解的任何标识符:

值示例
Evergreen version ID6172c9b65623435a4c0bdb1a
完整 git commit hashd9c83ee0c93970029e41234c77dc20b2c5ca6291
Evergreen task IDmongodb_mongo_master_enterprise_rhel_80_..._22_02_16_03_30_27

含多个 multiversion dep 的测试,需为每个版本分别传一个 pin 标志。pin 标志由宏用string_flag(name + "-pin")创建(multiversion.bzl)。

从历史运行中找到 EVG version ID:每次测试运行都会写入multiversion-downloads.json,记录实际下载的 Evergreen version,并按运行保留在测试输出中:

bazel-testlogs/.../test.outputs/multiversion-downloads-last-continuous.json

(shim 在 resmoke_shim.py 中把每个--multiversionDir下的该文件复制到未声明输出目录。)用该文件中的 ID 配合<name>-pin即可用完全相同的二进制复现失败。

6.6 多版本对等版本的可用性计算

仓库还通过 multiversion/version_compat.bzl 中的 module extension 计算两个布尔常量:

  • LAST_CONTINUOUS_IS_LAST_LTS:当 last-continuous 解析到与 last-lts 相同的 FCV 或 EOL 版本时为 True,此时专属 last-continuous 套件会与 last-lts 套件完全重复,应被跳过;
  • LAST_PATCH_HAS_GA_RELEASE:当前系列是否已有早于 HEAD 的 GA 发布标签,False 时(如 master 上只有 alpha 标签)db-contrib-tool无二进制可下载,专属 last-patch 套件会被标记为 incompatible 而非构建失败。

两者分别成为 multiversion/BUILD.bazel 中bool_flag的默认值,可在命令行覆盖(如--//bazel/resmoke/multiversion:last_patch_has_ga_release=True),这也是某些刻意针对预发布对等版本(如 disagg 套件针对 Atlas RC)测试的逃生通道。

七、jstestfuzz_generate:生成随机化模糊测试

jstestfuzz_generate调用 10gen/jstestfuzz 生成随机化的.js测试文件,并通过srcs喂给resmoke_suite_test。

7.1 示例

load("//bazel/resmoke:jstestfuzz.bzl", "jstestfuzz_generate") load("//bazel/resmoke:resmoke.bzl", "resmoke_suite_test") jstestfuzz_generate( name = "jstestfuzz_generated", npm_command = "jstestfuzz", num_generated_files = 10, ) resmoke_suite_test( name = "jstestfuzz", srcs = [":jstestfuzz_generated"], config = ":suites/jstestfuzz.yml", deps = [ "//src/mongo/db:mongod", "//src/mongo/shell:mongo", ], )

7.2 属性详解

属性类型必填默认值说明
nameName是—目标唯一名称
num_generated_files整数是—要产出的.js测试文件总数(跨全部分片),转发给 jstestfuzz 的--numGeneratedFiles
shard_count整数否0并行生成 action 数;每个分片用base_seed + shard_index的种子向自己输出目录生成约num_generated_files/shard_count个文件;0表示自动分片,约每分片 15 个文件
seedString否""本目标固定种子;设置时覆盖--//bazel/resmoke:jstestfuzz_seed,留空则用该标志(默认每次构建随机)
npm_commandString否"jstestfuzz"jstestfuzz 的package.json中要运行的 npm 脚本,如jstestfuzz、agg-fuzzer、query-fuzzer、update-fuzzer、rollback-fuzzer
js_testsLabel否None作为--jsTestsDir传给 jstestfuzz 的模板语料库;默认 None(不传);对会修改既有语料库的命令(如jstestfuzz、agg-fuzzer)可设为//jstests:all_subpackage_javascript_files或其子树
extra_args字符串列表否[]原样转发给 jstestfuzz 的额外 CLI 标志,如['--opType', 'moveCollection']

生成动作通过 jstestfuzz_run.sh 执行:用规则提供的 Hermetic Node.js 16.18.1(BUILD.bazel,仅支持 Linux aarch64/x86_64 执行平台)解包预制的 jstestfuzz 源码包,运行 npm 脚本,并把产出的.js收集进声明输出目录。分片并行生成时,每个分片的种子为base_seed + seed_offset,且 jstestfuzz 会把种子嵌入文件名,保证分片输出互不冲突。由于输出依赖种子(默认来自 volatile-status 的构建时间戳,每次构建不同),未固定种子时动作标记为no-cache(见 jstestfuzz.bzl)。

7.3 复现失败:种子 + 上游提交

要逐字节复现一次失败,两个标志必须一致:jstestfuzz 使用的种子,以及其生成所基于的上游提交。

标志固定的内容
--//bazel/resmoke:jstestfuzz_seed=<n>传给 jstestfuzz 的种子(默认每次构建随机)
--repo_env=JSTESTFUZZ_COMMIT=<sha>要克隆的上游 jstestfuzz 提交(默认master最新提交)

示例:

bazel test //buildscripts/resmokeconfig:jstestfuzz \ --//bazel/resmoke:jstestfuzz_seed=42 \ --repo_env=JSTESTFUZZ_COMMIT=f21b49c53824f677af60f766eda029366e82513e

从历史运行中找种子与提交:每次测试运行都把两个值记录到test.outputs/:

bazel-testlogs/.../test.outputs/jstestfuzz_seed.txt bazel-testlogs/.../test.outputs/jstestfuzz_commit_sha.txt

这两个文件由 shim 在 resmoke_shim.py 中把生成目录里的.jstestfuzz_seed与.jstestfuzz_commit_sha元数据复制到未声明输出目录得到。此外,BUILD.bazel 中还有--//bazel/resmoke:jstestfuzz_branch(转发给 jstestfuzz 的--branch,选择生成的测试可使用的服务端特性,默认master)与--//bazel/resmoke:config_fuzz_seed(配置模糊测试种子,留空时从 volatile-status 推导,使所有分片共享同一种子)两个相关标志。

八、配套基础设施与测试选择服务

除上述三条规则外,bazel/resmoke还包含与 Evergreen 测试选择服务(Test Selection Services, TSS)相关的组件:generate_tss_test_list.py为每个套件生成测试列表文件,download_historic_runtimes.py从 S3 拉取历史测试运行时长用于测试超时预测(对应--historicTestRuntimes参数),tss_settings则把--//bazel/resmoke:enable_test_selection与--//bazel/resmoke:test_selection_strategies两个构建设置物化成文件供 genrule 读取。enable_test_selection默认关闭,由 evergreen/resmoke_tests_execute_bazel.sh 根据是否为 patch build 决定是否开启。

九、总结

bazel/resmoke把 MongoDB 的 jstest 测试体系完整地融入了 Bazel 的构建与测试模型:resmoke_suite_test通过预构建生成器自动推导套件测试文件、以 shim 适配 resmoke 的路径与参数习惯,并提供原生分片支持;multiversion_setup解决了多版本测试中旧二进制下载、不兼容测试自动排除与失败复现三大痛点;jstestfuzz_generate则把随机化模糊测试变成可复现、可分片的 Bazel 目标。三者的组合让开发者可以用bazel test一条命令,复现从单元级 jstest 到跨版本、跨引擎模糊测试的完整测试矩阵,并且每一个失败都能通过 pin 标志、种子与提交 SHA 精确还原。

【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询