首页
/ TiDB BR 备份恢复集成测试体系解析:从单元测试到全集群验证的完整实践指南

TiDB BR 备份恢复集成测试体系解析:从单元测试到全集群验证的完整实践指南

2026-09-05 20:44:54作者:裴麒琰

本文以 br/tests/README.md 为主线,系统讲解 TiDB BR(Backup & Restore)模块的测试体系:如何运行不依赖外部进程的单元测试、如何搭建真实的 PD + TiKV + TiDB 集群跑集成测试、如何编写和分组提交新的集成测试用例。读完本文后,你将掌握 BR 测试的两套执行方式(make br_unit_testbr/tests/run.sh),理解 run_brrun_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_encryptionbr_crypter)、对象存储(br_s3br_gcsbr_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

这个注意事项并非空穴来风。查看 Makefilebr_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,见 makebin 目标)。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 客户端)、curlopensslwgetlsofpsmisc

(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/tidbpingcap/tikvpingcap/pdpingcap/ticdcpingcap/tiflashpingcap/tidb-lightningnightly 镜像并打标签;
  • 用内嵌 Dockerfile 多阶段构建:从各官方镜像中 COPYtidb-servertikv-serverpd-serverpd-ctlcdctiflash(连同 libtiflash_proxy.so)、miniomc 等二进制到 /br/bin,额外用 golang:1.16.4-buster 现场编译 go-ycsb
  • 容器内预装 curlwgetopenssllsofpsmiscjqdefault-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 标准执行四步

  1. make build_for_br_integration_test 构建 br.test 二进制;
  2. 确认 9 个必需可执行文件与 br 可执行文件都存在;
  3. export TEST_NAME="<test_name1> <test_name2> ..." 选择要跑的测试;
  4. 执行 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 实现):

  1. source tests/_utils/run_services 后在后台以本地存储启动 PD、TiKV、TiDB(服务端口定义见 tests/_utils/run_services:PD 2379、TiDB 4000/10080、TiKV 2016/2018,TIKV_COUNT=3),并带上 --debug 参数时会在服务启动后暂停等待回车,方便调试;
  2. 通过 https://$PD_ADDR/pd/api/v1/version 获取集群版本,解析出 CLUSTER_VERSION_MAJOR/MINOR/REVISION 传给每个用例;
  3. 查找所有 tests/*/run.sh(或被 TEST_NAME 过滤后的子集)并逐个执行;
  4. 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_ADDRTIDB_ADDRTIDB_STATUS_ADDRTIKV_ADDRTEST_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 源码来看:

  1. 用例中调用 run_br <args>
  2. run_br 脚本先注入 --checksum=true,再调用 tests/_utils/run_br 执行真正的 br.test,并追加 $ENCRYPTION_ARGS
  3. 执行完备份后,若 ENABLE_ENCRYPTION_CHECK=true,则调用 bin/utils validateBackupFiles --command="$*" --encryption="$ENCRYPTION_ARGS" 校验产物。

validateBackupFilesbr/tests/utils.go 实现,其逻辑(utils.go):

  • 解析命令参数,仅当命令是 backuprestore point 且存储路径为 local://... 时才需要校验(日志备份命令返回后存储中尚无可校验文件,故跳过,输出 "No need to validate");
  • 遍历存储路径下所有 .sst 文件,读取文件末尾 8 字节检查 RocksDB Block-Based Table 魔数(0xdb4775248b80fb570x88e241b785f4cff7)——存在魔数说明是明文 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" 部分给出的三步流程:

  1. 新集成测试写成 tests/TEST_NAME/run.sh shell 脚本,失败时必须以非零错误码退出;
  2. 推荐TEST_NAME 加入 run_group_br_tests.sh 中已有的分组,或新建一个分组;
  3. 若新建了分组,分组名必须同步加入 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.govalidateBackupFiles 对 SST 魔数 / zstd 头做加密断言,这是保证加密备份测试不"假通过"的关键设计。
登录后查看全文
热门项目推荐
相关项目推荐