TiDB 测试 Diff 分诊指南:三步定位"与 PR 无关"的测试行为变化
当 TiDB 仓库中的 planner/executor testdata 在 merge、rebase 之后突然发生变化,或单测与全量测试的输出不一致、且 PR 改动的代码无法解释这种变化时,tidb-test-diff-triage 技能提供了一套标准化的分诊流程:先排除 failpoint 环境配置问题,再通过 git bisect 隔离 merge 引入的影响,最后才允许更新期望输出。读完本文,你将掌握 TiDB 测试体系中"环境性问题 / 上游行为变化 / 本地回归"三分类的完整判定方法,并能直接使用仓库内现成的 failpoint 运行脚本和决策依据完成一次可复现、可追溯的排查。
适用场景:什么时候启动分诊流程
该技能定义于 SKILL.md,并在仓库级技能总览 .agents/skills/README.md 中被登记为"triage unexpected test diffs (failpoint vs upstream vs local regression)"的操作性工作流。文档给出的触发条件有三类:
- 出现了测试 diff,但 PR 中实际触碰的代码无法解释该行为变化;
- 单测运行(single test run)与全量套件运行(full-suite run)产生了不同输出;
- merge/rebase 之后,planner/executor 的 testdata 意外改变。
这三类场景的共同特征是:diff 的成因尚未被证明。文档反复强调的核心立场是——在根因被证实之前,不要动期望输出(testdata),否则一次环境性问题会被永久"固化"进仓库。
规则一:先排除 failpoint 配置问题
这是分诊流程的第一步,也是 TiDB 中最容易踩中的坑。文档原文指出:
In TiDB, many test behaviors rely on failpoint instrumentation.
-tags=intest,deadlockdoes not enable failpoints.
也就是说,-tags=intest,deadlock 只是构建标签,它本身并不启用 failpoint。大量测试行为依赖 failpoint 插桩(代码中的 failpoint.Inject / failpoint.Enable 调用),若运行环境未开启 failpoint 转换,测试会走"未插桩"分支,从而产生与 CI 或他人环境不一致的结果。
仓库中确实大量存在这类插桩。例如 pkg/ddl/backfilling_dist_scheduler_test.go 中的分布式回填测试用 testfailpoint.Enable(t, "...", ...) 注入模拟行为,pkg/session/session.go 中也存在 failpoint.Inject("mockCommitError", ...)、failpoint.Inject("preCommitHook", ...) 等注入点。如果这些测试在未启用 failpoint 的状态下运行,行为将与期望输出产生偏离。
决策依据:对受影响的包做 failpoint 检测
技能文档要求对照 docs/agents/testing-flow.md 的 Failpoint decision for unit tests 小节对受影响的包做检查,该小节给出的标准命令是:
rg -n --fixed-strings -- "failpoint." pkg/<package_name>
rg -n --fixed-strings -- "testfailpoint." pkg/<package_name>
# If BUILD.bazel exists, also check failpoint dependency.
test -f pkg/<package_name>/BUILD.bazel && rg -n --fixed-strings -- "@com_github_pingcap_failpoint//:failpoint" pkg/<package_name>/BUILD.bazel
决策规则很直接:
rg命中(代码或BUILD.bazel依赖中出现 failpoint)→ 必须用 failpoint-enabled 方式重跑,并在go test命令中加-count=1保证可复现(避免 Go test 缓存掩盖真实输出);rg无命中 → 按普通方式运行,并在最终报告中说明检查证据。
该要求同时被写入根级策略文件 AGENTS.md 的 Quick Decision Matrix:Unit tests in a package that uses failpoints | MUST enable failpoints before tests and disable afterward。
用 failpoint-enabled 方式重跑
docs/agents/testing-flow.md 推荐的运行方式是仓库提供的封装脚本:
./tools/check/failpoint-go-test.sh pkg/<package_name> -run <TestName> -count=1
阅读 tools/check/failpoint-go-test.sh 的源码可以确认它的完整行为,这正是"脚本在 cleanup 时一定会禁用 failpoint"承诺的实现:
- 未显式传
-tags时,脚本默认使用-tags=intest,deadlock(见第 54 行tags="intest,deadlock"与第 126 行的调用拼装);若测试还需要nextgen标签,应显式传-tags=intest,deadlock,nextgen; - 脚本先在仓库根目录执行
make failpoint-enable,然后进入目标包目录执行go test; - 通过
trap cleanup EXIT INT TERM(第 113 行)保证无论go test成功、失败还是被中断,退出前都会执行make failpoint-disable,避免工作区残留已转换的 failpoint 代码。
对照 Makefile 可知,failpoint-enable / failpoint-disable 目标的实现是调用 tools/bin/failpoint-ctl 完成 gofail 插桩代码的转换与还原(第 343–351 行),注释明确写着 "Converting gofail failpoints..." / "Restoring gofail failpoints..."。
另外文档特别提醒:不要在同一个 worktree 的并行任务中直接调用 tools/bin/failpoint-ctl。因为底层的启用/禁用状态由 tools/check/failpoint-state.sh 序列化——该脚本使用 .git/.failpoint-state/lock 目录锁 + refcount 文件做引用计数(enable 时计数为 0 才真正执行 failpoint-ctl enable 并计数加一,disable 时计数归零才真正还原),并支持 FAILPOINT_LOCK_WAIT_SECONDS(默认 600 秒)等环境变量。绕过它直接跑 failpoint-ctl 会破坏这套互斥与计数机制。
若使用 Bazel 直接运行测试,则应先执行 make bazel-failpoint-enable、测试后执行 make bazel-failpoint-disable;若走 make bazel_test,从 Makefile 第 709 行可见该目标本身已依赖 bazel-failpoint-enable,无需单独启用,但测试结束后仍需执行 make bazel-failpoint-disable。
判定规则
如果启用 failpoint 后 diff 消失,则将该问题分类为环境/配置问题(setup issue),而不是逻辑回归——这解释了为什么"同一段代码,我的本地结果和别人/CI 不一致"在 TiDB 里最常见的答案就是 failpoint 状态不同。
规则二:隔离 merge 的影响
若 failpoint 排除后 diff 依然存在,说明变化大概率来自合并进来的上游提交。文档给出的三步是:
- 用最小目标复现:
-run <TestName> -count=1; - 对比 good/bad 两个 commit,对合并区间做二分;
- 在更新任何期望输出之前,先确定第一个引入坏行为的 commit(first bad commit)。
文档提供的 bisect 命令:
git bisect start
git bisect bad <bad_commit>
git bisect good <good_commit>
这里的实操要点是"先定位、后改输出"。对 TiDB 这种 planner testdata 高度敏感的仓库,merge 引入的优化器行为微调(如代价模型参数、rule 开关变化)会让大片 testdata/*.json 的 plan 文本漂移;此时如果直接全量同步 testdata,等于把上游改动和本地回归混在一个 diff 里,无法区分。bisect 找到 first bad commit 后,可以精确判断每个 testdata 变化是否与该 commit 的意图一致。
规则三:只有根因被证明后才能更新 testdata
文档给出了同步期望 plan/result 的两个且仅两个条件:
- 上游合并有意改变了优化器行为(upstream merge intentionally changed optimizer behavior);
- 现有测试期望本身已过时(stale),但查询语义并未改变。
并明确禁止"在根因被识别之前记录/更新 testdata"。这条规则与前两条规则共同构成一条因果链:症状 → 环境排除 → 提交定位 → 才谈得上改期望。任何一步未走通就动 testdata,都会让测试失去回归检测能力。
对集成测试面,docs/agents/testing-flow.md 还补充了配套约定:tests/integrationtest 的输入在 t/ 目录、期望结果在 r/ 目录,结果文件"通常不需要手工编辑;若必须编辑,保持最小改动并在报告前验证正确性"。
排查记录的输出格式
文档要求排查过程沉淀为结构化笔记,字段固定为六项,便于在 PR 描述或 issue 中直接复用:
| 字段 | 含义 |
|---|---|
Symptom |
diff 到底改了什么 |
Scope |
是单个测试还是全量套件出现差异 |
Failpoint check |
执行的命令与结果(规则一的证据) |
First bad commit |
bisect 得到的 commit hash 与标题(若做了二分) |
Conclusion |
三选一:setup issue / upstream behavior change / local regression |
Action |
三选一:带 failpoint 重跑、同步期望输出、或继续修代码 |
这种"结论必须归入三类之一 + 行动必须对应其一"的写法,正是该技能面向 Agent 自动化的设计意图:排查结果可以被下游流程机器化消费,而不是停留在一段自由文本里。
小结
回到仓库内的完整引用链:策略层 AGENTS.md 声明"用 failpoint 的包必须先启用 failpoint 再跑测试";操作层 docs/agents/testing-flow.md 给出 rg 检测命令与 ./tools/check/failpoint-go-test.sh 标准运行方式;实现层 tools/check/failpoint-go-test.sh 与 tools/check/failpoint-state.sh 用 trap 清理和目录锁/引用计数保证了启用/禁用的原子性与可恢复性。而 SKILL.md 分诊流程将这一切组织成"先排除环境、再二分定位、最后才改期望"的可执行决策路径——这也是在 TiDB 仓库中处理一切"看起来与 PR 无关"的测试 diff 时的标准姿势。
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