TiDB BR 备份恢复集成测试体系解析:从单元测试到全集群验证的完整实践指南
本文以 br/tests/README.md 为主线,系统讲解 TiDB BR(Backup & Restore)模块的测试体系:如何运行不依赖外部进程的单元测试、如何搭建真实的 PD + TiKV + TiDB 集群跑集成测试、如何编写和分组提交新的集成测试用例。读完本文后,你将掌握 BR 测试的两套执行方式(make br_unit_test 与 br/tests/run.sh),理解 run_br、run_sql 等工具函数的底层实现,并能独立完成一个新集成测试用例的编写与分组注册。
一、测试体系总览:单元测试与集成测试的边界
BR 的测试被明确划分为两类,这条边界决定了你该用哪套命令:
- 单元测试(
*_test.go,位于源码目录内):绝不依赖任何外部程序(不连真实的 TiDB/TiKV/PD)。这是 README 中用加粗语气强调的原则; - 集成测试(
br/tests/目录):所有依赖外部进程(如真实 TiDB 服务)的测试都在这里,每个测试是一个独立子目录,内含一个run.sh脚本。
从 br/tests/ 目录可以看到,集成测试目前有 90 余个用例,覆盖全量备份(br_full)、增量备份(br_incremental)、物理恢复(br_restore_physical)、PITR 日志备份恢复(br_pitr 系列)、加密(br_encryption、br_crypter)、对象存储(br_s3、br_gcs、br_azblob)、TiKV 故障注入(br_tikv_outage 系列)等核心场景。
二、单元测试:如何运行
2.1 使用 make 目标
运行 BR 全部单元测试:
make br_unit_test
运行特定测试时,把包路径和 Go 测试参数通过 ARGS 传入:
make br_unit_test ARGS='github.com/pingcap/tidb/br/pkg/cdclog --test.v --check.v --check.f TestColumn'
# ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# which package to test more extra test flags
2.2 直接使用 go test
也可以绕过 make 直接用 go test,但有一个关键差异——failpoint 必须手动开关:
make failpoint-enable
go test github.com/pingcap/tidb/br/pkg/cdclog --test.v --check.v --check.f TestColumn
make failpoint-disable
这个注意事项并非空穴来风。查看 Makefile 中 br_unit_test 目标的实现可以确认,make 目标本身就自动包裹了 failpoint 的开关:
br_unit_test: export ARGS=$$($(BR_PACKAGES))
br_unit_test: ## Run BR (backup and restore) unit tests
@make failpoint-enable
@export TZ='Asia/Shanghai';
$(GOTEST) --tags=deadlock,intest $(RACE_FLAG) -ldflags '$(LDFLAGS)' $(ARGS) -coverprofile=coverage.txt || ( make failpoint-disable && exit 1 )
@make failpoint-disable
即:make 路径会在开/关 failpoint 之间执行带 deadlock,intest 构建标签、开启 race 检测的测试并产出 coverage;若你自己用 go test 直跑,就必须像 README 那样手动补上 make failpoint-enable / make failpoint-disable,否则依赖 failpoint 的测试代码不会生效。
三、集成测试:环境准备
3.1 本地直接运行所需的前提
根据 README,本地运行集成测试需要满足三项条件:
(1)TiDB 仓库根目录下的 bin 文件夹中必须存在以下 9 个可执行文件,且版本 ≥ 2.1.0:
bin/tidb-server
bin/tikv-server
bin/pd-server
bin/pd-ctl
bin/go-ycsb
bin/minio
bin/mc
bin/tiflash
bin/cdc
其中 TiFlash 还需要其动态链接库(libtiflash_proxy.so,见 make 的 bin 目标)。README 给出的依赖安装命令:
cd ${WORKSPACE}/tidb
rm -rf third_bin/
rm -rf bin/
br/tests/download_integration_test_binaries.sh
mkdir -p bin && mv third_bin/* bin/
rm -rf third_bin/
make failpoint-enable && make && make failpoint-disable #make tidb
对应的 download_integration_test_binaries.sh 负责下载各组件二进制,download_tools.sh 负责下载辅助工具。
(2)主机上必须安装的系统级工具:mysql(CLI 客户端)、curl、openssl、wget、lsof、psmisc。
(3)权限要求:执行测试的用户必须有权创建 /tmp/backup_restore_test 目录——所有测试产物都会写入该目录。这一点可以从 br/tests/run.sh 得到印证:脚本开头即 export TEST_DIR=/tmp/backup_restore_test,并用 rm -rf $TEST_DIR && mkdir -p $TEST_DIR 重置该目录,之后每个用例的 TEST_DIR 都被传入子进程。
3.2 使用 Docker 容器(推荐)
如果装了 Docker,可以跳过上述第(1)(2)步,直接在 tidb 仓库根目录运行:
br/tests/up.sh --pull-images
该命令会构建并启动一个测试用 Docker 容器。查看 up.sh 的实现可以看到它做了哪些事:
--pull-images拉取pingcap/tidb、pingcap/tikv、pingcap/pd、pingcap/ticdc、pingcap/tiflash、pingcap/tidb-lightning的nightly镜像并打标签;- 用内嵌 Dockerfile 多阶段构建:从各官方镜像中
COPY出tidb-server、tikv-server、pd-server、pd-ctl、cdc、tiflash(连同libtiflash_proxy.so)、minio、mc等二进制到/br/bin,额外用golang:1.16.4-buster现场编译go-ycsb; - 容器内预装
curl、wget、openssl、lsof、psmisc、jq、default-mysql-client——恰好对应 3.1 节第(2)步的依赖清单; - 启动时把当前
br/tests目录下所有文件挂载进容器,并持久化/tmp/br/tests数据与 bash history,已存在的容器会直接attach复用。
up.sh 还提供其他选项:--tag TAG 指定镜像 tag、--bind-bin 挂载宿主机 bin 目录、--cleanup-docker / --cleanup-data / --cleanup-all 用于清理。
四、集成测试:执行流程
4.1 标准执行四步
- 用
make build_for_br_integration_test构建br.test二进制; - 确认 9 个必需可执行文件与
br可执行文件都存在; - 用
export TEST_NAME="<test_name1> <test_name2> ..."选择要跑的测试; - 执行
br/tests/run.sh。
如果前两步此前已经完成,直接跑 tests/run.sh 即可。
查看 Makefile 中该目标的实现,可以看到它除了编译主 br.test 外,还一并编译了若干测试专用辅助二进制——这些二进制对应 br/tests 下含 Go 代码的特殊用例:
build_for_br_integration_test:
@make failpoint-enable
($(GOTEST) -c -cover -covermode=count \
-coverpkg=github.com/pingcap/tidb/br/... \
-o $(BR_BIN).test \
github.com/pingcap/tidb/br/cmd/br && \
$(GOBUILD) $(RACE_FLAG) -o bin/locker br/tests/br_key_locked/*.go && \
$(GOBUILD) $(RACE_FLAG) -o bin/gc br/tests/br_z_gc_safepoint/*.go && \
$(GOBUILD) $(RACE_FLAG) -o bin/fake-oauth tools/fake-oauth/main.go && \
$(GOBUILD) $(RACE_FLAG) -o bin/rawkv br/tests/br_rawkv/*.go && \
$(GOBUILD) $(RACE_FLAG) -o bin/txnkv br/tests/br_txn/*.go && \
$(GOBUILD) $(RACE_FLAG) -o bin/utils br/tests/utils.go \
) || (make failpoint-disable && exit 1)
@make failpoint-disable
注意 br.test 编译时带了 -cover -covermode=count -coverpkg=github.com/pingcap/tidb/br/...,这正是后续能产出覆盖率报告的来源;bin/utils 则由 br/tests/utils.go 编译而来(后文加密校验会用到)。
run.sh 实际完成的事(与 README 描述一致,见 run.sh 实现):
- source
tests/_utils/run_services后在后台以本地存储启动 PD、TiKV、TiDB(服务端口定义见 tests/_utils/run_services:PD 2379、TiDB 4000/10080、TiKV 2016/2018,TIKV_COUNT=3),并带上--debug参数时会在服务启动后暂停等待回车,方便调试; - 通过
https://$PD_ADDR/pd/api/v1/version获取集群版本,解析出CLUSTER_VERSION_MAJOR/MINOR/REVISION传给每个用例; - 查找所有
tests/*/run.sh(或被TEST_NAME过滤后的子集)并逐个执行; trap stop_services EXIT保证退出时清理所有服务进程。
此外 run.sh 还做两件 README 未细述但很重要的事(run.sh):
- 设置
ENABLE_ENCRYPTION_CHECK=true,并生成一个 32 字节 hex 的本地主密钥文件,拼出ENCRYPTION_ARGS(--crypter.method aes128-ctr --crypter.key ... --master-key-crypter-method AES256-CTR --master-key local://...),供需要加密备份的用例使用,从而对所有测试做备份文件是否真正加密的验证; - 向每个用例注入
PD_ADDR、TIDB_ADDR、TIDB_STATUS_ADDR、TIKV_ADDR、TEST_DIR等环境变量,并统一开启BR_LOG_TO_TERM=1让 BR 日志打到终端。
4.2 调试模式
br/tests/run.sh --debug
对应 run.sh 中的实现:服务全部启动后打印提示并阻塞等待回车,你可以从另一个终端连接集群手工调试。另外 make br_integration_test_debug 会执行 tests/run.sh --no-tiflash(见 Makefile),即不带 TiFlash 启动集群。
4.3 覆盖率报告
全部测试执行完毕后:
make br_coverage
覆盖率报告输出在 /tmp/backup_restore_test/all_cov.html。其前提是 br.test 编译时已带 -covermode=count -coverpkg=github.com/pingcap/tidb/br/...(见 4.1 节 Makefile 片段),各用例运行产生的 coverage 数据再由汇总流程合并。
五、测试脚本工具函数:run_br 的完整链路
README 列出的一组便捷函数是所有集成用例的"语法糖":
| 函数 | 作用 |
|---|---|
run_sql <SQL> |
在 TiDB 上执行一条 SQL 查询 |
run_br |
带必要参数执行 br.test |
run_lightning [CONFIG] |
用 tests/TEST_NAME/CONFIG.toml 启动 tidb-lightning |
check_contains <TEXT> |
检查上一条 run_sql 的结果是否包含指定文本(-E 正则格式) |
check_not_contains <TEXT> |
检查上一条 run_sql 的结果不包含指定文本(-E 正则格式) |
这些函数中 run_sql 等定义在共享的 tests/_utils/run_services 中(br/tests/run.sh 第 28 行 source 了它)。而 run_br 本身是一条有趣的"三层链路",结合 br/tests/run_br 源码来看:
- 用例中调用
run_br <args>; run_br脚本先注入--checksum=true,再调用tests/_utils/run_br执行真正的br.test,并追加$ENCRYPTION_ARGS;- 执行完备份后,若
ENABLE_ENCRYPTION_CHECK=true,则调用bin/utils validateBackupFiles --command="$*" --encryption="$ENCRYPTION_ARGS"校验产物。
validateBackupFiles 由 br/tests/utils.go 实现,其逻辑(utils.go):
- 解析命令参数,仅当命令是
backup或restore point且存储路径为local://...时才需要校验(日志备份命令返回后存储中尚无可校验文件,故跳过,输出 "No need to validate"); - 遍历存储路径下所有
.sst文件,读取文件末尾 8 字节检查 RocksDB Block-Based Table 魔数(0xdb4775248b80fb57或0x88e241b785f4cff7)——存在魔数说明是明文 SST,即未加密; - 遍历所有
.log文件,用 zstd decoder 试读 1 字节判断是否被 zstd 压缩; - 根据
--encryption参数是否为空,断言"全部文件均已加密"或"全部文件均未加密",混用则判定失败。
也就是说,run_br 不只是跑备份命令,还自带一道加密正确性的自动化断言——这是 README 未展开、但从源码可以确认的机制。
5.1 用例写法示例
以最简单的 br/tests/br_db/run.sh 为例,一个集成用例的完整形态是:
set -eu
DB="$TEST_NAME"
run_sql "CREATE DATABASE $DB;"
run_sql "CREATE TABLE $DB.usertable1 (...);"
run_sql "INSERT INTO $DB.usertable1 VALUES (\"a\", \"b\");"
# backup db(通过 GO_FAILPOINTS 注入进度回调用 failpoint)
export GO_FAILPOINTS="github.com/pingcap/tidb/br/pkg/task/progress-call-back=return(\"$PROGRESS_FILE\")"
run_br --pd $PD_ADDR backup db --db "$DB" -s "local://$TEST_DIR/$DB"
export GO_FAILPOINTS=""
# restore db
run_sql "DROP DATABASE $DB;"
run_br restore db --db $DB -s "local://$TEST_DIR/$DB" --pd $PD_ADDR
# 校验:表数量、统计信息、DDL 历史
table_count=$(run_sql "use $DB; show tables;" | grep "Tables_in" | wc -l)
[ "$table_count" -ne "2" ] && exit 1
run_curl https://$TIDB_STATUS_ADDR/ddl/history | grep -E '/\*from\(br\)\*/CREATE TABLE'
要点:
- 脚本必须
set -eu风格地保证失败时以非零码退出(README "Writing new tests" 第 1 条的要求); - 用例内可用
GO_FAILPOINTS环境变量注入 failpoint(如上面的进度回调用例),可用run_curl访问 TiDB status 端口做 HTTP 断言; - 日志备份相关用例可以复用 br/tests/br_test_utils.sh 中的
wait_log_checkpoint_advance,它会轮询br log status --json的 checkpoint、必要时用br operator force-flush推进,超过 50 次仍未推进则判失败——这是 PITR 类用例(br_pitr系列)的通用等待模式。
六、编写新测试与 CI 分组
README "Writing new tests" 部分给出的三步流程:
- 新集成测试写成
tests/TEST_NAME/run.shshell 脚本,失败时必须以非零错误码退出; - 推荐把
TEST_NAME加入 run_group_br_tests.sh 中已有的分组,或新建一个分组; - 若新建了分组,分组名必须同步加入 CI 的 br-integration-test 流水线(PingCAP-QE/ci 仓库的
pull_br_integration_test.groovy,脚本注释中已注明此依赖)。
run_group_br_tests.sh 把全部集成用例切成了 9 组(G00–G08),目的是支持并行执行、平衡每组耗时("每个分组尽量消耗同等时间,从而减少 CI 等待时间;多个轻量用例合并一组,重量级用例单独成组")。脚本还内置了防漏保护:遍历 */run.sh 收集所有 br 前缀用例,凡是没被任何分组收录的用例会进入 others 桶——当以 others 组运行时若存在未分组用例会直接报错退出,从而强制开发者把新用例注册进分组。
运行方式:
# 跑某一个分组(如 G01)
br/tests/run_group_br_tests.sh G01
该脚本对每个用例会先重置 /tmp/backup_restore_test,再设置 TEST_NAME 调用 run.sh。
七、小结
- 单元测试走
make br_unit_test(或手动failpoint-enable+go test),原则是不碰外部进程; - 集成测试的前提是
bin/下 9 个二进制 + 6 个系统工具 +/tmp/backup_restore_test写权限,或直接br/tests/up.sh --pull-images用 Docker 环境; - 执行入口是
br/tests/run.sh(可配合TEST_NAME选择用例、--debug暂停调试),覆盖率用make br_coverage汇总; - 新用例 =
tests/TEST_NAME/run.sh+ 注册进run_group_br_tests.sh分组(新分组还要改 CI 配置); - 底层机制上,
run_br在真正执行备份后还会通过 utils.go 的validateBackupFiles对 SST 魔数 / zstd 头做加密断言,这是保证加密备份测试不"假通过"的关键设计。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00